1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-07-24 15:59:41 +02:00

feat(sidebar): add dark theme colors [EE-3666] (#7414)

This commit is contained in:
Chaim Lev-Ari 2022-08-10 07:12:20 +03:00 committed by GitHub
parent fb3a31a4fd
commit c3ce4d8b53
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 1738 additions and 1200 deletions

View file

@ -0,0 +1,65 @@
import clsx from 'clsx';
import { PropsWithChildren } from 'react';
import ReactTooltip from 'react-tooltip';
import './BoxSelectorItem.css';
import { BoxSelectorOption } from './types';
interface Props<T extends number | string> {
radioName: string;
option: BoxSelectorOption<T>;
onChange?(value: T): void;
selectedValue: T;
disabled?: boolean;
tooltip?: string;
className?: string;
type?: 'radio' | 'checkbox';
}
export function BoxOption<T extends number | string>({
radioName,
option,
onChange = () => {},
selectedValue,
disabled,
tooltip,
className,
type = 'radio',
children,
}: PropsWithChildren<Props<T>>) {
const tooltipId = `box-option-${radioName}-${option.id}`;
return (
<div
className={clsx('box-selector-item', className)}
data-tip
data-for={tooltipId}
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
>
<input
type={type}
name={radioName}
id={option.id}
checked={option.value === selectedValue}
value={option.value}
disabled={disabled}
onChange={() => onChange(option.value)}
/>
<label htmlFor={option.id} data-cy={`${radioName}_${option.value}`}>
{children}
</label>
{tooltip && (
<ReactTooltip
place="bottom"
className="portainer-tooltip"
id={tooltipId}
>
{tooltip}
</ReactTooltip>
)}
</div>
);
}

View file

@ -13,26 +13,6 @@
}
}
.boxselector_wrapper input[type='radio']:checked + label,
.box-selector-item input[type='radio']:checked + label {
background-color: var(--bg-blocklist-hover-color) !important;
color: black !important;
border-radius: 8px;
border-color: var(--ui-blue-7);
padding: 15px;
box-shadow: none;
}
.boxselector_wrapper input[type='radio']:not(:disabled) + label,
.box-selector-item input[type='radio']:not(:disabled) + label {
background: var(--ui-gray-2);
color: var(--black-color) !important;
border-radius: 8px;
border-color: var(--ui-gray-5);
padding: 15px;
box-shadow: none;
}
.row.header {
background-color: var(--bg-body-color) !important;
margin-bottom: 5px !important;

View file

@ -1,6 +1,5 @@
.boxselector_wrapper > div,
.box-selector-item {
--selected-item-color: var(--ui-blue-6);
flex: 1;
}
@ -22,38 +21,40 @@
display: none;
}
.boxselector_wrapper input[type='radio']:not(:disabled) ~ label,
.box-selector-item input[type='radio']:not(:disabled) ~ label {
cursor: pointer;
background-color: var(--bg-boxselector-wrapper-disabled-color);
.boxselector_wrapper label,
.box-selector-item label {
@apply border border-solid;
@apply bg-gray-2 border-gray-5 text-black;
@apply th-dark:bg-gray-iron-10 th-dark:border-gray-neutral-8 th-dark:text-white;
font-weight: normal;
font-size: 12px;
display: block;
border-radius: 8px;
padding: 15px;
text-align: left;
box-shadow: var(--shadow-boxselector-color);
position: relative;
text-align: left;
height: 100%;
}
.boxselector_wrapper input[type='radio']:not(:disabled):hover ~ label:hover,
.box-selector-item input[type='radio']:not(:disabled):hover ~ label:hover {
/* not disabled */
.boxselector_wrapper input[type='radio']:not(:disabled) ~ label,
.box-selector-item input[type='radio']:not(:disabled) ~ label {
box-shadow: none;
cursor: pointer;
}
.boxselector_wrapper label,
.box-selector-item label {
font-weight: normal;
font-size: 12px;
display: block;
background: var(--bg-boxselector-color);
border: 1px solid var(--border-boxselector-color);
border-radius: 2px;
padding: 10px 10px 0 10px;
text-align: left;
box-shadow: var(--shadow-boxselector-color);
position: relative;
}
/* disabled */
.box-selector-item input:disabled + label,
.boxselector_wrapper label.boxselector_disabled {
background: var(--bg-boxselector-disabled-color) !important;
border-color: #787878;
color: #787878;
@apply !bg-white;
@apply th-dark:!bg-gray-7;
@apply th-highcontrast:!bg-black;
filter: opacity(0.3) grayscale(1);
cursor: not-allowed;
pointer-events: none;
}
@ -63,30 +64,19 @@
pointer-events: auto;
}
/* checked */
.boxselector_wrapper input[type='radio']:checked + label,
.box-selector-item input[type='radio']:checked + label {
color: white;
@apply bg-blue-3 border-blue-6;
@apply th-dark:bg-blue-10 th-dark:border-blue-7;
background-image: url(../../../assets/ico/checked.svg);
background-repeat: no-repeat;
background-position: right 15px top 15px;
border-color: var(--selected-item-color);
}
.box-selector-item input[type='radio']:checked:disabled + label {
color: #787878;
}
.boxselector_wrapper input[type='radio']:checked + label .box_selector_mask_icon {
color: var(--selected-item-color);
}
:root[theme='highcontrast'] .box_selector_mask_icon,
:root[theme='dark'] .box_selector_mask_icon {
color: var(--bg-boxselector-wrapper-disabled-color);
}
.box_selector_mask_icon {
color: var(--bg-boxselector-color);
border-radius: 8px;
padding: 15px;
box-shadow: none;
}
@media only screen and (max-width: 700px) {
@ -95,48 +85,23 @@
}
}
.box-selector-item.limited.business {
--selected-item-color: var(--BE-only);
}
.box-selector-item.limited.business label {
border-color: var(--BE-only);
border-width: 2px;
}
.box-selector-item .limited-icon {
position: absolute;
left: 1em;
top: calc(50% - 0.5em);
height: 1em;
}
@media (min-width: 992px) {
.box-selector-item .limited-icon {
left: 2em;
}
}
.box-selector-item.limited.business :checked + label {
background-color: initial;
color: initial;
.box-selector-item.limited.business label,
.box-selector-item.limited.business input[type='radio']:checked + label {
@apply border-warning-7 bg-warning-1 text-black;
@apply th-dark:bg-warning-3;
}
.boxselector_img_container {
width: 100%;
margin-bottom: 20px;
text-align: left;
}
.boxselector_img {
height: 48px;
width: 48px;
left: 5px;
line-height: 90px;
margin-bottom: 0;
}
.boxselector_icon,
.boxselector_icon img {
color: var(--ui-blue-8);
font-size: 90px;
display: block;
}
@ -149,16 +114,6 @@
padding-left: 20px;
}
.boxselector_wrapper input[type='radio']:not(:disabled) ~ label,
.box-selector-item input[type='radio']:not(:disabled) ~ label {
background-color: var(--ui-gray-2);
}
.boxselector_img_container {
line-height: 90px;
margin-bottom: 0;
}
.box-selector-item p {
margin-bottom: 0;
}

View file

@ -1,5 +1,4 @@
import clsx from 'clsx';
import ReactTooltip from 'react-tooltip';
import { isLimitedToBE } from '@/portainer/feature-flags/feature-flags.service';
import { Icon } from '@/react/components/Icon';
@ -7,6 +6,8 @@ import { Icon } from '@/react/components/Icon';
import './BoxSelectorItem.css';
import { BoxSelectorOption } from './types';
import { LimitedToBeIndicator } from './LimitedToBeIndicator';
import { BoxOption } from './BoxOption';
interface Props<T extends number | string> {
radioName: string;
@ -27,53 +28,38 @@ export function BoxSelectorItem<T extends number | string>({
}: Props<T>) {
const limitedToBE = isLimitedToBE(option.feature);
const tooltipId = `box-selector-item-${radioName}-${option.id}`;
const beIndicatorTooltipId = `box-selector-item-${radioName}-${option.id}-limited`;
return (
<div
className={clsx('box-selector-item', {
<BoxOption
className={clsx({
business: limitedToBE,
limited: limitedToBE,
})}
data-tip
data-for={tooltipId}
tooltip-append-to-body="true"
tooltip-placement="bottom"
tooltip-class="portainer-tooltip"
radioName={radioName}
option={option}
selectedValue={selectedValue}
disabled={disabled}
onChange={(value) => onChange(value, limitedToBE)}
tooltip={tooltip}
>
<input
type="radio"
name={radioName}
id={option.id}
checked={option.value === selectedValue}
value={option.value}
disabled={disabled}
onChange={() => onChange(option.value, limitedToBE)}
/>
<label htmlFor={option.id} data-cy={`${radioName}_${option.value}`}>
{limitedToBE && <i className="fas fa-briefcase limited-icon" />}
<div className="boxselector_img_container">
{!!option.icon && (
<Icon
icon={option.icon}
feather={option.featherIcon}
className="boxselector_icon space-right"
/>
)}
<>
{limitedToBE && (
<LimitedToBeIndicator tooltipId={beIndicatorTooltipId} />
)}
<div className={clsx({ 'opacity-30': limitedToBE })}>
<div className="boxselector_img_container">
{!!option.icon && (
<Icon
icon={option.icon}
feather={option.featherIcon}
className="boxselector_icon space-right"
/>
)}
</div>
<div className="boxselector_header">{option.label}</div>
<p className="box-selector-item-description">{option.description}</p>
</div>
<div className="boxselector_header">{option.label}</div>
<p className="box-selector-item-description">{option.description}</p>
</label>
{tooltip && (
<ReactTooltip
place="bottom"
className="portainer-tooltip"
id={tooltipId}
>
{tooltip}
</ReactTooltip>
)}
</div>
</>
</BoxOption>
);
}

View file

@ -0,0 +1,35 @@
import { HelpCircle } from 'react-feather';
import ReactTooltip from 'react-tooltip';
interface Props {
tooltipId: string;
}
export function LimitedToBeIndicator({ tooltipId }: Props) {
return (
<>
<div className="absolute left-0 top-0 w-full">
<div className="mx-auto max-w-fit bg-warning-4 rounded-b-lg py-1 px-3 flex gap-1 text-sm items-center">
<span className="text-warning-9">Pro Feature</span>
<HelpCircle
className="feather !text-warning-7"
data-tip
data-for={tooltipId}
tooltip-append-to-body="true"
tooltip-placement="top"
tooltip-class="portainer-tooltip"
/>
</div>
</div>
<ReactTooltip
className="portainer-tooltip"
id={tooltipId}
place="top"
delayHide={1000}
>
Business Edition feature. <br />
This feature is currently limited to Business Edition users only.
</ReactTooltip>
</>
);
}