Skip to content

Commit cc3e148

Browse files
committed
Refactor Phosphor setup to use typesafe string keys
Avoiding using the components directly avoids accidental imports, makes icon-using code simpler in lots of places, and makes it much easier to start setting up a progressive migration from FontAwesome to PI, by supporting array + string key in various APIs and handling both, until FA is gone.
1 parent d3da10c commit cc3e148

File tree

8 files changed

+51
-30
lines changed

8 files changed

+51
-30
lines changed

src/components/app.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import { PlanPicker } from './account/plan-picker';
3434
import { ModalOverlay } from './account/modal-overlay';
3535
import { CheckoutSpinner } from './account/checkout-spinner';
3636
import { HtmlContextMenu } from './html-context-menu';
37-
import { Icons } from '../icons';
3837

3938
const AppContainer = styled.div<{ inert?: boolean }>`
4039
display: flex;
@@ -110,15 +109,15 @@ class App extends React.Component<{
110109
{
111110
name: 'Intercept',
112111
title: `Connect clients to HTTP Toolkit (${Ctrl}+1)`,
113-
icon: Icons.Plugs,
112+
icon: 'Plugs',
114113
position: 'top',
115114
type: 'router',
116115
url: '/intercept'
117116
},
118117
{
119118
name: 'View',
120119
title: `View intercepted HTTP traffic (${Ctrl}+2)`,
121-
icon: Icons.MagnifyingGlass,
120+
icon: 'MagnifyingGlass',
122121
position: 'top',
123122
type: 'router',
124123
url: '/view'
@@ -134,7 +133,7 @@ class App extends React.Component<{
134133
? [{
135134
name: 'Mock',
136135
title: `Add rules to mock & rewrite HTTP traffic (${Ctrl}+3)`,
137-
icon: Icons.Pencil,
136+
icon: 'Pencil',
138137
position: 'top',
139138
type: 'router',
140139
url: '/mock'
@@ -146,7 +145,7 @@ class App extends React.Component<{
146145
? [{
147146
name: 'Send',
148147
title: `Send HTTP requests directly (${Ctrl}+4)`,
149-
icon: Icons.PaperPlaneTilt,
148+
icon: 'PaperPlaneTilt',
150149
position: 'top',
151150
type: 'router',
152151
url: '/send'
@@ -158,15 +157,15 @@ class App extends React.Component<{
158157
? {
159158
name: 'Settings',
160159
title: `Reconfigure HTTP Toolkit and manage your account (${Ctrl}+9)`,
161-
icon: Icons.GearSix,
160+
icon: 'GearSix',
162161
position: 'bottom',
163162
type: 'router',
164163
url: '/settings'
165164
}
166165
: {
167166
name: 'Get Pro',
168167
title: "Sign up for HTTP Toolkit Pro",
169-
icon: Icons.Star,
168+
icon: 'Star',
170169
position: 'bottom',
171170
type: 'callback',
172171
onClick: () => this.props.accountStore.getPro('sidebar')
@@ -176,7 +175,7 @@ class App extends React.Component<{
176175
{
177176
name: 'Give feedback',
178177
title: "Suggest features or report issues",
179-
icon: Icons.ChatText,
178+
icon: 'ChatText',
180179
position: 'bottom',
181180
highlight: true,
182181
type: 'web',

src/components/common/empty-state.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import * as React from 'react';
22
import * as _ from 'lodash';
33

44
import { styled } from '../../styles'
5-
import { PhosphorIcon } from '../../icons';
5+
import { IconKey, PhosphorIcon } from '../../icons';
66

77
export const EmptyState = styled((props: React.HTMLAttributes<HTMLDivElement> & {
88
className?: string,
9-
icon: PhosphorIcon,
9+
icon: IconKey,
1010
children?: React.ReactNode
1111
}) => (
1212
<div className={props.className}>
13-
<props.icon />
13+
<PhosphorIcon icon={props.icon} />
1414
{ props.children && <>
1515
<br/>
1616
{ props.children }

src/components/common/icon-button.tsx

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import * as React from 'react';
22

33
import { styled } from '../../styles'
4-
import { Icon, IconProp } from '../../icons';
4+
import { Icon, IconProp, IconKey, PhosphorIcon } from '../../icons';
55

66
import { UnstyledButton, UnstyledButtonLink } from './inputs';
77

88
export const IconButton = styled((p: {
99
className?: string,
1010
title: string,
11-
icon: IconProp,
11+
icon: IconProp | IconKey,
1212
disabled?: boolean,
1313
fixedWidth?: boolean,
1414
tabIndex?: number,
@@ -23,10 +23,16 @@ export const IconButton = styled((p: {
2323
onClick={p.onClick}
2424
onKeyDown={p.onKeyDown}
2525
>
26-
<Icon
27-
icon={p.icon}
28-
fixedWidth={p.fixedWidth ? true : false}
29-
/>
26+
{ Array.isArray(p.icon)
27+
? <Icon
28+
icon={p.icon}
29+
fixedWidth={p.fixedWidth ? true : false}
30+
/>
31+
: <PhosphorIcon
32+
icon={p.icon as IconKey}
33+
size='1.25em'
34+
/>
35+
}
3036
</UnstyledButton>
3137
)`
3238
color: ${p => p.theme.mainColor};
@@ -43,6 +49,10 @@ export const IconButton = styled((p: {
4349
color: ${p => p.theme.popColor};
4450
}
4551
}
52+
53+
.phosphor-icon {
54+
margin: 0 -3px; /* Fix alignment with FontAwesome in rows e.g. View right footer */
55+
}
4656
`;
4757

4858
export const IconButtonLink = styled((p: {

src/components/sidebar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import { Link, Match } from '@reach/router';
55
import * as dedent from 'dedent';
66

77
import { styled, css, Theme } from '../styles';
8-
import { PhosphorIcon } from '../icons';
8+
import { IconKey, PhosphorIcon } from '../icons';
99
import { UI_VERSION, desktopVersion, serverVersion } from '../services/service-versions';
1010

1111
import { UnstyledButton } from './common/inputs';
1212
import logo from '../images/logo-icon.svg';
1313

1414
export interface SidebarItem {
1515
name: string;
16-
icon: PhosphorIcon;
16+
icon: IconKey;
1717
position: 'top' | 'bottom';
1818
highlight?: true;
1919

@@ -151,7 +151,7 @@ const SidebarButton = styled(
151151
export const Sidebar = observer((props: SidebarProps) => {
152152
const items = props.items.map((item, i) => {
153153
const itemContent = <>
154-
<item.icon size={34} />
154+
<PhosphorIcon icon={item.icon} size={34} />
155155
{ item.name }
156156
</>;
157157

src/components/view/view-event-list.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import AutoSizer from 'react-virtualized-auto-sizer';
77
import { FixedSizeList as List, ListChildComponentProps } from 'react-window';
88

99
import { styled } from '../../styles'
10-
import { ArrowIcon, Icon, WarningIcon, Icons } from '../../icons';
10+
import { ArrowIcon, Icon, PhosphorIcon, WarningIcon } from '../../icons';
1111

1212
import {
1313
CollectedEvent,
@@ -789,16 +789,16 @@ export class ViewEventList extends React.Component<ViewEventListProps> {
789789
{
790790
events.length === 0
791791
? (isPaused
792-
? <EmptyStateOverlay icon={Icons.Pause}>
792+
? <EmptyStateOverlay icon='Pause'>
793793
Interception is paused, resume it to collect intercepted requests
794794
</EmptyStateOverlay>
795-
: <EmptyStateOverlay icon={Icons.Plug}>
795+
: <EmptyStateOverlay icon='Plug'>
796796
Connect a client and intercept some requests, and they'll appear here
797797
</EmptyStateOverlay>
798798
)
799799

800800
: filteredEvents.length === 0
801-
? <EmptyStateOverlay icon={Icons.QuestionMark}>
801+
? <EmptyStateOverlay icon='QuestionMark'>
802802
No requests match this search filter{
803803
isPaused ? ' and interception is paused' : ''
804804
}

src/components/view/view-page.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import * as portals from 'react-reverse-portal';
1515

1616
import { WithInjected, CollectedEvent } from '../../types';
1717
import { NARROW_LAYOUT_BREAKPOINT, styled } from '../../styles';
18-
import { Icons } from '../../icons';
1918
import { useHotkeys, isEditable, windowSize } from '../../util/ui';
2019
import { debounceComputed } from '../../util/observable';
2120
import { UnreachableCheck } from '../../util/error';
@@ -307,7 +306,7 @@ class ViewPage extends React.Component<ViewPageProps> {
307306
let rightPane: JSX.Element | null;
308307
if (!this.selectedEvent) {
309308
if (this.splitDirection === 'vertical') {
310-
rightPane = <EmptyState key='details' icon={Icons.ArrowLeft}>
309+
rightPane = <EmptyState key='details' icon='ArrowLeft'>
311310
Select an exchange to see the full details.
312311
</EmptyState>;
313312
} else {

src/icons.ts renamed to src/icons.tsx

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { styled, warningColor } from './styles';
33

44
// Import required Phosphor icons:
55
import {
6-
type Icon as PhosphorIcon,
76
type IconProps as PhosphorIconProps,
87

98
// Sidebar icons
@@ -22,10 +21,19 @@ import {
2221
ArrowLeft,
2322
} from '@phosphor-icons/react';
2423

25-
export type { PhosphorIcon, PhosphorIconProps };
26-
export type PhosphorIconKey = keyof typeof Icons;
24+
export type IconKey = keyof typeof Icons;
2725

28-
export const Icons = {
26+
export const PhosphorIcon = React.memo((props: { icon: IconKey } & PhosphorIconProps) => {
27+
const { icon, ...otherProps } = props;
28+
const PIcon = Icons[props.icon];
29+
30+
return <PIcon
31+
className='phosphor-icon'
32+
{...otherProps}
33+
/>;
34+
});
35+
36+
const Icons = {
2937
Plugs,
3038
MagnifyingGlass,
3139
Pencil,

src/styles.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,5 +331,10 @@ export const GlobalStyles = createGlobalStyle`
331331
}
332332
}
333333
334+
.phosphor-icon {
335+
/* Ensures icons line up with FontAwesome & neighbouring text */
336+
vertical-align: -0.125em;
337+
}
338+
334339
${p => p.theme.globalCss}
335340
`;

0 commit comments

Comments
 (0)