Skip to content

Commit b66668c

Browse files
feat(web): Support InteractionStates like hovered and focused for web (#68)
* feat(web): extending InteractionState to support hovered and focused * chore(web): adding documentation example * chore(changeset): adding changeset for changelog * chore(lint): pass tests
1 parent 3c3891d commit b66668c

File tree

4 files changed

+142
-31
lines changed

4 files changed

+142
-31
lines changed

.changeset/rich-cars-taste.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@marceloterreiro/flash-calendar": minor
3+
---
4+
5+
Added support for react-native-web Pressable InteractionState to support hovered and focused alongside pressed

apps/docs/docs/fundamentals/customization.mdx

+80
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,83 @@ export const PerfTestCalendarItemDayWithContainer = ({
219219
);
220220
};
221221
```
222+
223+
### Overriding the hovered and focused interaction states
224+
225+
If you need to support web [Pressable InteractionStates](https://necolas.github.io/react-native-web/docs/pressable/#examples), you can also leverage `isHovered` and `isFocused` alongside `isPressed`:
226+
227+
```tsx
228+
import { CalendarTheme } from "@marceloterreiro/flash-calendar";
229+
230+
const linearAccent = "#585ABF";
231+
232+
const linearTheme: CalendarTheme = {
233+
rowMonth: {
234+
content: {
235+
textAlign: "left",
236+
color: "rgba(255, 255, 255, 0.5)",
237+
fontWeight: "700",
238+
},
239+
},
240+
rowWeek: {
241+
container: {
242+
borderBottomWidth: 1,
243+
borderBottomColor: "rgba(255, 255, 255, 0.1)",
244+
borderStyle: "solid",
245+
},
246+
},
247+
itemWeekName: { content: { color: "rgba(255, 255, 255, 0.5)" } },
248+
itemDayContainer: {
249+
activeDayFiller: {
250+
backgroundColor: linearAccent,
251+
},
252+
},
253+
itemDay: {
254+
idle: ({ isPressed, isHovered, isWeekend }) => ({
255+
container: {
256+
backgroundColor: isPressed || isHovered ? linearAccent : "transparent",
257+
borderRadius: 4,
258+
},
259+
content: {
260+
color: isWeekend && !isPressed ? "rgba(255, 255, 255, 0.5)" : "#ffffff",
261+
},
262+
}),
263+
today: ({ isPressed, isHovered }) => ({
264+
container: {
265+
borderColor: "rgba(255, 255, 255, 0.5)",
266+
borderRadius: isPressed || isHovered ? 4 : 30,
267+
backgroundColor: isPressed || isHovered ? linearAccent : "transparent",
268+
},
269+
content: {
270+
color: isPressed || isHovered ? "#ffffff" : "rgba(255, 255, 255, 0.5)",
271+
},
272+
}),
273+
active: ({ isEndOfRange, isStartOfRange }) => ({
274+
container: {
275+
backgroundColor: linearAccent,
276+
borderTopLeftRadius: isStartOfRange ? 4 : 0,
277+
borderBottomLeftRadius: isStartOfRange ? 4 : 0,
278+
borderTopRightRadius: isEndOfRange ? 4 : 0,
279+
borderBottomRightRadius: isEndOfRange ? 4 : 0,
280+
},
281+
content: {
282+
color: "#ffffff",
283+
},
284+
}),
285+
},
286+
};
287+
288+
export const LinearCalendar = memo(() => {
289+
return (
290+
<Calendar
291+
calendarDayHeight={30}
292+
calendarFirstDayOfWeek="sunday"
293+
calendarMonthId={toDateId(new Date())}
294+
calendarRowHorizontalSpacing={16}
295+
calendarRowVerticalSpacing={16}
296+
onCalendarDayPress={(dateId) => console.log(`Pressed date ${dateId}`)}
297+
theme={linearTheme}
298+
/>
299+
);
300+
});
301+
```

apps/example/src/components/GithubIssues/CalendarListGithubIssues.stories.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import type { Meta } from "@storybook/react";
99
import { useCallback, useMemo, useState } from "react";
1010
import { Alert, Text, View } from "react-native";
1111
import { useTheme } from "@/hooks/useTheme";
12-
import { VStack } from "@/components/VStack";
1312

1413
const CalendarMeta: Meta<typeof Calendar> = {
1514
title: "Calendar.List/Github Issues",

packages/flash-calendar/src/components/CalendarItemDay.tsx

+57-30
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ import type { CalendarDayMetadata } from "@/hooks/useCalendar";
88
import { useOptimizedDayMetadata } from "@/hooks/useOptimizedDayMetadata";
99
import { useTheme } from "@/hooks/useTheme";
1010

11+
// react-native-web/overrides.ts
12+
declare module "react-native" {
13+
interface PressableStateCallbackType {
14+
hovered?: boolean;
15+
focused?: boolean;
16+
}
17+
}
18+
1119
const styles = StyleSheet.create({
1220
baseContainer: {
1321
padding: 6,
@@ -33,6 +41,8 @@ type CalendarItemDayTheme = Record<
3341
isStartOfRange: boolean;
3442
isEndOfRange: boolean;
3543
isPressed: boolean;
44+
isHovered?: boolean;
45+
isFocused?: boolean;
3646
}) => DayTheme
3747
>;
3848

@@ -43,28 +53,29 @@ const buildBaseStyles = (theme: BaseTheme): CalendarItemDayTheme => {
4353
};
4454

4555
return {
46-
active: ({ isPressed, isStartOfRange, isEndOfRange }) => {
47-
const baseStyles: DayTheme & { container: ViewStyle } = isPressed
48-
? {
49-
container: {
50-
...styles.baseContainer,
51-
backgroundColor: theme.colors.background.tertiary,
52-
},
53-
content: {
54-
...baseContent,
55-
color: theme.colors.content.primary,
56-
},
57-
}
58-
: {
59-
container: {
60-
...styles.baseContainer,
61-
backgroundColor: theme.colors.background.inverse.primary,
62-
},
63-
content: {
64-
...baseContent,
65-
color: theme.colors.content.inverse.primary,
66-
},
67-
};
56+
active: ({ isPressed, isHovered, isStartOfRange, isEndOfRange }) => {
57+
const baseStyles: DayTheme & { container: ViewStyle } =
58+
isPressed || isHovered
59+
? {
60+
container: {
61+
...styles.baseContainer,
62+
backgroundColor: theme.colors.background.tertiary,
63+
},
64+
content: {
65+
...baseContent,
66+
color: theme.colors.content.primary,
67+
},
68+
}
69+
: {
70+
container: {
71+
...styles.baseContainer,
72+
backgroundColor: theme.colors.background.inverse.primary,
73+
},
74+
content: {
75+
...baseContent,
76+
color: theme.colors.content.inverse.primary,
77+
},
78+
};
6879

6980
baseStyles.container.borderRadius = 0;
7081
if (isStartOfRange) {
@@ -87,8 +98,8 @@ const buildBaseStyles = (theme: BaseTheme): CalendarItemDayTheme => {
8798
color: theme.colors.content.disabled,
8899
},
89100
}),
90-
idle: ({ isPressed }) => {
91-
return isPressed
101+
idle: ({ isPressed, isHovered }) => {
102+
return isPressed || isHovered
92103
? {
93104
container: {
94105
...styles.baseContainer,
@@ -104,8 +115,8 @@ const buildBaseStyles = (theme: BaseTheme): CalendarItemDayTheme => {
104115
content: baseContent,
105116
};
106117
},
107-
today: ({ isPressed }) => {
108-
return isPressed
118+
today: ({ isPressed, isHovered }) => {
119+
return isPressed || isHovered
109120
? {
110121
container: {
111122
...styles.baseContainer,
@@ -136,6 +147,8 @@ export interface CalendarItemDayProps {
136147
(
137148
params: CalendarDayMetadata & {
138149
isPressed: boolean;
150+
isHovered?: boolean;
151+
isFocused?: boolean;
139152
}
140153
) => Partial<DayTheme>
141154
>
@@ -175,9 +188,15 @@ export const CalendarItemDay = ({
175188
<Pressable
176189
disabled={metadata.state === "disabled"}
177190
onPress={handlePress}
178-
style={({ pressed: isPressed }) => {
191+
style={({
192+
pressed: isPressed,
193+
hovered: isHovered,
194+
focused: isFocused,
195+
}) => {
179196
const params = {
180197
isPressed,
198+
isHovered,
199+
isFocused,
181200
isEndOfRange: metadata.isEndOfRange ?? false,
182201
isStartOfRange: metadata.isStartOfRange ?? false,
183202
};
@@ -190,9 +209,11 @@ export const CalendarItemDay = ({
190209
};
191210
}}
192211
>
193-
{({ pressed: isPressed }) => {
212+
{({ pressed: isPressed, hovered: isHovered, focused: isFocused }) => {
194213
const params = {
195214
isPressed,
215+
isHovered,
216+
isFocused,
196217
isEndOfRange: metadata.isEndOfRange ?? false,
197218
isStartOfRange: metadata.isStartOfRange ?? false,
198219
};
@@ -203,8 +224,14 @@ export const CalendarItemDay = ({
203224
style={{
204225
...content,
205226
...(textProps?.style ?? ({} as object)),
206-
...theme?.base?.({ ...metadata, isPressed }).content,
207-
...theme?.[metadata.state]?.({ ...metadata, isPressed }).content,
227+
...theme?.base?.({ ...metadata, isPressed, isHovered, isFocused })
228+
.content,
229+
...theme?.[metadata.state]?.({
230+
...metadata,
231+
isPressed,
232+
isHovered,
233+
isFocused,
234+
}).content,
208235
}}
209236
>
210237
{children}

0 commit comments

Comments
 (0)