init
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import AppIcon from './AppIcon';
|
||||
import { APP_ICON_SIZE } from '../../config';
|
||||
import { randomColor, randomText } from '@/utils';
|
||||
import { ICONS } from './config';
|
||||
|
||||
const ComponentToTest = AppIcon;
|
||||
|
||||
/**
|
||||
* Tests for <AppIcon/> component
|
||||
*/
|
||||
describe('<AppIcon/> component', () => {
|
||||
it('renders itself', () => {
|
||||
const testId = randomText(8);
|
||||
render(<ComponentToTest data-testid={testId} />);
|
||||
const svg = screen.getByTestId(testId);
|
||||
expect(svg).toBeDefined();
|
||||
expect(svg).toHaveAttribute('data-icon', 'default');
|
||||
expect(svg).toHaveAttribute('size', String(APP_ICON_SIZE)); // default size
|
||||
expect(svg).toHaveAttribute('height', String(APP_ICON_SIZE)); // default size when .size is not set
|
||||
expect(svg).toHaveAttribute('width', String(APP_ICON_SIZE)); // default size when .size is not se
|
||||
});
|
||||
|
||||
it('supports .color property', () => {
|
||||
const testId = randomText(8);
|
||||
const color = randomColor(); // Note: 'rgb(255, 128, 0)' format is used by react-icons npm, so tests may fail
|
||||
render(<ComponentToTest data-testid={testId} color={color} />);
|
||||
const svg = screen.getByTestId(testId);
|
||||
expect(svg).toHaveAttribute('data-icon', 'default');
|
||||
// expect(svg).toHaveAttribute('color', color); // TODO: Looks like MUI Icons exclude .color property from <svg> rendering
|
||||
expect(svg).toHaveStyle(`color: ${color}`);
|
||||
expect(svg).toHaveAttribute('fill', 'currentColor'); // .fill must be 'currentColor' when .color property is set
|
||||
});
|
||||
|
||||
it('supports .icon property', () => {
|
||||
// Verify that all icons are supported
|
||||
for (const icon of Object.keys(ICONS)) {
|
||||
const testId = randomText(8);
|
||||
render(<ComponentToTest data-testid={testId} icon={icon} />);
|
||||
const svg = screen.getByTestId(testId);
|
||||
expect(svg).toBeDefined();
|
||||
expect(svg).toHaveAttribute('data-icon', icon.toLowerCase());
|
||||
}
|
||||
});
|
||||
|
||||
it('supports .size property', () => {
|
||||
const testId = randomText(8);
|
||||
const size = Math.floor(Math.random() * 128) + 1;
|
||||
render(<ComponentToTest data-testid={testId} size={size} />);
|
||||
const svg = screen.getByTestId(testId);
|
||||
expect(svg).toHaveAttribute('size', String(size));
|
||||
expect(svg).toHaveAttribute('height', String(size));
|
||||
expect(svg).toHaveAttribute('width', String(size));
|
||||
});
|
||||
|
||||
it('supports .title property', () => {
|
||||
const testId = randomText(8);
|
||||
const title = randomText(16);
|
||||
render(<ComponentToTest data-testid={testId} title={title} />);
|
||||
const svg = screen.getByTestId(testId);
|
||||
expect(svg).toBeDefined();
|
||||
expect(svg).toHaveAttribute('title', title);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { ComponentType, FunctionComponent, SVGAttributes } from 'react';
|
||||
import { APP_ICON_SIZE } from '../../config';
|
||||
import { IconName, ICONS } from './config';
|
||||
|
||||
/**
|
||||
* Props of the AppIcon component, also can be used for SVG icons
|
||||
*/
|
||||
export interface Props extends SVGAttributes<SVGElement> {
|
||||
color?: string;
|
||||
icon?: IconName | string;
|
||||
size?: string | number;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders SVG icon by given Icon name
|
||||
* @component AppIcon
|
||||
* @param {string} [color] - color of the icon as a CSS color value
|
||||
* @param {string} [icon] - name of the Icon to render
|
||||
* @param {string} [title] - title/hint to show when the cursor hovers the icon
|
||||
* @param {string | number} [size] - size of the icon, default is ICON_SIZE
|
||||
*/
|
||||
const AppIcon: FunctionComponent<Props> = ({
|
||||
color,
|
||||
icon = 'default',
|
||||
size = APP_ICON_SIZE,
|
||||
style,
|
||||
...restOfProps
|
||||
}) => {
|
||||
const iconName = (icon || 'default').trim().toLowerCase() as IconName;
|
||||
|
||||
let ComponentToRender: ComponentType = ICONS[iconName];
|
||||
if (!ComponentToRender) {
|
||||
console.warn(`AppIcon: icon "${iconName}" is not found!`);
|
||||
ComponentToRender = ICONS.default; // ICONS['default'];
|
||||
}
|
||||
|
||||
const propsToRender = {
|
||||
height: size,
|
||||
color,
|
||||
fill: color && 'currentColor',
|
||||
size,
|
||||
style: { ...style, color },
|
||||
width: size,
|
||||
...restOfProps,
|
||||
};
|
||||
|
||||
return <ComponentToRender data-icon={iconName} {...propsToRender} />;
|
||||
};
|
||||
|
||||
export default AppIcon;
|
||||
@@ -0,0 +1,56 @@
|
||||
// SVG assets
|
||||
import PencilIcon from './icons/PencilIcon';
|
||||
// MUI Icons
|
||||
import DefaultIcon from '@mui/icons-material/MoreHoriz';
|
||||
import SettingsIcon from '@mui/icons-material/Settings';
|
||||
import VisibilityIcon from '@mui/icons-material/Visibility';
|
||||
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
|
||||
import MenuIcon from '@mui/icons-material/Menu';
|
||||
import CloseIcon from '@mui/icons-material/Close';
|
||||
import DayNightIcon from '@mui/icons-material/Brightness4';
|
||||
import NightIcon from '@mui/icons-material/Brightness3';
|
||||
import DayIcon from '@mui/icons-material/Brightness5';
|
||||
import SearchIcon from '@mui/icons-material/Search';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import HomeIcon from '@mui/icons-material/Home';
|
||||
import AccountCircle from '@mui/icons-material/AccountCircle';
|
||||
import PersonAddIcon from '@mui/icons-material/PersonAdd';
|
||||
import PersonIcon from '@mui/icons-material/Person';
|
||||
import ExitToAppIcon from '@mui/icons-material/ExitToApp';
|
||||
import NotificationsIcon from '@mui/icons-material/NotificationsOutlined';
|
||||
import DangerousIcon from '@mui/icons-material/Dangerous';
|
||||
|
||||
/**
|
||||
* List of all available Icon names
|
||||
*/
|
||||
export type IconName = keyof typeof ICONS;
|
||||
|
||||
/**
|
||||
* How to use:
|
||||
* 1. Import all required React, MUI or other SVG icons into this file.
|
||||
* 2. Add icons with "unique lowercase names" into ICONS object. Lowercase is a must!
|
||||
* 3. Use icons everywhere in the App by their names in <Icon icon="xxx" /> component
|
||||
* Important: properties of ICONS object MUST be lowercase!
|
||||
* Note: You can use camelCase or UPPERCASE in the <Icon icon="someIconByName" /> component
|
||||
*/
|
||||
export const ICONS /* Note: Setting type disables property autocomplete :( was - : Record<string, ComponentType> */ = {
|
||||
default: DefaultIcon,
|
||||
logo: PencilIcon,
|
||||
close: CloseIcon,
|
||||
menu: MenuIcon,
|
||||
settings: SettingsIcon,
|
||||
visibilityon: VisibilityIcon,
|
||||
visibilityoff: VisibilityOffIcon,
|
||||
daynight: DayNightIcon,
|
||||
night: NightIcon,
|
||||
day: DayIcon,
|
||||
search: SearchIcon,
|
||||
info: InfoIcon,
|
||||
home: HomeIcon,
|
||||
account: AccountCircle,
|
||||
signup: PersonAddIcon,
|
||||
login: PersonIcon,
|
||||
logout: ExitToAppIcon,
|
||||
notifications: NotificationsIcon,
|
||||
error: DangerousIcon,
|
||||
};
|
||||
@@ -0,0 +1,29 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import { IconProps } from '../utils';
|
||||
|
||||
const CurrencyIcon: FunctionComponent<IconProps> = (props) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 36 36" {...props}>
|
||||
<path
|
||||
fill="#D99E82"
|
||||
d="M35.222 33.598c-.647-2.101-1.705-6.059-2.325-7.566-.501-1.216-.969-2.438-1.544-3.014-.575-.575-1.553-.53-2.143.058 0 0-2.469 1.675-3.354 2.783-1.108.882-2.785 3.357-2.785 3.357-.59.59-.635 1.567-.06 2.143.576.575 1.798 1.043 3.015 1.544 1.506.62 5.465 1.676 7.566 2.325.359.11 1.74-1.271 1.63-1.63z"
|
||||
/>
|
||||
<path
|
||||
fill="#EA596E"
|
||||
d="M13.643 5.308c1.151 1.151 1.151 3.016 0 4.167l-4.167 4.168c-1.151 1.15-3.018 1.15-4.167 0L1.141 9.475c-1.15-1.151-1.15-3.016 0-4.167l4.167-4.167c1.15-1.151 3.016-1.151 4.167 0l4.168 4.167z"
|
||||
/>
|
||||
<path fill="#FFCC4D" d="M31.353 23.018l-4.17 4.17-4.163 4.165L7.392 15.726l8.335-8.334 15.626 15.626z" />
|
||||
<path
|
||||
fill="#292F33"
|
||||
d="M32.078 34.763s2.709 1.489 3.441.757c.732-.732-.765-3.435-.765-3.435s-2.566.048-2.676 2.678z"
|
||||
/>
|
||||
<path fill="#CCD6DD" d="M2.183 10.517l8.335-8.335 5.208 5.209-8.334 8.335z" />
|
||||
<path
|
||||
fill="#99AAB5"
|
||||
d="M3.225 11.558l8.334-8.334 1.042 1.042L4.267 12.6zm2.083 2.086l8.335-8.335 1.042 1.042-8.335 8.334z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default CurrencyIcon;
|
||||
@@ -0,0 +1,29 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import { IconProps } from '../utils';
|
||||
|
||||
const PencilIcon: FunctionComponent<IconProps> = (props) => {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 36 36" {...props}>
|
||||
<path
|
||||
fill="#D99E82"
|
||||
d="M35.222 33.598c-.647-2.101-1.705-6.059-2.325-7.566-.501-1.216-.969-2.438-1.544-3.014-.575-.575-1.553-.53-2.143.058 0 0-2.469 1.675-3.354 2.783-1.108.882-2.785 3.357-2.785 3.357-.59.59-.635 1.567-.06 2.143.576.575 1.798 1.043 3.015 1.544 1.506.62 5.465 1.676 7.566 2.325.359.11 1.74-1.271 1.63-1.63z"
|
||||
/>
|
||||
<path
|
||||
fill="#EA596E"
|
||||
d="M13.643 5.308c1.151 1.151 1.151 3.016 0 4.167l-4.167 4.168c-1.151 1.15-3.018 1.15-4.167 0L1.141 9.475c-1.15-1.151-1.15-3.016 0-4.167l4.167-4.167c1.15-1.151 3.016-1.151 4.167 0l4.168 4.167z"
|
||||
/>
|
||||
<path fill="#FFCC4D" d="M31.353 23.018l-4.17 4.17-4.163 4.165L7.392 15.726l8.335-8.334 15.626 15.626z" />
|
||||
<path
|
||||
fill="#292F33"
|
||||
d="M32.078 34.763s2.709 1.489 3.441.757c.732-.732-.765-3.435-.765-3.435s-2.566.048-2.676 2.678z"
|
||||
/>
|
||||
<path fill="#CCD6DD" d="M2.183 10.517l8.335-8.335 5.208 5.209-8.334 8.335z" />
|
||||
<path
|
||||
fill="#99AAB5"
|
||||
d="M3.225 11.558l8.334-8.334 1.042 1.042L4.267 12.6zm2.083 2.086l8.335-8.335 1.042 1.042-8.335 8.334z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default PencilIcon;
|
||||
@@ -0,0 +1,125 @@
|
||||
import { FunctionComponent } from 'react';
|
||||
import { IconProps } from '../utils';
|
||||
|
||||
const YellowPlaneIcon: FunctionComponent<IconProps> = (props) => {
|
||||
const styleOpacityAndEnableBackground = {
|
||||
opacity: 0.2,
|
||||
// enableBackground: 'new'
|
||||
};
|
||||
|
||||
return (
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xmlSpace="preserve" {...props}>
|
||||
<path
|
||||
style={{ fill: '#FFCE00' }}
|
||||
d="M142.21,493.991c12.991,12.991,34.057,12.991,47.05,0c12.991-12.991,12.991-34.057,0-47.048
|
||||
L65.057,322.742c-12.993-12.992-34.059-12.992-47.05,0s-12.989,34.055,0,47.048L142.21,493.991z"
|
||||
/>
|
||||
<circle style={{ fill: '#7D868C' }} cx="386.857" cy="125.141" r="35.932" />
|
||||
<path
|
||||
style={styleOpacityAndEnableBackground}
|
||||
d="M391.846,120.154c-9.556-9.556-18.727-17.187-27.59-22.948
|
||||
c-0.969,0.785-1.908,1.627-2.807,2.527c-14.033,14.034-14.033,36.786,0,50.816c14.031,14.034,36.786,14.034,50.816,0
|
||||
c0.908-0.907,1.754-1.853,2.546-2.829C409.07,138.902,401.453,129.759,391.846,120.154z"
|
||||
/>
|
||||
<path
|
||||
style={styleOpacityAndEnableBackground}
|
||||
d="M75.949,333.636c-15.914,18.935-28.002,33.038-32.3,37.337
|
||||
c-4.221,4.221-7.777,8.86-10.672,13.785l94.264,94.266c4.925-2.894,9.565-6.449,13.789-10.672
|
||||
c4.298-4.301,18.399-16.384,37.334-32.303L75.949,333.636z"
|
||||
/>
|
||||
<path
|
||||
style={{ fill: '#333E48' }}
|
||||
d="M384.037,127.964c-30.663-30.663-63.439-47.604-94.099-16.941
|
||||
C254.182,146.782,79.164,363.201,57.52,384.842c-19.227,19.231-19.23,50.409,0,69.636c19.23,19.233,50.409,19.228,69.636,0
|
||||
c21.644-21.641,238.063-196.657,273.82-232.416C431.639,191.4,414.698,158.626,384.037,127.964z"
|
||||
/>
|
||||
<circle style={{ fill: '#7D868C' }} cx="218.905" cy="293.104" r="35.93" />
|
||||
<path
|
||||
style={styleOpacityAndEnableBackground}
|
||||
d="M278.708,123.019c-25.32,28.083-74.009,86.548-119.486,141.296
|
||||
l88.463,88.463c54.748-45.477,113.215-94.162,141.296-119.487L278.708,123.019z"
|
||||
/>
|
||||
<g>
|
||||
<path
|
||||
style={{ fill: '#FFCE00' }}
|
||||
d="M384.46,458.665c27.283,27.281,71.513,27.279,98.795-0.003c27.28-27.279,27.283-71.511,0-98.793
|
||||
L152.13,28.746c-27.283-27.283-71.513-27.283-98.793,0c-27.283,27.281-27.285,71.514-0.002,98.793L384.46,458.665z"
|
||||
/>
|
||||
<path
|
||||
style={{ fill: '#FFCE00' }}
|
||||
d="M84.341,435.945c-2.121,0-4.241-0.809-5.857-2.426c-3.236-3.235-3.236-8.48-0.002-11.716
|
||||
l50.812-50.814c3.236-3.236,8.483-3.235,11.716-0.001c3.236,3.235,3.236,8.48,0,11.714l-50.812,50.814
|
||||
C88.58,435.136,86.459,435.945,84.341,435.945z"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="174.379"
|
||||
y="88.222"
|
||||
transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 200.0443 399.0237)"
|
||||
style={styleOpacityAndEnableBackground}
|
||||
width="16.568"
|
||||
height="139.719"
|
||||
/>
|
||||
<rect
|
||||
x="117.601"
|
||||
y="31.432"
|
||||
transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 143.2742 261.929)"
|
||||
style={styleOpacityAndEnableBackground}
|
||||
width="16.568"
|
||||
height="139.719"
|
||||
/>
|
||||
<rect
|
||||
x="345.637"
|
||||
y="259.464"
|
||||
transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 371.313 812.4501)"
|
||||
style={styleOpacityAndEnableBackground}
|
||||
width="16.568"
|
||||
height="139.719"
|
||||
/>
|
||||
<rect
|
||||
x="402.427"
|
||||
y="316.259"
|
||||
transform="matrix(-0.7071 -0.7071 0.7071 -0.7071 428.1006 949.5629)"
|
||||
style={styleOpacityAndEnableBackground}
|
||||
width="16.568"
|
||||
height="139.719"
|
||||
/>
|
||||
<path
|
||||
style={{ fill: '#1E252B' }}
|
||||
d="M489.114,354.011L383.944,248.841c10.747-9.425,18.276-16.308,22.89-20.921
|
||||
c16.43-16.43,22.038-34.95,16.673-55.044c-1.363-5.108-3.454-10.308-6.267-15.628c0.296-0.281,0.596-0.553,0.885-0.842
|
||||
c2.167-2.167,4.076-4.523,5.724-7.024l41.209,41.209c1.618,1.617,3.739,2.426,5.858,2.426c2.12,0,4.24-0.808,5.858-2.426
|
||||
c3.235-3.236,3.235-8.48,0-11.716l-46.323-46.323c0.406-2.427,0.626-4.902,0.626-7.411c0-11.811-4.599-22.914-12.95-31.265
|
||||
c-8.351-8.352-19.454-12.953-31.265-12.953c-2.511,0-4.987,0.22-7.415,0.627L333.126,35.23c-3.235-3.235-8.48-3.236-11.716-0.001
|
||||
c-3.236,3.235-3.236,8.48-0.001,11.714l41.209,41.21c-2.503,1.647-4.858,3.555-7.025,5.722c-0.288,0.288-0.562,0.59-0.843,0.886
|
||||
c-5.319-2.812-10.518-4.902-15.627-6.267c-20.099-5.369-38.615,0.243-55.044,16.673c-4.61,4.611-11.492,12.142-20.921,22.893
|
||||
L157.989,22.89C143.229,8.129,123.605,0,102.733,0S62.237,8.129,47.48,22.888c-14.76,14.76-22.89,34.383-22.89,55.254
|
||||
c-0.001,20.873,8.127,40.497,22.887,55.254l114.564,114.564c-6.337,7.624-12.648,15.223-18.86,22.703
|
||||
c-19.346,23.293-38.189,45.983-53.839,64.649l-18.428-18.429c-7.848-7.848-18.284-12.17-29.383-12.17s-21.534,4.322-29.382,12.172
|
||||
c-16.198,16.199-16.198,42.559,0,58.761l25.683,25.684c-6.704,20.045-2.103,43.073,13.83,59.004
|
||||
c10.865,10.867,25.311,16.85,40.677,16.85c6.339,0,12.515-1.034,18.354-2.994l25.658,25.658c8.102,8.101,18.74,12.15,29.381,12.15
|
||||
c10.64,0,21.284-4.051,29.384-12.15c16.201-16.201,16.201-42.562-0.001-58.763l-18.43-18.43
|
||||
c18.659-15.643,41.335-34.476,64.615-53.811c7.49-6.221,15.101-12.541,22.736-18.887l114.566,114.564
|
||||
c14.757,14.756,34.379,22.885,55.251,22.887c0.002,0,0.002,0,0.004,0c20.87,0,40.495-8.129,55.254-22.89
|
||||
c14.758-14.759,22.887-34.381,22.888-55.253C512.002,388.394,503.872,368.77,489.114,354.011z M386.86,97.491
|
||||
c7.385,0,14.328,2.876,19.55,8.099c5.222,5.222,8.097,12.165,8.097,19.55c0,6.56-2.273,12.766-6.438,17.731
|
||||
c-4.96-6.686-10.995-13.587-18.174-20.765c-7.179-7.179-14.08-13.215-20.767-18.176C374.093,99.765,380.301,97.491,386.86,97.491z
|
||||
M295.795,116.881c12.28-12.282,24.689-16.218,39.053-12.38c12.86,3.434,27.034,13.026,43.329,29.323
|
||||
c16.296,16.295,25.886,30.468,29.32,43.329c3.836,14.362-0.098,26.772-12.382,39.054c-4.413,4.414-12.113,11.434-22.915,20.895
|
||||
l-97.303-97.303C284.365,128.993,291.385,121.29,295.795,116.881z M23.866,363.932c-9.74-9.742-9.74-25.593,0-35.333
|
||||
c4.717-4.718,10.992-7.317,17.666-7.317c6.675,0,12.949,2.599,17.668,7.317l19.438,19.441C65.414,363.702,55.648,375,51.662,378.986
|
||||
c-2.168,2.168-4.12,4.47-5.868,6.876L23.866,363.932z M183.401,452.801c9.742,9.741,9.742,25.59,0.001,35.331
|
||||
c-9.742,9.741-25.594,9.742-35.336,0l-21.927-21.926c2.417-1.763,4.717-3.717,6.873-5.872c3.985-3.985,15.285-13.751,30.947-26.976
|
||||
L183.401,452.801z M230.718,356.101c-53.485,44.418-99.676,82.779-109.419,92.521c-7.735,7.735-18.02,11.995-28.96,11.996
|
||||
c-10.939,0-21.225-4.26-28.961-11.997c-15.968-15.966-15.968-41.949,0-57.92c9.744-9.743,48.118-55.949,92.55-109.452
|
||||
c5.891-7.094,11.87-14.293,17.879-21.523l78.468,78.468C245.033,344.21,237.822,350.2,230.718,356.101z M477.397,452.804
|
||||
c-11.631,11.632-27.093,18.038-43.539,18.038h-0.003c-16.447-0.001-31.909-6.406-43.538-18.034L59.192,121.681
|
||||
c-11.631-11.628-18.036-27.09-18.034-43.538c0-16.447,6.406-31.91,18.037-43.541c11.631-11.629,27.093-18.034,43.539-18.034
|
||||
c16.447,0,31.91,6.405,43.54,18.036l331.124,331.123c11.63,11.629,18.036,27.093,18.036,43.54
|
||||
C495.434,425.713,489.029,441.175,477.397,452.804z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export default YellowPlaneIcon;
|
||||
@@ -0,0 +1,3 @@
|
||||
import AppIcon from './AppIcon';
|
||||
|
||||
export { AppIcon as default, AppIcon };
|
||||
@@ -0,0 +1,11 @@
|
||||
import { SVGAttributes } from 'react';
|
||||
|
||||
/**
|
||||
* Props to use with custom SVG icons, similar to AppIcon's Props
|
||||
*/
|
||||
export interface IconProps extends SVGAttributes<SVGElement> {
|
||||
color?: string;
|
||||
icon?: string;
|
||||
size?: string | number;
|
||||
title?: string;
|
||||
}
|
||||
Reference in New Issue
Block a user