Skip to content

Commit

Permalink
refactor: propose enhancement for calendar 0.1.0 (#511)
Browse files Browse the repository at this point in the history
## 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
sunjungAn and hyochan authored Feb 4, 2024
1 parent 3a72b31 commit 1bbf896
Show file tree
Hide file tree
Showing 12 changed files with 854 additions and 534 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
131 changes: 131 additions & 0 deletions packages/CalendarCarousel/CalendarItem/DateItem.tsx
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>
);
}
93 changes: 93 additions & 0 deletions packages/CalendarCarousel/CalendarItem/index.tsx
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>
);
}
72 changes: 72 additions & 0 deletions packages/CalendarCarousel/MonthHeader.tsx
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>
);
}
67 changes: 67 additions & 0 deletions packages/CalendarCarousel/WeekdayItem.tsx
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>
);
}
Loading

0 comments on commit 1bbf896

Please sign in to comment.