@lazerlen/legend-calendar 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/.eslintrc.js +29 -0
  2. package/.turbo/turbo-build.log +19 -0
  3. package/.turbo/turbo-lint.log +14 -0
  4. package/.turbo/turbo-test.log +261 -0
  5. package/.turbo/turbo-typecheck.log +1 -0
  6. package/CHANGELOG.md +127 -0
  7. package/dist/index.d.mts +679 -0
  8. package/dist/index.d.ts +679 -0
  9. package/dist/index.js +2 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/index.mjs +2 -0
  12. package/dist/index.mjs.map +1 -0
  13. package/package.json +47 -0
  14. package/src/components/Calendar.stories.tsx +226 -0
  15. package/src/components/Calendar.tsx +224 -0
  16. package/src/components/CalendarItemDay.tsx +385 -0
  17. package/src/components/CalendarItemEmpty.tsx +30 -0
  18. package/src/components/CalendarItemWeekName.tsx +67 -0
  19. package/src/components/CalendarList.stories.tsx +326 -0
  20. package/src/components/CalendarList.tsx +373 -0
  21. package/src/components/CalendarRowMonth.tsx +62 -0
  22. package/src/components/CalendarRowWeek.tsx +46 -0
  23. package/src/components/CalendarThemeProvider.tsx +43 -0
  24. package/src/components/HStack.tsx +67 -0
  25. package/src/components/VStack.tsx +67 -0
  26. package/src/components/index.ts +108 -0
  27. package/src/developer/decorators.tsx +54 -0
  28. package/src/developer/loggginHandler.tsx +6 -0
  29. package/src/developer/useRenderCount.ts +7 -0
  30. package/src/helpers/dates.test.ts +567 -0
  31. package/src/helpers/dates.ts +163 -0
  32. package/src/helpers/functions.ts +327 -0
  33. package/src/helpers/numbers.ts +11 -0
  34. package/src/helpers/strings.ts +2 -0
  35. package/src/helpers/tokens.ts +71 -0
  36. package/src/helpers/types.ts +3 -0
  37. package/src/hooks/useCalendar.test.ts +381 -0
  38. package/src/hooks/useCalendar.ts +351 -0
  39. package/src/hooks/useCalendarList.test.ts +382 -0
  40. package/src/hooks/useCalendarList.tsx +291 -0
  41. package/src/hooks/useDateRange.test.ts +128 -0
  42. package/src/hooks/useDateRange.ts +94 -0
  43. package/src/hooks/useOptimizedDayMetadata.test.ts +582 -0
  44. package/src/hooks/useOptimizedDayMetadata.ts +93 -0
  45. package/src/hooks/useTheme.ts +14 -0
  46. package/src/index.ts +24 -0
  47. package/tsconfig.json +13 -0
  48. package/tsup.config.ts +15 -0
@@ -0,0 +1,62 @@
1
+ import type { ReactNode } from "react";
2
+ import { useMemo } from "react";
3
+ import type { TextStyle, ViewStyle } from "react-native";
4
+ import { StyleSheet, Text, View } from "react-native";
5
+
6
+ import { useTheme } from "@/hooks/useTheme";
7
+
8
+ const styles = StyleSheet.create({
9
+ container: {
10
+ width: "100%",
11
+ alignItems: "center",
12
+ justifyContent: "center",
13
+ },
14
+ content: {
15
+ textAlign: "center",
16
+ width: "100%",
17
+ },
18
+ });
19
+
20
+ interface CalendarRowMonthTheme {
21
+ container?: ViewStyle;
22
+ content?: TextStyle;
23
+ }
24
+
25
+ export interface CalendarRowMonthProps {
26
+ children: ReactNode;
27
+ /**
28
+ * The height of the month row, needed to correctly measure the calendar's
29
+ * height.
30
+ */
31
+ height: number;
32
+ /** The theme of the month row, useful for customizing the component. */
33
+ theme?: CalendarRowMonthTheme;
34
+ }
35
+
36
+ export const CalendarRowMonth = ({
37
+ children,
38
+ height,
39
+ theme,
40
+ }: CalendarRowMonthProps) => {
41
+ const baseTheme = useTheme();
42
+ const { containerStyles, contentStyles } = useMemo(() => {
43
+ const containerStyles = [styles.container, { height }, theme?.container];
44
+ const contentStyles = [
45
+ styles.content,
46
+ { color: baseTheme.colors.content.primary },
47
+ theme?.content,
48
+ ];
49
+ return { containerStyles, contentStyles };
50
+ }, [
51
+ baseTheme.colors.content.primary,
52
+ height,
53
+ theme?.container,
54
+ theme?.content,
55
+ ]);
56
+
57
+ return (
58
+ <View style={containerStyles}>
59
+ <Text style={contentStyles}>{children}</Text>
60
+ </View>
61
+ );
62
+ };
@@ -0,0 +1,46 @@
1
+ import type { ReactNode } from "react";
2
+ import { useMemo } from "react";
3
+ import type { ViewStyle } from "react-native";
4
+ import { StyleSheet } from "react-native";
5
+
6
+ import { HStack } from "@/components/HStack";
7
+ import type { BaseTheme } from "@/helpers/tokens";
8
+
9
+ export interface CalendarRowWeekProps {
10
+ children: ReactNode;
11
+ spacing?: keyof BaseTheme["spacing"];
12
+ theme?: CalendarRowWeekTheme;
13
+ }
14
+
15
+ interface CalendarRowWeekTheme {
16
+ container?: ViewStyle;
17
+ }
18
+
19
+ const styles = StyleSheet.create({
20
+ container: {
21
+ width: "100%",
22
+ },
23
+ });
24
+
25
+ export const CalendarRowWeek = ({
26
+ children,
27
+ spacing = 0,
28
+ theme,
29
+ }: CalendarRowWeekProps) => {
30
+ const { containerStyles } = useMemo(() => {
31
+ return {
32
+ containerStyles: { ...styles.container, ...(theme?.container ?? {}) },
33
+ };
34
+ }, [theme?.container]);
35
+ return (
36
+ <HStack
37
+ alignItems="center"
38
+ grow
39
+ justifyContent="space-between"
40
+ spacing={spacing}
41
+ style={containerStyles}
42
+ >
43
+ {children}
44
+ </HStack>
45
+ );
46
+ };
@@ -0,0 +1,43 @@
1
+ import type { ReactNode } from "react";
2
+ import { createContext, useContext, useMemo } from "react";
3
+ import type { ColorSchemeName } from "react-native";
4
+
5
+ export interface CalendarThemeContextType {
6
+ colorScheme?: ColorSchemeName;
7
+ }
8
+
9
+ const CalendarThemeContext = createContext<CalendarThemeContextType>({
10
+ colorScheme: undefined,
11
+ });
12
+
13
+ export const CalendarThemeProvider = ({
14
+ children,
15
+ colorScheme,
16
+ }: {
17
+ children: ReactNode;
18
+ /**
19
+ * When set, Legend Calendar will use this color scheme instead of the system's
20
+ * value (`light|dark`). This is useful if your app doesn't support dark-mode,
21
+ * for example.
22
+ *
23
+ * We don't advise using this prop - ideally, your app should reflect the
24
+ * user's preferences.
25
+ */
26
+ colorScheme?: ColorSchemeName;
27
+ }) => {
28
+ const calendarThemeContextValue = useMemo<CalendarThemeContextType>(
29
+ () => ({ colorScheme }),
30
+ [colorScheme]
31
+ );
32
+
33
+ return (
34
+ <CalendarThemeContext.Provider value={calendarThemeContextValue}>
35
+ {children}
36
+ </CalendarThemeContext.Provider>
37
+ );
38
+ };
39
+
40
+ export const useCalendarTheme = () => {
41
+ const context = useContext(CalendarThemeContext);
42
+ return context;
43
+ };
@@ -0,0 +1,67 @@
1
+ import { useMemo, type ReactNode } from "react";
2
+ import { StyleSheet, View, type ViewStyle } from "react-native";
3
+
4
+ const styles = StyleSheet.create({
5
+ container: {
6
+ alignItems: "center",
7
+ flexDirection: "row",
8
+ flexGrow: 0,
9
+ flexShrink: 0,
10
+ flexWrap: "nowrap",
11
+ justifyContent: "flex-start",
12
+ },
13
+ });
14
+
15
+ export interface HStackProps {
16
+ alignItems?: ViewStyle["alignItems"];
17
+ justifyContent?: ViewStyle["justifyContent"];
18
+ children: ReactNode;
19
+ grow?: boolean;
20
+ shrink?: boolean;
21
+ spacing?: number;
22
+ wrap?: ViewStyle["flexWrap"];
23
+ backgroundColor?: string;
24
+ style?: ViewStyle;
25
+ width?: ViewStyle["width"];
26
+ }
27
+
28
+ export const HStack = ({
29
+ alignItems,
30
+ children,
31
+ justifyContent = "flex-start",
32
+ grow = false,
33
+ shrink = false,
34
+ spacing = 0,
35
+ wrap = "nowrap",
36
+ backgroundColor,
37
+ width,
38
+ style = {},
39
+ }: HStackProps) => {
40
+ const containerStyles = useMemo<ViewStyle[]>(
41
+ () => [
42
+ styles.container,
43
+ { gap: spacing },
44
+ grow ? { flexGrow: 1 } : {},
45
+ shrink ? { flexShrink: 1 } : {},
46
+ wrap ? { flexWrap: wrap } : {},
47
+ alignItems ? { alignItems } : {},
48
+ justifyContent ? { justifyContent } : {},
49
+ backgroundColor ? { backgroundColor } : {},
50
+ width ? { width } : {},
51
+ style,
52
+ ],
53
+ [
54
+ alignItems,
55
+ backgroundColor,
56
+ grow,
57
+ justifyContent,
58
+ shrink,
59
+ spacing,
60
+ style,
61
+ width,
62
+ wrap,
63
+ ]
64
+ );
65
+
66
+ return <View style={containerStyles}>{children}</View>;
67
+ };
@@ -0,0 +1,67 @@
1
+ import {
2
+ Children,
3
+ Fragment,
4
+ isValidElement,
5
+ useMemo,
6
+ type ReactElement,
7
+ type ReactNode,
8
+ } from "react";
9
+ import { StyleSheet, View, type ViewStyle } from "react-native";
10
+
11
+ const styles = StyleSheet.create({
12
+ container: {
13
+ flexDirection: "column",
14
+ },
15
+ });
16
+
17
+ export interface VStackDividerProps {
18
+ marginBottom: number;
19
+ }
20
+
21
+ export interface VStackProps {
22
+ children: ReactNode;
23
+ spacing?: number;
24
+
25
+ alignItems?: ViewStyle["alignItems"];
26
+ justifyContent?: ViewStyle["justifyContent"];
27
+
28
+ /** If the VStack should `flex: 1` to fill the parent's height */
29
+ grow?: boolean;
30
+ }
31
+
32
+ function isFragment(
33
+ child: ReactNode
34
+ ): child is ReactElement<{ children?: ReactNode }> {
35
+ return isValidElement(child) && child.type === Fragment;
36
+ }
37
+
38
+ export function VStack({
39
+ children,
40
+ spacing = 0,
41
+ alignItems,
42
+ justifyContent,
43
+ grow,
44
+ }: VStackProps) {
45
+ const containerStyles = useMemo<ViewStyle>(
46
+ () => ({
47
+ ...styles.container,
48
+ gap: spacing,
49
+ alignItems,
50
+ justifyContent,
51
+ flex: grow ? 1 : undefined,
52
+ }),
53
+ [alignItems, grow, justifyContent, spacing]
54
+ );
55
+
56
+ return (
57
+ <View style={containerStyles}>
58
+ {Children.toArray(children)
59
+ .map((c) => (isFragment(c) ? c.props.children : c))
60
+ .flat()
61
+ .filter((c) => c !== null && typeof c !== "undefined")
62
+ .map((child, i) => (
63
+ <Fragment key={i}>{child}</Fragment>
64
+ ))}
65
+ </View>
66
+ );
67
+ }
@@ -0,0 +1,108 @@
1
+ import { Calendar as CalendarDefault } from "@/components/Calendar";
2
+ import {
3
+ CalendarItemDay,
4
+ CalendarItemDayContainer,
5
+ CalendarItemDayWithContainer,
6
+ } from "@/components/CalendarItemDay";
7
+ import { CalendarItemEmpty } from "@/components/CalendarItemEmpty";
8
+ import { CalendarItemWeekName } from "@/components/CalendarItemWeekName";
9
+ import { CalendarList } from "@/components/CalendarList";
10
+ import { CalendarRowMonth } from "@/components/CalendarRowMonth";
11
+ import { CalendarRowWeek } from "@/components/CalendarRowWeek";
12
+ import { HStack } from "@/components/HStack";
13
+ import { VStack } from "@/components/VStack";
14
+
15
+ /**
16
+ * This file houses the public API for the legend-calendar package.
17
+ */
18
+
19
+ type CalendarItemDayNamespace = {
20
+ Container: typeof CalendarItemDayContainer;
21
+ WithContainer: typeof CalendarItemDayWithContainer;
22
+ } & typeof CalendarItemDay;
23
+
24
+ const CalendarItemDayWithNamespace =
25
+ CalendarItemDay as CalendarItemDayNamespace;
26
+
27
+ CalendarItemDayWithNamespace.Container = CalendarItemDayContainer;
28
+ CalendarItemDayWithNamespace.WithContainer = CalendarItemDayWithContainer;
29
+ export type {
30
+ CalendarOnDayPress,
31
+ CalendarProps,
32
+ CalendarTheme,
33
+ } from "@/components/Calendar";
34
+ export type {
35
+ CalendarItemDayContainerProps,
36
+ CalendarItemDayProps,
37
+ CalendarItemDayWithContainerProps,
38
+ } from "@/components/CalendarItemDay";
39
+ export type { CalendarItemEmptyProps } from "@/components/CalendarItemEmpty";
40
+ export type { CalendarItemWeekNameProps } from "@/components/CalendarItemWeekName";
41
+ export type {
42
+ CalendarListProps,
43
+ CalendarListRef,
44
+ CalendarMonthEnhanced,
45
+ } from "@/components/CalendarList";
46
+ export type { CalendarRowMonthProps } from "@/components/CalendarRowMonth";
47
+ export type { CalendarRowWeekProps } from "@/components/CalendarRowWeek";
48
+ export type { HStackProps } from "@/components/HStack";
49
+ export type { VStackProps } from "@/components/VStack";
50
+
51
+ interface CalendarItemNamespace {
52
+ /**
53
+ * Renders the day item of the calendar (e.g. `1`, `2`, `3`, etc.)
54
+ */
55
+ Day: typeof CalendarItemDayWithNamespace;
56
+ /**
57
+ * Renders the week day name item of the calendar (e.g. `Sun`, `Mon`, `Tue`, etc.)
58
+ */
59
+ WeekName: typeof CalendarItemWeekName;
60
+ /**
61
+ * Renders an empty item to fill the calendar's grid in the start or end of
62
+ * the month.
63
+ */
64
+ Empty: typeof CalendarItemEmpty;
65
+ }
66
+
67
+ const CalendarItemWithNamespace = {} as CalendarItemNamespace;
68
+ CalendarItemWithNamespace.Day = CalendarItemDayWithNamespace;
69
+
70
+ CalendarItemWithNamespace.WeekName = CalendarItemWeekName;
71
+
72
+ CalendarItemWithNamespace.Empty = CalendarItemEmpty;
73
+
74
+ interface CalendarRowNamespace {
75
+ /**
76
+ * Renders the month row of the calendar (e.g. `January`, `February`, `March`, etc.)
77
+ */
78
+ Month: typeof CalendarRowMonth;
79
+ /**
80
+ * Renders each week row of the calendar, including the week day names.
81
+ */
82
+ Week: typeof CalendarRowWeek;
83
+ }
84
+
85
+ const CalendarRowWithNamespace = {} as CalendarRowNamespace;
86
+ CalendarRowWithNamespace.Month = CalendarRowMonth;
87
+
88
+ CalendarRowWithNamespace.Week = CalendarRowWeek;
89
+
90
+ type CalendarNamespace = {
91
+ Item: typeof CalendarItemWithNamespace;
92
+ Row: typeof CalendarRowWithNamespace;
93
+ List: typeof CalendarList;
94
+ HStack: typeof HStack;
95
+ VStack: typeof VStack;
96
+ } & typeof CalendarDefault;
97
+
98
+ const CalendarWithNamespace = CalendarDefault as CalendarNamespace;
99
+ CalendarWithNamespace.Item = CalendarItemWithNamespace;
100
+ CalendarWithNamespace.Row = CalendarRowWithNamespace;
101
+
102
+ CalendarWithNamespace.List = CalendarList;
103
+
104
+ // Useful for customizing the layout of the calendar, re-exported for convenience
105
+ CalendarWithNamespace.HStack = HStack;
106
+ CalendarWithNamespace.VStack = VStack;
107
+
108
+ export const Calendar = CalendarWithNamespace;
@@ -0,0 +1,54 @@
1
+ import type { PropsWithChildren, ReactNode } from "react";
2
+ import { StatusBar } from "expo-status-bar";
3
+ import { memo, useMemo } from "react";
4
+ import { StyleSheet, View, type ViewStyle } from "react-native";
5
+
6
+ import { useTheme } from "@/hooks/useTheme";
7
+
8
+ const styles = StyleSheet.create({
9
+ centered: {
10
+ justifyContent: "center",
11
+ alignItems: "center",
12
+ flex: 1,
13
+ },
14
+ paddedContainer: {
15
+ padding: 12,
16
+ flex: 1,
17
+ },
18
+ container: {
19
+ flex: 1,
20
+ },
21
+ });
22
+
23
+ export const centeredDecorator = (storyFn: () => ReactNode) => (
24
+ <View style={styles.centered}>{storyFn()}</View>
25
+ );
26
+
27
+ export const paddingDecorator = (storyFn: () => ReactNode) => (
28
+ <View style={styles.paddedContainer}>{storyFn()}</View>
29
+ );
30
+
31
+ const BackgroundStory = memo(({ children }: PropsWithChildren) => {
32
+ const { colors } = useTheme();
33
+ const containerStyles = useMemo<ViewStyle[]>(
34
+ () => [
35
+ styles.container,
36
+ {
37
+ backgroundColor: colors.background.primary,
38
+ },
39
+ ],
40
+ [colors]
41
+ );
42
+
43
+ return (
44
+ <View style={containerStyles}>
45
+ <StatusBar />
46
+ {children}
47
+ </View>
48
+ );
49
+ });
50
+ BackgroundStory.displayName = "BackgroundStory";
51
+
52
+ export const backgroundDecorator = (storyFn: () => ReactNode) => (
53
+ <BackgroundStory>{storyFn()}</BackgroundStory>
54
+ );
@@ -0,0 +1,6 @@
1
+ export const loggingHandler =
2
+ (msg: string, printArgs = true) =>
3
+ (...args: any[]) => {
4
+ // eslint-disable-next-line no-console
5
+ console.log(...[msg, ...(printArgs ? args : [])].filter(Boolean));
6
+ };
@@ -0,0 +1,7 @@
1
+ import { useRef } from "react";
2
+
3
+ export const useRenderCount = () => {
4
+ const renderCount = useRef(0);
5
+ renderCount.current += 1;
6
+ return renderCount.current;
7
+ };