1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-05 05:45:22 +02:00

feat(ui): portainer base component css change [EE-3381] (#7115)

This commit is contained in:
Chaim Lev-Ari 2022-06-23 09:32:18 +03:00 committed by GitHub
parent 825269c119
commit f78a6568a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 999 additions and 1596 deletions

View file

@ -2,6 +2,7 @@ import clsx from 'clsx';
import ReactTooltip from 'react-tooltip';
import { isLimitedToBE } from '@/portainer/feature-flags/feature-flags.service';
import { Icon } from '@/react/components/Icon';
import './BoxSelectorItem.css';
@ -53,9 +54,10 @@ export function BoxSelectorItem<T extends number | string>({
<div className="boxselector_header">
{!!option.icon && (
<i
className={clsx(option.icon, 'space-right')}
aria-hidden="true"
<Icon
icon={option.icon}
feather={option.featherIcon}
className="space-right"
/>
)}
{option.label}

View file

@ -3,6 +3,7 @@ import type { FeatureId } from '@/portainer/feature-flags/enums';
export interface BoxSelectorOption<T> {
id: string;
icon: string;
featherIcon?: boolean;
label: string;
description: string;
value: T;

View file

@ -10,12 +10,6 @@ test('should show provided resource value', async () => {
expect(value).toHaveTextContent('1');
});
test('should show provided icon', async () => {
const { getByLabelText } = renderComponent(0, 'fa fa-th-list');
const icon = getByLabelText('icon');
expect(icon).toHaveClass('fa-th-list');
});
test('should show provided resource type', async () => {
const { getByLabelText } = renderComponent(0, '', 'Test');
const title = getByLabelText('resourceType');

View file

@ -1,19 +1,30 @@
import { ReactNode } from 'react';
import { Icon, IconProps } from '@/react/components/Icon';
import { Widget, WidgetBody } from '@@/Widget';
interface Props {
value: number;
icon: string;
interface Props extends IconProps {
value?: number;
type: string;
children?: ReactNode;
}
export function DashboardItem({ value, icon, type }: Props) {
export function DashboardItem({
value,
icon,
type,
children,
featherIcon,
}: Props) {
return (
<div className="col-sm-12 col-md-6" aria-label={type}>
<Widget>
<WidgetBody>
<div className="widget-icon blue pull-left">
<i className={icon} aria-hidden="true" aria-label="icon" />
<Icon icon={icon} feather={featherIcon} />
</div>
<div className="pull-right">{children}</div>
<div className="title" aria-label="value">
{value}
</div>

View file

@ -0,0 +1,48 @@
import clsx from 'clsx';
import { ComponentType, ReactNode, useEffect } from 'react';
import featherIcons from 'feather-icons';
import { isValidElementType } from 'react-is';
export interface IconProps {
icon: ReactNode | ComponentType<unknown>;
featherIcon?: boolean;
}
interface Props {
icon: ReactNode | ComponentType<unknown>;
feather?: boolean;
className?: string;
}
export function Icon({ icon, feather, className }: Props) {
useEffect(() => {
if (feather) {
featherIcons.replace();
}
}, [feather]);
if (typeof icon !== 'string') {
const Icon = isValidElementType(icon) ? icon : null;
return (
<span className={className} aria-hidden="true" role="img">
{Icon == null ? <>{icon}</> : <Icon />}
</span>
);
}
if (feather) {
return (
<i
data-feather={icon}
className={className}
aria-hidden="true"
role="img"
/>
);
}
return (
<i className={clsx('fa', icon, className)} aria-hidden="true" role="img" />
);
}

View file

@ -10,7 +10,7 @@ export interface Crumb {
linkParams?: Record<string, unknown>;
}
interface Props {
breadcrumbs: Crumb[];
breadcrumbs: (Crumb | string)[];
}
export function Breadcrumbs({ breadcrumbs }: Props) {
@ -26,7 +26,11 @@ export function Breadcrumbs({ breadcrumbs }: Props) {
);
}
function renderCrumb(crumb: Crumb) {
function renderCrumb(crumb: Crumb | string) {
if (typeof crumb === 'string') {
return crumb;
}
if (crumb.link) {
return (
<Link to={crumb.link} params={crumb.linkParams}>

View file

@ -1,19 +0,0 @@
.user-links {
margin-right: 25px;
}
.user-links > * + * {
margin-left: 5px;
}
.link {
cursor: pointer;
}
.link .link-text {
text-decoration: underline;
}
.link .link-icon {
margin-right: 2px;
}

View file

@ -1,6 +1,4 @@
import { UserContext } from '@/portainer/hooks/useUser';
import { UserViewModel } from '@/portainer/models/user';
import { render } from '@/react-tools/test-utils';
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { HeaderContainer } from './HeaderContainer';
import { HeaderContent } from './HeaderContent';
@ -11,7 +9,7 @@ test('should not render without a wrapping HeaderContainer', async () => {
.mockImplementation(() => jest.fn());
function renderComponent() {
return render(<HeaderContent />);
return renderWithQueryClient(<HeaderContent />);
}
expect(renderComponent).toThrowErrorMatchingSnapshot();
@ -20,22 +18,14 @@ test('should not render without a wrapping HeaderContainer', async () => {
});
test('should display a HeaderContent', async () => {
const username = 'username';
const user = new UserViewModel({ Username: username });
const userProviderState = { user };
const content = 'content';
const { queryByText } = render(
<UserContext.Provider value={userProviderState}>
<HeaderContainer>
<HeaderContent>{content}</HeaderContent>
</HeaderContainer>
</UserContext.Provider>
const { queryByText } = renderWithQueryClient(
<HeaderContainer>
<HeaderContent>{content}</HeaderContent>
</HeaderContainer>
);
const contentElement = queryByText(content);
expect(contentElement).toBeVisible();
expect(queryByText('my account')).toBeVisible();
expect(queryByText('log out')).toBeVisible();
});

View file

@ -1,43 +1,13 @@
import { PropsWithChildren } from 'react';
import clsx from 'clsx';
import { useUser } from '@/portainer/hooks/useUser';
import { Link } from '@@/Link';
import styles from './HeaderContent.module.css';
import { useHeaderContext } from './HeaderContainer';
export function HeaderContent({ children }: PropsWithChildren<unknown>) {
useHeaderContext();
const { user } = useUser();
return (
<div className="breadcrumb-links">
<div className="pull-left">{children}</div>
{user && !window.ddExtension && (
<div className={clsx('pull-right', styles.userLinks)}>
<Link to="portainer.account" className={styles.link}>
<i
className={clsx('fa fa-wrench', styles.linkIcon)}
aria-hidden="true"
/>
<span className={styles.linkText}>my account</span>
</Link>
<Link
to="portainer.logout"
params={{ performApiLogout: true }}
className={clsx('text-danger', styles.link)}
data-cy="template-logoutButton"
>
<i
className={clsx('fa fa-sign-out-alt', styles.linkIcon)}
aria-hidden="true"
/>
<span className={styles.linkText}>log out</span>
</Link>
</div>
)}
</div>
);
}

View file

@ -0,0 +1,31 @@
.menu-button {
border: 0px;
font-size: 17px;
background: none;
margin-right: 15px;
}
.menu-list {
background: var(--bg-dropdown-menu-color);
border-radius: 8px;
border: 1px solid var(--ui-grey-1) !important;
width: 180px;
padding: 5px !important;
box-shadow: 0 6px 12px rgb(0 0 0 / 18%);
}
.menu-link {
display: block;
padding: 5px 20px;
font-weight: 500;
line-height: 1.42857143;
white-space: nowrap;
font-size: 14px;
color: var(--text-dropdown-menu-color);
text-decoration: none !important;
}
.menu-link:hover {
background: var(--bg-dropdown-hover);
color: var(--text-dropdown-menu-color);
}

View file

@ -1,8 +1,14 @@
import { PropsWithChildren } from 'react';
import { Menu, MenuButton, MenuList, MenuLink } from '@reach/menu-button';
import clsx from 'clsx';
import { User, ChevronDown } from 'react-feather';
import { useUser } from '@/portainer/hooks/useUser';
import { Link } from '@@/Link';
import { useHeaderContext } from './HeaderContainer';
import styles from './HeaderTitle.module.css';
interface Props {
title: string;
@ -16,12 +22,25 @@ export function HeaderTitle({ title, children }: PropsWithChildren<Props>) {
<div className="page white-space-normal">
{title}
<span className="header_title_content">{children}</span>
{user && !window.ddExtension && (
<span className="pull-right user-box">
<i className="fa fa-user-circle" aria-hidden="true" />
{user.Username}
</span>
)}
<Menu>
<MenuButton className={clsx('pull-right', styles.menuButton)}>
<User className="feather" />
{user && <span>{user.Username}</span>}
<ChevronDown className="feather" />
</MenuButton>
<MenuList className={styles.menuList}>
<MenuLink
className={styles.menuLink}
as={Link}
to="portainer.account"
>
My account
</MenuLink>
<MenuLink className={styles.menuLink} as={Link} to="portainer.logout">
Log out
</MenuLink>
</MenuList>
</Menu>
</div>
);
}

View file

@ -1,4 +1,5 @@
import { useRouter } from '@uirouter/react';
import { RefreshCw } from 'react-feather';
import { Button } from '../buttons';
@ -11,12 +12,25 @@ import styles from './PageHeader.module.css';
interface Props {
reload?: boolean;
loading?: boolean;
onReload?(): Promise<void> | void;
breadcrumbs?: Crumb[];
title: string;
}
export function PageHeader({ title, breadcrumbs = [], reload }: Props) {
export function PageHeader({
title,
breadcrumbs = [],
reload,
loading,
onReload,
}: Props) {
const router = useRouter();
function onClickedRefresh() {
return onReload ? onReload() : router.stateService.reload();
}
return (
<HeaderContainer>
<HeaderTitle title={title}>
@ -24,10 +38,11 @@ export function PageHeader({ title, breadcrumbs = [], reload }: Props) {
<Button
color="link"
size="medium"
onClick={() => router.stateService.reload()}
onClick={onClickedRefresh}
className={styles.reloadButton}
disabled={loading}
>
<i className="fa fa-sync" aria-hidden="true" />
<RefreshCw className="feather" />
</Button>
)}
</HeaderTitle>

View file

@ -65,7 +65,7 @@
.pagination > .active > span:focus,
.pagination > .active > button:focus {
z-index: 3;
color: #fff;
color: #1d2939;
cursor: default;
background-color: var(--text-pagination-span-color);
border-color: var(--text-pagination-span-color);

View file

@ -0,0 +1,8 @@
import { ReactQueryDevtools } from 'react-query/devtools';
export function ReactQueryDevtoolsWrapper() {
const showReactQueryDevtools =
process.env.SHOW_REACT_QUERY_DEV_TOOLS === 'true';
return <>{showReactQueryDevtools && <ReactQueryDevtools />}</>;
}

View file

@ -4,12 +4,10 @@
}
.tooltip {
font-family: Montserrat !important;
background-color: var(--bg-tooltip-color) !important;
padding: 0.833em 1em !important;
color: var(--text-tooltip-color) !important;
border: 1px solid var(--border-tooltip-color) !important;
border-radius: 0.14285714rem !important;
border-radius: 10px !important;
box-shadow: 0 2px 4px 0 rgba(34, 36, 38, 0.12), 0 2px 10px 0 rgba(34, 36, 38, 0.15) !important;
max-width: 200px;
text-align: center;
@ -19,5 +17,5 @@
.icon {
margin-left: 5px;
font-size: 1.3em;
cursor: pointer;
}

View file

@ -1,5 +1,5 @@
import ReactTooltip from 'react-tooltip';
import clsx from 'clsx';
import { HelpCircle } from 'react-feather';
import styles from './Tooltip.module.css';
@ -12,12 +12,8 @@ export interface Props {
export function Tooltip({ message, position = 'bottom' }: Props) {
return (
<span className="interactive">
<i
className={clsx('fa fa-question-circle blue-icon', styles.icon)}
aria-hidden="true"
data-tip={message}
/>
<span data-tip={message} className={styles.icon}>
<HelpCircle className="feather" aria-hidden="true" />
<ReactTooltip
multiline
type="info"

View file

@ -1,11 +1,14 @@
import clsx from 'clsx';
import { PropsWithChildren, ReactNode } from 'react';
import { Icon } from '@/react/components/Icon';
import { useWidgetContext } from './Widget';
interface Props {
title: ReactNode;
icon: ReactNode;
featherIcon?: boolean;
className?: string;
}
@ -14,6 +17,7 @@ export function WidgetTitle({
icon,
className,
children,
featherIcon,
}: PropsWithChildren<Props>) {
useWidgetContext();
@ -21,7 +25,7 @@ export function WidgetTitle({
<div className="widget-header">
<div className="row">
<span className={clsx('pull-left', className)}>
{typeof icon === 'string' ? <i className={clsx('fa', icon)} /> : icon}
<Icon icon={icon} feather={featherIcon} className="space-right" />
<span>{title}</span>
</span>
<span className={clsx('pull-right', className)}>{children}</span>

View file

@ -2,7 +2,15 @@ import { MouseEventHandler, PropsWithChildren } from 'react';
import clsx from 'clsx';
type Type = 'submit' | 'button' | 'reset';
type Color = 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'link';
type Color =
| 'default'
| 'primary'
| 'success'
| 'warning'
| 'danger'
| 'link'
| 'light'
| 'dangerlight';
type Size = 'xsmall' | 'small' | 'medium' | 'large';
export interface Props {

View file

@ -1,6 +1,7 @@
import clsx from 'clsx';
import { Menu, MenuButton, MenuList } from '@reach/menu-button';
import { ColumnInstance } from 'react-table';
import { Columns } from 'react-feather';
import { Checkbox } from '@@/form-components/Checkbox';
@ -28,7 +29,13 @@ export function ColumnVisibilityMenu<D extends object>({
'setting-active': isExpanded,
})}
>
<i className="fa fa-columns" aria-hidden="true" /> Columns
<Columns
size="13"
className="space-right"
strokeWidth="3px"
aria-hidden="true"
aria-label="Columns"
/>
</MenuButton>
<MenuList>
<div className="tableMenu">

View file

@ -1,6 +1,7 @@
import clsx from 'clsx';
import { Menu, MenuButton, MenuList } from '@reach/menu-button';
import { PropsWithChildren, ReactNode } from 'react';
import { MoreVertical } from 'react-feather';
import { useTableContext } from './TableContainer';
@ -23,7 +24,13 @@ export function TableSettingsMenu({
'setting-active': isExpanded,
})}
>
<i className="fa fa-cog" aria-hidden="true" /> Settings
<MoreVertical
size="13"
className="space-right"
strokeWidth="3px"
aria-hidden="true"
aria-label="Settings"
/>
</MenuButton>
<MenuList>
<div className="tableMenu">

View file

@ -1,15 +1,18 @@
import clsx from 'clsx';
import { PropsWithChildren } from 'react';
import { Icon } from '@/react/components/Icon';
import { useTableContext } from './TableContainer';
interface Props {
icon: string;
label: string;
featherIcon?: boolean;
}
export function TableTitle({
icon,
featherIcon,
label,
children,
}: PropsWithChildren<Props>) {
@ -18,7 +21,8 @@ export function TableTitle({
return (
<div className="toolBar">
<div className="toolBarTitle">
<i className={clsx('space-right', 'fa', icon)} aria-hidden="true" />
<Icon icon={icon} feather={featherIcon} className="space-right" />
{label}
</div>
{children}

View file

@ -28,5 +28,5 @@
.slider :global .rc-slider-mark-text,
.slider :global .rc-slider-tooltip-inner {
font-family: Montserrat, serif;
font-family: Inter, serif;
}

View file

@ -49,7 +49,7 @@ export function Switch({
disabled={disabled || limitedToBE}
onChange={({ target: { checked } }) => onChange(checked)}
/>
<i data-cy={dataCy} />
<span className="slider round" data-cy={dataCy} />
</label>
{limitedToBE && <BEFeatureIndicator featureId={featureId} />}
</>