From bfc622de2dc3ac1aa8a7eda78eed37c13922e086 Mon Sep 17 00:00:00 2001 From: Louis Rouffineau Date: Tue, 18 Feb 2025 14:29:14 +0100 Subject: [PATCH 1/6] Remove the segmented controller and replace with new toggle design --- design-system/chip.tsx | 6 +- ...ation-requests-list-segment-controller.tsx | 83 ------------------- .../conversation-requests-list-toggle.tsx | 38 +++++++++ .../conversation-requests-list.screen.tsx | 24 +++--- i18n/translations/en.ts | 1 - i18n/translations/fr.ts | 2 +- 6 files changed, 56 insertions(+), 98 deletions(-) delete mode 100644 features/conversation-requests-list/conversation-requests-list-segment-controller.tsx create mode 100644 features/conversation-requests-list/conversation-requests-list-toggle.tsx diff --git a/design-system/chip.tsx b/design-system/chip.tsx index 9e91e9d07..70d12b544 100644 --- a/design-system/chip.tsx +++ b/design-system/chip.tsx @@ -11,6 +11,7 @@ type ChipProps = { isSelected?: boolean; onPress?: () => void; size?: "sm" | "md"; + showAvatar?: boolean; }; export function Chip({ @@ -19,6 +20,7 @@ export function Chip({ isSelected, onPress, size = "sm", + showAvatar = true, }: ChipProps) { const { theme } = useAppTheme(); @@ -33,7 +35,9 @@ export function Chip({ // {...debugBorder()} style={styles.$content} > - + {showAvatar && ( + + )} {name} diff --git a/features/conversation-requests-list/conversation-requests-list-segment-controller.tsx b/features/conversation-requests-list/conversation-requests-list-segment-controller.tsx deleted file mode 100644 index 19411e792..000000000 --- a/features/conversation-requests-list/conversation-requests-list-segment-controller.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { - backgroundColor, - tertiaryBackgroundColor, - textPrimaryColor, -} from "@styles/colors"; -import React from "react"; -import { - Text, - TouchableOpacity, - View, - useColorScheme, - Platform, -} from "react-native"; - -type IConversationRequestsListSegmentControllerProps = { - options: string[]; - selectedIndex: number; - onSelect: (index: number) => void; -}; - -export const ConversationRequestsListSegmentController: React.FC< - IConversationRequestsListSegmentControllerProps -> = ({ options, selectedIndex, onSelect }) => { - const colorScheme = useColorScheme(); - - return ( - - {options.map((option, index) => ( - onSelect(index)} - > - - {option} - - - ))} - - ); -}; diff --git a/features/conversation-requests-list/conversation-requests-list-toggle.tsx b/features/conversation-requests-list/conversation-requests-list-toggle.tsx new file mode 100644 index 000000000..c460918ea --- /dev/null +++ b/features/conversation-requests-list/conversation-requests-list-toggle.tsx @@ -0,0 +1,38 @@ +import { Chip } from "@/design-system/chip"; +import React from "react"; +import { View } from "react-native"; + +type IConversationRequestsToggleProps = { + options: string[]; + selectedIndex: number; + onSelect: (index: number) => void; +}; + +export function ConversationRequestsToggle({ + options, + selectedIndex, + onSelect, +}: IConversationRequestsToggleProps) { + return ( + + {options.map((option, index) => ( + + onSelect(index)} + showAvatar={false} + /> + + ))} + + ); +} diff --git a/features/conversation-requests-list/conversation-requests-list.screen.tsx b/features/conversation-requests-list/conversation-requests-list.screen.tsx index a153f4c62..f4201ec15 100644 --- a/features/conversation-requests-list/conversation-requests-list.screen.tsx +++ b/features/conversation-requests-list/conversation-requests-list.screen.tsx @@ -1,6 +1,6 @@ import { Screen } from "@/components/Screen/ScreenComp/Screen"; import { ConversationList } from "@/features/conversation-list/conversation-list"; -import { ConversationRequestsListSegmentController } from "@/features/conversation-requests-list/conversation-requests-list-segment-controller"; +import { ConversationRequestsToggle } from "@/features/conversation-requests-list/conversation-requests-list-toggle"; import { useConversationRequestsListScreenHeader } from "@/features/conversation-requests-list/conversation-requests-list.screen-header"; import { translate } from "@/i18n"; import { memo, useCallback, useState } from "react"; @@ -8,37 +8,37 @@ import { useConversationRequestsListItem } from "./use-conversation-requests-lis import { $globalStyles } from "@/theme/styles"; export const ConversationRequestsListScreen = memo(function () { - const [selectedSegment, setSelectedSegment] = useState(0); + const [selectedIndex, setSelectedIndex] = useState(0); useConversationRequestsListScreenHeader(); - const handleSegmentChange = useCallback((index: number) => { - setSelectedSegment(index); + const handleToggleSelect = useCallback((index: number) => { + setSelectedIndex(index); }, []); return ( - - + ); }); const ConversationListWrapper = memo(function ConversationListWrapper({ - selectedSegment, + selectedIndex, }: { - selectedSegment: number; + selectedIndex: number; }) { const { likelyNotSpam, likelySpam } = useConversationRequestsListItem(); return ( ); }); diff --git a/i18n/translations/en.ts b/i18n/translations/en.ts index 8c4153d6d..2da3efebd 100644 --- a/i18n/translations/en.ts +++ b/i18n/translations/en.ts @@ -275,7 +275,6 @@ export const en = { "You currently have no message requests. We'll make suggestions here as you connect with others.", hidden_requests_warn: "Requests containing messages that may be offensive or unwanted are moved to this folder.", - message_requests: "Message requests", // Conversation accept: "Accept", diff --git a/i18n/translations/fr.ts b/i18n/translations/fr.ts index ee1535af5..e675b36bd 100644 --- a/i18n/translations/fr.ts +++ b/i18n/translations/fr.ts @@ -265,7 +265,7 @@ export const fr = { "Vous n'avez actuellement aucune demande de message. Nous ferons des suggestions ici à mesure que vous vous connecterez avec d'autres.", hidden_requests_warn: "Les demandes contenant des messages potentiellement offensants ou indésirables sont déplacées dans ce dossier.", - message_requests: "Demandes de message", + // Conversation accept: "Accepter", block: "Bloquer", From 2123fe1562f6c19ef08c7476a5d1f820d5db5525 Mon Sep 17 00:00:00 2001 From: Louis Rouffineau Date: Tue, 18 Feb 2025 14:29:39 +0100 Subject: [PATCH 2/6] Implement right action UI for deleting all requests --- ...nversation-requests-list.screen-header.tsx | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/features/conversation-requests-list/conversation-requests-list.screen-header.tsx b/features/conversation-requests-list/conversation-requests-list.screen-header.tsx index 116b9232a..c974a0428 100644 --- a/features/conversation-requests-list/conversation-requests-list.screen-header.tsx +++ b/features/conversation-requests-list/conversation-requests-list.screen-header.tsx @@ -1,15 +1,42 @@ import { translate } from "@/i18n"; import { useHeader } from "@/navigation/use-header"; import { useRouter } from "@/navigation/useNavigation"; +import { HeaderAction } from "@/design-system/Header/HeaderAction"; +import { Alert } from "react-native"; export function useConversationRequestsListScreenHeader() { const router = useRouter(); + const handleDeleteAll = () => { + Alert.alert( + translate("Delete all requests"), + translate("Would you like to delete all requests?"), + [ + { + text: translate("cancel"), + style: "cancel", + }, + { + text: translate("delete"), + style: "destructive", + onPress: () => { + // TODO: Implement delete all functionality + console.log("Delete all requests"); + router.goBack(); + }, + }, + ] + ); + }; + useHeader({ safeAreaEdges: ["top"], onBack: () => { router.goBack(); }, - title: translate("message_requests"), + title: translate("Requests"), + RightActionComponent: ( + + ), }); } From 1cd7abcf9ca7b2484c8a2f5720695838bf99b158 Mon Sep 17 00:00:00 2001 From: Louis Rouffineau Date: Tue, 18 Feb 2025 14:54:03 +0100 Subject: [PATCH 3/6] Use theme spacing values --- .../conversation-requests-list-toggle.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/features/conversation-requests-list/conversation-requests-list-toggle.tsx b/features/conversation-requests-list/conversation-requests-list-toggle.tsx index c460918ea..1c4cd6b50 100644 --- a/features/conversation-requests-list/conversation-requests-list-toggle.tsx +++ b/features/conversation-requests-list/conversation-requests-list-toggle.tsx @@ -1,4 +1,5 @@ import { Chip } from "@/design-system/chip"; +import { useAppTheme } from "@/theme/useAppTheme"; import React from "react"; import { View } from "react-native"; @@ -13,13 +14,15 @@ export function ConversationRequestsToggle({ selectedIndex, onSelect, }: IConversationRequestsToggleProps) { + const { theme } = useAppTheme(); + return ( {options.map((option, index) => ( From 814522d1a2ede7d205e38f3862ccc69c3e03345c Mon Sep 17 00:00:00 2001 From: Louis Rouffineau Date: Tue, 18 Feb 2025 15:52:23 +0100 Subject: [PATCH 4/6] Fix --- .../conversation-requests-list-toggle.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/features/conversation-requests-list/conversation-requests-list-toggle.tsx b/features/conversation-requests-list/conversation-requests-list-toggle.tsx index 1c4cd6b50..fde5058e6 100644 --- a/features/conversation-requests-list/conversation-requests-list-toggle.tsx +++ b/features/conversation-requests-list/conversation-requests-list-toggle.tsx @@ -1,4 +1,5 @@ import { Chip } from "@/design-system/chip"; +import { HStack } from "@/design-system/HStack"; import { useAppTheme } from "@/theme/useAppTheme"; import React from "react"; import { View } from "react-native"; @@ -17,9 +18,8 @@ export function ConversationRequestsToggle({ const { theme } = useAppTheme(); return ( - ))} - + ); } From 4e29a16ee171b209ce258ad32e61619f2b5309ec Mon Sep 17 00:00:00 2001 From: Louis Rouffineau Date: Tue, 18 Feb 2025 17:26:01 +0100 Subject: [PATCH 5/6] Implement composable `Chip` component --- design-system/chip.examples.tsx | 67 +++++++++ design-system/chip.tsx | 139 ++++++++++++++---- .../conversation-requests-list-toggle.tsx | 6 +- .../conversation-create-search-input.tsx | 10 +- features/profiles/components/social-names.tsx | 5 +- 5 files changed, 187 insertions(+), 40 deletions(-) create mode 100644 design-system/chip.examples.tsx diff --git a/design-system/chip.examples.tsx b/design-system/chip.examples.tsx new file mode 100644 index 000000000..ce524e6be --- /dev/null +++ b/design-system/chip.examples.tsx @@ -0,0 +1,67 @@ +import React from "react"; +import { View } from "react-native"; +import { Chip } from "./chip"; +import { Icon } from "@/design-system/Icon/Icon"; + +export default { + title: "Design System/Chip", +}; + +export function ChipExamples() { + return ( + + {/* Basic usage with just text */} + + Basic Chip + + + {/* With avatar and text */} + + + John Doe + + + {/* With custom icon */} + + + + + With Icon + + + {/* Filled variant */} + + Filled Chip + + + {/* Disabled state */} + + Disabled Chip + + + {/* Selected state with medium size */} + + Selected (Medium) + + + {/* Interactive chip with onPress */} + alert("Chip pressed!")}> + Pressable Chip + + + {/* Combination of multiple props */} + alert("Complex chip pressed!")} + > + + + + + Complex Chip + + + ); +} diff --git a/design-system/chip.tsx b/design-system/chip.tsx index 70d12b544..de7c2d508 100644 --- a/design-system/chip.tsx +++ b/design-system/chip.tsx @@ -1,58 +1,117 @@ -import { Avatar } from "@/components/Avatar"; +import { Avatar as RNAvatar } from "@/components/Avatar"; import { Center } from "@/design-system/Center"; -import { Text } from "@/design-system/Text"; +import { Text as RNText } from "@/design-system/Text"; import { useAppTheme } from "@/theme/useAppTheme"; import React from "react"; -import { Pressable, StyleProp, ViewStyle } from "react-native"; +import { StyleProp, ViewStyle, TextStyle } from "react-native"; +import { AnimatedPressable } from "@design-system/Pressable"; -type ChipProps = { - name: string; - avatarUri?: string; +const ChipContext = React.createContext<{ + size: "sm" | "md"; + disabled?: boolean; + isSelected?: boolean; +} | null>(null); + +function useChipContext() { + const context = React.useContext(ChipContext); + if (!context) { + throw new Error("Chip components must be used within a Chip"); + } + return context; +} + +type IChipProps = { isSelected?: boolean; onPress?: () => void; size?: "sm" | "md"; - showAvatar?: boolean; + disabled?: boolean; + variant?: "filled" | "outlined"; + children: React.ReactNode; }; export function Chip({ - name, - avatarUri, + children, isSelected, onPress, size = "sm", - showAvatar = true, -}: ChipProps) { - const { theme } = useAppTheme(); + disabled, + variant = "outlined", +}: IChipProps) { + const styles = useChipStyles({ variant }); - const styles = useChipStyles(); + return ( + + [ + styles.$container, + isSelected && styles.$selectedContainer, + disabled && styles.$disabledContainer, + pressed && styles.$pressedContainer, + ]} + > +
{children}
+
+
+ ); +} + +type IChipTextProps = { + children: string; + style?: StyleProp; +}; + +export function ChipText({ children, style }: IChipTextProps) { + const { size, disabled, isSelected } = useChipContext(); + const styles = useChipStyles({ variant: "outlined" }); return ( - -
- {showAvatar && ( - - )} - {name} -
-
+ {children} + ); } -export function useChipStyles() { +type IChipIconProps = { + children: React.ReactNode; +}; + +export function ChipIcon({ children }: IChipIconProps) { + return children; +} + +type IChipAvatarProps = { + uri?: string; + name: string; +}; + +export function ChipAvatar({ uri, name }: IChipAvatarProps) { + const { theme } = useAppTheme(); + return ; +} + +export function useChipStyles({ variant }: { variant: "filled" | "outlined" }) { const { theme } = useAppTheme(); const $container = { borderRadius: theme.spacing.xs, - borderWidth: theme.borderWidth.sm, + borderWidth: variant === "outlined" ? theme.borderWidth.sm : 0, borderColor: theme.colors.border.subtle, - backgroundColor: theme.colors.background.surface, - paddingVertical: theme.spacing.xxs - theme.borderWidth.sm * 2, + backgroundColor: + variant === "outlined" + ? theme.colors.background.surface + : theme.colors.fill.minimal, + paddingVertical: + theme.spacing.xxs - + (variant === "outlined" ? theme.borderWidth.sm * 2 : 0), paddingHorizontal: theme.spacing.xs, } satisfies StyleProp; @@ -70,8 +129,26 @@ export function useChipStyles() { $container, $selectedContainer: { backgroundColor: theme.colors.fill.minimal, - borderColor: theme.colors.fill.minimal, + borderColor: + variant === "outlined" ? theme.colors.fill.minimal : "transparent", }, $content, + $disabledContainer: { + opacity: 0.5, + }, + $pressedContainer: { + opacity: 0.8, + }, + $disabledText: { + color: theme.colors.text.inactive, + }, + $selectedText: { + color: theme.colors.text.primary, + }, } as const; } + +// Add Chip compound components to the main export +Chip.Text = ChipText; +Chip.Icon = ChipIcon; +Chip.Avatar = ChipAvatar; diff --git a/features/conversation-requests-list/conversation-requests-list-toggle.tsx b/features/conversation-requests-list/conversation-requests-list-toggle.tsx index fde5058e6..cad7178c9 100644 --- a/features/conversation-requests-list/conversation-requests-list-toggle.tsx +++ b/features/conversation-requests-list/conversation-requests-list-toggle.tsx @@ -29,11 +29,11 @@ export function ConversationRequestsToggle({ onSelect(index)} - showAvatar={false} - /> + > + {option} + ))} diff --git a/features/conversation/conversation-create/components/conversation-create-search-input.tsx b/features/conversation/conversation-create/components/conversation-create-search-input.tsx index 4daae3290..3e9fb7a3f 100644 --- a/features/conversation/conversation-create/components/conversation-create-search-input.tsx +++ b/features/conversation/conversation-create/components/conversation-create-search-input.tsx @@ -123,7 +123,8 @@ export const ConversationCreateSearchInput = memo( function useConversationCreateSearchStyles() { const { theme } = useAppTheme(); - const chipStyles = useChipStyles(); + + const chipStyles = useChipStyles({ variant: "outlined" }); const $container = { backgroundColor: theme.colors.background.surfaceless, @@ -178,12 +179,13 @@ const UserChip = memo(function UserChip(props: { inboxId: InboxId }) { return ( + > + + {name} + ); }); diff --git a/features/profiles/components/social-names.tsx b/features/profiles/components/social-names.tsx index 0319b25d6..68c9633a2 100644 --- a/features/profiles/components/social-names.tsx +++ b/features/profiles/components/social-names.tsx @@ -52,9 +52,10 @@ export function SocialNames({ socials }: ISocialNamesProps) { return items?.map((item) => ( handleNamePress(getValue(item))} - /> + > + {getValue(item)} + )); }; From 3ad20ae348b17873f5da5128d4472e349df97f68 Mon Sep 17 00:00:00 2001 From: Louis Rouffineau Date: Tue, 18 Feb 2025 17:48:33 +0100 Subject: [PATCH 6/6] Fixes --- design-system/chip.tsx | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/design-system/chip.tsx b/design-system/chip.tsx index de7c2d508..fc05a6754 100644 --- a/design-system/chip.tsx +++ b/design-system/chip.tsx @@ -1,13 +1,15 @@ -import { Avatar as RNAvatar } from "@/components/Avatar"; +import { Avatar } from "@/components/Avatar"; import { Center } from "@/design-system/Center"; -import { Text as RNText } from "@/design-system/Text"; +import { Text } from "@/design-system/Text"; import { useAppTheme } from "@/theme/useAppTheme"; import React from "react"; import { StyleProp, ViewStyle, TextStyle } from "react-native"; import { AnimatedPressable } from "@design-system/Pressable"; +type IChipSize = "sm" | "md"; + const ChipContext = React.createContext<{ - size: "sm" | "md"; + size: IChipSize; disabled?: boolean; isSelected?: boolean; } | null>(null); @@ -23,7 +25,7 @@ function useChipContext() { type IChipProps = { isSelected?: boolean; onPress?: () => void; - size?: "sm" | "md"; + size?: IChipSize; disabled?: boolean; variant?: "filled" | "outlined"; children: React.ReactNode; @@ -44,11 +46,10 @@ export function Chip({ [ + style={() => [ styles.$container, isSelected && styles.$selectedContainer, disabled && styles.$disabledContainer, - pressed && styles.$pressedContainer, ]} >
{children}
@@ -67,7 +68,7 @@ export function ChipText({ children, style }: IChipTextProps) { const styles = useChipStyles({ variant: "outlined" }); return ( - {children} - + ); } @@ -95,7 +96,7 @@ type IChipAvatarProps = { export function ChipAvatar({ uri, name }: IChipAvatarProps) { const { theme } = useAppTheme(); - return ; + return ; } export function useChipStyles({ variant }: { variant: "filled" | "outlined" }) { @@ -136,9 +137,6 @@ export function useChipStyles({ variant }: { variant: "filled" | "outlined" }) { $disabledContainer: { opacity: 0.5, }, - $pressedContainer: { - opacity: 0.8, - }, $disabledText: { color: theme.colors.text.inactive, },