Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Conversation requests screen UI components #1608

Merged
merged 6 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions design-system/chip.examples.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<View style={{ gap: 10, padding: 10 }}>
{/* Basic usage with just text */}
<Chip>
<Chip.Text>Basic Chip</Chip.Text>
</Chip>

{/* With avatar and text */}
<Chip>
<Chip.Avatar uri="https://example.com/avatar.jpg" name="John Doe" />
<Chip.Text>John Doe</Chip.Text>
</Chip>

{/* With custom icon */}
<Chip>
<Chip.Icon>
<Icon icon="star" size={16} />
</Chip.Icon>
<Chip.Text>With Icon</Chip.Text>
</Chip>

{/* Filled variant */}
<Chip variant="filled">
<Chip.Text>Filled Chip</Chip.Text>
</Chip>

{/* Disabled state */}
<Chip disabled>
<Chip.Text>Disabled Chip</Chip.Text>
</Chip>

{/* Selected state with medium size */}
<Chip size="md" isSelected>
<Chip.Text>Selected (Medium)</Chip.Text>
</Chip>

{/* Interactive chip with onPress */}
<Chip onPress={() => alert("Chip pressed!")}>
<Chip.Text>Pressable Chip</Chip.Text>
</Chip>

{/* Combination of multiple props */}
<Chip
variant="filled"
size="md"
isSelected
onPress={() => alert("Complex chip pressed!")}
>
<Chip.Avatar uri="https://example.com/avatar.jpg" name="Jane Smith" />
<Chip.Icon>
<Icon icon="checkmark" size={16} />
</Chip.Icon>
<Chip.Text>Complex Chip</Chip.Text>
</Chip>
</View>
);
}
139 changes: 108 additions & 31 deletions design-system/chip.tsx
Original file line number Diff line number Diff line change
@@ -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";
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why Text as RNText ? Why not just Text? same with RNAvatar

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! That should not have been there :)

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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should have type IChipSize = 'sm' | 'md'

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done!

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 (
<ChipContext.Provider value={{ size, disabled, isSelected }}>
<AnimatedPressable
onPress={onPress}
disabled={disabled}
style={({ pressed }) => [
styles.$container,
isSelected && styles.$selectedContainer,
disabled && styles.$disabledContainer,
pressed && styles.$pressedContainer,
]}
>
<Center style={styles.$content}>{children}</Center>
</AnimatedPressable>
</ChipContext.Provider>
);
}

type IChipTextProps = {
children: string;
style?: StyleProp<TextStyle>;
};

export function ChipText({ children, style }: IChipTextProps) {
const { size, disabled, isSelected } = useChipContext();
const styles = useChipStyles({ variant: "outlined" });

return (
<Pressable
onPress={onPress}
style={[styles.$container, isSelected && styles.$selectedContainer]}
<RNText
preset={size === "sm" ? "small" : "body"}
style={[
style,
disabled && styles.$disabledText,
isSelected && styles.$selectedText,
]}
>
<Center
// {...debugBorder()}
style={styles.$content}
>
{showAvatar && (
<Avatar uri={avatarUri} name={name} size={theme.avatarSize.xs} />
)}
<Text preset={size === "sm" ? "small" : "body"}>{name}</Text>
</Center>
</Pressable>
{children}
</RNText>
);
}

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 <RNAvatar uri={uri} name={name} size={theme.avatarSize.xs} />;
}

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<ViewStyle>;

Expand All @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ export function ConversationRequestsToggle({
<View key={index} style={{ flex: 1 }}>
<Chip
key={index}
name={option}
isSelected={index === selectedIndex}
onPress={() => onSelect(index)}
showAvatar={false}
/>
>
<Chip.Text>{option}</Chip.Text>
</Chip>
</View>
))}
</HStack>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -178,12 +179,13 @@ const UserChip = memo(function UserChip(props: { inboxId: InboxId }) {

return (
<Chip
avatarUri={avatarUri}
name={name}
isSelected={selectedChipInboxId === inboxId}
onPress={handlePress}
size="md"
/>
>
<Chip.Avatar uri={avatarUri} name={name} />
<Chip.Text>{name}</Chip.Text>
</Chip>
);
});

Expand Down
5 changes: 3 additions & 2 deletions features/profiles/components/social-names.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ export function SocialNames({ socials }: ISocialNamesProps) {
return items?.map((item) => (
<Chip
key={getValue(item)}
name={getValue(item)}
onPress={() => handleNamePress(getValue(item))}
/>
>
<Chip.Text>{getValue(item)}</Chip.Text>
</Chip>
));
};

Expand Down
Loading