Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
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