diff --git a/package-lock.json b/package-lock.json index b0d00071..57aec016 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23968,10 +23968,12 @@ "version": "5.0.0-prerelease.0", "license": "MIT", "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", "clsx": "^2.0.0", + "lodash": "^4.17.21", "react-jss": "^10.10.0" }, "devDependencies": { diff --git a/package.json b/package.json index 58b83e90..b90c3317 100644 --- a/package.json +++ b/package.json @@ -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", @@ -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" } } diff --git a/packages/module/package.json b/packages/module/package.json index 7f5cb0d3..049592cb 100644 --- a/packages/module/package.json +++ b/packages/module/package.json @@ -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" }, diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Status/LinkStatusExample.tsx b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Status/LinkStatusExample.tsx new file mode 100644 index 00000000..d71201c7 --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Status/LinkStatusExample.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import { SecondaryStatus } from '@patternfly/react-component-groups/dist/dynamic/Status'; + +export const BasicExample: React.FunctionComponent = () => ( + +); diff --git a/packages/module/patternfly-docs/content/extensions/component-groups/examples/Status/Status.md b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Status/Status.md new file mode 100644 index 00000000..275435ca --- /dev/null +++ b/packages/module/patternfly-docs/content/extensions/component-groups/examples/Status/Status.md @@ -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" + +``` \ No newline at end of file diff --git a/packages/module/src/Status/LinkStatus.tsx b/packages/module/src/Status/LinkStatus.tsx new file mode 100644 index 00000000..2a40d0e2 --- /dev/null +++ b/packages/module/src/Status/LinkStatus.tsx @@ -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 = ({ linkTitle, ...other }) => + + +export interface LinkStatusProps extends React.ComponentProps { + linkTitle?: string; +}; + +export default LinkStatus; diff --git a/packages/module/src/Status/NodeUnschedulableStatus.tsx b/packages/module/src/Status/NodeUnschedulableStatus.tsx new file mode 100644 index 00000000..3a150ff2 --- /dev/null +++ b/packages/module/src/Status/NodeUnschedulableStatus.tsx @@ -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 = ({ + status, + title, + iconOnly, + noTooltip, + className, +}) => { + const statusProps = { title: title || status, iconOnly, noTooltip, className }; + return ; +}; + +export interface NodeUnschedulableStatusProps extends StatusComponentProps { + status: string; +}; + +export default NodeUnschedulableStatus; diff --git a/packages/module/src/Status/SecondaryStatus.tsx b/packages/module/src/Status/SecondaryStatus.tsx new file mode 100644 index 00000000..6080e873 --- /dev/null +++ b/packages/module/src/Status/SecondaryStatus.tsx @@ -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 = ({ status, className, dataStatusID }) => { + const statusLabel = _.compact(_.concat([], status)).join(', '); + const cssClassName = className || ''; + if (statusLabel) { + return ( +
+ {statusLabel} +
+ ); + } + return null; +}; + +export default SecondaryStatus; diff --git a/packages/module/src/Status/Status.tsx b/packages/module/src/Status/Status.tsx new file mode 100644 index 00000000..c91ed861 --- /dev/null +++ b/packages/module/src/Status/Status.tsx @@ -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 = ({ status }) => ( + +); \ No newline at end of file diff --git a/packages/module/src/Status/StatusCompnent.tsx b/packages/module/src/Status/StatusCompnent.tsx new file mode 100644 index 00000000..a79e6f3b --- /dev/null +++ b/packages/module/src/Status/StatusCompnent.tsx @@ -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 = ({ 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 = ( + + {words!.map((word, i) => ( + // eslint-disable-next-line react/no-array-index-key + + {word} + {i !== words!.length - 1 && } + + ))} + + ); + 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 + * + * ``` + */ +const StatusIconAndText: React.FC = ({ icon, title, spin, iconOnly, noTooltip, className }) => { + if (!title) { + return <>{DASH}; + } + + return ( + + {icon && + React.cloneElement(icon, { + className: clsx( + spin && 'fa-spin', + icon.props.className, + !iconOnly && 'co-icon-and-text__icon co-icon-flex-child' + ) + })} + {!iconOnly && } + + ); +}; + +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 + * + * ``` + */ +export const Status: React.FC = ({ status, title, children, iconOnly, noTooltip, className }) => { + const statusProps = { title: title || status, iconOnly, noTooltip, className }; + switch (status) { + case 'New': + return } />; + + case 'Pending': + return } />; + + case 'Planning': + return } />; + + case 'ContainerCreating': + case 'UpgradePending': + case 'PendingUpgrade': + case 'PendingRollback': + return ; + + case 'In Progress': + case 'Installing': + case 'InstallReady': + case 'Replacing': + case 'Running': + case 'Updating': + case 'Upgrading': + case 'PendingInstall': + return } />; + + case 'Cancelled': + case 'Deleting': + case 'Expired': + case 'Not Ready': + case 'Cancelling': + case 'Terminating': + case 'Superseded': + case 'Uninstalling': + return } />; + + case 'Warning': + case 'RequiresApproval': + return } />; + + 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 {children}; + + 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 ; + + case 'Info': + return {children}; + + case 'Unknown': + return } />; + + case 'PipelineNotStarted': + return } />; + + default: + return status ? : <>{DASH}; + } +}; + +export default Status; diff --git a/packages/module/src/Status/icons.tsx b/packages/module/src/Status/icons.tsx new file mode 100644 index 00000000..64c778f4 --- /dev/null +++ b/packages/module/src/Status/icons.tsx @@ -0,0 +1,94 @@ +import * as React from 'react'; +import { Icon } from '@patternfly/react-core'; +import { ArrowCircleUpIcon } from '@patternfly/react-icons/dist/esm/icons/arrow-circle-up-icon'; +import { ResourcesAlmostFullIcon } from '@patternfly/react-icons/dist/esm/icons/resources-almost-full-icon'; +import { ResourcesFullIcon } from '@patternfly/react-icons/dist/esm/icons/resources-full-icon'; +import { SyncAltIcon } from '@patternfly/react-icons/dist/esm/icons/sync-alt-icon'; +import { UnknownIcon } from '@patternfly/react-icons/dist/esm/icons/unknown-icon'; + + +export interface ColoredIconProps { + className?: string; + title?: string; + size?: 'sm' | 'md' | 'lg' | 'xl'; +}; + +/** + * Component for displaying a green check mark circle icon + * @param {string} [className] - (optional) additional class name for the component + * @param {string} [title] - (optional) icon title + * @param {string} [size] - (optional) icon size: ('sm', 'md', 'lg', 'xl') + * @example + * ```tsx + * + * ``` + */ +export declare const GreenCheckCircleIcon: React.FC; +/** + * Component for displaying a red exclamation mark circle icon + * @param {string} [className] - (optional) additional class name for the component + * @param {string} [title] - (optional) icon title + * @param {string} [size] - (optional) icon size: ('sm', 'md', 'lg', 'xl') + * @example + * ```tsx + * + * ``` + */ +export declare const RedExclamationCircleIcon: React.FC; +/** + * Component for displaying a yellow triangle exclamation icon + * @param {string} [className] - (optional) additional class name for the component + * @param {string} [title] - (optional) icon title + * @param {string} [size] - (optional) icon size: ('sm', 'md', 'lg', 'xl') + * @example + * ```tsx + * + * ``` + */ +export declare const YellowExclamationTriangleIcon: React.FC; +/** + * Component for displaying a blue info circle icon + * @param {string} [className] - (optional) additional class name for the component + * @param {string} [title] - (optional) icon title + * @param {string} [size] - (optional) icon size: ('sm', 'md', 'lg', 'xl') + * @example + * ```tsx + * + * ``` + */ +export declare const BlueInfoCircleIcon: React.FC; + + +export const GrayUnknownIcon: React.FC = ({ className, title, size }) => ( + + + +); + +export const BlueSyncIcon: React.FC = ({ className, title, size }) => ( + + + +); + +export const RedResourcesFullIcon: React.FC = ({ className, title, size }) => ( + + + +); + +export const YellowResourcesAlmostFullIcon: React.FC = ({ + className, + title, + size, +}) => ( + + + +); + +export const BlueArrowCircleUpIcon: React.FC = ({ className, title, size }) => ( + + + +); diff --git a/packages/module/src/Status/index.tsx b/packages/module/src/Status/index.tsx new file mode 100644 index 00000000..69c8be5e --- /dev/null +++ b/packages/module/src/Status/index.tsx @@ -0,0 +1,6 @@ +export * from './icons'; +export * from './statuses'; +export * from './Status'; + +export { default as SecondaryStatus } from './SecondaryStatus'; +export { default as NodeUnschedulableStatus } from './NodeUnschedulableStatus'; diff --git a/packages/module/src/Status/statuses.tsx b/packages/module/src/Status/statuses.tsx new file mode 100644 index 00000000..fbec2e7f --- /dev/null +++ b/packages/module/src/Status/statuses.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; +import { HourglassHalfIcon } from '@patternfly/react-icons/dist/esm/icons/hourglass-half-icon'; +import { useTranslation } from 'react-i18next'; +import { + GenericStatus, + StatusComponentProps, + ErrorStatus as SdkErrorStatus, + InfoStatus as SdkInfoStatus, + ProgressStatus as SdkProgressStatus, + SuccessStatus as SdkSuccessStatus, +} from '@openshift-console/dynamic-plugin-sdk'; +import { YellowExclamationTriangleIcon } from './icons'; + +export const ErrorStatus: React.FC = ({ title, ...props }) => { + const { t } = useTranslation(); + return ; +}; + +export const InfoStatus: React.FC = ({ title, ...props }) => { + const { t } = useTranslation(); + return ; +}; + +export const ProgressStatus: React.FC = ({ title, ...props }) => { + const { t } = useTranslation(); + return ; +}; + +export const SuccessStatus: React.FC = ({ title, ...props }) => { + const { t } = useTranslation(); + return ; +}; + +export const PendingStatus: React.FC = (props) => { + const { t } = useTranslation(); + return ( + + ); +}; +PendingStatus.displayName = 'PendingStatus'; + +export const WarningStatus: React.FC = (props) => { + const { t } = useTranslation(); + return ( + + ); +}; +WarningStatus.displayName = 'WarningStatus';