Commit 2b2332c4 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Icons in footer links (#2809)

* Icons in footer links

Fixes #2539

* fix envs validator
parent ff04dc85
...@@ -580,6 +580,7 @@ const footerLinkSchema: yup.ObjectSchema<CustomLink> = yup ...@@ -580,6 +580,7 @@ const footerLinkSchema: yup.ObjectSchema<CustomLink> = yup
.object({ .object({
text: yup.string().required(), text: yup.string().required(),
url: yup.string().test(urlTest).required(), url: yup.string().test(urlTest).required(),
iconUrl: yup.array().of(yup.string().required().test(urlTest)),
}); });
const footerLinkGroupSchema: yup.ObjectSchema<CustomLinksGroup> = yup const footerLinkGroupSchema: yup.ObjectSchema<CustomLinksGroup> = yup
......
...@@ -17,7 +17,11 @@ ...@@ -17,7 +17,11 @@
"links": [ "links": [
{ {
"text": "Develop", "text": "Develop",
"url": "https://example.com" "url": "https://example.com",
"iconUrl": [
"https://example.com/mocks/image_s.jpg",
"https://example.com/mocks/image_svg.svg"
]
}, },
{ {
"text": "Grants", "text": "Grants",
......
...@@ -192,7 +192,7 @@ The app version shown in the footer is derived from build-time ENV variables `NE ...@@ -192,7 +192,7 @@ The app version shown in the footer is derived from build-time ENV variables `NE
| Variable | Type| Description | Compulsoriness | Default value | Example value | | Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| title | `string` | Title of link group | Required | - | `Company` | | title | `string` | Title of link group | Required | - | `Company` |
| links | `Array<{'text':string;'url':string;}>` | list of links | Required | - | `[{'text':'Homepage','url':'https://www.blockscout.com'}]` | | links | `Array<{'text':string;'url':string;'iconUrl'?:[string,string]}>` | An array contains a list of links in the column. Each link can optionally have an `icon_url` property, which should include an array of two external image URLs for light and dark themes, respectively. If only one URL is provided, it will be used for both color schemes. We expect the icons to be square, with a minimum size of 40px by 40px or in SVG format. | Required | - | `[{'text':'Homepage','url':'https://www.blockscout.com'}]` |
&nbsp; &nbsp;
......
...@@ -79,6 +79,10 @@ export const FOOTER_LINKS: Array<CustomLinksGroup> = [ ...@@ -79,6 +79,10 @@ export const FOOTER_LINKS: Array<CustomLinksGroup> = [
{ {
text: 'MetaDock', text: 'MetaDock',
url: 'https://blocksec.com/metadock', url: 'https://blocksec.com/metadock',
iconUrl: [
'http://localhost:3000/mocks/image_s.jpg',
'http://localhost:3000/mocks/image_svg.svg',
],
}, },
{ {
text: 'Sourcify', text: 'Sourcify',
......
export type CustomLink = { export type CustomLink = {
text: string; text: string;
url: string; url: string;
iconUrl?: Array<string>;
}; };
export type CustomLinksGroup = { export type CustomLinksGroup = {
......
...@@ -9,7 +9,7 @@ import Footer from './Footer'; ...@@ -9,7 +9,7 @@ import Footer from './Footer';
const FOOTER_LINKS_URL = 'https://localhost:3000/footer-links.json'; const FOOTER_LINKS_URL = 'https://localhost:3000/footer-links.json';
test.describe('with custom links, max cols', () => { test.describe('with custom links, max cols', () => {
test.beforeEach(async({ render, mockApiResponse, mockConfigResponse, injectMetaMaskProvider, mockEnvs }) => { test.beforeEach(async({ render, mockApiResponse, mockConfigResponse, injectMetaMaskProvider, mockEnvs, mockAssetResponse }) => {
await mockEnvs([ await mockEnvs([
[ 'NEXT_PUBLIC_FOOTER_LINKS', FOOTER_LINKS_URL ], [ 'NEXT_PUBLIC_FOOTER_LINKS', FOOTER_LINKS_URL ],
]); ]);
...@@ -21,6 +21,8 @@ test.describe('with custom links, max cols', () => { ...@@ -21,6 +21,8 @@ test.describe('with custom links, max cols', () => {
indexed_internal_transactions_ratio: '0.1', indexed_internal_transactions_ratio: '0.1',
indexed_blocks_ratio: '0.1', indexed_blocks_ratio: '0.1',
}); });
await mockAssetResponse(FOOTER_LINKS[3].links[0].iconUrl?.[0]!, './playwright/mocks/image_s.jpg');
await mockAssetResponse(FOOTER_LINKS[3].links[0].iconUrl?.[1]!, './playwright/mocks/image_svg.svg');
await render(<Footer/>); await render(<Footer/>);
}); });
......
import { Center } from '@chakra-ui/react'; import { Center } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import { useColorModeValue } from 'toolkit/chakra/color-mode';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link'; import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton'; import { Skeleton } from 'toolkit/chakra/skeleton';
import type { IconName } from 'ui/shared/IconSvg'; import type { IconName } from 'ui/shared/IconSvg';
...@@ -9,23 +11,55 @@ import IconSvg from 'ui/shared/IconSvg'; ...@@ -9,23 +11,55 @@ import IconSvg from 'ui/shared/IconSvg';
type Props = { type Props = {
icon?: IconName; icon?: IconName;
iconSize?: string; iconSize?: string;
iconUrl?: Array<string>;
text: string; text: string;
url: string; url: string;
isLoading?: boolean; isLoading?: boolean;
}; };
const FooterLinkItem = ({ icon, iconSize, text, url, isLoading }: Props) => { const FooterLinkItemIconExternal = ({ iconUrl, text }: { iconUrl: Array<string>; text: string }) => {
const [ lightIconUrl, darkIconUrl ] = iconUrl;
const imageSrc = useColorModeValue(lightIconUrl, darkIconUrl || lightIconUrl);
return (
<Image
src={ imageSrc }
alt={ `${ text } icon` }
boxSize={ 5 }
objectFit="contain"
/>
);
};
const FooterLinkItem = ({ icon, iconSize, iconUrl, text, url, isLoading }: Props) => {
if (isLoading) { if (isLoading) {
return <Skeleton loading my="3px">{ text }</Skeleton>; return <Skeleton loading my="3px">{ text }</Skeleton>;
} }
const iconElement = (() => {
if (iconUrl && Array.isArray(iconUrl)) {
const [ lightIconUrl, darkIconUrl ] = iconUrl;
if (typeof lightIconUrl === 'string' && (typeof darkIconUrl === 'string' || !darkIconUrl)) {
return <FooterLinkItemIconExternal iconUrl={ iconUrl } text={ text }/>;
}
}
if (icon) {
return ( return (
<Link href={ url } display="flex" alignItems="center" h="30px" variant="subtle" target="_blank" textStyle="xs"> <Center minW={ 6 }>
{ icon && (
<Center minW={ 6 } mr={ 2 }>
<IconSvg boxSize={ iconSize || 5 } name={ icon }/> <IconSvg boxSize={ iconSize || 5 } name={ icon }/>
</Center> </Center>
) } );
}
return null;
})();
return (
<Link href={ url } display="flex" alignItems="center" h="30px" variant="subtle" target="_blank" textStyle="xs" columnGap={ 2 }>
{ iconElement }
{ text } { text }
</Link> </Link>
); );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment