1
0
Fork 0
mirror of https://github.com/portainer/portainer.git synced 2025-08-02 20:35:25 +02:00

fix(ui): fix ui bugs [EE-3847] (#7453)

This commit is contained in:
Chaim Lev-Ari 2022-08-12 06:47:56 +03:00 committed by GitHub
parent dd372637cb
commit 95fb5a4baa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
43 changed files with 246 additions and 448 deletions

View file

@ -12,8 +12,3 @@
flex-direction: column;
}
}
.row.header {
background-color: var(--bg-body-color) !important;
margin-bottom: 5px !important;
}

View file

@ -1,12 +0,0 @@
.breadcrumb-links {
font-size: 10px;
}
.breadcrumb-links a {
color: var(--ui-blue-8);
}
.breadcrumb-links a:hover {
text-decoration: underline;
color: var(--ui-blue-9);
}

View file

@ -8,8 +8,9 @@ test('should display a Breadcrumbs, breadcrumbs should be separated by >', async
{ label: 'bread2' },
{ label: 'bread3' },
];
const { queryByText } = render(<Breadcrumbs breadcrumbs={breadcrumbs} />);
const { container } = render(<Breadcrumbs breadcrumbs={breadcrumbs} />);
const heading = queryByText(breadcrumbs.map((b) => b.label).join(' > '));
expect(heading).toBeVisible();
expect(container.firstChild?.textContent).toEqual(
breadcrumbs.map((b) => b.label).join('>')
);
});

View file

@ -2,8 +2,6 @@ import { Fragment } from 'react';
import { Link } from '@@/Link';
import './Breadcrumbs.css';
export interface Crumb {
label: string;
link?: string;
@ -19,11 +17,11 @@ export function Breadcrumbs({ breadcrumbs }: Props) {
: [breadcrumbs];
return (
<div className="breadcrumb-links">
<div className="text-sm font-medium text-gray-7 th-dark:text-gray-5 th-highcontrast:text-white space-x-2">
{breadcrumbsArray.map((crumb, index) => (
<Fragment key={index}>
{renderCrumb(crumb)}
{index !== breadcrumbsArray.length - 1 ? ' > ' : ''}
<span>{renderCrumb(crumb)}</span>
{index !== breadcrumbsArray.length - 1 && <span>&gt;</span>}
</Fragment>
))}
</div>
@ -44,7 +42,7 @@ function renderCrumb(crumb: Crumb | string) {
<Link
to={crumb.link}
params={crumb.linkParams}
className="text-blue-9 hover:underline"
className="text-blue-9 hover:underline hover:text-blue-11 th-dark:text-blue-7 th-dark:hover:text-blue-9 th-highcontrast:text-blue-5"
>
{crumb.label}
</Link>

View file

@ -1,33 +1,29 @@
.row.header .meta .page {
padding-top: 7px;
.header {
min-height: 60px;
margin-bottom: 15px;
background-color: var(--bg-body-color);
margin-bottom: 5px !important;
}
.row.header {
min-height: 60px;
background: var(--bg-row-header-color);
margin-bottom: 15px;
}
.row.header > div:last-child {
.header > div:last-child {
padding-right: 0;
}
.row.header .meta .page {
font-size: 17px;
padding-top: 11px;
}
.row.header .meta div {
.header .meta div {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.row.header .login a {
.header .login a {
padding: 18px;
display: block;
}
.row.header .user {
.header .user {
min-width: 130px;
}
.row.header .user > .item {
.header .user > .item {
width: 65px;
height: 60px;
float: right;
@ -35,36 +31,36 @@
text-align: center;
vertical-align: middle;
}
.row.header .user > .item a {
.header .user > .item a {
color: #919191;
display: block;
}
.row.header .user > .item i {
.header .user > .item i {
font-size: 20px;
line-height: 55px;
}
.row.header .user > .item img {
.header .user > .item img {
width: 40px;
height: 40px;
margin-top: 10px;
border-radius: 2px;
}
.row.header .user > .item ul.dropdown-menu {
.header .user > .item ul.dropdown-menu {
border-radius: 2px;
-webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.05);
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.05);
}
.row.header .user > .item ul.dropdown-menu .dropdown-header {
.header .user > .item ul.dropdown-menu .dropdown-header {
text-align: center;
}
.row.header .user > .item ul.dropdown-menu li.link {
.header .user > .item ul.dropdown-menu li.link {
text-align: left;
}
.row.header .user > .item ul.dropdown-menu li.link a {
.header .user > .item ul.dropdown-menu li.link a {
padding-left: 7px;
padding-right: 7px;
}
.row.header .user > .item ul.dropdown-menu:before {
.header .user > .item ul.dropdown-menu:before {
position: absolute;
top: -7px;
right: 23px;
@ -74,7 +70,7 @@
border-left: 7px solid transparent;
content: '';
}
.row.header .user > .item ul.dropdown-menu:after {
.header .user > .item ul.dropdown-menu:after {
position: absolute;
top: -6px;
right: 24px;

View file

@ -7,7 +7,6 @@ import { UserViewModel } from '@/portainer/models/user';
import { HeaderContainer } from './HeaderContainer';
import { Breadcrumbs } from './Breadcrumbs';
import { HeaderTitle } from './HeaderTitle';
import { HeaderContent } from './HeaderContent';
export default {
component: HeaderContainer,
@ -28,14 +27,13 @@ function Template({ title }: StoryProps) {
<UserContext.Provider value={state}>
<HeaderContainer>
<HeaderTitle title={title} />
<HeaderContent>
<Breadcrumbs
breadcrumbs={[
{ link: 'example', label: 'crumb1' },
{ label: 'crumb2' },
]}
/>
</HeaderContent>
<Breadcrumbs
breadcrumbs={[
{ link: 'example', label: 'crumb1' },
{ label: 'crumb2' },
]}
/>
</HeaderContainer>
</UserContext.Provider>
);

View file

@ -1,6 +1,7 @@
import { PropsWithChildren, createContext, useContext } from 'react';
import clsx from 'clsx';
import './HeaderContainer.css';
import styles from './HeaderContainer.module.css';
const Context = createContext<null | boolean>(null);
@ -15,10 +16,10 @@ export function useHeaderContext() {
export function HeaderContainer({ children }: PropsWithChildren<unknown>) {
return (
<Context.Provider value>
<div className="row header">
<div className={clsx('row', styles.header)}>
<div id="loadingbar-placeholder" />
<div className="col-xs-12">
<div className="meta">{children}</div>
<div className={styles.meta}>{children}</div>
</div>
</div>
</Context.Provider>

View file

@ -1,31 +0,0 @@
import { renderWithQueryClient } from '@/react-tools/test-utils';
import { HeaderContainer } from './HeaderContainer';
import { HeaderContent } from './HeaderContent';
test('should not render without a wrapping HeaderContainer', async () => {
const consoleErrorFn = jest
.spyOn(console, 'error')
.mockImplementation(() => jest.fn());
function renderComponent() {
return renderWithQueryClient(<HeaderContent />);
}
expect(renderComponent).toThrowErrorMatchingSnapshot();
consoleErrorFn.mockRestore();
});
test('should display a HeaderContent', async () => {
const content = 'content';
const { queryByText } = renderWithQueryClient(
<HeaderContainer>
<HeaderContent>{content}</HeaderContent>
</HeaderContainer>
);
const contentElement = queryByText(content);
expect(contentElement).toBeVisible();
});

View file

@ -1,13 +0,0 @@
import { PropsWithChildren } from 'react';
import { useHeaderContext } from './HeaderContainer';
export function HeaderContent({ children }: PropsWithChildren<unknown>) {
useHeaderContext();
return (
<div className="breadcrumb-links">
<div className="pull-left">{children}</div>
</div>
);
}

View file

@ -24,13 +24,17 @@ export function HeaderTitle({ title, children }: PropsWithChildren<Props>) {
const { user } = useUser();
return (
<div className="page white-space-normal">
{title}
<span className="header_title_content">{children}</span>
<div className="flex justify-between whitespace-normal pt-3">
<div className="flex items-center gap-2">
<div className="font-medium text-3xl text-gray-11 th-dark:text-white th-highcontrast:text-white">
{title}
</div>
{children && <span>{children}</span>}
</div>
<Menu>
<MenuButton
className={clsx(
'pull-right flex items-center gap-1',
'ml-auto flex items-center gap-1 self-start',
styles.menuButton
)}
data-cy="userMenu-button"

View file

@ -6,7 +6,6 @@ import { Button } from '../buttons';
import { Breadcrumbs } from './Breadcrumbs';
import { Crumb } from './Breadcrumbs/Breadcrumbs';
import { HeaderContainer } from './HeaderContainer';
import { HeaderContent } from './HeaderContent';
import { HeaderTitle } from './HeaderTitle';
import styles from './PageHeader.module.css';
@ -33,9 +32,8 @@ export function PageHeader({
return (
<HeaderContainer>
<HeaderContent>
<Breadcrumbs breadcrumbs={breadcrumbs} />
</HeaderContent>
<Breadcrumbs breadcrumbs={breadcrumbs} />
<HeaderTitle title={title}>
{reload && (
<Button

View file

@ -1,3 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should not render without a wrapping HeaderContainer 1`] = `"Should be nested inside a HeaderContainer component"`;

View file

@ -1,7 +1 @@
import { Breadcrumbs } from './Breadcrumbs';
import { PageHeader } from './PageHeader';
import { HeaderContainer } from './HeaderContainer';
import { HeaderContent } from './HeaderContent';
import { HeaderTitle } from './HeaderTitle';
export { PageHeader, Breadcrumbs, HeaderContainer, HeaderContent, HeaderTitle };
export { PageHeader } from './PageHeader';

View file

@ -13,10 +13,8 @@ export function DifferentTheme() {
const colors = [
'primary',
'secondary',
'success',
'danger',
'dangerlight',
'warning',
'light',
'link',
] as const;
@ -97,22 +95,6 @@ export function Disabled() {
);
}
export function Warning() {
return (
<Button color="warning" onClick={() => {}}>
Warning Button
</Button>
);
}
export function Success() {
return (
<Button color="success" onClick={() => {}}>
Success Button
</Button>
);
}
export function Danger() {
return (
<Button color="danger" onClick={() => {}}>

View file

@ -17,8 +17,6 @@ type Color =
| 'default'
| 'primary'
| 'secondary'
| 'success'
| 'warning'
| 'danger'
| 'link'
| 'light'

View file

@ -14,7 +14,7 @@ function Template({
}: JSX.IntrinsicAttributes & PropsWithChildren<Props>) {
return (
<ButtonGroup size={size}>
<Button color="success" onClick={() => {}}>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
Start
</Button>
@ -50,7 +50,7 @@ Primary.args = {
export function Xsmall() {
return (
<ButtonGroup size="xsmall">
<Button color="success" onClick={() => {}}>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
Start
</Button>
@ -58,7 +58,7 @@ export function Xsmall() {
<i className="fa fa-stop space-right" aria-hidden="true" />
Stop
</Button>
<Button color="success" onClick={() => {}}>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
Start
</Button>
@ -73,7 +73,7 @@ export function Xsmall() {
export function Small() {
return (
<ButtonGroup size="small">
<Button color="success" onClick={() => {}}>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
Start
</Button>
@ -81,7 +81,7 @@ export function Small() {
<i className="fa fa-stop space-right" aria-hidden="true" />
Stop
</Button>
<Button color="success" onClick={() => {}}>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
Start
</Button>
@ -96,7 +96,7 @@ export function Small() {
export function Large() {
return (
<ButtonGroup size="large">
<Button color="success" onClick={() => {}}>
<Button color="primary" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
Start
</Button>
@ -104,7 +104,7 @@ export function Large() {
<i className="fa fa-stop space-right" aria-hidden="true" />
Stop
</Button>
<Button color="success" onClick={() => {}}>
<Button color="light" onClick={() => {}}>
<i className="fa fa-play space-right" aria-hidden="true" />
Start
</Button>

View file

@ -1,4 +1,13 @@
import { useRouter } from '@uirouter/react';
import {
Pause,
Play,
Plus,
RefreshCw,
Slash,
Square,
Trash2,
} from 'react-feather';
import * as notifications from '@/portainer/services/notifications';
import { useAuthorizations, Authorized } from '@/portainer/hooks/useUser';
@ -84,8 +93,8 @@ export function ContainersDatatableActions({
color="light"
onClick={() => onStartClick(selectedItems)}
disabled={selectedItemCount === 0 || !hasStoppedItemsSelected}
icon={Play}
>
<i className="fa fa-play space-right" aria-hidden="true" />
Start
</Button>
</Authorized>
@ -95,8 +104,8 @@ export function ContainersDatatableActions({
color="light"
onClick={() => onStopClick(selectedItems)}
disabled={selectedItemCount === 0 || !hasRunningItemsSelected}
icon={Square}
>
<i className="fa fa-stop space-right" aria-hidden="true" />
Stop
</Button>
</Authorized>
@ -106,8 +115,8 @@ export function ContainersDatatableActions({
color="light"
onClick={() => onKillClick(selectedItems)}
disabled={selectedItemCount === 0 || hasStoppedItemsSelected}
icon={Slash}
>
<i className="fa fa-bomb space-right" aria-hidden="true" />
Kill
</Button>
</Authorized>
@ -117,8 +126,8 @@ export function ContainersDatatableActions({
color="light"
onClick={() => onRestartClick(selectedItems)}
disabled={selectedItemCount === 0}
icon={RefreshCw}
>
<i className="fa fa-sync space-right" aria-hidden="true" />
Restart
</Button>
</Authorized>
@ -128,8 +137,8 @@ export function ContainersDatatableActions({
color="light"
onClick={() => onPauseClick(selectedItems)}
disabled={selectedItemCount === 0 || !hasRunningItemsSelected}
icon={Pause}
>
<i className="fa fa-pause space-right" aria-hidden="true" />
Pause
</Button>
</Authorized>
@ -139,8 +148,8 @@ export function ContainersDatatableActions({
color="light"
onClick={() => onResumeClick(selectedItems)}
disabled={selectedItemCount === 0 || !hasPausedItemsSelected}
icon={Play}
>
<i className="fa fa-play space-right" aria-hidden="true" />
Resume
</Button>
</Authorized>
@ -150,8 +159,8 @@ export function ContainersDatatableActions({
color="dangerlight"
onClick={() => onRemoveClick(selectedItems)}
disabled={selectedItemCount === 0}
icon={Trash2}
>
<i className="fa fa-trash-alt space-right" aria-hidden="true" />
Remove
</Button>
</Authorized>
@ -160,10 +169,7 @@ export function ContainersDatatableActions({
{isAddActionVisible && (
<Authorized authorizations="DockerContainerCreate">
<Link to="docker.containers.new" className="space-left">
<Button>
<i className="fa fa-plus space-right" aria-hidden="true" />
Add container
</Button>
<Button icon={Plus}>Add container</Button>
</Link>
</Authorized>
)}