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:
MiZiet 2021-03-24 10:27:37 +01:00 committed by GitHub
parent 5f9907b04a
commit 5b65c111d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 424 additions and 132 deletions

View File

@ -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;

View File

@ -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 />

View File

@ -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;
}
}
}

View File

@ -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 {

View File

@ -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 {

View File

@ -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 />&nbsp;

View File

@ -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;
}
`);

View File

@ -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);
}
`);

View File

@ -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;

View File

@ -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 {

View 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;
}
`);

View File

@ -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 {

View 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;
`);

View File

@ -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;
}
}
`);

View File

@ -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;
}

View File

@ -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';

View File

@ -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,