Skip to content

Commit

Permalink
38 Icon Library (#39)
Browse files Browse the repository at this point in the history
* icon component

* tests

* remove ionicons package
  • Loading branch information
mwarman authored Aug 14, 2024
1 parent 6ef161d commit 1137eb8
Show file tree
Hide file tree
Showing 24 changed files with 276 additions and 86 deletions.
46 changes: 45 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"@capacitor/haptics": "6.0.0",
"@capacitor/keyboard": "6.0.1",
"@capacitor/status-bar": "6.0.0",
"@fortawesome/fontawesome-svg-core": "6.6.0",
"@fortawesome/free-solid-svg-icons": "6.6.0",
"@fortawesome/react-fontawesome": "0.2.2",
"@ionic/react": "8.2.6",
"@ionic/react-router": "8.2.6",
"@tanstack/react-query": "5.51.23",
Expand All @@ -34,7 +37,6 @@
"classnames": "2.5.1",
"dayjs": "1.11.12",
"formik": "2.4.6",
"ionicons": "7.4.0",
"lodash": "4.17.21",
"react": "18.3.1",
"react-dom": "18.3.1",
Expand Down
5 changes: 2 additions & 3 deletions src/common/components/Card/EmptyCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { informationCircle } from 'ionicons/icons';

import MessageCard, { MessageCardProps } from './MessageCard';
import { IconName } from '../Icon/Icon';

/**
* Properties for the `EmptyCard` component.
Expand All @@ -16,7 +15,7 @@ interface EmptyCardProps extends MessageCardProps {}
* @returns JSX
*/
const EmptyCard = ({
icon = informationCircle,
icon = IconName.CircleInfo,
testid = 'card-empty',
title = 'No data',
...cardProps
Expand Down
5 changes: 2 additions & 3 deletions src/common/components/Card/ErrorCard.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { warning } from 'ionicons/icons';

import MessageCard, { MessageCardProps } from './MessageCard';
import { IconName } from '../Icon/Icon';

/**
* Properties for the `ErrorCard` component.
Expand All @@ -16,7 +15,7 @@ interface ErrorCardProps extends MessageCardProps {}
*/
const ErrorCard = ({
color = 'danger',
icon = warning,
icon = IconName.TriangleExclamation,
testid = 'card-error',
title = 'Uh oh',
...cardProps
Expand Down
2 changes: 1 addition & 1 deletion src/common/components/Card/MessageCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
align-items: center;

.icon {
font-size: 1.5rem;
font-size: 1.25rem;
margin-right: 0.5rem;
}
}
Expand Down
8 changes: 3 additions & 5 deletions src/common/components/Card/MessageCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import {
IonCardHeader,
IonCardSubtitle,
IonCardTitle,
IonIcon,
} from '@ionic/react';
import { ComponentPropsWithoutRef, ReactNode } from 'react';
import classNames from 'classnames';

import './MessageCard.scss';
import { BaseComponentProps } from '../types';
import Icon, { IconProps } from '../Icon/Icon';

/**
* Properties for the `MessageCard` component.
Expand All @@ -24,7 +24,7 @@ import { BaseComponentProps } from '../types';
export interface MessageCardProps
extends BaseComponentProps,
Pick<ComponentPropsWithoutRef<typeof IonCard>, 'color'>,
Pick<ComponentPropsWithoutRef<typeof IonIcon>, 'icon'> {
Partial<Pick<IconProps, 'icon'>> {
content?: ReactNode;
subtitle?: ReactNode;
title?: ReactNode;
Expand Down Expand Up @@ -52,9 +52,7 @@ const MessageCard = ({
{title && (
<IonCardHeader className="header">
<IonCardTitle className="title-block">
{icon && (
<IonIcon icon={icon} className="icon" data-testid={`${testid}-icon`}></IonIcon>
)}
{icon && <Icon icon={icon} data-testid={`${testid}-icon`} />}
<div className="title" data-testid={`${testid}-title`}>
{title}
</div>
Expand Down
9 changes: 9 additions & 0 deletions src/common/components/Content/PageHeader.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,13 @@
.title {
font-size: 2rem;
}

ion-buttons {
ion-button {
height: 3rem;
width: 3rem;

margin: 0;
}
}
}
2 changes: 1 addition & 1 deletion src/common/components/Content/PageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ interface PageHeaderProps extends BaseComponentProps {
* buttons={
* <>
* <IonButton>
* <IonIcon slot="icon-only" icon={add} />
* <Icon icon={IconName.Users} />
* </IonButton>
* </>
* }
Expand Down
9 changes: 9 additions & 0 deletions src/common/components/Header/Header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,13 @@
font-size: 1rem;
}
}

ion-buttons[slot='end'] {
ion-button {
height: 3rem;
width: 3rem;

margin: 0;
}
}
}
94 changes: 94 additions & 0 deletions src/common/components/Icon/Icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { ComponentPropsWithoutRef } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import {
faBuilding,
faCircleInfo,
faEnvelope,
faHouse,
faLink,
faMapLocationDot,
faPenToSquare,
faPhone,
faSignOutAlt,
faTrash,
faTriangleExclamation,
faUser,
faUserGear,
faUsers,
} from '@fortawesome/free-solid-svg-icons';
import classNames from 'classnames';

import { BaseComponentProps } from '../types';

/**
* Properties for the `Icon` component.
* @see {@link BaseComponentProps}
* @see {@link FontAwesomeIcon}
*/
export interface IconProps
extends BaseComponentProps,
Omit<ComponentPropsWithoutRef<typeof FontAwesomeIcon>, 'icon'> {
icon: IconName;
}

/**
* Icon names.
*/
export enum IconName {
Building = 'building',
CircleInfo = 'circle_info',
Envelope = 'envelope',
House = 'house',
Link = 'link',
MapLocationDot = 'map_location_dot',
PenToSquare = 'pen_to_square',
Phone = 'phone',
SignOut = 'sign_out',
Trash = 'trash',
TriangleExclamation = 'triangle_exclamation',
User = 'user',
Users = 'users',
UserGear = 'user_gear',
}

/**
* A key/value mapping of every icon used in the application.
*/
const icons: Record<IconName, IconProp> = {
building: faBuilding,
circle_info: faCircleInfo,
envelope: faEnvelope,
house: faHouse,
link: faLink,
map_location_dot: faMapLocationDot,
pen_to_square: faPenToSquare,
phone: faPhone,
sign_out: faSignOutAlt,
trash: faTrash,
triangle_exclamation: faTriangleExclamation,
user_gear: faUserGear,
user: faUser,
users: faUsers,
};

/**
* The `Icon` component renders an icon. Wraps the `FontAwesomeIcon` component.
*
* @param {IconProps} props - Component properties.
* @returns {JSX.Element} JSX
* @see {@link FontAwesomeIcon}
*/
const Icon = ({ className, icon, testid = 'icon', ...iconProps }: IconProps): JSX.Element => {
const faIcon = icons[icon];
return (
<FontAwesomeIcon
className={classNames('icon', className)}
icon={faIcon}
{...iconProps}
data-testid={testid}
/>
);
};

export default Icon;
16 changes: 16 additions & 0 deletions src/common/components/Icon/__tests__/Icon.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';

import { render, screen } from 'test/test-utils';

import Icon, { IconName } from '../Icon';

describe('Icon', () => {
it('should render successfully', async () => {
// ARRANGE
render(<Icon icon={IconName.Building} />);
await screen.findByTestId('icon');

// ASSERT
expect(screen.getByTestId('icon')).toBeDefined();
});
});
2 changes: 2 additions & 0 deletions src/common/components/Menu/AppMenu.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@

.icon {
margin-right: 1rem;

opacity: 0.5;
}
}
11 changes: 5 additions & 6 deletions src/common/components/Menu/AppMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import {
IonContent,
IonHeader,
IonIcon,
IonItem,
IonLabel,
IonMenu,
Expand All @@ -10,14 +9,14 @@ import {
IonTitle,
IonToolbar,
} from '@ionic/react';
import { home, logOut, people, personCircle } from 'ionicons/icons';
import classNames from 'classnames';

import './AppMenu.scss';
import { BaseComponentProps } from '../types';
import { useAuth } from 'common/hooks/useAuth';
import { useGetCurrentUser } from 'common/api/useGetCurrentUser';
import Avatar from '../Icon/Avatar';
import Icon, { IconName } from '../Icon/Icon';

/**
* Properties for the `AppMenu` component.
Expand Down Expand Up @@ -64,13 +63,13 @@ const AppMenu = ({ className, testid = 'menu-app' }: AppMenuProps): JSX.Element
<>
<IonMenuToggle>
<IonItem routerLink="/tabs/home" lines="full" data-testid={`${testid}-item-home`}>
<IonIcon icon={home} className="icon" />
<Icon icon={IconName.House} fixedWidth className="icon" />
<IonLabel>Home</IonLabel>
</IonItem>
</IonMenuToggle>
<IonMenuToggle>
<IonItem routerLink="/tabs/users" lines="full" data-testid={`${testid}-item-users`}>
<IonIcon icon={people} className="icon" />
<Icon icon={IconName.Users} fixedWidth className="icon" />
<IonLabel>Users</IonLabel>
</IonItem>
</IonMenuToggle>
Expand All @@ -80,7 +79,7 @@ const AppMenu = ({ className, testid = 'menu-app' }: AppMenuProps): JSX.Element
lines="full"
data-testid={`${testid}-item-account`}
>
<IonIcon icon={personCircle} className="icon" />
<Icon icon={IconName.UserGear} fixedWidth className="icon" />
<IonLabel>Account</IonLabel>
</IonItem>
</IonMenuToggle>
Expand All @@ -90,7 +89,7 @@ const AppMenu = ({ className, testid = 'menu-app' }: AppMenuProps): JSX.Element
lines="full"
data-testid={`${testid}-item-signout`}
>
<IonIcon icon={logOut} className="icon" />
<Icon icon={IconName.SignOut} fixedWidth className="icon" />
<IonLabel>Sign Out</IonLabel>
</IonItem>
</IonMenuToggle>
Expand Down
8 changes: 8 additions & 0 deletions src/common/components/Router/TabNavigation.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.tab-navigation {
ion-tab-button {
.icon {
margin-top: 0.375rem;
margin-bottom: 0.125rem;
}
}
}
Loading

0 comments on commit 1137eb8

Please sign in to comment.