From 73092633a0805aae6df233212680dadce753feb0 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 11 May 2026 16:04:34 +0300 Subject: [PATCH] empty states --- ...bleSmile_stroke2_corner0_rounded_large.svg | 1 + .../inbox_stroke2_corner2_rounded_large.svg | 1 + src/components/Button.tsx | 3 +- src/components/icons/Bubble.tsx | 11 +++- src/components/icons/Inbox.tsx | 5 ++ src/components/icons/TEMPLATE.tsx | 32 +++------- src/components/icons/common.tsx | 1 + src/screens/Messages/ChatList.tsx | 60 +++++++++++++++---- .../splitView/MessagesSplitViewLayout.tsx | 5 +- .../Settings/components/SettingsList.tsx | 3 +- src/view/com/util/EmptyState.tsx | 18 ++++-- 11 files changed, 97 insertions(+), 43 deletions(-) create mode 100644 assets/icons/bubbleSmile_stroke2_corner0_rounded_large.svg create mode 100644 assets/icons/inbox_stroke2_corner2_rounded_large.svg diff --git a/assets/icons/bubbleSmile_stroke2_corner0_rounded_large.svg b/assets/icons/bubbleSmile_stroke2_corner0_rounded_large.svg new file mode 100644 index 0000000000..5d0f9fddca --- /dev/null +++ b/assets/icons/bubbleSmile_stroke2_corner0_rounded_large.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/inbox_stroke2_corner2_rounded_large.svg b/assets/icons/inbox_stroke2_corner2_rounded_large.svg new file mode 100644 index 0000000000..0804046fa2 --- /dev/null +++ b/assets/icons/inbox_stroke2_corner2_rounded_large.svg @@ -0,0 +1 @@ + diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 45c54d4a7a..a7acc8508b 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -811,14 +811,15 @@ export function ButtonIcon({ * also so that we can calculate transforms. */ const iconSize = { + '2xs': 8, xs: 12, sm: 16, md: 18, lg: 24, xl: 28, - '2xs': 8, '2xl': 32, '3xl': 40, + '4xl': 48, }[iconSizeShorthand] /* diff --git a/src/components/icons/Bubble.tsx b/src/components/icons/Bubble.tsx index 3654fcf785..8657d8220e 100644 --- a/src/components/icons/Bubble.tsx +++ b/src/components/icons/Bubble.tsx @@ -1,4 +1,4 @@ -import {createSinglePathSVG} from './TEMPLATE' +import {createMultiPathSVG, createSinglePathSVG} from './TEMPLATE' export const BubbleQuestion_Stroke2_Corner0_Rounded = createSinglePathSVG({ path: 'M5.002 17.036V5h14v12.036h-3.986a1 1 0 0 0-.639.23l-2.375 1.968-2.344-1.965a1 1 0 0 0-.643-.233H5.002ZM20.002 3h-16a1 1 0 0 0-1 1v14.036a1 1 0 0 0 1 1h4.65l2.704 2.266a1 1 0 0 0 1.28.004l2.74-2.27h4.626a1 1 0 0 0 1-1V4a1 1 0 0 0-1-1Zm-7.878 3.663c-1.39 0-2.5 1.135-2.5 2.515a1 1 0 0 0 2 0c0-.294.232-.515.5-.515a.507.507 0 0 1 .489.6.174.174 0 0 1-.027.048 1.1 1.1 0 0 1-.267.226c-.508.345-1.128.923-1.286 1.978a1 1 0 1 0 1.978.297.762.762 0 0 1 .14-.359c.063-.086.155-.169.293-.262.436-.297 1.18-.885 1.18-2.013 0-1.38-1.11-2.515-2.5-2.515ZM12 15.75a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5Z', @@ -15,3 +15,12 @@ export const Bubble_Stroke2_Corner3_Rounded = createSinglePathSVG({ export const Bubbles_Stroke2_Corner2_Rounded = createSinglePathSVG({ path: 'M6.002 6a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v6a3 3 0 0 1-3 3h-1v1a3 3 0 0 1-3 3h-4.24l-4.274 2.374a1 1 0 0 1-1.486-.874V19a3 3 0 0 1-3-3v-6a3 3 0 0 1 3-3h1V6Zm-1 3a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h1a1 1 0 0 1 1 1v.8l3.015-1.674a1 1 0 0 1 .485-.126h4.5a1 1 0 0 0 1-1v-1.933a1 1 0 0 1 0-.134V10a1 1 0 0 0-1-1h-10Zm13 4v-3a3 3 0 0 0-3-3h-7V6a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1v6a1 1 0 0 1-1 1h-1Z', }) + +export const BubbleSmile_Stroke2_Corner2_Rounded_Large = createMultiPathSVG({ + viewBox: '0 0 64 64', + paths: [ + 'M55 32C55 19.298 44.703 9 32 9S9 19.298 9 32c0 3.463.765 6.745 2.134 9.688.358.769.479 1.658.267 2.525l-2.076 8.51a1.667 1.667 0 0 0 1.987 2.02l8.876-2.005a3.84 3.84 0 0 1 2.412.26A22.9 22.9 0 0 0 32 55c12.703 0 23-10.297 23-23Zm2 0c0 13.807-11.193 25-25 25-3.638 0-7.098-.778-10.219-2.177a1.84 1.84 0 0 0-1.152-.133l-8.877 2.004c-2.655.6-5.015-1.802-4.37-4.446l2.077-8.51c.094-.384.046-.809-.139-1.207A24.9 24.9 0 0 1 7 32C7 18.193 18.193 7 32 7s25 11.193 25 25Z', + 'M17.667 32a2.333 2.333 0 1 0 4.667 0 2.333 2.333 0 0 0-4.667 0Zm24 0a2.334 2.334 0 1 0 4.667 0 2.334 2.334 0 0 0-4.667 0Z', + 'M35.137 37.215a1 1 0 0 1 1.414 1.414 6.143 6.143 0 0 1-8.687 0 1 1 0 0 1 1.415-1.414 4.14 4.14 0 0 0 5.858 0Z', + ], +}) diff --git a/src/components/icons/Inbox.tsx b/src/components/icons/Inbox.tsx index fad9fdbb9d..d6eb34edb5 100644 --- a/src/components/icons/Inbox.tsx +++ b/src/components/icons/Inbox.tsx @@ -3,3 +3,8 @@ import {createSinglePathSVG} from './TEMPLATE' export const Inbox_Stroke2_Corner2_Rounded = createSinglePathSVG({ path: 'M19 14h-2.417a5 5 0 0 1-9.166 0H5v4a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-4Zm0-8a1 1 0 0 0-.898-.995L18 5H6a1 1 0 0 0-1 1v6h3.126a1 1 0 0 1 .969.751 3.001 3.001 0 0 0 5.81 0l.056-.16a1 1 0 0 1 .913-.591H19V6Zm2 12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3h12l.154.004A3 3 0 0 1 21 6v12Z', }) + +export const Inbox_Stroke2_Corner2_Rounded_Large = createSinglePathSVG({ + viewBox: '0 0 64 64', + path: 'M57.666 33.416q-.001-.21-.022-.419L57.6 33H44a4.33 4.33 0 0 0-3.466 1.734l-.8 1.066a6.34 6.34 0 0 1-5.068 2.533h-5.333a6.33 6.33 0 0 1-5.066-2.533l-.8-1.066A4.33 4.33 0 0 0 20 33H6.4l-.045-.003a4 4 0 0 0-.022.42v11.916a4.333 4.333 0 0 0 4.333 4.333h42.667a4.333 4.333 0 0 0 4.333-4.333V33.416Zm-38.57-19.083a4.33 4.33 0 0 0-3.764 2.184L7.057 31H20c1.994 0 3.87.939 5.067 2.533l.8 1.067a4.33 4.33 0 0 0 3.466 1.733h5.333a4.33 4.33 0 0 0 3.467-1.733l.8-1.067A6.34 6.34 0 0 1 44 31h12.944l-8.277-14.483a4.33 4.33 0 0 0-3.762-2.184h-25.81Zm40.57 31a6.333 6.333 0 0 1-6.333 6.333H10.666a6.333 6.333 0 0 1-6.333-6.333V33.416c0-1.102.288-2.185.835-3.142l8.428-14.75a6.33 6.33 0 0 1 5.5-3.19h25.809a6.33 6.33 0 0 1 5.499 3.19l8.427 14.75h.001c.547.957.834 2.04.834 3.142v11.917Z', +}) diff --git a/src/components/icons/TEMPLATE.tsx b/src/components/icons/TEMPLATE.tsx index 4ca1674919..4feaaef359 100644 --- a/src/components/icons/TEMPLATE.tsx +++ b/src/components/icons/TEMPLATE.tsx @@ -51,7 +51,7 @@ export function createSinglePathSVG({ fill="none" {...rest} ref={ref} - viewBox={viewBox || '0 0 24 24'} + viewBox={viewBox ?? '0 0 24 24'} width={size} height={size} style={[style]}> @@ -71,7 +71,13 @@ export function createSinglePathSVG({ }) } -export function createSinglePathSVG2({path}: {path: string}) { +export function createMultiPathSVG({ + paths, + viewBox, +}: { + paths: string[] + viewBox?: string +}) { return forwardRef(function LogoImpl(props, ref) { const {fill, size, style, gradient, ...rest} = useCommonSVGProps(props) @@ -80,27 +86,7 @@ export function createSinglePathSVG2({path}: {path: string}) { fill="none" {...rest} ref={ref} - viewBox="0 0 24 24" - width={size} - height={size} - style={style}> - {gradient} - - - ) - }) -} - -export function createMultiPathSVG({paths}: {paths: string[]}) { - return forwardRef(function LogoImpl(props, ref) { - const {fill, size, style, gradient, ...rest} = useCommonSVGProps(props) - - return ( - diff --git a/src/components/icons/common.tsx b/src/components/icons/common.tsx index 46df59207c..448808f830 100644 --- a/src/components/icons/common.tsx +++ b/src/components/icons/common.tsx @@ -21,6 +21,7 @@ export const sizes = { xl: 28, '2xl': 32, '3xl': 48, + '4xl': 64, } as const export function useCommonSVGProps(props: Props) { diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx index 9d87db6360..a9a74cfec8 100644 --- a/src/screens/Messages/ChatList.tsx +++ b/src/screens/Messages/ChatList.tsx @@ -28,9 +28,11 @@ import {type DialogControlProps, useDialogControl} from '#/components/Dialog' import {NewChat} from '#/components/dms/dialogs/NewChatDialog' import {useRefreshOnFocus} from '#/components/hooks/useRefreshOnFocus' import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as RetryIcon} from '#/components/icons/ArrowRotate' +import {BubbleSmile_Stroke2_Corner2_Rounded_Large as BubbleSmileIcon} from '#/components/icons/Bubble' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo' +import {Inbox_Stroke2_Corner2_Rounded_Large as InboxLargeIcon} from '#/components/icons/Inbox' import { - Message_Stroke2_Corner0_Rounded as MessageIcon, + MessagePlus_Stroke2_Corner0_Rounded as MessagePlusIcon, MessagePlus_Stroke2_Corner0_Rounded as NewChatIcon, } from '#/components/icons/Message' import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2' @@ -90,6 +92,7 @@ export function MessagesScreen(props: Props) { export function MessagesScreenInner({navigation, route}: Props) { const {isWithinSplitView} = useIsWithinSplitView() const {t: l} = useLingui() + const t = useTheme() const newChatControl = useDialogControl() const pushToConversation = route.params?.pushToConversation @@ -132,15 +135,18 @@ export function MessagesScreenInner({navigation, route}: Props) { return ( <> @@ -152,18 +158,37 @@ export function MessagesScreenInner({navigation, route}: Props) { return (
- +
) } -export function ChatList({selectedChat}: {selectedChat?: string}) { +export function ChatList({ + selectedChat, + newChatControl, +}: { + selectedChat?: string + newChatControl: DialogControlProps +}) { const t = useTheme() const {t: l} = useLingui() const scrollElRef: ListRef = useAnimatedRef() const {isWithinSplitView} = useIsWithinSplitView() + const openChatControl = useCallback(() => { + newChatControl.open() + }, [newChatControl]) + + const requireEmailVerification = useRequireEmailVerification() + const wrappedOpenChatControl = requireEmailVerification(openChatControl, { + instructions: [ + + Before you can message another user, you must first verify your email. + , + ], + }) + const initialNumToRender = useInitialNumToRender({minItemHeight: 80}) const [isPTRing, setIsPTRing] = useState(false) @@ -289,14 +314,27 @@ export function ChatList({selectedChat}: {selectedChat?: string}) { ) : isWithinSplitView ? ( ) : ( )} diff --git a/src/screens/Messages/components/splitView/MessagesSplitViewLayout.tsx b/src/screens/Messages/components/splitView/MessagesSplitViewLayout.tsx index 871063f0d8..8b8da31eb1 100644 --- a/src/screens/Messages/components/splitView/MessagesSplitViewLayout.tsx +++ b/src/screens/Messages/components/splitView/MessagesSplitViewLayout.tsx @@ -94,7 +94,10 @@ function MessagesSplitViewLayout({children, navigation, route}: LayoutProps) { {width: centerColumnOffset ? 300 : 350}, ]}> - + diff --git a/src/screens/Settings/components/SettingsList.tsx b/src/screens/Settings/components/SettingsList.tsx index 27cc29096a..b23eab85f4 100644 --- a/src/screens/Settings/components/SettingsList.tsx +++ b/src/screens/Settings/components/SettingsList.tsx @@ -197,11 +197,12 @@ export function ItemIcon({ '2xs': 8, xs: 12, sm: 16, - md: 20, + md: 18, lg: 24, xl: 28, '2xl': 32, '3xl': 40, + '4xl': 48, }[size] const color = diff --git a/src/view/com/util/EmptyState.tsx b/src/view/com/util/EmptyState.tsx index 8362cf2918..59799cf949 100644 --- a/src/view/com/util/EmptyState.tsx +++ b/src/view/com/util/EmptyState.tsx @@ -3,19 +3,26 @@ import {type StyleProp, type TextStyle, type ViewStyle} from 'react-native' import {View} from 'react-native' import {atoms as a, useBreakpoints, useTheme} from '#/alf' -import {Button, type ButtonProps, ButtonText} from '#/components/Button' +import { + Button, + ButtonIcon, + type ButtonProps, + ButtonText, +} from '#/components/Button' import {EditBig_Stroke1_Corner0_Rounded as EditIcon} from '#/components/icons/EditBig' import {Text} from '#/components/Typography' export type EmptyStateButtonProps = Omit & { label: string text: string + icon?: React.ComponentProps['icon'] } export function EmptyState({ testID, icon, iconSize = '3xl', + iconColor, message, style, textStyle, @@ -23,7 +30,8 @@ export function EmptyState({ }: { testID?: string icon?: React.ComponentType | React.ReactElement | null - iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' + iconSize?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | '3xl' | '4xl' + iconColor?: string message: string style?: StyleProp textStyle?: StyleProp @@ -57,8 +65,7 @@ export function EmptyState({ return ( ) } @@ -99,8 +106,9 @@ export function EmptyState({ {message} {button && ( - +