Feature/UI tweaks (#419)

* some initial ui tweaks w/ burnettk

* left align more items as per the figma page w/ burnettk

* pyl w/ burnettk

* left align additional pages w/ burnettk

* use carbon components for the rjsf forms as well w/ burnettk

* do not raise if the app cannot load an env specific config file w/ burnettk

* attempting to center all the main elements except the diagram page WIP w/ burnettk

* fix build issue

* document media query

* fixed centering of elements in webui w/ burnettk

* some tweaks to taskshow markdown w/ burnettk

* make filter button smaller as a medium size

* medium styles for these buttons to the top right of tables

* inscreased cap for metadata key when displayed and allow word-wrapping w/ burnettk

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
Co-authored-by: burnettk <burnettk@users.noreply.github.com>
This commit is contained in:
jasquat 2023-09-07 10:12:13 -04:00 committed by GitHub
parent 9bb9ce47f8
commit c747c4be56
21 changed files with 265 additions and 190 deletions

View File

@ -31,11 +31,7 @@ on:
push:
branches:
- main
- feature/event-payloads-part-2
- feature/event-payload-migration-fix
- spiffdemo
- feature/business_end_states
- feature/allow-markdown-in-extension-results
jobs:
create_frontend_docker_image:

View File

@ -13,7 +13,6 @@ import AdminRoutes from './routes/AdminRoutes';
import { AbilityContext } from './contexts/Can';
import UserService from './services/UserService';
import ErrorDisplay from './components/ErrorDisplay';
import APIErrorProvider from './contexts/APIErrorContext';
import ScrollToTop from './components/ScrollToTop';
import EditorRoutes from './routes/EditorRoutes';
@ -27,6 +26,11 @@ export default function App() {
const ability = defineAbility(() => {});
let contentClassName = 'main-site-body-centered';
if (window.location.pathname.startsWith('/editor/')) {
contentClassName = 'no-center-stuff';
}
return (
<div className="cds--white">
{/* @ts-ignore */}
@ -34,9 +38,8 @@ export default function App() {
<APIErrorProvider>
<BrowserRouter>
<NavigationBar />
<Content>
<Content className={contentClassName}>
<ScrollToTop />
<ErrorDisplay />
<ErrorBoundary>
<Routes>
<Route path="/*" element={<HomePageRoutes />} />

View File

@ -50,7 +50,7 @@ export default function Filters({
renderIcon={Filter}
iconDescription="Filter Options"
hasIconOnly
size="lg"
size="md"
onClick={toggleShowFilterOptions}
/>
</Column>

View File

@ -79,7 +79,7 @@ export default function InstructionsForEndUser({
}
return (
<div style={{ margin: '20px 0 20px 0' }}>
<div>
<div className={className}>
{/*
https://www.npmjs.com/package/@uiw/react-md-editor switches to dark mode by default by respecting @media (prefers-color-scheme: dark)

View File

@ -49,6 +49,7 @@ import {
REFRESH_INTERVAL_SECONDS,
REFRESH_TIMEOUT_SECONDS,
titleizeString,
truncateString,
} from '../helpers';
import { useUriListForPermissions } from '../hooks/UriListForPermissions';
@ -1268,7 +1269,7 @@ export default function ProcessInstanceListTable({
setReportColumnFormMode('edit');
}}
>
{reportColumnLabel}
{truncateString(reportColumnLabel, 10)}
</Button>
<Button
data-qa="remove-report-column"
@ -1713,6 +1714,7 @@ export default function ProcessInstanceListTable({
kind="secondary"
href={taskShowUrl}
style={{ width: '60px' }}
size="sm"
>
Go
</Button>
@ -1830,7 +1832,7 @@ export default function ProcessInstanceListTable({
renderIcon={ArrowRight}
iconDescription="View Filterable List"
hasIconOnly
size="lg"
size="md"
onClick={() =>
navigate(`/admin/process-instances?report_hash=${reportHash}`)
}
@ -1840,7 +1842,12 @@ export default function ProcessInstanceListTable({
}
return (
<>
<Column sm={{ span: 3 }} md={{ span: 7 }} lg={{ span: 15 }}>
<Column
sm={{ span: 3 }}
md={{ span: 7 }}
lg={{ span: 15 }}
style={{ height: '48px' }}
>
{headerElement}
</Column>
{filterButtonLink}
@ -1897,7 +1904,7 @@ export default function ProcessInstanceListTable({
{reportColumnForm()}
{advancedOptionsModal()}
{processInstanceReportSaveTag()}
<Grid fullWidth condensed>
<Grid fullWidth condensed className="megacondensed">
{tableTitleLine()}
<Column sm={{ span: 4 }} md={{ span: 8 }} lg={{ span: 16 }}>
<Filters

View File

@ -133,28 +133,25 @@ export default function ProcessInstanceRun({
});
};
// if checkPermissions is false then assume the page using this component has already checked the permissions
if (checkPermissions) {
return (
<Can I="POST" a={processInstanceCreatePath} ability={ability}>
<Button
data-qa="start-process-instance"
onClick={processInstanceCreateAndRun}
className={className}
disabled={disableStartButton}
>
Start
</Button>
</Can>
);
}
return (
const startButton = (
<Button
data-qa="start-process-instance"
onClick={processInstanceCreateAndRun}
className={className}
disabled={disableStartButton}
size="md"
>
Start
</Button>
);
// if checkPermissions is false then assume the page using this component has already checked the permissions
if (checkPermissions) {
return (
<Can I="POST" a={processInstanceCreatePath} ability={ability}>
{startButton}
</Can>
);
}
return startButton;
}

View File

@ -135,7 +135,12 @@ export default function ProcessInterstitial({
) => {
return (
<div>
<InlineNotification kind={kind} subtitle={subtitle} title={title} />
<InlineNotification
kind={kind}
subtitle={subtitle}
title={title}
lowContrast
/>
</div>
);
};

View File

@ -208,6 +208,7 @@ export default function TaskListTable({
variant="primary"
href={taskUrl}
disabled={!hasAccessToCompleteTask}
size="sm"
>
Go
</Button>

View File

@ -7,6 +7,7 @@
.megacondensed {
padding-left: 0px;
margin-left: 0px;
}
/* defaults to 3rem, which isn't long sufficient for "elizabeth" */
@ -61,8 +62,9 @@ h3 {
color: #161616;
}
.span-tag {
.tag-within-dl {
color: black;
margin-left: -3px;
}
.cds--btn.button-white-background {
@ -121,6 +123,34 @@ h3 {
padding-left: 0;
}
/* make the colors black to make it easier to read */
.cds--btn--tertiary {
color: black;
border-color: black;
}
.cds--btn--tertiary:visited {
color: black;
border-color: black;
}
/* make the colors a little lighter black on hover
* so they match our normal scheme better
*/
.cds--btn--tertiary:focus {
color: white;
border-color: #393939;
background-color: #393939;
}
.cds--btn--tertiary:hover {
color: white;
border-color: #474747;
background-color: #474747;
}
.cds--btn--tertiary:visited:hover {
color: white;
border-color: #474747;
background-color: #474747;
}
.cds--header__global .cds--btn--primary.button-link {
color: #f4f4f4;
background-color: #393939;
@ -189,8 +219,9 @@ h1.with-icons {
dl {
display: block;
grid-template-columns: 35% 65%;
font-size: .8rem;
font-size: 14px;
line-height: 1.2rem;
padding-top: 9px;
}
dl dt {
@ -200,6 +231,11 @@ dl dt {
color: #161616;
}
dl.metadata-display dt {
width: 7rem;
word-wrap: break-word;
}
dl dd {
display: inline-block;
padding-left: 1rem;
@ -347,7 +383,7 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
.cds--tile.tile-process-group {
padding: 0px;
margin: 12px;
margin: 12px 24px 12px 0px;
width: 320px;
height: 264px;
background: #F4F4F4;
@ -404,6 +440,12 @@ in on this with the react-jsonschema-form repo. This is just a patch fix to allo
max-width: 20rem;
}
/* carbon wants padding on all sides. our UI person wanted everything leaning against the left wall. */
.cds--tab-content {
padding-left: 0;
padding-right: 0;
}
.clear-left {
clear: left;
}
@ -427,10 +469,13 @@ td.actions-cell {
.no-results-message {
font-style: italic;
margin-left: 2rem;
font-size: 14px;
}
th.table-header-right-align .cds--data-table, th.table-header-right-align .cds--table-header-label{
text-align: right;
}
.data-table-description {
font-size: 14px;
line-height: 18px;
@ -447,6 +492,7 @@ td.actions-cell {
.filter-icon {
text-align: right;
padding-bottom: 10px;
margin-right: 0px;
}
.cds--btn--ghost:not([disabled]).red-icon svg {
@ -683,3 +729,85 @@ hr {
#hidden-form-for-autosave {
display: none;
}
fieldset legend.header {
margin-bottom: 32px;
}
fieldset fieldset fieldset legend.header {
margin-bottom: 16px;
}
.fixed-width-container {
margin: auto;
justify-content: center;
overflow: visible;
/* border: 1px solid black; */
}
/* media min-width 75 means: do the stuff in the block if the browser width is greater than 75 */
@media (min-width: 75rem) {
.fixed-width-container {
max-width: 75rem;
}
}
p, li, h1, h2, h3, h4, h5, h6, blockquote, hr {
max-width: 640px;
}
li.cds--accordion__item {
max-width: 100%;
}
div.markdown {
padding: 15px 0 15px 0;
overflow: visible;
}
div.markdown-collapsed {
max-height: 200px;
overflow: hidden;
--mask: linear-gradient(to bottom,
rgba(0,0,0, 1) 0, rgba(0,0,0, 1) 40%,
rgba(0,0,0, 0) 95%, rgba(0,0,0, 0) 0
) 100% 50% / 100% 100% repeat-x;
font: 2em/1.6em Arial;
-webkit-mask: var(--mask);
mask: var(--mask);
}
.cds--data-table-content {
overflow-x: scroll;
}
.wmde-markdown table {
width: fit-content;
overflow: scroll;
}
/* override carbon variable so we can left align accordions*/
.cds--accordion {
--cds-layout-density-padding-inline-local: 0px;
}
.megacondensed-button .cds--btn {
--cds-layout-density-padding-inline-local: 0px;
}
.wmde-markdown ol {
list-style: decimal;
}
.wmde-markdown ul {
list-style: disc;
}
div.cds--tag svg {
vertical-align: middle;
display: inline-block;
}
div.onboarding {
margin-bottom: 2rem;
}

View File

@ -21,77 +21,3 @@
main {
}
.fixed-width-container {
padding-left: 0;
overflow: visible;
}
@media (min-width: 99rem) {
.fixed-width-container {
max-width: 99rem;
padding-right: 0;
}
}
@media (min-width: 80rem) {
.fixed-width-container {
padding-left: 10rem;
padding-right: 2rem;
}
}
p, li, h1, h2, h3, h4, h5, h6, blockquote {
max-width: 640px;
}
li.cds--accordion__item {
max-width: 100%;
}
div.markdown {
max-width: 1000px;
padding: 15px 0 15px 0;
overflow: visible;
}
div.markdown-collapsed {
max-height: 200px;
overflow: hidden;
--mask: linear-gradient(to bottom,
rgba(0,0,0, 1) 0, rgba(0,0,0, 1) 40%,
rgba(0,0,0, 0) 95%, rgba(0,0,0, 0) 0
) 100% 50% / 100% 100% repeat-x;
font: 2em/1.6em Arial;
-webkit-mask: var(--mask);
mask: var(--mask);
}
.cds--data-table-content {
width: fit-content;
min-width: 100%;
overflow-x: visible;
}
.wmde-markdown table {
width: fit-content;
}
.wmde-markdown ol {
list-style: decimal;
}
.wmde-markdown ul {
list-style: disc;
}
div.cds--tag svg {
vertical-align: middle;
display: inline-block;
}
div.onboarding {
margin-bottom: 2rem;
}

View File

@ -5,11 +5,8 @@ import {
RJSFSchema,
StrictRJSFSchema,
} from '@rjsf/utils';
// @ts-ignore
import { AddAlt } from '@carbon/icons-react';
import IconButton from '../IconButton/IconButton';
import { Button } from '@carbon/react';
import { Add } from '@carbon/icons-react';
/** The `AddButton` renders a button that represent the `Add` action on a form
*/
@ -19,18 +16,17 @@ export default function AddButton<
F extends FormContextType = any
>({ className, onClick, disabled, registry }: IconButtonProps<T, S, F>) {
return (
<div className="row">
<p className={`col-xs-3 col-xs-offset-9 text-right ${className}`}>
<IconButton
iconType="info"
icon="plus"
className="btn-add col-xs-12"
title="Add"
onClick={onClick}
disabled={disabled}
registry={registry}
/>
</p>
</div>
<Button
iconType="info"
kind="tertiary"
size="sm"
renderIcon={Add}
title="Add"
onClick={onClick}
disabled={disabled}
registry={registry}
>
Add new
</Button>
);
}

View File

@ -1,4 +1,5 @@
import React from 'react';
import { Button } from '@carbon/react';
import {
FormContextType,
IconButtonProps,
@ -6,7 +7,6 @@ import {
StrictRJSFSchema,
} from '@rjsf/utils';
// @ts-ignore
import { Add, TrashCan, ArrowUp, ArrowDown } from '@carbon/icons-react';
export default function IconButton<
@ -20,32 +20,32 @@ export default function IconButton<
className,
uiSchema,
registry,
title,
...otherProps
} = props;
// icon string optios: plus, remove, arrow-up, arrow-down
let carbonIcon = (
<p>
Add new <Add />
</p>
);
let carbonIcon = Add;
if (icon === 'remove') {
carbonIcon = <TrashCan />;
carbonIcon = TrashCan;
}
if (icon === 'arrow-up') {
carbonIcon = <ArrowUp />;
carbonIcon = ArrowUp;
}
if (icon === 'arrow-down') {
carbonIcon = <ArrowDown />;
carbonIcon = ArrowDown;
}
return (
<button
type="button"
<Button
className={`btn btn-${iconType} ${className}`}
iconDescription={title}
kind="tertiary"
title={null}
hasIconOnly
size="sm"
renderIcon={carbonIcon}
{...otherProps}
>
{carbonIcon}
</button>
/>
);
}

View File

@ -1,6 +1,7 @@
// @ts-ignore
import { Table } from '@carbon/react';
import { useEffect, useState } from 'react';
import ErrorDisplay from '../components/ErrorDisplay';
import appVersionInfo from '../helpers/appVersionInfo';
import { ObjectWithStringKeysAndValues } from '../interfaces';
import HttpService from '../services/HttpService';
@ -57,6 +58,7 @@ export default function About() {
return (
<div>
<ErrorDisplay />
<h1>About</h1>
{versionInfoFromDict('Frontend version information', frontendVersionInfo)}
{versionInfoFromDict('Backend version information', backendVersionInfo)}

View File

@ -22,6 +22,7 @@ import ProcessInstanceFindById from './ProcessInstanceFindById';
import ProcessInterstitialPage from './ProcessInterstitialPage';
import MessageListPage from './MessageListPage';
import DataStorePage from './DataStorePage';
import ErrorDisplay from '../components/ErrorDisplay';
export default function AdminRoutes() {
const location = useLocation();
@ -31,6 +32,7 @@ export default function AdminRoutes() {
if (UserService.hasRole(['admin'])) {
return (
<div className="fixed-width-container">
<ErrorDisplay />
<Routes>
<Route path="/" element={<ProcessGroupList />} />
<Route path="process-groups" element={<ProcessGroupList />} />

View File

@ -11,7 +11,7 @@ export default function EditorRoutes() {
if (UserService.hasRole(['admin'])) {
return (
<div className="full-width-container">
<div className="full-width-container no-center-stuff">
<Routes>
<Route
path="process-models/:process_model_id/files"

View File

@ -15,6 +15,7 @@ import useAPIError from '../hooks/UseApiError';
import { recursivelyChangeNullAndUndefined } from '../helpers';
import CustomForm from '../components/CustomForm';
import { BACKEND_BASE_URL } from '../config';
import ErrorDisplay from '../components/ErrorDisplay';
// eslint-disable-next-line sonarjs/cognitive-complexity
export default function Extension() {
@ -209,7 +210,12 @@ export default function Extension() {
);
}
}
return <div className="fixed-width-container">{componentsToDisplay}</div>;
return (
<div className="fixed-width-container">
<ErrorDisplay />
{componentsToDisplay}
</div>
);
}
return null;
}

View File

@ -7,6 +7,7 @@ import CompletedInstances from './CompletedInstances';
import CreateNewInstance from './CreateNewInstance';
import InProgressInstances from './InProgressInstances';
import OnboardingView from './OnboardingView';
import ErrorDisplay from '../components/ErrorDisplay';
export default function HomePageRoutes() {
const location = useLocation();
@ -49,6 +50,7 @@ export default function HomePageRoutes() {
return (
<div className="fixed-width-container">
<ErrorDisplay />
<OnboardingView />
{renderTabs()}
<Routes>

View File

@ -45,6 +45,7 @@ import {
getLastMilestoneFromProcessInstance,
HUMAN_TASK_TYPES,
modifyProcessIdentifierForPathParam,
truncateString,
unModifyProcessIdentifierForPathParam,
} from '../helpers';
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
@ -359,7 +360,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
);
return (
<Grid condensed fullWidth>
<Grid condensed fullWidth className="megacondensed">
<Column sm={4} md={4} lg={5}>
<dl>
<dt>Status:</dt>
@ -367,7 +368,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
<Tag
type={statusColor}
size="sm"
className="span-tag process-instance-status"
className="tag-within-dl process-instance-status"
>
{processInstance.status} {statusIcon}
</Tag>
@ -418,8 +419,10 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
<Column sm={4} md={4} lg={8}>
{(processInstance.process_metadata || []).map(
(processInstanceMetadata) => (
<dl>
<dt>{processInstanceMetadata.key}:</dt>
<dl className="metadata-display">
<dt title={processInstanceMetadata.key}>
{truncateString(processInstanceMetadata.key, 50)}:
</dt>
<dd>{processInstanceMetadata.value}</dd>
</dl>
)
@ -1287,6 +1290,9 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
[`Process Instance Id: ${processInstance.id}`],
]}
/>
{taskUpdateDisplayArea()}
{processDataDisplayArea()}
{viewMostRecentStateComponent()}
<Stack orientation="horizontal" gap={1}>
<h1 className="with-icons">
Process Instance Id: {processInstance.id}
@ -1294,6 +1300,7 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
{buttonIcons()}
</Stack>
{getInfoTag()}
<br />
<ProcessInterstitial
processInstanceId={processInstance.id}
processInstanceShowPageUrl={processInstanceShowPageBaseUrl}
@ -1302,31 +1309,24 @@ export default function ProcessInstanceShow({ variant }: OwnProps) {
collapsableInstructions
executeTasks={false}
/>
<Grid condensed fullWidth>
<Column md={6} lg={8} sm={4}>
<TaskListTable
apiPath="/tasks"
additionalParams={`process_instance_id=${processInstance.id}`}
tableTitle="Tasks I can complete"
tableDescription="These are tasks that can be completed by you, either because they were assigned to a group you are in, or because they were assigned directly to you."
paginationClassName="with-large-bottom-margin"
textToShowIfEmpty="There are no tasks you can complete for this process instance."
shouldPaginateTable={false}
showProcessModelIdentifier={false}
showProcessId={false}
showStartedBy={false}
showTableDescriptionAsTooltip
showDateStarted={false}
showLastUpdated={false}
hideIfNoTasks
canCompleteAllTasks
/>
</Column>
</Grid>
{taskUpdateDisplayArea()}
{processDataDisplayArea()}
<br />
{viewMostRecentStateComponent()}
<TaskListTable
apiPath="/tasks"
additionalParams={`process_instance_id=${processInstance.id}`}
tableTitle="Tasks I can complete"
tableDescription="These are tasks that can be completed by you, either because they were assigned to a group you are in, or because they were assigned directly to you."
paginationClassName="with-large-bottom-margin"
textToShowIfEmpty="There are no tasks you can complete for this process instance."
shouldPaginateTable={false}
showProcessModelIdentifier={false}
showProcessId={false}
showStartedBy={false}
showTableDescriptionAsTooltip
showDateStarted={false}
showLastUpdated={false}
hideIfNoTasks
canCompleteAllTasks
/>
{getTabs()}
</>
);

View File

@ -834,12 +834,14 @@ export default function ProcessModelEditDiagram() {
onRequestClose={handleMarkdownEditorClose}
size="lg"
>
<MDEditor
height={500}
highlightEnable={false}
value={markdownText}
onChange={setMarkdownText}
/>
<div data-color-mode="light">
<MDEditor
height={500}
highlightEnable={false}
value={markdownText}
onChange={setMarkdownText}
/>
</div>
</Modal>
);
};

View File

@ -7,7 +7,6 @@ import {
TrashCan,
Upload,
View,
// @ts-ignore
} from '@carbon/icons-react';
import {
Accordion,
@ -25,7 +24,6 @@ import {
TableHead,
TableHeader,
TableRow,
// @ts-ignore
} from '@carbon/react';
import { Can } from '@casl/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
@ -346,7 +344,7 @@ export default function ProcessModelShow() {
let actionsTableCell = null;
if (processModelFile.name.match(/\.(dmn|bpmn|json|md)$/)) {
actionsTableCell = (
<TableCell key={`${processModelFile.name}-cell`}>
<TableCell key={`${processModelFile.name}-cell`} align="right">
{renderButtonElements(processModelFile, isPrimaryBpmnFile)}
</TableCell>
);
@ -378,16 +376,20 @@ export default function ProcessModelShow() {
return constructedTag;
});
const headers = ['Name', 'Actions'];
return (
<Table size="lg" useZebraStyles={false}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader id={header} key={header}>
{header}
</TableHeader>
))}
<TableHeader id="Name" key="Name">
Name
</TableHeader>
<TableHeader
id="Actions"
key="Actions"
className="table-header-right-align"
>
Actions
</TableHeader>
</TableRow>
</TableHead>
<TableBody>{tags}</TableBody>
@ -525,7 +527,7 @@ export default function ProcessModelShow() {
titleText=""
size="lg"
label="Add File"
type="inline"
type="default"
data-qa="process-model-add-file"
onChange={(a: any) => {
if (a.selectedItem.text === 'New BPMN File') {
@ -564,7 +566,7 @@ export default function ProcessModelShow() {
className="megacondensed process-model-files-section"
>
<Column md={8} lg={14} sm={4}>
<Accordion align="end" open>
<Accordion align="end" open className="megacondensed-button">
<AccordionItem
open
data-qa="files-accordion"

View File

@ -281,7 +281,7 @@ export default function TaskShow() {
// this allows us to autosave form data without extra attributes and without validations
// but still requires validations when the user submits the form that they can edit.
return (
<Grid fullWidth condensed>
<Grid fullWidth condensed className="megacondensed">
<Column sm={4} md={5} lg={8}>
<CustomForm
id="form-to-submit"
@ -342,10 +342,10 @@ export default function TaskShow() {
/>
);
pageElements.push(
<h3>
<h1>
Task: {basicTask.name_for_display} (
{basicTask.process_model_display_name}){statusString}
</h3>
</h1>
);
}
if (basicTask && taskData) {