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:
parent
fb3a31a4fd
commit
c3ce4d8b53
83 changed files with 1738 additions and 1200 deletions
65
app/react/components/BoxSelector/BoxOption.tsx
Normal file
65
app/react/components/BoxSelector/BoxOption.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
35
app/react/components/BoxSelector/LimitedToBeIndicator.tsx
Normal file
35
app/react/components/BoxSelector/LimitedToBeIndicator.tsx
Normal 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>
|
||||
</>
|
||||
);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue