Add new menu styling (#4317)
* POC displaying route in Tabs * WIP Add new menu style Co-authored-by: Krzysztof Jelski <krzysztof-jelski@users.noreply.github.com> * Rename Section to CurrentSection * Add new Tabs styling * WIP Add new menu styling * Set menu's max-width * Add theme styles to menu * Change font size and font weight * Remove r * Remove console logs * Update header styles * Fix settings link padding * Change nav link font weight * Change settings link font weight * Fix settings link paddings * Change nav links font size * Add menu padding * Change badge paddings * Change counter badge color to grey * Add space between menu items * Change chain name font styles * Remove color setting from chain css class * Restore previous whitish color * Move NodeInfo component to .menuContainer * Restore font-weights * Increase the max-width of chain name * Set darker color for TabsSectionDelimiter * Center vertically the left chain icon * Change ChainInfo's elements align and font-sizes * storke highlight * Restore ChainInfo alignment * Revert badges colors * Align right search bar * Change menu styling to prevent menu lists blending * Remove scrollbar from tabs * Resolve problem with search bar placement * Add margin-bottom to settings tab * Change setting padding when there is no badge * Make tab underline to ignore badge and move it under tabs bar * WIP Change menu * Apply contrast to menu hover * Add bright menu hover for chains with dark chain color * Add chain color to menu list * Set menu list hover * Add badge styles * Align items badge Co-authored-by: Krzysztof Jelski <krzysztof-jelski@users.noreply.github.com> Co-authored-by: Krzysztof Jelski <krzysztof@ethworks.io> Co-authored-by: alexcherman <sashacherman@gmail.com> Co-authored-by: Jaco Greeff <jacogr@gmail.com> Co-authored-by: Ewa Kowalska <ewcia.kowalska@gmail.com>
This commit is contained in:
parent
5f9907b04a
commit
5b65c111d1
@ -16,6 +16,7 @@
|
||||
--font-weight-normal: 400;
|
||||
--width-full: 1750px;
|
||||
--width-half: 875px;
|
||||
--bg-menu-hover: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
@ -23,10 +24,9 @@
|
||||
--bg-input-error: #48393f;
|
||||
--bg-inverse: rgba(78, 78, 78, 0.66);
|
||||
--bg-menu: #26272c;
|
||||
--bg-menu-hover: rgba(255, 255, 255, 0.05);
|
||||
--bg-page: #26272c;
|
||||
--bg-table: #3b3d3f;
|
||||
--bg-tabs: #3b3d3f;
|
||||
--bg-tabs: #38393f;
|
||||
--bg-toggle: #58595f;
|
||||
--border-table: #343536;
|
||||
--border-tabs: #343536;
|
||||
@ -44,7 +44,6 @@
|
||||
--bg-input-error: #fff6f6;
|
||||
--bg-inverse: rgba(244, 242, 240, 0.91);
|
||||
--bg-menu: #ffffff;
|
||||
--bg-menu-hover: rgba(255, 255, 255, 0.5);
|
||||
--bg-page: #f5f3f1;
|
||||
--bg-table: #ffffff;
|
||||
--bg-tabs: #ffffff;
|
||||
|
||||
@ -8,7 +8,7 @@ import { useLocation } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import createRoutes from '@polkadot/apps-routing';
|
||||
import { ErrorBoundary, Spinner, StatusContext } from '@polkadot/react-components';
|
||||
import { ErrorBoundary, SectionContext, Spinner, StatusContext } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
|
||||
import { findMissingApis } from '../endpoint';
|
||||
@ -38,7 +38,7 @@ function Content ({ className }: Props): React.ReactElement<Props> {
|
||||
const { api, isApiConnected, isApiReady } = useApi();
|
||||
const { queueAction } = useContext(StatusContext);
|
||||
|
||||
const { Component, display: { needsApi }, name } = useMemo(
|
||||
const { Component, display: { needsApi }, icon, name, text } = useMemo(
|
||||
(): Route => {
|
||||
const app = location.pathname.slice(1) || '';
|
||||
|
||||
@ -61,23 +61,25 @@ function Content ({ className }: Props): React.ReactElement<Props> {
|
||||
<>
|
||||
<Suspense fallback='...'>
|
||||
<ErrorBoundary trigger={name}>
|
||||
{missingApis.length
|
||||
? (
|
||||
<NotFound
|
||||
basePath={`/${name}`}
|
||||
location={location}
|
||||
missingApis={missingApis}
|
||||
onStatusChange={queueAction}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Component
|
||||
basePath={`/${name}`}
|
||||
location={location}
|
||||
onStatusChange={queueAction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<SectionContext.Provider value={{ icon, text }}>
|
||||
{missingApis.length
|
||||
? (
|
||||
<NotFound
|
||||
basePath={`/${name}`}
|
||||
location={location}
|
||||
missingApis={missingApis}
|
||||
onStatusChange={queueAction}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Component
|
||||
basePath={`/${name}`}
|
||||
location={location}
|
||||
onStatusChange={queueAction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
</SectionContext.Provider>
|
||||
</ErrorBoundary>
|
||||
</Suspense>
|
||||
<Status />
|
||||
|
||||
@ -58,7 +58,7 @@ function ChainInfo ({ className }: Props): React.ReactElement<Props> {
|
||||
|
||||
export default React.memo(styled(ChainInfo)`
|
||||
box-sizing: border-box;
|
||||
padding: 0.75rem 1rem 0.75rem 1.5rem;
|
||||
padding: 0.642rem 1rem 0.642rem 1.5rem;
|
||||
margin: 0;
|
||||
|
||||
.apps--SideBar-logo-inner {
|
||||
@ -91,16 +91,24 @@ export default React.memo(styled(ChainInfo)`
|
||||
.info {
|
||||
flex: 1;
|
||||
padding-right: 0.5rem;
|
||||
text-align: right;
|
||||
|
||||
.bestNumber,
|
||||
.chain {
|
||||
max-width: 16rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.chain, .bestNumber {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.runtimeVersion {
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.2;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.2;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,15 +12,17 @@ import Item from './Item';
|
||||
|
||||
interface Props extends Group {
|
||||
className?: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
const SHA_COL = 'rgba(34, 36, 38, 0.12)';
|
||||
const SHA_OFF = '5px';
|
||||
|
||||
function Grouping ({ className = '', name, routes }: Props): React.ReactElement<Props> {
|
||||
function Grouping ({ className = '', isActive, name, routes }: Props): React.ReactElement<Props> {
|
||||
if (routes.length === 1) {
|
||||
return (
|
||||
<Item
|
||||
className={isActive ? 'isActive' : ''}
|
||||
isToplevel
|
||||
route={routes[0]}
|
||||
/>
|
||||
@ -28,12 +30,12 @@ function Grouping ({ className = '', name, routes }: Props): React.ReactElement<
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={className}>
|
||||
<div className='groupHdr highlight--color-contrast'>
|
||||
<li className={`${className} ${isActive ? 'isActive' : ''}`}>
|
||||
<div className={`groupHdr ${!isActive ? 'highlight--color-contrast' : ''}`}>
|
||||
<span>{name}</span>
|
||||
<Icon icon='caret-down' />
|
||||
</div>
|
||||
<ul className='groupMenu highlight--bg-light'>
|
||||
<ul className='groupMenu'>
|
||||
{routes.map((route): React.ReactNode => (
|
||||
<Item
|
||||
key={route.name}
|
||||
@ -48,24 +50,43 @@ function Grouping ({ className = '', name, routes }: Props): React.ReactElement<
|
||||
export default React.memo(styled(Grouping)`
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
margin-bottom: -5px;
|
||||
|
||||
.groupHdr {
|
||||
border-radius: 0.25rem 0.25rem 0 0;
|
||||
padding: 1rem 1.25rem 1rem 1.5rem;
|
||||
border-radius: 0.285rem;
|
||||
padding: 0.857rem 0.857em 0.786rem 1rem;
|
||||
margin-bottom: 1.071rem;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.214rem;
|
||||
|
||||
> .ui--Icon {
|
||||
margin-left: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.isActive .groupHdr {
|
||||
background-color: var(--bg-tabs);
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
padding: 0.857rem 1.429rem 1.857rem;
|
||||
margin-bottom: 0;
|
||||
|
||||
&:hover {
|
||||
padding: 0.857rem 1.429rem 0.786rem 1.429rem;
|
||||
margin-bottom: 1.071rem;
|
||||
}
|
||||
}
|
||||
|
||||
.groupMenu {
|
||||
border-radius: 0 0 0.25rem 0.25rem;
|
||||
border-radius: 0.286rem;
|
||||
box-shadow: 0 ${SHA_OFF} ${SHA_OFF} -${SHA_OFF} ${SHA_COL}, ${SHA_OFF} 0 ${SHA_OFF} -${SHA_OFF} ${SHA_COL}, -${SHA_OFF} 0 ${SHA_OFF} -${SHA_OFF} ${SHA_COL};
|
||||
display: none;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 2.9rem;
|
||||
z-index: 250;
|
||||
|
||||
> li {
|
||||
@ -75,17 +96,21 @@ export default React.memo(styled(Grouping)`
|
||||
padding-right: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
&::before {
|
||||
bottom: 0;
|
||||
content: ' ';
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.groupHdr,
|
||||
.groupMenu li {
|
||||
background: var(--bg-menu);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.groupHdr {
|
||||
box-shadow: 0 -${SHA_OFF} ${SHA_OFF} -${SHA_OFF} ${SHA_COL}, ${SHA_OFF} 0 ${SHA_OFF} -${SHA_OFF} ${SHA_COL}, -${SHA_OFF} 0 ${SHA_OFF} -${SHA_OFF} ${SHA_COL};
|
||||
box-shadow: 0px 4px 37px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.groupMenu {
|
||||
|
||||
@ -11,18 +11,19 @@ import { useToggle } from '@polkadot/react-hooks';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
isLink?: boolean;
|
||||
isToplevel?: boolean;
|
||||
route: ItemRoute;
|
||||
}
|
||||
|
||||
const DUMMY_COUNTER = () => 0;
|
||||
|
||||
function Item ({ className = '', isToplevel, route: { Modal, href, icon, name, text, useCounter = DUMMY_COUNTER } }: Props): React.ReactElement<Props> {
|
||||
function Item ({ className = '', isLink, isToplevel, route: { Modal, href, icon, name, text, useCounter = DUMMY_COUNTER } }: Props): React.ReactElement<Props> {
|
||||
const [isModalVisible, toggleModal] = useToggle();
|
||||
const count = useCounter();
|
||||
|
||||
return (
|
||||
<li className={`${className}${count ? ' withCounter' : ''} ${isToplevel ? 'topLevel highlight--color-contrast' : ''}`}>
|
||||
<li className={`ui--MenuItem ${className}${count ? ' withCounter' : ''} ${isLink ? 'isLink' : ''} ${isToplevel ? 'topLevel highlight--color-contrast' : ''}`}>
|
||||
<a
|
||||
href={Modal ? undefined : (href || `#/${name}`)}
|
||||
onClick={Modal ? toggleModal : undefined}
|
||||
@ -33,7 +34,7 @@ function Item ({ className = '', isToplevel, route: { Modal, href, icon, name, t
|
||||
{text}
|
||||
{!!count && (
|
||||
<Badge
|
||||
color={isToplevel ? 'counterInvert' : 'counter'}
|
||||
color={'white'}
|
||||
info={count}
|
||||
/>
|
||||
)}
|
||||
@ -51,32 +52,66 @@ export default React.memo(styled(Item)`
|
||||
white-space: nowrap;
|
||||
|
||||
&.topLevel {
|
||||
padding: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.214rem;
|
||||
border-radius: 0.15rem;
|
||||
margin-bottom: -5px;
|
||||
|
||||
a {
|
||||
padding: 1rem 1.5rem;
|
||||
padding: 0.857rem 0.857em 0.786rem 1rem;
|
||||
margin-bottom: 1.071rem;
|
||||
line-height: 1.214rem;
|
||||
border-radius: 0.285rem;
|
||||
}
|
||||
|
||||
&.isActive.highlight--color-contrast {
|
||||
font-size: 1.15rem;
|
||||
font-weight: 400;
|
||||
color: var(--color-text);
|
||||
|
||||
a {
|
||||
background-color: var(--bg-tabs);
|
||||
}
|
||||
}
|
||||
|
||||
&.isActive {
|
||||
border-radius: 0.15rem 0.15rem 0 0;
|
||||
margin-bottom: -5px;
|
||||
|
||||
a {
|
||||
padding: 0.857rem 1.429rem 1.857rem;
|
||||
margin-bottom: 0;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
&&.withCounter a {
|
||||
padding-right: 3.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--Badge {
|
||||
top: 0.95rem;
|
||||
top: 0.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.withCounter a {
|
||||
padding-right: 3rem;
|
||||
&&.withCounter a {
|
||||
padding-right: 3.2rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit !important;
|
||||
display: block;
|
||||
padding: 0.75rem 1.5rem;
|
||||
padding: 0.5rem 1.15rem 0.57rem;
|
||||
text-decoration: none;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.ui--Badge {
|
||||
position: absolute;
|
||||
right: 0.25rem;
|
||||
top: 0.7rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.ui--Icon {
|
||||
|
||||
@ -18,7 +18,7 @@ function NodeInfo ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
const { api, isApiReady } = useApi();
|
||||
|
||||
return (
|
||||
<div className={`${className} media--1400 highlight--color-contrast`}>
|
||||
<div className={`${className} media--1400 highlight--color-contrast ui--NodeInfo`}>
|
||||
{isApiReady && (
|
||||
<div>
|
||||
<NodeName />
|
||||
|
||||
@ -12,7 +12,6 @@ import { useLocation } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import createRoutes from '@polkadot/apps-routing';
|
||||
import { Icon } from '@polkadot/react-components';
|
||||
import { useAccounts, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
import { findMissingApis } from '../endpoint';
|
||||
@ -125,46 +124,54 @@ function Menu ({ className = '' }: Props): React.ReactElement<Props> {
|
||||
|
||||
return (
|
||||
<div className={`${className}${isLoading ? ' isLoading' : ''} highlight--bg`}>
|
||||
<div className='menuSection'>
|
||||
<ChainInfo />
|
||||
{activeRoute && (
|
||||
<div className='menuActive'>
|
||||
<Icon icon={activeRoute.icon} />
|
||||
<span>{activeRoute.text}</span>
|
||||
</div>
|
||||
)}
|
||||
<ul className='menuItems'>
|
||||
{visibleGroups.map(({ name, routes }): React.ReactNode => (
|
||||
<Grouping
|
||||
key={name}
|
||||
name={name}
|
||||
routes={routes}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
<div className='menuContainer'>
|
||||
<div className='menuSection'>
|
||||
<ChainInfo />
|
||||
<ul className='menuItems'>
|
||||
{visibleGroups.map(({ name, routes }): React.ReactNode => (
|
||||
<Grouping
|
||||
isActive={ activeRoute && activeRoute.group === name.toLowerCase()}
|
||||
key={name}
|
||||
name={name}
|
||||
routes={routes}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<div className='menuSection media--1200'>
|
||||
<ul className='menuItems'>
|
||||
{externalRef.current.map((route): React.ReactNode => (
|
||||
<Item
|
||||
isLink
|
||||
isToplevel
|
||||
key={route.name}
|
||||
route={route}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<NodeInfo className='media--1400' />
|
||||
</div>
|
||||
<div className='menuSection media--1200'>
|
||||
<ul className='menuItems'>
|
||||
{externalRef.current.map((route): React.ReactNode => (
|
||||
<Item
|
||||
isToplevel
|
||||
key={route.name}
|
||||
route={route}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<NodeInfo />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Menu)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
z-index: 220;
|
||||
position: relative;
|
||||
|
||||
& .menuContainer {
|
||||
flex-direction: row;
|
||||
align-items: flex-end;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 1.5rem;
|
||||
width: 100%;
|
||||
max-width: 1750px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
&.isLoading {
|
||||
background: #999 !important;
|
||||
@ -184,7 +191,6 @@ export default React.memo(styled(Menu)`
|
||||
|
||||
.menuSection {
|
||||
align-items: flex-end;
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@ -206,10 +212,19 @@ export default React.memo(styled(Menu)`
|
||||
flex: 1 1;
|
||||
list-style: none;
|
||||
margin: 0 1rem 0 0;
|
||||
padding: 0;
|
||||
padding: 0.786rem 0 0;
|
||||
|
||||
> li {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
> li + li {
|
||||
margin-left: 0.25rem
|
||||
}
|
||||
}
|
||||
|
||||
.ui--NodeInfo {
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
`);
|
||||
|
||||
@ -11,7 +11,7 @@ import Tooltip from './Tooltip';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
color: 'blue' | 'counter' | 'counterInvert' | 'gray' | 'green' | 'highlight' | 'normal' | 'orange' | 'purple' | 'red' | 'transparent';
|
||||
color: 'blue' | 'gray' | 'green' | 'highlight' | 'normal' | 'orange' | 'purple' | 'red' | 'transparent' | 'white';
|
||||
hover?: React.ReactNode;
|
||||
icon?: IconName;
|
||||
info?: React.ReactNode;
|
||||
@ -19,8 +19,6 @@ interface Props {
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const HIGHLIGHTS = ['counter', 'highlight'];
|
||||
|
||||
let badgeId = 0;
|
||||
|
||||
function Badge ({ className = '', color = 'normal', hover, icon, info, isSmall, onClick }: Props): React.ReactElement<Props> | null {
|
||||
@ -28,12 +26,12 @@ function Badge ({ className = '', color = 'normal', hover, icon, info, isSmall,
|
||||
const extraProps = hover
|
||||
? { 'data-for': trigger, 'data-tip': true }
|
||||
: {};
|
||||
const isHighlight = HIGHLIGHTS.includes(color);
|
||||
const isHighlight = color === 'highlight';
|
||||
|
||||
return (
|
||||
<div
|
||||
{...extraProps}
|
||||
className={`ui--Badge${hover ? ' isTooltip' : ''}${isSmall ? ' isSmall' : ''}${onClick ? ' isClickable' : ''}${isHighlight ? ' highlight--bg' : ''}${color === 'counterInvert' ? ' highlight--bg-contrast highlight--color' : ''} ${color}Color ${className}`}
|
||||
className={`ui--Badge${hover ? ' isTooltip' : ''}${isSmall ? ' isSmall' : ''}${onClick ? ' isClickable' : ''}${isHighlight ? ' highlight--bg' : ''} ${color}Color ${className}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className={isHighlight ? 'highlight--color-contrast' : ''}>{info || (icon && <Icon icon={icon} />)}</div>
|
||||
@ -95,8 +93,7 @@ export default React.memo(styled(Badge)`
|
||||
background: steelblue;
|
||||
}
|
||||
|
||||
&.counterColor,
|
||||
&.counterInvertColor {
|
||||
&.counterColor {
|
||||
margin: 0 0.5rem;
|
||||
vertical-align: middle;
|
||||
}
|
||||
@ -126,4 +123,8 @@ export default React.memo(styled(Badge)`
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
&.whiteColor {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
`);
|
||||
|
||||
@ -27,11 +27,15 @@ export default React.memo(styled(FilterOverlay)`
|
||||
}
|
||||
right: calc(50% - var(--width-half) + 1.5rem);
|
||||
|
||||
&& .ui--Input {
|
||||
margin: 0.29rem 0;
|
||||
}
|
||||
|
||||
${media.DESKTOP`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
position: absolute;
|
||||
top: 0.25rem;
|
||||
top: 0rem;
|
||||
|
||||
> div {
|
||||
max-width: 35rem !important;
|
||||
@ -44,8 +48,7 @@ export default React.memo(styled(FilterOverlay)`
|
||||
.ui.selection.dropdown {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
`}
|
||||
`};
|
||||
|
||||
@media (max-width: var(--width-full)) {
|
||||
right: 1.5rem;
|
||||
|
||||
@ -47,13 +47,14 @@ export default React.memo(styled(HelpOverlay)`
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
font-size: 2rem;
|
||||
padding: 0.65rem 1.5rem 0 0;
|
||||
padding: 0.35rem 1.5rem 0 0;
|
||||
}
|
||||
|
||||
> .help-button {
|
||||
position: absolute;
|
||||
right: 0rem;
|
||||
top: 0rem;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.help-slideout {
|
||||
|
||||
45
packages/react-components/src/Tabs/CurrentSection.tsx
Normal file
45
packages/react-components/src/Tabs/CurrentSection.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
// Copyright 2017-2020 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { IconName } from '@fortawesome/fontawesome-svg-core';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import Icon from '../Icon';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
icon: IconName;
|
||||
text: string;
|
||||
}
|
||||
|
||||
function CurrentSection ({ className = '', icon, text }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<div className={`${className} active-tab`}>
|
||||
<Icon icon={icon} />
|
||||
<span>{text}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(CurrentSection)`
|
||||
margin: 0 2.5rem 0 1.5rem;
|
||||
font-weight: 400;
|
||||
font-size: 1rem;
|
||||
line-height: 1.57rem;
|
||||
min-width: max-content;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-text);
|
||||
|
||||
.ui--Icon {
|
||||
margin-right: 0.85rem;
|
||||
max-width: 1rem;
|
||||
max-height: 1rem;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 900px) {
|
||||
margin: 0 1.5rem;
|
||||
}
|
||||
`);
|
||||
@ -33,7 +33,10 @@ function Tab ({ basePath, className = '', count, hasParams, index, isExact, isRo
|
||||
strict={tabIsExact}
|
||||
to={to}
|
||||
>
|
||||
{text}{!!count && (
|
||||
<div className='tabLinkText'>
|
||||
{text}
|
||||
</div>
|
||||
{!!count && (
|
||||
<Badge
|
||||
className='tabCounter'
|
||||
color='counter'
|
||||
@ -45,21 +48,48 @@ function Tab ({ basePath, className = '', count, hasParams, index, isExact, isRo
|
||||
}
|
||||
|
||||
export default React.memo(styled(Tab)`
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--color-text) !important;
|
||||
margin-bottom: -3px;
|
||||
padding: 0.5rem 1.5rem 0.75rem;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #8B8B8B;
|
||||
padding: 0 1.5rem;
|
||||
height: 100%;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
|
||||
|
||||
&:hover {
|
||||
color: #8B8B8B;
|
||||
|
||||
.tabLinkText::after{
|
||||
background-color: #8B8B8B;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover .tabLinkText::after,
|
||||
&.tabLinkActive .tabLinkText::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 3.14rem;
|
||||
height: 2px;
|
||||
bottom: -2px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
&.tabLinkActive {
|
||||
border-bottom-color: #e6e6e6;
|
||||
color: var(--color-text) !important;
|
||||
font-weight: 400;
|
||||
&:hover {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
filter: highlight(120%);
|
||||
|
||||
&:not(.tabLinkActive) {
|
||||
border-bottom-color: #e6e6e6;
|
||||
}
|
||||
.tabLinkText {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tabCounter {
|
||||
|
||||
31
packages/react-components/src/Tabs/TabsSectionDelimiter.tsx
Normal file
31
packages/react-components/src/Tabs/TabsSectionDelimiter.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2017-2020 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
interface TabsSectionDelimiterProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function TabsSectionDelimiter ({ className = '' }: TabsSectionDelimiterProps): React.ReactElement {
|
||||
return (
|
||||
<div className={className}>
|
||||
<svg fill='none'
|
||||
height='47'
|
||||
viewBox='0 0 17 65'
|
||||
width='17'
|
||||
xmlns='http://www.w3.org/2000/svg'>
|
||||
<path
|
||||
className='highlight--stroke'
|
||||
d='M1 1L16 32.5L1 64'
|
||||
stroke='#D1D1D1'/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(TabsSectionDelimiter)`
|
||||
height: 100%;
|
||||
width: auto;
|
||||
`);
|
||||
@ -1,13 +1,17 @@
|
||||
// Copyright 2017-2021 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TabItem } from './types';
|
||||
import type { SectionType, TabItem } from './types';
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import CurrentSection from './CurrentSection';
|
||||
import Tab from './Tab';
|
||||
import TabsSectionDelimiter from './TabsSectionDelimiter';
|
||||
|
||||
export const SectionContext = React.createContext<SectionType>({});
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@ -40,40 +44,63 @@ function Tabs ({ basePath, className = '', hidden, items }: Props): React.ReactE
|
||||
? items.filter(({ name }) => !hidden.includes(name))
|
||||
: items;
|
||||
|
||||
const { icon, text } = React.useContext(SectionContext);
|
||||
|
||||
return (
|
||||
<header className={`ui--Tabs ${className}`}>
|
||||
<div className='tabs-container'>
|
||||
{filtered.map((tab, index) => (
|
||||
<Tab
|
||||
{...tab}
|
||||
basePath={basePath}
|
||||
index={index}
|
||||
key={tab.name}
|
||||
/>
|
||||
))}
|
||||
{
|
||||
text && icon && <CurrentSection icon={icon}
|
||||
text={text}/>
|
||||
}
|
||||
<TabsSectionDelimiter />
|
||||
<ul className='ui--TabsList'>
|
||||
{filtered.map((tab, index) => (
|
||||
<li key={index}>
|
||||
<Tab
|
||||
{...tab}
|
||||
basePath={basePath}
|
||||
index={index}
|
||||
key={tab.name}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Tabs)`
|
||||
align-items: flex-end;
|
||||
background: var(--bg-tabs);
|
||||
border-bottom: 1px solid var(--border-tabs);
|
||||
|
||||
text-align: left;
|
||||
z-index: 1;
|
||||
|
||||
& .tabs-container {
|
||||
max-width: var(--width-full);
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
padding: 1.25rem 1.5rem 0;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin: 0 auto; max-width: var(--width-full);
|
||||
padding: 0 1.5rem;
|
||||
height: 3.286rem;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.ui--TabsList {
|
||||
display: flex;
|
||||
list-style: none;
|
||||
height: 100%;
|
||||
margin: 0 1.4rem;
|
||||
white-space: nowrap;
|
||||
padding: 0;
|
||||
|
||||
@media only screen and (max-width: 900px) {
|
||||
margin: 0 2.72rem 0 2.35rem;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
// Copyright 2017-2021 @polkadot/react-components authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { IconName } from '@fortawesome/fontawesome-svg-core';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export interface TabItem {
|
||||
@ -12,3 +14,8 @@ export interface TabItem {
|
||||
name: string;
|
||||
text: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface SectionType {
|
||||
icon?: IconName;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
@ -86,7 +86,7 @@ export { default as Static } from './Static';
|
||||
export { default as Status, StatusContext } from './Status';
|
||||
export { default as SummaryBox } from './SummaryBox';
|
||||
export { default as Table } from './Table';
|
||||
export { default as Tabs } from './Tabs';
|
||||
export { default as Tabs, SectionContext } from './Tabs';
|
||||
export { default as Tag } from './Tag';
|
||||
export { default as Tags } from './Tags';
|
||||
export { default as Toggle } from './Toggle';
|
||||
|
||||
@ -19,6 +19,7 @@ interface Props {
|
||||
const BRIGHTNESS = 128 + 32;
|
||||
const FACTORS = [0.2126, 0.7152, 0.0722];
|
||||
const PARTS = [0, 2, 4];
|
||||
const VERY_DARK = 16;
|
||||
|
||||
const defaultHighlight = '#f19135'; // '#f19135'; // #999
|
||||
|
||||
@ -26,15 +27,32 @@ function getHighlight (uiHighlight: string | undefined): string {
|
||||
return (uiHighlight || defaultHighlight);
|
||||
}
|
||||
|
||||
function getContrast (uiHighlight: string | undefined): string {
|
||||
function countBrightness (uiHighlight: string | undefined): number {
|
||||
const hc = getHighlight(uiHighlight).replace('#', '').toLowerCase();
|
||||
const brightness = PARTS.reduce((b, p, index) => b + (parseInt(hc.substr(p, 2), 16) * FACTORS[index]), 0);
|
||||
|
||||
return PARTS.reduce((b, p, index) => b + (parseInt(hc.substr(p, 2), 16) * FACTORS[index]), 0);
|
||||
}
|
||||
|
||||
function getContrast (uiHighlight: string | undefined): string {
|
||||
const brightness = countBrightness(uiHighlight);
|
||||
|
||||
return brightness > BRIGHTNESS
|
||||
? 'rgba(45, 43, 41, 0.875)'
|
||||
: 'rgba(255, 253, 251, 0.875)';
|
||||
}
|
||||
|
||||
function getMenuHoverContrast (uiHighlight: string | undefined): string {
|
||||
const brightness = countBrightness(uiHighlight);
|
||||
|
||||
if (brightness < VERY_DARK) {
|
||||
return 'rgba(255, 255, 255, 0.15)';
|
||||
}
|
||||
|
||||
return brightness < BRIGHTNESS
|
||||
? 'rgba(0, 0, 0, 0.15)'
|
||||
: 'rgba(255, 255, 255, 0.15)';
|
||||
}
|
||||
|
||||
export default createGlobalStyle<Props & ThemeProps>(({ theme, uiHighlight }: Props & ThemeProps) => `
|
||||
.highlight--all {
|
||||
background: ${getHighlight(uiHighlight)} !important;
|
||||
@ -58,6 +76,27 @@ export default createGlobalStyle<Props & ThemeProps>(({ theme, uiHighlight }: Pr
|
||||
background: ${getContrast(uiHighlight)};
|
||||
}
|
||||
|
||||
.ui--MenuItem.isActive .ui--Badge {
|
||||
background: ${getHighlight(uiHighlight)};
|
||||
color: ${getContrast(uiHighlight)} !important;
|
||||
}
|
||||
|
||||
.ui--MenuItem {
|
||||
& .ui--Badge {
|
||||
color: ${countBrightness(uiHighlight) < BRIGHTNESS ? '#fff' : '#424242'};
|
||||
}
|
||||
|
||||
&:hover:not(.isActive) .ui--Badge {
|
||||
background: ${countBrightness(uiHighlight) < BRIGHTNESS ? 'rgba(255, 255, 255, 0.8)' : '#4D4D4D'};
|
||||
color: ${countBrightness(uiHighlight) > BRIGHTNESS ? '#fff' : '#424242'};
|
||||
}
|
||||
}
|
||||
|
||||
.ui--Tab .ui--Badge {
|
||||
background: ${getHighlight(uiHighlight)};
|
||||
color: ${countBrightness(uiHighlight) < BRIGHTNESS ? '#fff' : '#424242'};
|
||||
}
|
||||
|
||||
.highlight--bg-faint,
|
||||
.highlight--bg-light {
|
||||
background: var(--bg-table);
|
||||
@ -103,6 +142,32 @@ export default createGlobalStyle<Props & ThemeProps>(({ theme, uiHighlight }: Pr
|
||||
background: ${`linear-gradient(90deg, ${uiHighlight || defaultHighlight}, transparent)`};
|
||||
}
|
||||
|
||||
.ui--MenuItem.topLevel:hover,
|
||||
.ui--MenuItem.isActive.topLevel:hover {
|
||||
color: ${getContrast(uiHighlight)};
|
||||
|
||||
a {
|
||||
background-color: ${getMenuHoverContrast(uiHighlight)};
|
||||
}
|
||||
}
|
||||
|
||||
.menuItems li:hover .groupHdr {
|
||||
background: ${getMenuHoverContrast(uiHighlight)};
|
||||
color: ${getContrast(uiHighlight)};
|
||||
}
|
||||
|
||||
.groupMenu {
|
||||
background: ${getHighlight(uiHighlight)} !important;
|
||||
|
||||
&::before {
|
||||
background: ${getMenuHoverContrast(uiHighlight)};
|
||||
color: ${getContrast(uiHighlight)};
|
||||
}
|
||||
li {
|
||||
color: ${getContrast(uiHighlight)};
|
||||
}
|
||||
}
|
||||
|
||||
.highlight--hover-bg:hover {
|
||||
background: ${getHighlight(uiHighlight)} !important;
|
||||
}
|
||||
@ -182,10 +247,8 @@ export default createGlobalStyle<Props & ThemeProps>(({ theme, uiHighlight }: Pr
|
||||
|
||||
.theme--dark,
|
||||
.theme--light {
|
||||
.ui--Tabs {
|
||||
.ui--Tab.tabLinkActive {
|
||||
border-bottom-color: ${getHighlight(uiHighlight)};
|
||||
}
|
||||
.ui--Tabs .tabLinkActive .tabLinkText::after{
|
||||
background: ${getHighlight(uiHighlight)};
|
||||
}
|
||||
|
||||
.ui.primary.button,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user