diff --git a/node_modules/react-native-ui-lib/src/components/WheelPicker/index.js b/node_modules/react-native-ui-lib/src/components/WheelPicker/index.js index 718a3a9..3c3cca8 100644 --- a/node_modules/react-native-ui-lib/src/components/WheelPicker/index.js +++ b/node_modules/react-native-ui-lib/src/components/WheelPicker/index.js @@ -94,6 +94,7 @@ const WheelPicker = props => { value, index } = getRowItemAtOffset(event.nativeEvent.contentOffset.y); + offset.value = event.nativeEvent.contentOffset.y; _onChange(value, index); }, [_onChange, getRowItemAtOffset]); const onMomentumScrollEndAndroid = index => { diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/Agenda.d.ts b/node_modules/react-native-ui-lib/src/incubator/Calendar/Agenda.d.ts new file mode 100644 index 0000000..969128a --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/Agenda.d.ts @@ -0,0 +1,4 @@ +import React from 'react'; +import { AgendaProps } from './types'; +declare function Agenda(props: AgendaProps): React.JSX.Element; +export default Agenda; diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/Agenda.js b/node_modules/react-native-ui-lib/src/incubator/Calendar/Agenda.js new file mode 100644 index 0000000..b997a29 --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/Agenda.js @@ -0,0 +1,175 @@ +import React, { useContext, useCallback, useRef } from 'react'; +import { ActivityIndicator, StyleSheet } from 'react-native'; +import { runOnJS, useAnimatedReaction, useSharedValue } from 'react-native-reanimated'; +import { FlashListPackage } from "../../optionalDependencies"; +import { BorderRadiuses, Colors } from "../../style"; +import View from "../../components/view"; +import Text from "../../components/text"; +import { isSameDay, isSameMonth } from "./helpers/DateUtils"; +import { UpdateSource } from "./types"; +import CalendarContext from "./CalendarContext"; +const FlashList = FlashListPackage?.FlashList; + +// TODO: Fix initial scrolling +function Agenda(props) { + const { + renderEvent, + renderHeader, + itemHeight = 50, + onEndReached, + showLoader + } = props; + const { + data, + selectedDate, + setDate, + updateSource + } = useContext(CalendarContext); + const flashList = useRef(null); + const closestSectionHeader = useSharedValue(null); + const scrolledByUser = useSharedValue(false); + + /* const keyExtractor = useCallback((item: InternalEvent) => { + return item.type === 'Event' ? item.id : item.header; + }, []); */ + + const _renderEvent = useCallback(eventItem => { + if (renderEvent) { + return + {renderEvent(eventItem)} + ; + } + return + + Item for + {new Date(eventItem.start).toLocaleString('en-GB', { + month: 'short', + day: 'numeric', + hour12: false, + hour: '2-digit', + minute: '2-digit' + })} + -{new Date(eventItem.end).toLocaleString('en-GB', { + hour12: false, + hour: '2-digit', + minute: '2-digit' + })} + + ; + }, [renderEvent, itemHeight]); + const _renderHeader = useCallback(headerItem => { + if (renderHeader) { + return {renderHeader(headerItem)}; + } + return + {headerItem.header} + ; + }, [renderHeader, itemHeight]); + const renderItem = useCallback(({ + item + }) => { + switch (item.type) { + case 'Event': + return _renderEvent(item); + case 'Header': + return _renderHeader(item); + } + }, [_renderEvent, _renderHeader]); + const getItemType = useCallback(item => item.type, []); + const findClosestDateAfter = useCallback(selected => { + 'worklet'; + + for (let index = 0; index < data.length; ++index) { + const item = data[index]; + if (item.type === 'Header') { + if (item.date >= selected) { + return { + dateSectionHeader: item, + index + }; + } + } + } + return null; + }, [data]); + const scrollToIndex = useCallback((index, animated) => { + flashList.current?.scrollToIndex({ + index, + animated + }); + }, []); + useAnimatedReaction(() => { + return selectedDate.value; + }, (selected, previous) => { + if (updateSource.value !== UpdateSource.AGENDA_SCROLL) { + if (selected !== previous && (closestSectionHeader.value?.date === undefined || !isSameDay(selected, closestSectionHeader.value?.date))) { + const result = findClosestDateAfter(selected); + if (result !== null) { + const { + dateSectionHeader, + index + } = result; + closestSectionHeader.value = dateSectionHeader; + scrolledByUser.value = false; + // TODO: Can the animation be improved (not in JS)? + if (previous) { + const _isSameMonth = isSameMonth(selected, previous); + runOnJS(scrollToIndex)(index, _isSameMonth); + } + } + } + } + }, [findClosestDateAfter]); + + // TODO: look at https://docs.swmansion.com/react-native-reanimated/docs/api/hooks/useAnimatedScrollHandler + const onViewableItemsChanged = useCallback(({ + viewableItems + }) => { + if (scrolledByUser.value) { + const result = viewableItems.find(item => item.item.type === 'Header'); + if (result) { + const { + item + } = result; + if (closestSectionHeader.value?.date !== item.date) { + closestSectionHeader.value = item; + setDate(item.date, UpdateSource.AGENDA_SCROLL); + } + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const onMomentumScrollBegin = useCallback(() => { + scrolledByUser.value = true; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const onScrollBeginDrag = useCallback(() => { + scrolledByUser.value = true; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const _onEndReached = useCallback(() => { + onEndReached?.(selectedDate.value); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [onEndReached]); + return + + {showLoader && + + } + ; +} +export default Agenda; +const styles = StyleSheet.create({ + eventContainer: { + overflow: 'hidden' + }, + event: { + borderWidth: 1, + borderRadius: BorderRadiuses.br20 + } +}); \ No newline at end of file diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarContext.d.ts b/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarContext.d.ts new file mode 100644 index 0000000..3a3ccf0 --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarContext.d.ts @@ -0,0 +1,4 @@ +/// +import { CalendarContextProps } from './types'; +declare const CalendarContext: import("react").Context; +export default CalendarContext; diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarContext.js b/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarContext.js new file mode 100644 index 0000000..552fcfe --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarContext.js @@ -0,0 +1,8 @@ +import { createContext } from 'react'; +import { FirstDayOfWeek } from "./types"; + +// @ts-ignore +const CalendarContext = createContext({ + firstDayOfWeek: FirstDayOfWeek.MONDAY +}); +export default CalendarContext; \ No newline at end of file diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarItem.d.ts b/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarItem.d.ts new file mode 100644 index 0000000..d5d2032 --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarItem.d.ts @@ -0,0 +1,5 @@ +import React from 'react'; +import { CalendarItemProps } from './types'; +declare function CalendarItem(props: CalendarItemProps): React.JSX.Element | null; +declare const _default: React.MemoExoticComponent; +export default _default; diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarItem.js b/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarItem.js new file mode 100644 index 0000000..f29c05a --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/CalendarItem.js @@ -0,0 +1,39 @@ +import React, { useContext, useMemo } from 'react'; +import { StyleSheet } from 'react-native'; +import { Constants } from "../../commons/new"; +import View from "../../components/view"; +import CalendarContext from "./CalendarContext"; +import Month from "./Month"; +import Header from "./Header"; +const CALENDAR_HEIGHT = Constants.isAndroid ? 280 : 270; +function CalendarItem(props) { + const { + year, + month + } = props; + const { + staticHeader, + headerHeight + } = useContext(CalendarContext); + const calendarStyle = useMemo(() => { + // TODO: dynamic height: calc calendar height with month's number of weeks + return [styles.container, { + height: CALENDAR_HEIGHT - (staticHeader ? headerHeight.value : 0) + }]; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [staticHeader]); + if (month !== undefined) { + return + {!staticHeader &&
} + + ; + } + return null; +} +export default React.memo(CalendarItem); +const styles = StyleSheet.create({ + container: { + width: Constants.windowWidth, + borderBottomWidth: 1 + } +}); \ No newline at end of file diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/Day.d.ts b/node_modules/react-native-ui-lib/src/incubator/Calendar/Day.d.ts new file mode 100644 index 0000000..4470505 --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/Day.d.ts @@ -0,0 +1,4 @@ +import React from 'react'; +import { DayProps } from './types'; +declare const Day: (props: DayProps) => React.JSX.Element; +export default Day; diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/Day.js b/node_modules/react-native-ui-lib/src/incubator/Calendar/Day.js new file mode 100644 index 0000000..82be2a1 --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/Day.js @@ -0,0 +1,97 @@ +import React, { useContext, useCallback, useMemo, useState } from 'react'; +import { StyleSheet, View, Text, TouchableWithoutFeedback } from 'react-native'; +import { useAnimatedReaction, runOnJS } from 'react-native-reanimated'; +import { Colors } from "../../style"; +import { getDateObject, isSameDay } from "./helpers/DateUtils"; +import { UpdateSource } from "./types"; +import CalendarContext from "./CalendarContext"; +const DAY_SIZE = 32; +const NO_COLOR = Colors.transparent; +const TEXT_COLOR = Colors.$textPrimary; +const TODAY_BACKGROUND_COLOR = Colors.$backgroundPrimaryLight; +const SELECTED_BACKGROUND_COLOR = Colors.$backgroundPrimaryHeavy; +const SELECTED_TEXT_COLOR = Colors.$textDefaultLight; +const INACTIVE_TEXT_COLOR = Colors.$textNeutralLight; +const Day = props => { + const { + date, + onPress, + currentMonth + } = props; + const { + selectedDate, + setDate, + showExtraDays, + today + } = useContext(CalendarContext); + const [selected, setSelected] = useState(false); + const dateObject = useMemo(() => getDateObject(date), [date]); + const day = dateObject ? dateObject.day : ''; + useAnimatedReaction(() => date ? isSameDay(selectedDate.value, date) : false, (selected, prevSelected) => { + if (selected !== prevSelected) { + runOnJS(setSelected)(selected); + } + }); + const _onPress = useCallback(() => { + setDate(date, UpdateSource.DAY_SELECT); + onPress?.(date); + }, [setDate, date, onPress]); + const isToday = isSameDay(today, date); + const inactive = dateObject ? dateObject.month !== currentMonth : false; + const isHidden = !showExtraDays && inactive; + const textStyle = useMemo(() => { + if (isHidden) { + return styles.textHidden; + } else if (inactive) { + return styles.textInactive; + } else if (selected) { + return styles.textSelected; + } + return styles.text; + }, [selected, inactive, isHidden]); + return + + {isToday && } + {selected && } + {day} + + ; +}; +export default Day; +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + width: DAY_SIZE, + height: DAY_SIZE + }, + text: { + color: TEXT_COLOR + }, + textSelected: { + color: SELECTED_TEXT_COLOR + }, + textInactive: { + color: INACTIVE_TEXT_COLOR + }, + textHidden: { + color: NO_COLOR + }, + selectedIndicator: { + position: 'absolute', + width: DAY_SIZE, + height: DAY_SIZE, + flex: 1, + borderRadius: 999, + backgroundColor: SELECTED_BACKGROUND_COLOR + }, + todayIndicator: { + position: 'absolute', + width: DAY_SIZE, + height: DAY_SIZE, + flex: 1, + borderRadius: 999, + backgroundColor: TODAY_BACKGROUND_COLOR + } +}); \ No newline at end of file diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/Day_OLD.d.ts b/node_modules/react-native-ui-lib/src/incubator/Calendar/Day_OLD.d.ts new file mode 100644 index 0000000..4470505 --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/Day_OLD.d.ts @@ -0,0 +1,4 @@ +import React from 'react'; +import { DayProps } from './types'; +declare const Day: (props: DayProps) => React.JSX.Element; +export default Day; diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/Day_OLD.js b/node_modules/react-native-ui-lib/src/incubator/Calendar/Day_OLD.js new file mode 100644 index 0000000..fbafb0f --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/Day_OLD.js @@ -0,0 +1,102 @@ +import isNull from 'lodash/isNull'; +import React, { useContext, useCallback, useMemo } from 'react'; +import { StyleSheet } from 'react-native'; +import Reanimated, { useSharedValue, useAnimatedStyle, useAnimatedReaction, withTiming } from 'react-native-reanimated'; +import { Colors } from "../../style"; +import View from "../../components/view"; +import TouchableOpacity from "../../components/touchableOpacity"; +import Text from "../../components/text"; +import { getDateObject, isSameDay } from "./helpers/DateUtils"; +import { UpdateSource } from "./types"; +import CalendarContext from "./CalendarContext"; +const DAY_SIZE = 32; +const SELECTION_SIZE = 24; +const NO_COLOR = Colors.transparent; +const TEXT_COLOR = Colors.$textPrimary; +const TODAY_BACKGROUND_COLOR = Colors.$backgroundPrimaryLight; +const INACTIVE_TODAY_BACKGROUND_COLOR = Colors.$backgroundNeutral; +const SELECTED_BACKGROUND_COLOR = Colors.$backgroundPrimaryHeavy; +const SELECTED_TEXT_COLOR = Colors.$textDefaultLight; +const INACTIVE_TEXT_COLOR = Colors.$textNeutralLight; +const AnimatedText = Reanimated.createAnimatedComponent(Text); +const Day = props => { + const { + date, + onPress, + currentMonth + } = props; + const { + selectedDate, + setDate, + showExtraDays, + today + } = useContext(CalendarContext); + const dateObject = useMemo(() => { + return !isNull(date) && getDateObject(date); + }, [date]); + const day = dateObject ? dateObject.day : ''; + const isSelected = useSharedValue(!isNull(date) ? isSameDay(selectedDate.value, date) : false); + const inactive = useMemo(() => { + // inactive have different look but is still pressable + if (dateObject) { + const dayMonth = dateObject.month; + return dayMonth !== currentMonth; + } + }, [dateObject, currentMonth]); + const isHidden = !showExtraDays && inactive; + const backgroundColor = useMemo(() => { + return !isSameDay(date, today) ? NO_COLOR : inactive ? INACTIVE_TODAY_BACKGROUND_COLOR : TODAY_BACKGROUND_COLOR; + }, [date, inactive, today]); + const textColor = useMemo(() => { + return inactive ? showExtraDays ? INACTIVE_TEXT_COLOR : NO_COLOR : TEXT_COLOR; + }, [inactive, showExtraDays]); + useAnimatedReaction(() => { + return selectedDate.value; + }, selected => { + isSelected.value = !inactive && isSameDay(selected, date); + }, []); + const animatedTextStyles = useAnimatedStyle(() => { + return { + color: withTiming(isSelected.value ? SELECTED_TEXT_COLOR : textColor, { + duration: 100 + }) + }; + }); + const animatedSelectionStyles = useAnimatedStyle(() => { + return { + backgroundColor: withTiming(isSelected.value ? SELECTED_BACKGROUND_COLOR : backgroundColor, { + duration: 100 + }) + }; + }); + const selectionStyle = useMemo(() => { + return [styles.selection, animatedSelectionStyles]; + }, [animatedSelectionStyles]); + const _onPress = useCallback(() => { + if (date !== null && !isHidden) { + isSelected.value = true; + setDate(date, UpdateSource.DAY_SELECT); + onPress?.(date); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [date, setDate, onPress]); + return + + + {day} + + ; +}; +export default Day; +const styles = StyleSheet.create({ + dayContainer: { + width: DAY_SIZE, + height: DAY_SIZE + }, + selection: { + position: 'absolute', + width: SELECTION_SIZE, + height: SELECTION_SIZE, + borderRadius: SELECTION_SIZE / 2 + } +}); \ No newline at end of file diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/Header.d.ts b/node_modules/react-native-ui-lib/src/incubator/Calendar/Header.d.ts new file mode 100644 index 0000000..ab7cc77 --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/Header.d.ts @@ -0,0 +1,4 @@ +import React from 'react'; +import { HeaderProps } from './types'; +declare const Header: (props: HeaderProps) => React.JSX.Element; +export default Header; diff --git a/node_modules/react-native-ui-lib/src/incubator/Calendar/Header.js b/node_modules/react-native-ui-lib/src/incubator/Calendar/Header.js new file mode 100644 index 0000000..b443aef --- /dev/null +++ b/node_modules/react-native-ui-lib/src/incubator/Calendar/Header.js @@ -0,0 +1,103 @@ +import throttle from 'lodash/throttle'; +import React, { useContext, useCallback } from 'react'; +import { StyleSheet, TextInput, Text } from 'react-native'; +import Reanimated, { useAnimatedProps } from 'react-native-reanimated'; +import { Colors, Typography } from "../../style"; +import View from "../../components/view"; +import Button from "../../components/button"; +import { getDateObject, getMonthForIndex, addMonths } from "./helpers/DateUtils"; +import { DayNamesFormat, UpdateSource } from "./types"; +import CalendarContext from "./CalendarContext"; +import WeekDaysNames from "./WeekDaysNames"; +const WEEK_NUMBER_WIDTH = 32; +const ARROW_NEXT = require("./assets/arrowNext.png"); +const ARROW_BACK = require("./assets/arrowBack.png"); +const AnimatedTextInput = Reanimated.createAnimatedComponent(TextInput); +const Header = props => { + const { + month, + year + } = props; + const { + selectedDate, + setDate, + showWeeksNumbers, + staticHeader, + setHeaderHeight + } = useContext(CalendarContext); + const getNewDate = useCallback(count => { + return addMonths(selectedDate.value, count, true); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const onLeftArrowPress = useCallback(throttle(() => { + setDate(getNewDate(-1), UpdateSource.MONTH_ARROW); + }, 300), [setDate, getNewDate]); + const onRightArrowPress = useCallback(throttle(() => { + setDate(getNewDate(1), UpdateSource.MONTH_ARROW); + }, 300), [setDate, getNewDate]); + const getTitle = useCallback(date => { + 'worklet'; + + const dateObject = getDateObject(date); + const m = dateObject.month; + const y = dateObject.year; + return getMonthForIndex(m) + ` ${y}`; + }, []); + const animatedProps = useAnimatedProps(() => { + // get called only on value update + return { + text: getTitle(selectedDate.value) + }; + }); + const onLayout = useCallback(event => { + setHeaderHeight?.(event.nativeEvent.layout.height); + }, [setHeaderHeight]); + const renderTitle = () => { + if (!staticHeader) { + const title = getMonthForIndex(month) + ` ${year}`; + return {title}; + } + return ( + //@ts-expect-error - hack to animate the title text change + + ); + }; + const renderArrow = (source, onPress) => { + return