-
Notifications
You must be signed in to change notification settings - Fork 31
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor: propose enhancement for calendar 0.1.0 (#511)
## Description This PR refactors the calendar component to make the code more concise. By leveraging `date-fns`, we’re now offering more intuitive internationalization. Additionally, a `styles` prop has been introduced, allowing for customization of all sub-component styles. To optimize the scrolling experience, the implementation now utilizes `FlatList`, ensuring smoother horizontal transitions. However, a limitation was encountered with `scrollToIndex` in FlatList, which didn’t trigger as expected. As a workaround, and as documented in the comments, we’ve implemented the [onScrollToIndexFailed](https://reactnative.dev/docs/virtualizedlist#onscrolltoindexfailed) solution suggested in this [Stack Overflow post](https://stackoverflow.com/questions/53059609/flat-list-scrolltoindex-should-be-used-in-conjunction-with-getitemlayout-or-on/60320726#60320726). Lastly, updates have been made to `CalendarCarouselBasicStory` to reflect the current changes. ## Test Plan You can test by injecting props into `CalendarCarouselBasicStory`. Currently, it’s a very basic version, and you can primarily try making style-centric changes. ## Related Issues N/A ## Tests WIP ## Checklist Before you create this PR confirms that it meets all requirements listed below by checking the relevant checkboxes (`[x]`). This will ensure a smooth and quick review process. - [x] I read the [Contributor Guide](https://github.com/dooboolab-community/dooboo-ui/blob/main/CONTRIBUTING.md) and followed the process outlined there for submitting PRs. - [x] Run `yarn test:all` and make sure nothing fails. - [x] I am willing to follow-up on review comments in a timely manner. --------- Co-authored-by: hyochan <dooboolab@gmail.com>
- Loading branch information
Showing
12 changed files
with
854 additions
and
534 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ViewStyle>; | ||
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 ( | ||
<View | ||
style={css` | ||
width: ${dayWidth + 'px'}; | ||
height: ${dayHeight + 'px'}; | ||
`} | ||
/> | ||
); | ||
} | ||
|
||
return ( | ||
<TouchableOpacity | ||
disabled={!onPress} | ||
onPress={() => { | ||
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' | ||
} | ||
> | ||
<Text | ||
numberOfLines={1} | ||
style={[ | ||
styles?.text, | ||
css` | ||
font-size: 18px; | ||
color: ${theme.text.basic}; | ||
`, | ||
!!isNextMonth && { | ||
...css` | ||
color: ${theme.text.disabled}; | ||
`, | ||
...styles?.nextText, | ||
}, | ||
!!isPrevMonth && { | ||
...css` | ||
color: ${theme.text.disabled}; | ||
`, | ||
...styles?.prevText, | ||
}, | ||
selected && { | ||
...css` | ||
color: #1b1b1b; | ||
`, | ||
...styles?.selectedText, | ||
}, | ||
]} | ||
> | ||
{dateObject.date.getDate()} | ||
</Text> | ||
</TouchableOpacity> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 ( | ||
<Container width={width}> | ||
<View | ||
style={css` | ||
flex-direction: row; | ||
align-items: center; | ||
flex-wrap: wrap; | ||
gap: 4px; | ||
`} | ||
> | ||
{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 ( | ||
<DateItem | ||
calendarWidth={width} | ||
dateObject={dateObj} | ||
isEvent={events?.includes(dateObj.date)} | ||
key={`${dateObj}-${i}`} | ||
onPress={onPressDateItem} | ||
selected={selected} | ||
showNextDates={showNextDates} | ||
showPrevDates={showPrevDates} | ||
styles={styles} | ||
/> | ||
); | ||
})} | ||
</View> | ||
</Container> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ViewStyle>; | ||
text?: StyleProp<TextStyle>; | ||
}; | ||
} | ||
|
||
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 ( | ||
<Container style={styles?.container}> | ||
<TouchableOpacity | ||
onPress={() => { | ||
const previous = getPreviousMonth(date); | ||
onChange(previous); | ||
}} | ||
testID="month-header-prev-btn" | ||
> | ||
{iconLeft || null} | ||
</TouchableOpacity> | ||
|
||
<Text | ||
style={[ | ||
css` | ||
color: ${theme.text.basic}; | ||
font-weight: bold; | ||
font-size: 20px; | ||
`, | ||
styles?.text, | ||
]} | ||
testID="month-header-title" | ||
> | ||
{date.getFullYear()}년 {date.getMonth() + 1}월 | ||
</Text> | ||
<TouchableOpacity | ||
onPress={() => { | ||
const next = getNextMonth(date); | ||
onChange(next); | ||
}} | ||
testID="month-header-next-btn" | ||
> | ||
{iconRight || null} | ||
</TouchableOpacity> | ||
</Container> | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<ViewStyle>; | ||
text?: StyleProp<TextStyle>; | ||
weekendContainer?: StyleProp<ViewStyle>; | ||
weekendText?: StyleProp<TextStyle>; | ||
}; | ||
} | ||
|
||
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 ( | ||
<View | ||
style={css` | ||
width: ${width + 'px'}; | ||
flex-direction: row; | ||
justify-content: space-between; | ||
padding: 10px 0; | ||
`} | ||
> | ||
{weekdays.map((item, index) => { | ||
return ( | ||
<View | ||
key={index} | ||
style={[ | ||
css` | ||
flex: 1; | ||
align-items: center; | ||
`, | ||
styles?.container, | ||
(index === 0 || index === 6) && styles?.weekendContainer, | ||
]} | ||
testID="weekday" | ||
> | ||
<Text | ||
style={[ | ||
css` | ||
font-size: 16px; | ||
color: ${theme.text.basic}; | ||
`, | ||
styles?.text, | ||
(index === 0 || index === 6) && styles?.weekendText, | ||
]} | ||
> | ||
{item} | ||
</Text> | ||
</View> | ||
); | ||
})} | ||
</View> | ||
); | ||
} |
Oops, something went wrong.