diff --git a/package.json b/package.json index c4989976..81385c29 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,6 @@ "gh-pages": "^6.1.1", "givens": "^1.3.9", "husky": "^9.0.9", - "intl": "^1.2.5", "jest": "^29.7.0", "jest-expo": "^49.0.0", "jest-plugin-context": "^2.9.0", diff --git a/packages/CalendarCarousel/CalendarItem/DateItem.tsx b/packages/CalendarCarousel/CalendarItem/DateItem.tsx new file mode 100644 index 00000000..eddf664c --- /dev/null +++ b/packages/CalendarCarousel/CalendarItem/DateItem.tsx @@ -0,0 +1,131 @@ +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {Text, TouchableOpacity, View} from 'react-native'; +import {useTheme} from '@dooboo-ui/theme'; +import {css} from '@emotion/native'; + +import type {DateObject} from '.'; + +export type DateItemStyles = { + container?: StyleProp; + text?: TextStyle; + selectedContainer?: ViewStyle; + selectedText?: TextStyle; + event?: ViewStyle; + prevContainer?: ViewStyle; + prevText?: TextStyle; + nextContainer?: ViewStyle; + nextText?: TextStyle; + weekendContainer?: ViewStyle; + weekendText?: TextStyle; +}; + +export type DateItemProp = { + isEvent?: boolean; + dateObject: DateObject; + calendarWidth: number; + showPrevDates: boolean; + showNextDates: boolean; + onPress?: (date: DateObject) => void; + selected?: boolean; + styles?: DateItemStyles; +}; + +export default function DateItem({ + isEvent, + dateObject, + calendarWidth, + onPress, + selected = false, + styles, + showNextDates = true, + showPrevDates = true, +}: DateItemProp): JSX.Element { + const {theme} = useTheme(); + + const {isNextMonth, isPrevMonth} = dateObject; + const dateWidth = calendarWidth - 24; + const dayHeight = dateWidth / 7; + const dayWidth = Math.floor(dateWidth / 7); + + if ((!!isNextMonth && !showNextDates) || (!!isPrevMonth && !showPrevDates)) { + return ( + + ); + } + + return ( + { + onPress?.(dateObject); + }} + style={[ + css` + width: ${dayWidth + 'px'}; + height: ${dayHeight + 'px'}; + + justify-content: center; + align-items: center; + `, + styles?.container, + !!isNextMonth && { + ...styles?.nextContainer, + }, + !!isPrevMonth && { + ...styles?.prevContainer, + }, + selected && { + ...css` + background-color: ${theme.role.accent}; + border-radius: 99px; + `, + ...styles?.selectedContainer, + }, + isEvent && styles?.event, + ]} + testID={ + isPrevMonth + ? 'prev-dateItem' + : isNextMonth + ? 'next-dateItem' + : 'dateItem' + } + > + + {dateObject.date.getDate()} + + + ); +} diff --git a/packages/CalendarCarousel/CalendarItem/index.tsx b/packages/CalendarCarousel/CalendarItem/index.tsx new file mode 100644 index 00000000..c07be885 --- /dev/null +++ b/packages/CalendarCarousel/CalendarItem/index.tsx @@ -0,0 +1,93 @@ +import {View} from 'react-native'; +import styled, {css} from '@emotion/native'; +import {isSameDay, isWeekend} from 'date-fns'; + +import type {DateItemStyles} from './DateItem'; +import DateItem from './DateItem'; + +const Container = styled.View<{width: number}>` + width: ${(props) => props.width + 'px'}; +`; + +export type DateObject = { + date: Date; + isPrevMonth?: boolean; + isNextMonth?: boolean; +}; + +interface CalendarItemProp { + dateObjects: DateObject[]; + events?: Date[]; + width: number; + showPrevDates?: boolean; + showNextDates?: boolean; + selectedDateObject?: DateObject; + styles?: DateItemStyles; + onDatePress?: (date: DateObject) => void; + renderDate?: (props: { + date: Date; + selected?: boolean; + isPrev?: boolean; + isNext?: boolean; + isWeekend?: boolean; + }) => JSX.Element; +} + +export default function CalendarItem({ + dateObjects, + selectedDateObject, + width, + events, + showPrevDates = false, + showNextDates = false, + styles, + onDatePress, + renderDate, +}: CalendarItemProp): JSX.Element { + const onPressDateItem = (date: DateObject): void => { + onDatePress?.(date); + }; + + return ( + + + {dateObjects.map((dateObj, i) => { + const selected = + selectedDateObject && + isSameDay(selectedDateObject.date, dateObj.date); + + if (renderDate) { + return renderDate({ + date: dateObj.date, + selected, + isNext: dateObj.isNextMonth, + isPrev: dateObj.isPrevMonth, + isWeekend: isWeekend(dateObj.date), + }); + } + + return ( + + ); + })} + + + ); +} diff --git a/packages/CalendarCarousel/MonthHeader.tsx b/packages/CalendarCarousel/MonthHeader.tsx new file mode 100644 index 00000000..c36afaa9 --- /dev/null +++ b/packages/CalendarCarousel/MonthHeader.tsx @@ -0,0 +1,72 @@ +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {Text, TouchableOpacity} from 'react-native'; +import {useTheme} from '@dooboo-ui/theme'; +import styled, {css} from '@emotion/native'; + +import {getNextMonth, getPreviousMonth} from './utils'; + +interface Prop { + date: Date; + onChange: (date: Date) => void; + iconLeft?: JSX.Element; + iconRight?: JSX.Element; + styles?: { + container?: StyleProp; + text?: StyleProp; + }; +} + +const Container = styled.View` + padding: 10px; + + flex-direction: row; + justify-content: space-between; + align-items: center; +`; + +export default function MonthHeader({ + date, + onChange, + styles, + iconLeft, + iconRight, +}: Prop): JSX.Element { + const {theme} = useTheme(); + + return ( + + { + const previous = getPreviousMonth(date); + onChange(previous); + }} + testID="month-header-prev-btn" + > + {iconLeft || null} + + + + {date.getFullYear()}년 {date.getMonth() + 1}월 + + { + const next = getNextMonth(date); + onChange(next); + }} + testID="month-header-next-btn" + > + {iconRight || null} + + + ); +} diff --git a/packages/CalendarCarousel/WeekdayItem.tsx b/packages/CalendarCarousel/WeekdayItem.tsx new file mode 100644 index 00000000..4f15ae90 --- /dev/null +++ b/packages/CalendarCarousel/WeekdayItem.tsx @@ -0,0 +1,67 @@ +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {Text, View} from 'react-native'; +import {useTheme} from '@dooboo-ui/theme'; +import {css} from '@emotion/native'; + +interface Prop { + locale?: Locale; + width?: number; + styles?: { + container?: StyleProp; + text?: StyleProp; + weekendContainer?: StyleProp; + weekendText?: StyleProp; + }; +} + +export default function WeekdayItem({ + width = 350, + locale, + styles, +}: Prop): JSX.Element { + const {theme} = useTheme(); + const weekdays = [...Array(7).keys()].map((i) => + locale?.localize?.day(i, {width: 'abbreviated'}), + ); + + return ( + + {weekdays.map((item, index) => { + return ( + + + {item} + + + ); + })} + + ); +} diff --git a/packages/CalendarCarousel/index.tsx b/packages/CalendarCarousel/index.tsx index 1cd3d7cc..a45c2a4d 100644 --- a/packages/CalendarCarousel/index.tsx +++ b/packages/CalendarCarousel/index.tsx @@ -1,493 +1,199 @@ -import 'intl'; -import 'intl/locale-data/jsonp/en'; - -import type {PropsWithChildren} from 'react'; -import React, {useRef, useState} from 'react'; +import {useEffect, useRef, useState} from 'react'; import type { - NativeScrollEvent, - NativeSyntheticEvent, + FlatListProps, + StyleProp, TextStyle, ViewStyle, } from 'react-native'; -import { - FlatList, - SafeAreaView, - ScrollView, - StyleSheet, - Text, - TouchableOpacity, - View, -} from 'react-native'; -import {useTheme} from '@dooboo-ui/theme'; - -interface Style { - wrapperContainer: ViewStyle; - calendarContainer: ViewStyle; - headerStyle: ViewStyle; - arrowText: TextStyle; - titleContainer: ViewStyle; - titleText: TextStyle; - yearText: TextStyle; - rowContainer: ViewStyle; - weekdayText: TextStyle; - dayContainer: ViewStyle; - defaultView: ViewStyle; - otherDaysText: TextStyle; - currentDayView: ViewStyle; - currentDayText: TextStyle; - inactiveText: TextStyle; - activeView: ViewStyle; - activeText: TextStyle; - mark: ViewStyle; - eventContainer: ViewStyle; - eventText: TextStyle; - eventDate: TextStyle; -} - -const styles = StyleSheet.create