Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,18 @@
},
"devDependencies": {
"@babel/core": "^7.19.6",
"@babel/plugin-proposal-private-property-in-object": "^7.19.6",
"@babel/preset-env": "^7.19.4",
"@babel/preset-flow": "^7.18.6",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.18.6",
"@octokit/rest": "^18.0.0",
"@testing-library/dom": "^8.0.7",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "14.4.3",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.49.0",
"@babel/plugin-proposal-private-property-in-object": "^7.19.6",
"@octokit/rest": "^18.0.0",
"babel-jest": "^29.2.2",
"babel-polyfill": "6.26.0",
"chokidar": "^3.5.3",
Expand Down Expand Up @@ -68,6 +68,6 @@
"serve": "^14.1.2",
"surge": "^0.23.1",
"ts-jest": "29.0.3",
"whatwg-fetch": "^3.6.2"
"whatwg-fetch": "^3.6.2"
}
}
2 changes: 2 additions & 0 deletions packages/module/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@
"tag": "prerelease"
},
"dependencies": {
"@openshift-console/dynamic-plugin-sdk": "^1.1.0",
"@patternfly/react-core": "^5.1.1",
"@patternfly/react-icons": "^5.1.1",
"@patternfly/react-table": "^5.1.1",
"lodash": "^4.17.21",
"react-jss": "^10.10.0",
"clsx": "^2.0.0"
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from 'react';
import { SecondaryStatus } from '@patternfly/react-component-groups/dist/dynamic/Status';

export const BasicExample: React.FunctionComponent = () => (
<SecondaryStatus />
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
# Sidenav top-level section
# should be the same for all markdown files
section: extensions
subsection: Component groups
# Sidenav secondary level section
# should be the same for all markdown files
id: Status
# Tab (react | react-demos | html | html-demos | design-guidelines | accessibility)
source: react
# If you use typescript, the name of the interface to display props for
# These are found through the sourceProps function provided in patternfly-docs.source.js
propComponents: ['Status']
sourceLink: https://github.com/patternfly/react-component-groups/blob/main/packages/module/patternfly-docs/content/extensions/component-groups/examples/Status/Status.md
---

import Status from '@patternfly/react-component-groups/dist/dynamic/Status';

The **Status** component displays different statuses for an item. There are a variety of statuses that can be displayed components for example the link button status.

### Status supported

By default, the Status logo displays as normal and in full color, meaning that it is supported.

```js file="./LinkStatusExample.tsx"

```
14 changes: 14 additions & 0 deletions packages/module/src/Status/LinkStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import * as React from 'react';
import { Button } from '@patternfly/react-core';
import { StatusIconAndText } from '@openshift-console/dynamic-plugin-sdk';

const LinkStatus: React.FC<LinkStatusProps> = ({ linkTitle, ...other }) =>
<Button variant="link" title={linkTitle}>
<StatusIconAndText {...other} />
</Button>

export interface LinkStatusProps extends React.ComponentProps<typeof StatusIconAndText> {
linkTitle?: string;
};

export default LinkStatus;
27 changes: 27 additions & 0 deletions packages/module/src/Status/NodeUnschedulableStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from 'react';
import { WarningStatus } from './statuses';

export interface StatusComponentProps {
title?: string;
iconOnly?: boolean;
noTooltip?: boolean;
className?: string;
popoverTitle?: string;
};

const NodeUnschedulableStatus: React.FC<NodeUnschedulableStatusProps> = ({
status,
title,
iconOnly,
noTooltip,
className,
}) => {
const statusProps = { title: title || status, iconOnly, noTooltip, className };
return <WarningStatus {...statusProps} />;
};

export interface NodeUnschedulableStatusProps extends StatusComponentProps {
status: string;
};

export default NodeUnschedulableStatus;
23 changes: 23 additions & 0 deletions packages/module/src/Status/SecondaryStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from 'react';
import * as _ from 'lodash';

export interface SecondaryStatusProps {
status?: string | string[];
className?: string;
dataStatusID?: string;
};

const SecondaryStatus: React.FC<SecondaryStatusProps> = ({ status, className, dataStatusID }) => {
const statusLabel = _.compact(_.concat([], status)).join(', ');
const cssClassName = className || '';
if (statusLabel) {
return (
<div data-status-id={dataStatusID}>
<small className={`${cssClassName} text-muted`}>{statusLabel}</small>
</div>
);
}
return null;
};

export default SecondaryStatus;
10 changes: 10 additions & 0 deletions packages/module/src/Status/Status.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as React from 'react';
import { StatusComponent as Status } from './StatusCompnent';

export interface StatusIconProps {
status: string;
};

export const StatusIcon: React.FC<StatusIconProps> = ({ status }) => (
<Status status={status} iconOnly />
);
209 changes: 209 additions & 0 deletions packages/module/src/Status/StatusCompnent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
import * as React from 'react';
import { BanIcon } from '@patternfly/react-icons/dist/esm/icons/ban-icon';
import { ClipboardListIcon } from '@patternfly/react-icons/dist/esm/icons/clipboard-list-icon';
import { HourglassHalfIcon } from '@patternfly/react-icons/dist/esm/icons/hourglass-half-icon';
import { HourglassStartIcon } from '@patternfly/react-icons/dist/esm/icons/hourglass-start-icon';
import { NotStartedIcon } from '@patternfly/react-icons/dist/esm/icons/not-started-icon';
import { SyncAltIcon } from '@patternfly/react-icons/dist/esm/icons/sync-alt-icon';
import { UnknownIcon } from '@patternfly/react-icons/dist/esm/icons/unknown-icon';
import { YellowExclamationTriangleIcon } from './icons';
import { ErrorStatus, InfoStatus, ProgressStatus, SuccessStatus } from './statuses';
import clsx from 'clsx';

const DASH = '-';
const MEMO = {};

interface StatusComponentProps {
title?: string;
iconOnly?: boolean;
noTooltip?: boolean;
className?: string;
popoverTitle?: string;
}

const CamelCaseWrap: React.FC<CamelCaseWrapProps> = ({ value, dataTest }) => {
if (!value) {
return '-';
}

if (MEMO[value]) {
return MEMO[value];
}

// Add word break points before capital letters (but keep consecutive capital letters together).
const words = value.match(/[A-Z]+[^A-Z]*|[^A-Z]+/g);
const rendered = (
<span data-test={dataTest}>
{words!.map((word, i) => (
// eslint-disable-next-line react/no-array-index-key
<React.Fragment key={i}>
{word}
{i !== words!.length - 1 && <wbr />}
</React.Fragment>
))}
</span>
);
MEMO[value] = rendered;
return rendered;
};

interface CamelCaseWrapProps {
value: string;
dataTest?: string;
}

export interface StatusProps extends StatusComponentProps {
status?: string;
icon?: React.ReactElement;
spin?: boolean;
children?: React.ReactNode;
}

interface StatusIconAndTextProps extends StatusComponentProps {
icon?: React.ReactElement;
spin?: boolean;
};

/**
* Component for displaying a status icon and text
* @param {string} [title] - (optional) status text
* @param {boolean} [iconOnly] - (optional) if true, only displays icon
* @param {boolean} [noTooltip] - (optional) if true, tooltip won't be displayed
* @param {string} [className] - (optional) additional class name for the component
* @param {React.ReactElement} [icon] - (optional) icon to be displayed
* @param {boolean} [spin] - (optional) if true, icon rotates
* @example
* ```tsx
* <StatusIconAndText title={title} icon={renderIcon} />
* ```
*/
const StatusIconAndText: React.FC<StatusIconAndTextProps> = ({ icon, title, spin, iconOnly, noTooltip, className }) => {
if (!title) {
return <>{DASH}</>;
}

return (
<span className={clsx('co-icon-and-text', className)} title={iconOnly && !noTooltip ? title : undefined}>
{icon &&
React.cloneElement(icon, {
className: clsx(
spin && 'fa-spin',
icon.props.className,
!iconOnly && 'co-icon-and-text__icon co-icon-flex-child'
)
})}
{!iconOnly && <CamelCaseWrap value={title} dataTest="status-text" />}
</span>
);
};

interface StatusComponentProps {
title?: string;
iconOnly?: boolean;
noTooltip?: boolean;
className?: string;
popoverTitle?: string;
}

/**
* Component for displaying a status message
* @param {string} status - type of status to be displayed
* @param {string} [title] - (optional) status text
* @param {boolean} [iconOnly] - (optional) if true, only displays icon
* @param {boolean} [noTooltip] - (optional) if true, tooltip won't be displayed
* @param {string} [className] - (optional) additional class name for the component
* @param {string} [popoverTitle] - (optional) title for popover
* @param {ReactNode} [children] - (optional) children for the component
* @example
* ```tsx
* <Status status='Warning' />
* ```
*/
export const Status: React.FC<StatusProps> = ({ status, title, children, iconOnly, noTooltip, className }) => {
const statusProps = { title: title || status, iconOnly, noTooltip, className };
switch (status) {
case 'New':
return <StatusIconAndText {...statusProps} icon={<HourglassStartIcon />} />;

case 'Pending':
return <StatusIconAndText {...statusProps} icon={<HourglassHalfIcon />} />;

case 'Planning':
return <StatusIconAndText {...statusProps} icon={<ClipboardListIcon />} />;

case 'ContainerCreating':
case 'UpgradePending':
case 'PendingUpgrade':
case 'PendingRollback':
return <ProgressStatus {...statusProps} />;

case 'In Progress':
case 'Installing':
case 'InstallReady':
case 'Replacing':
case 'Running':
case 'Updating':
case 'Upgrading':
case 'PendingInstall':
return <StatusIconAndText {...statusProps} icon={<SyncAltIcon />} />;

case 'Cancelled':
case 'Deleting':
case 'Expired':
case 'Not Ready':
case 'Cancelling':
case 'Terminating':
case 'Superseded':
case 'Uninstalling':
return <StatusIconAndText {...statusProps} icon={<BanIcon />} />;

case 'Warning':
case 'RequiresApproval':
return <StatusIconAndText {...statusProps} icon={<YellowExclamationTriangleIcon />} />;

case 'ContainerCannotRun':
case 'CrashLoopBackOff':
case 'Critical':
case 'ErrImagePull':
case 'Error':
case 'Failed':
case 'Failure':
case 'ImagePullBackOff':
case 'InstallCheckFailed':
case 'Lost':
case 'Rejected':
case 'UpgradeFailed':
return <ErrorStatus {...statusProps}>{children}</ErrorStatus>;

case 'Accepted':
case 'Active':
case 'Bound':
case 'Complete':
case 'Completed':
case 'Created':
case 'Enabled':
case 'Succeeded':
case 'Ready':
case 'Up to date':
case 'Loaded':
case 'Provisioned as node':
case 'Preferred':
case 'Connected':
case 'Deployed':
return <SuccessStatus {...statusProps} />;

case 'Info':
return <InfoStatus {...statusProps}>{children}</InfoStatus>;

case 'Unknown':
return <StatusIconAndText {...statusProps} icon={<UnknownIcon />} />;

case 'PipelineNotStarted':
return <StatusIconAndText {...statusProps} icon={<NotStartedIcon />} />;

default:
return status ? <StatusIconAndText {...statusProps} /> : <>{DASH}</>;
}
};

export default Status;
Loading