Make header and footer responsive

Add time util for human readable string
pull/1/head
J M Rossy 2 years ago
parent 2f9f6c48f6
commit f064843bbb
  1. 2
      src/components/icons/ChainToChain.tsx
  2. 2
      src/components/nav/ContentFrame.tsx
  3. 6
      src/components/nav/Footer.tsx
  4. 52
      src/components/nav/Header.tsx
  5. 31
      src/features/search/MessageSearch.tsx
  6. 17
      src/features/search/MessageSummary.tsx
  7. 4
      src/images/icons/hamburger.svg
  8. 2
      src/styles/globals.css
  9. 28
      src/utils/time.ts

@ -15,7 +15,7 @@ function _ChainToChain({
size?: number;
}) {
return (
<div className="flex items-center justify-center space-x-1 sm:space-x-2">
<div className="flex items-center justify-center sm:space-x-1 md:space-x-2">
<ChainIcon chainId={originChainId} size={size} />
<Image src={ArrowRightIcon} alt="arrow-right" width={32} height={32} />
<ChainIcon chainId={destinationChainId} size={size} />

@ -4,7 +4,7 @@ export function ContentFrame(props: PropsWithChildren) {
return (
<div className="flex flex-col justify-center items-center min-h-full">
<div
style={{ width: 'min(900px,95vw)' }}
style={{ width: 'min(900px,96vw)' }}
className="relative overflow-visible my-8"
>
<div className="absolute -top-5 -left-4 -right-4 h-36 bg-green-500 rounded z-10"></div>

@ -10,7 +10,7 @@ import Twitter from '../../images/logos/twitter.svg';
export function Footer() {
return (
<footer className="mt-3 px-2 py-4 sm:pl-6 sm:pr-8 opacity-90">
<footer className="mt-3 px-4 py-4 sm:pl-6 sm:pr-8 opacity-90">
<div className="flex justify-between items-center">
<div className="flex items-center">
<Image src={Abacus} alt="Abacus Logo" width={52} height={52} />
@ -25,7 +25,7 @@ export function Footer() {
</p>
</div>
</div>
<div className="grid grid-rows-2 grid-cols-3 gap-y-3 gap-x-8">
<div className="grid grid-rows-2 grid-cols-3 gap-y-4 gap-x-5 md:gap-x-8">
<FooterIconLink
to="https://twitter.com/Abacus_Network"
imgSrc={Twitter}
@ -79,7 +79,7 @@ function FooterIconLink({
className="flex items-center hover:underline hover:opacity-70 transition-all"
>
<Image src={imgSrc} alt={text} width={18} height={18} />
<span className="ml-2 text-sm">{text}</span>
<span className="hidden sm:inline ml-2 text-sm">{text}</span>
</a>
);
}

@ -1,11 +1,15 @@
import Image from 'next/future/image';
import Link from 'next/link';
import useDropdownMenu from 'react-accessible-dropdown-menu-hook';
import Hamburger from '../../images/icons/hamburger.svg';
import Logo from '../../images/logos/abacus-with-name.svg';
export function Header() {
const { buttonProps, itemProps, isOpen, setIsOpen } = useDropdownMenu(3);
return (
<header className="p-2 sm:py-3 sm:pl-6 sm:pr-8">
<header className="p-2 sm:py-3 sm:pl-6 sm:pr-8 w-full">
<div className="flex items-center justify-between">
<Link href="/">
<a className="flex items-center">
@ -20,13 +24,13 @@ export function Header() {
</div>
<div
style={{ fontSize: '2rem' }}
className="font-serif text-green-500 ml-2 pt-px"
className="font-serif text-green-500 sm:ml-2 pt-px"
>
Explorer
</div>
</a>
</Link>
<div className="flex space-x-12">
<div className="hidden sm:flex sm:space-x-8 md:space-x-12">
<Link href="/">
<a className={styles.navLink}>Home</a>
</Link>
@ -47,6 +51,45 @@ export function Header() {
About
</a>
</div>
<div className="relative flex item-center sm:hidden mr-2">
<button className="hover:opactiy-70 transition-all" {...buttonProps}>
<Image src={Hamburger} alt="Nav menu" width={26} height={26} />
</button>
</div>
</div>
<div
className={`${styles.dropdownContainer} ${!isOpen && 'hidden'} right-0`}
role="menu"
>
<Link href="/">
<a
{...itemProps[0]}
className={styles.dropdownOption}
onClick={() => setIsOpen(false)}
>
<div>Home</div>
</a>
</Link>
<a
{...itemProps[1]}
onClick={() => setIsOpen(false)}
className={styles.dropdownOption}
target="_blank"
href="https://docs.useabacus.network"
rel="noopener noreferrer"
>
Docs
</a>
<a
{...itemProps[2]}
onClick={() => setIsOpen(false)}
className={styles.dropdownOption}
target="_blank"
href="https://www.useabacus.network"
rel="noopener noreferrer"
>
About
</a>
</div>
</header>
);
@ -55,4 +98,7 @@ export function Header() {
const styles = {
navLink:
'flex items-center text-lg tracking-wide hover:underline hover:opacity-70 transition-all',
dropdownContainer: 'dropdown-menu w-28 mt-1 mr-px bg-beige-500',
dropdownOption:
'flex items-center justify-center cursor-pointer p-2 mt-1 rounded hover:underline',
};

@ -4,7 +4,7 @@ import { ChangeEvent, useMemo, useState } from 'react';
import { SelectField } from '../../components/input/SelectField';
import { Card } from '../../components/layout/Card';
import { prodChains } from '../../consts/networksConfig';
import ArrowRightIcon from '../../images/icons/arrow-right.svg';
import ArrowRightIcon from '../../images/icons/arrow-right-short.svg';
import FunnelIcon from '../../images/icons/funnel.svg';
import SearchIcon from '../../images/icons/search.svg';
import { MOCK_MESSAGES } from '../../test/mockMessages';
@ -16,7 +16,7 @@ import { MessageSummary } from './MessageSummary';
// TODO text grays with ting of green
export function MessageSearch() {
const [searchInput, setSearchInput] = useState('');
const [originChainFilter, setoriginChainFilter] = useState('');
const [originChainFilter, setOriginChainFilter] = useState('');
const [destinationChainFilter, setDestinationChainFilter] = useState('');
const chainOptions = useMemo(getChainOptionList, []);
@ -25,8 +25,8 @@ export function MessageSearch() {
setSearchInput(event.target.value);
};
const onChangeoriginFilter = (value: string) => {
setoriginChainFilter(value);
const onChangeOriginFilter = (value: string) => {
setOriginChainFilter(value);
};
const onChangeDestinationFilter = (value: string) => {
@ -40,33 +40,33 @@ export function MessageSearch() {
value={searchInput}
onChange={onChangeSearch}
type="text"
placeholder="Search for messages by address, transaction hash, or block hash"
className="p2 sm:p-4 flex-1 h-10 sm:h-12 rounded focus:outline-none"
placeholder="Search for messages by address or transaction hash"
className="p-2 sm:p-4 flex-1 h-10 sm:h-12 rounded focus:outline-none"
/>
<div className="bg-beige-500 h-10 sm:h-12 w-10 sm:w-12 flex items-center justify-center rounded">
<Image src={SearchIcon} alt="Search" width={20} height={20} />
</div>
</div>
<Card width="w-full" classes="mt-6 p-0">
<div className="px-4 py-3 flex items-center justify-between border-b border-gray-300">
<div className="px-2 py-3 md:px-4 md:py-3 flex items-center justify-between border-b border-gray-300">
<h2 className="text-gray-800">Latest Messages</h2>
<div className="flex items-center space-x-3">
<div className="flex items-center space-x-2 md:space-x-3">
<div className="w-px h-8 bg-gray-300"></div>
<Image src={FunnelIcon} alt="Filter" width={22} height={22} />
<SelectField
classes="w-32"
classes="w-24 md:w-32"
options={chainOptions}
value={originChainFilter}
onValueSelect={onChangeoriginFilter}
onValueSelect={onChangeOriginFilter}
/>
<Image
src={ArrowRightIcon}
alt="Arrow-right"
width={24}
height={24}
width={30}
height={30}
/>
<SelectField
classes="w-32"
classes="w-24 md:w-32"
options={chainOptions}
value={destinationChainFilter}
onValueSelect={onChangeDestinationFilter}
@ -74,7 +74,10 @@ export function MessageSearch() {
</div>
</div>
{MOCK_MESSAGES.map((m) => (
<div key={`message-${m.id}`} className="px-4 py-3 border-b">
<div
key={`message-${m.id}`}
className="px-2 py-2 md:px-4 md:py-3 border-b"
>
<MessageSummary message={m} />
</div>
))}

@ -1,6 +1,7 @@
import { ChainToChain } from '../../components/icons/ChainToChain';
import { Message, MessageStatus } from '../../types';
import { shortenAddress } from '../../utils/addresses';
import { getHumanReadableTimeString } from '../../utils/time';
export function MessageSummary({ message }: { message: Message }) {
const {
@ -23,19 +24,19 @@ export function MessageSummary({ message }: { message: Message }) {
}
return (
<div className="flex items-center justify-between space-x-8 sm:space-x-12 md:space-x-16">
<div className="flex items-center justify-between space-x-5 sm:space-x-12 md:space-x-16">
<ChainToChain
originChainId={originChainId}
destinationChainId={destinationChainId}
/>
<div className="flex items-center justify-between flex-1 mx-4">
<div className="flex items-center justify-between flex-1">
<div className={styles.valueContainer}>
<div className={styles.label}>Sender</div>
<div className={styles.value}>
{shortenAddress(sender) || 'Invalid Address'}
</div>
</div>
<div className={styles.valueContainer}>
<div className="hidden sm:flex flex-col">
<div className={styles.label}>Recipient</div>
<div className={styles.value}>
{shortenAddress(recipient) || 'Invalid Address'}
@ -44,12 +45,12 @@ export function MessageSummary({ message }: { message: Message }) {
<div className={styles.valueContainer}>
<div className={styles.label}>Time sent</div>
<div className={styles.value}>
{getDateTimeString(originTimeSent)}
{getHumanReadableTimeString(originTimeSent)}
</div>
</div>
</div>
<div
className={`w-20 sm:w-24 py-2 text-sm text-center rounded ${statusColor}`}
className={`w-20 md:w-24 py-2 text-sm text-center rounded ${statusColor}`}
>
{statusText}
</div>
@ -57,12 +58,6 @@ export function MessageSummary({ message }: { message: Message }) {
);
}
// TODO smarter datetime
function getDateTimeString(timestamp: number) {
const date = new Date(timestamp);
return `${date.toLocaleTimeString()} - ${date.toLocaleDateString()}`;
}
const styles = {
valueContainer: 'flex flex-col',
label: 'text-sm text-gray-500',

@ -1,3 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="#2E3338" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="6 12 36 24"><path fill="#010101" d="M6 36v-3h36v3Zm0-10.5v-3h36v3ZM6 15v-3h36v3Z"></path></svg>

Before

Width:  |  Height:  |  Size: 320 B

After

Width:  |  Height:  |  Size: 144 B

@ -118,7 +118,7 @@ Dropdowns
position: absolute;
z-index: 100;
text-align: left;
padding: 0.6rem 0.7rem;
padding: 0.5rem 0.6rem;
border: 1px solid #f1f1f1;
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1),

@ -0,0 +1,28 @@
// Inspired by https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site
export function getHumanReadableTimeString(timestamp: number) {
const seconds = Math.floor((new Date().getTime() - timestamp) / 1000);
if (seconds <= 2) {
return 'Just now';
}
if (seconds <= 60) {
return `${seconds} seconds ago`;
}
const minutes = Math.floor(seconds / 3600);
if (minutes <= 1) {
return '1 minute ago';
}
if (minutes < 60) {
return `${minutes} minutes ago`;
}
const hours = Math.floor(minutes / 60);
if (hours <= 1) {
return '1 hour ago';
}
if (hours < 24) {
return `${hours} hours ago`;
}
const date = new Date(timestamp);
return date.toLocaleDateString();
}
Loading…
Cancel
Save