Skip to content

Commit d6ce49d

Browse files
committed
Merge branch 'mh/icon-cmp' into 'develop'
Single icon component See merge request polycentric/polycentric!526
2 parents f796c22 + fb06196 commit d6ce49d

31 files changed

Lines changed: 202 additions & 249 deletions
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { PaletteColorToken, useTheme } from '@/src/common/theme';
2+
import {
3+
Ionicons,
4+
MaterialCommunityIcons,
5+
MaterialIcons,
6+
} from '@expo/vector-icons';
7+
import { ComponentProps, ComponentType, ReactNode } from 'react';
8+
9+
function defineIcon<G extends string>(
10+
IconSet: ComponentType<any> & { glyphMap: Record<G, number | string> },
11+
name: NoInfer<G>,
12+
) {
13+
return {
14+
iconSet: IconSet,
15+
name,
16+
render: (p: object) => <IconSet name={name} {...p} />,
17+
};
18+
}
19+
20+
/**
21+
* Map of icon names to their icon set definitions
22+
*/
23+
export const IconsMap = {
24+
add: defineIcon(Ionicons, 'add-circle'),
25+
addOutline: defineIcon(Ionicons, 'add-circle-outline'),
26+
arrowBack: defineIcon(Ionicons, 'arrow-back'),
27+
ban: defineIcon(Ionicons, 'ban'),
28+
camera: defineIcon(Ionicons, 'camera-outline'),
29+
checkmark: defineIcon(Ionicons, 'checkmark'),
30+
checkmarkCircle: defineIcon(Ionicons, 'checkmark-circle'),
31+
checkmarkSharp: defineIcon(Ionicons, 'checkmark-sharp'),
32+
chevronBack: defineIcon(Ionicons, 'chevron-back'),
33+
chevronDown: defineIcon(Ionicons, 'chevron-down'),
34+
chevronForward: defineIcon(Ionicons, 'chevron-forward'),
35+
close: defineIcon(Ionicons, 'close'),
36+
closeSharp: defineIcon(Ionicons, 'close-sharp'),
37+
copy: defineIcon(Ionicons, 'copy-outline'),
38+
dotsVertical: defineIcon(MaterialCommunityIcons, 'dots-vertical'),
39+
emoji: defineIcon(MaterialIcons, 'emoji-emotions'),
40+
flag: defineIcon(Ionicons, 'flag-outline'),
41+
home: defineIcon(Ionicons, 'home-outline'),
42+
image: defineIcon(Ionicons, 'image-outline'),
43+
images: defineIcon(Ionicons, 'images-outline'),
44+
more: defineIcon(Ionicons, 'ellipsis-horizontal'),
45+
personAdd: defineIcon(Ionicons, 'person-add'),
46+
personAddOutline: defineIcon(Ionicons, 'person-add-outline'),
47+
personOutline: defineIcon(Ionicons, 'person-outline'),
48+
personRemove: defineIcon(Ionicons, 'person-remove'),
49+
quote: defineIcon(Ionicons, 'create'),
50+
reaction: defineIcon(Ionicons, 'heart'),
51+
reactionOutline: defineIcon(Ionicons, 'heart-outline'),
52+
remove: defineIcon(Ionicons, 'remove-circle-outline'),
53+
reply: defineIcon(Ionicons, 'chatbubble-outline'),
54+
repost: defineIcon(Ionicons, 'repeat'),
55+
search: defineIcon(Ionicons, 'search'),
56+
searchOutline: defineIcon(Ionicons, 'search-outline'),
57+
settings: defineIcon(Ionicons, 'settings-outline'),
58+
share: defineIcon(Ionicons, 'share-social-outline'),
59+
themeDark: defineIcon(Ionicons, 'moon'),
60+
themeLight: defineIcon(Ionicons, 'sunny'),
61+
time: defineIcon(Ionicons, 'time-outline'),
62+
trash: defineIcon(Ionicons, 'trash-outline'),
63+
trashBin: defineIcon(Ionicons, 'trash-bin'),
64+
};
65+
66+
export type IconName = keyof typeof IconsMap;
67+
68+
/** A palette token (resolved against the theme) or any raw color string. */
69+
type IconColor = PaletteColorToken | (string & {});
70+
71+
type IconProps = Omit<
72+
ComponentProps<typeof Ionicons>,
73+
'name' | 'color' | 'size'
74+
> & {
75+
name: IconName;
76+
size?: number;
77+
/** Palette token (resolved against the active theme) or a raw color string. */
78+
color?: IconColor;
79+
};
80+
81+
export default function Icon({
82+
name,
83+
size = 16,
84+
color = 'neutral_1000',
85+
...rest
86+
}: IconProps): ReactNode {
87+
const { theme } = useTheme();
88+
89+
// Palette tokens resolve against the theme; anything else is passed through as
90+
// a literal color (e.g. a resolved color injected via render-prop / cloneElement).
91+
const resolvedColor =
92+
color in theme.palette ? theme.palette[color as PaletteColorToken] : color;
93+
94+
return IconsMap[name].render({ size, color: resolvedColor, ...rest });
95+
}

apps/polycentric/src/common/components/composites/BackButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { IconButton } from '@/src/common/components/primitives';
2-
import { Ionicons } from '@expo/vector-icons';
2+
import Icon from '@/src/common/components/Icon';
33

44
interface BackButtonProps {
55
onPress: () => void;
@@ -8,7 +8,7 @@ interface BackButtonProps {
88
export function BackButton({ onPress }: BackButtonProps) {
99
return (
1010
<IconButton
11-
icon={(props) => <Ionicons name="arrow-back" {...props} />}
11+
icon={(props) => <Icon name="arrowBack" {...props} />}
1212
onPress={onPress}
1313
/>
1414
);

apps/polycentric/src/common/components/composites/CloseButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { IconButton } from '@/src/common/components/primitives';
2-
import { Ionicons } from '@expo/vector-icons';
2+
import Icon from '@/src/common/components/Icon';
33

44
interface CloseButtonProps {
55
onPress: () => void;
@@ -8,7 +8,7 @@ interface CloseButtonProps {
88
export function CloseButton({ onPress }: CloseButtonProps) {
99
return (
1010
<IconButton
11-
icon={(props) => <Ionicons name="close" {...props} />}
11+
icon={(props) => <Icon name="close" {...props} />}
1212
onPress={onPress}
1313
/>
1414
);
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { PillButton } from '@/src/common/components/primitives';
2-
import { Ionicons } from '@expo/vector-icons';
2+
import Icon from '@/src/common/components/Icon';
33

44
export function EditButton() {
55
return (
66
<PillButton
77
onPress={() => {}}
88
title="Edit"
99
variant="primary"
10-
icon={(props) => <Ionicons name="settings-outline" {...props} />}
10+
icon={(props) => <Icon name="settings" {...props} />}
1111
/>
1212
);
1313
}

apps/polycentric/src/common/components/composites/SocialButton.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { PillButton } from '@/src/common/components/primitives';
2-
import { Ionicons } from '@expo/vector-icons';
2+
import Icon, { IconName } from '@/src/common/components/Icon';
33

44
type SocialAction = 'follow' | 'unfollow' | 'block' | 'unblock';
55

@@ -12,18 +12,18 @@ const CONFIG: Record<
1212
SocialAction,
1313
{
1414
title: string;
15-
icon: keyof typeof Ionicons.glyphMap;
15+
icon: IconName;
1616
variant: 'primary' | 'secondary' | 'destructive';
1717
}
1818
> = {
1919
follow: {
2020
title: 'Follow',
21-
icon: 'person-add',
21+
icon: 'personAdd',
2222
variant: 'primary',
2323
},
2424
unfollow: {
2525
title: 'Unfollow',
26-
icon: 'person-remove',
26+
icon: 'personRemove',
2727
variant: 'primary',
2828
},
2929
block: {
@@ -33,7 +33,7 @@ const CONFIG: Record<
3333
},
3434
unblock: {
3535
title: 'Unblock',
36-
icon: 'checkmark-circle',
36+
icon: 'checkmarkCircle',
3737
variant: 'secondary',
3838
},
3939
};
@@ -46,7 +46,7 @@ export function SocialButton({ action, onPress }: SocialButtonProps) {
4646
onPress={onPress}
4747
title={title}
4848
variant={variant}
49-
icon={(props) => <Ionicons name={icon} {...props} />}
49+
icon={(props) => <Icon name={icon} {...props} />}
5050
/>
5151
);
5252
}

apps/polycentric/src/common/components/layout/Layout.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import {
1919
} from 'react-native';
2020
import { useSafeAreaInsets } from 'react-native-safe-area-context';
2121

22+
import Icon from '@/src/common/components/Icon';
2223
import { IdentityFooter } from '@/src/features/core/identity/IdentityFooter';
23-
import { Ionicons } from '@expo/vector-icons';
2424
import BLUE_LOGO from '../../assets/images/polycentric-logo-blue-256.png';
2525
import WHITE_LOGO from '../../assets/images/polycentric-logo-white-256.png';
2626
import { FUTO_URL, openCompose } from '../../constants';
@@ -261,7 +261,7 @@ export const LeftSidebar = memo(function LeftSidebar({
261261
size="md"
262262
fullWidth={!narrowSidebar}
263263
icon={({ size, color }) => (
264-
<Ionicons name="add-circle" size={size} color={color} />
264+
<Icon name="add" size={size} color={color} />
265265
)}
266266
onPress={() => openCompose()}
267267
/>
@@ -350,10 +350,10 @@ export const RightSidebar = memo(function RightSidebar({
350350
onPress={toggleTheme}
351351
style={({ pressed }) => [pressed && { opacity: 0.65 }]}
352352
>
353-
<Ionicons
354-
name={theme.name === 'dark' ? 'moon' : 'sunny'}
353+
<Icon
354+
name={theme.name === 'dark' ? 'themeDark' : 'themeLight'}
355355
size={typography.fontSize.sm}
356-
color={theme.palette.neutral_500}
356+
color="neutral_500"
357357
/>
358358
</Pressable>
359359
{LINKS.map(({ text, href }) => (

apps/polycentric/src/common/components/layout/Topbar.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Link, router, useSegments } from 'expo-router';
22
import { Pressable, View } from 'react-native';
33
import { Atoms, Spacing, useTheme, withHexOpacity } from '../../theme';
44
import { Image } from 'expo-image';
5-
import { Ionicons } from '@expo/vector-icons';
5+
import Icon from '@/src/common/components/Icon';
66
import BLUE_LOGO from '../../assets/images/polycentric-logo-blue-256.png';
77
import WHITE_LOGO from '../../assets/images/polycentric-logo-white-256.png';
88
import { ProfileAvatar, Text } from '../primitives';
@@ -64,11 +64,7 @@ function Topbar({ title, center, right }: TopbarProps) {
6464
hitSlop={Spacing['lg']}
6565
style={({ pressed }) => [pressed && { opacity: 0.5 }]}
6666
>
67-
<Ionicons
68-
name="chevron-back"
69-
size={24}
70-
color={theme.palette.neutral_900}
71-
/>
67+
<Icon name="chevronBack" size={24} color="neutral_900" />
7268
</Pressable>
7369
) : (
7470
<ProfileAvatar

apps/polycentric/src/common/components/layout/nav/NavItem.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010

1111
import { Pressable, Text, useWindowDimensions, View } from 'react-native';
1212

13-
import MaterialIcons from '@expo/vector-icons/MaterialIcons';
1413
import { Atoms, Breakpoints, useTheme } from '@/src/common/theme';
1514
import { flattenHref } from '@/src/utils/router';
1615

apps/polycentric/src/common/components/layout/nav/VerticalNav.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Atoms } from '@/src/common/theme';
22
import { ComponentProps } from 'react';
33

44
import { useCurrentIdentity } from '@/src/common/lib/polycentric-hooks';
5-
import { Ionicons } from '@expo/vector-icons';
5+
import Icon from '@/src/common/components/Icon';
66
import { View } from 'react-native';
77
import { NavItem } from './NavItem';
88

@@ -23,18 +23,18 @@ export function VerticalNav({ style }: VerticalNavProps) {
2323
>
2424
<NavItem
2525
label="Home"
26-
icon={<Ionicons name="home-outline" size={24} />}
26+
icon={<Icon name="home" size={24} />}
2727
href="/feed"
2828
/>
2929
<NavItem
3030
label="Explore"
31-
icon={<Ionicons name="search-outline" size={24} />}
31+
icon={<Icon name="searchOutline" size={24} />}
3232
href="/explore"
3333
/>
3434
{identity?.identityKey && (
3535
<NavItem
3636
label="Profile"
37-
icon={<Ionicons name="person-outline" size={24} />}
37+
icon={<Icon name="personOutline" size={24} />}
3838
href={{
3939
pathname: '/[identityId]',
4040
params: { identityId: identity?.identityKey },
@@ -44,7 +44,7 @@ export function VerticalNav({ style }: VerticalNavProps) {
4444

4545
<NavItem
4646
label="Settings"
47-
icon={<Ionicons name="settings-outline" size={24} />}
47+
icon={<Icon name="settings" size={24} />}
4848
href="/settings"
4949
/>
5050
</View>

apps/polycentric/src/common/components/layout/topbar/SettingsButton.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Atoms, Spacing, useTheme } from '@/src/common/theme';
2-
import { Ionicons } from '@expo/vector-icons';
2+
import Icon from '@/src/common/components/Icon';
33
import { router } from 'expo-router';
44
import { Pressable } from 'react-native';
55

@@ -15,11 +15,7 @@ export function TopbarSettingsButton() {
1515
]}
1616
hitSlop={Spacing['lg']}
1717
>
18-
<Ionicons
19-
name="settings-outline"
20-
size={20}
21-
color={theme.palette.neutral_800}
22-
/>
18+
<Icon name="settings" size={20} color="neutral_800" />
2319
</Pressable>
2420
);
2521
}

0 commit comments

Comments
 (0)