Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
# Sidenav top-level section
# should be the same for all markdown files
section: Component groups
subsection: Status and state indicators
# Sidenav secondary level section
# should be the same for all markdown files
id: Culling information
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
id: Culling information
id: Status timestamp

Previously, (via this Miro), we considered renaming "culling info" to be "status timestamp" for easier understanding. I don't see a timestamp in the examples, though, so maybe the design has changed. Do we still want to do that?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option could be "Stale warning", "Expiration warning", "Stale data warning"

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not able to access the Miro link, but there isn't a timestamp. It is either a warning/danger tooltip or a warning/danger icon with a custom message. Is there a naming option you would prefer?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, let's go with "stale data warning" if that sounds okay with you? I also don't mind sticking with "culling" if that's more accurate, but if so I would suggest "culling warning" instead of "culling information"

# 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: ['CullingInformation']
sourceLink: https://github.com/patternfly/react-component-groups/blob/main/packages/module/patternfly-docs/content/extensions/component-groups/examples/CullingInfo/CullingInfo.md
---

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

A **culling information** component displays a warning for when an object will become culled or stale. It can display this as a tooltip or text.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A **culling information** component displays a warning for when an object will become culled or stale. It can display this as a tooltip or text.
A **culling information** component displays a warning status when an object is stale and planned for removal. Additional warning details can be displayed a tooltip or text label.

if we change the component name, update here as well


## Examples

### Basic culling information

A basic culling information example
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
A basic culling information example
A basic culling information component displays a warning icon with additional details in a tooltip, including a timeline for data removal.


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

```

### Culling information with customized props

For further customization, you can choose to render the tooltip as a message beside the icon instead. And you can utilize all properties of the [Tooltip component](/components/tooltip), with the exception of `content`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
For further customization, you can choose to render the tooltip as a message beside the icon instead. And you can utilize all properties of the [Tooltip component](/components/tooltip), with the exception of `content`.
Instead of sharing details in a tooltip, you can place a short message beside the icon instead. You can still utilize all properties of the PatternFly [tooltip component](/components/tooltip), with the exception of `content`.


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

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import React from 'react';
import CullingInformation from '@patternfly/react-component-groups/dist/dynamic/CullingInfo';


export const CustomizedRenderExample: React.FunctionComponent = () => {
const staleDate = new Date('Sun Jan 26 2020');
const warningDate = new Date('Mon Feb 03 2025');
const cullingDate = new Date('Fri Feb 07 2025');
return <>
<CullingInformation
stale={staleDate}
currDate={new Date()}
culled={cullingDate}
staleWarning={warningDate}
render={({ msg }) => (<React.Fragment>{msg} Hello there. Last seen: {` `}</React.Fragment>)}>
</CullingInformation>
</>
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import CullingInformation from '@patternfly/react-component-groups/dist/dynamic/CullingInfo';

export const BasicExample: React.FunctionComponent = () => {
const staleDate = new Date('Sun Jan 26 2020');
const warningDate = new Date('Mon Feb 03 2025');
const cullingDate = new Date('Fri Feb 07 2025');
return <>
<CullingInformation
stale={staleDate}
currDate={new Date()}
culled={cullingDate}
staleWarning={warningDate}>
</CullingInformation>
</>
};
93 changes: 93 additions & 0 deletions packages/module/src/CullingInfo/CullingInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import React from 'react';
import { ExclamationCircleIcon, ExclamationTriangleIcon } from '@patternfly/react-icons';
import { Tooltip, TooltipProps } from '@patternfly/react-core';
import clsx from 'clsx';
import { createUseStyles } from 'react-jss';
import { CullingDate, CullingInfo, calculateTooltip } from './CullingInfoUtils';

export type Render = (config: { msg: string }) => React.ReactElement<any, any> | null;

Check warning on line 8 in packages/module/src/CullingInfo/CullingInfo.tsx

View workflow job for this annotation

GitHub Actions / call-build-lint-test-workflow / lint

Unexpected any. Specify a different type

Check warning on line 8 in packages/module/src/CullingInfo/CullingInfo.tsx

View workflow job for this annotation

GitHub Actions / call-build-lint-test-workflow / lint

Unexpected any. Specify a different type

const useStyles = createUseStyles({
inventoryCullingWarning: {
color: 'var(--pf-v6-global--warning-color--200)',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, we should avoid using tokens which end with a number - those are called base tokens. Instead we should always be using semantic tokens which will adjust automatically in dark theme and be more context aware.

In this case, I believe the correct variable is --pf-t--global--icon--color--status--warning--default

And for the danger variant, it should be --pf-t--global--icon--color--status--danger--default

if those colors look incorrect, we could consult @kaylachumley for different tokens

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I replaced them with semantic tokens

fontWeight: 'var(--pf-v6-global--FontWeight--bold)',
svg: {
marginRight: 'var(--pf-v5-global--spacer--sm)'
}
},
inventoryCullingDanger: {
color: 'var(--pf-v6-global--warning-color--200)',
fontWeight: 'var(--pf-v6-global--FontWeight--bold)',
svg: {
marginRight: 'var(--pf-v6-global--spacer--sm)'
}
}
});

export interface CullingInformation extends Omit<TooltipProps, 'content'> {
/** Option to add custom css classes */
className?: string;
/** Warning date for when object becomes stale */
staleWarning: CullingDate;
/** Date when object becomes culled */
culled: CullingDate;
/** Date when object becomes stale */
stale: CullingDate;
/** Current date */
currDate: CullingDate;
/** Optional prop to add custom children */
children?: React.ReactElement<any, string | React.JSXElementConstructor<any>> | undefined;

Check warning on line 39 in packages/module/src/CullingInfo/CullingInfo.tsx

View workflow job for this annotation

GitHub Actions / call-build-lint-test-workflow / lint

Unexpected any. Specify a different type

Check warning on line 39 in packages/module/src/CullingInfo/CullingInfo.tsx

View workflow job for this annotation

GitHub Actions / call-build-lint-test-workflow / lint

Unexpected any. Specify a different type
/** Option to add custom message ReactElement */
render?: Render;
}

const CullingInformation: React.FunctionComponent<CullingInformation> = ({
culled = new Date(0),
className,
staleWarning = new Date(0),
stale = new Date(0),
currDate = new Date(0),
children,
render,
...props
}) => {
const classes = useStyles();

if (new Date(currDate).valueOf() - new Date(stale).valueOf() < 0) {
return render
? render({
msg: '',
})
: children || null;
}

const { isWarn, isError, msg }: CullingInfo = calculateTooltip(culled, staleWarning, currDate);
if (render) {
return (
<span
className={clsx({ [classes.inventoryCullingWarning]: isWarn, [classes.inventoryCullingDanger]: isError }, className)}
>
{isWarn && <ExclamationTriangleIcon className={classes.inventoryCullingWarning}/>}
{isError && <ExclamationCircleIcon />}
{render({ msg })}
</span>
);
}

return (
<React.Fragment>
<Tooltip {...props} content={msg} position="bottom">
<span
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From an accessibility perspective, tooltips need to be placed on interactable elements, such as links or buttons in order for them to able to be accessed by keyboard and screen readers.

Could these components be placed in a plain button, rather than a span?

<Button variant="plain" aria-label="Action" icon={<TimesIcon />} />

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these buttons be inside a span or div? Or would that still get in the way of screen readers?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The button could be anywhere! The key is just that there needs to be an element which can take browser focus and has some sort of affordance which cues the user that there is more information available if they interact with this icon.

className={clsx({ [classes.inventoryCullingWarning]: isWarn, [classes.inventoryCullingDanger]: isError }, className)}
>
{isError && <ExclamationCircleIcon />}
{isWarn && <ExclamationTriangleIcon />}
{children}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first example is still not screen reader accessible - and it may be true that the button needs to replace the span - or the Tooltip needs to be placed on the button within the span.

you can see that the first example is screen reader inaccessible in the video below. you can also see how the tooltip is not being triggered by the screenreader since I think the tooltip needs to be placed on the button itself rather than the span outside the button.

i recommend testing the examples by tabbing through them as a sanity check - if they are keyboard accessible, it's much more likely that it'll be screen reader accessible.

Jan-30-2025 17-27-38

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops I forgot to add the buttons to the tooltip version of the CullingInfo. I was able to tab through them after the fix.

</span>
</Tooltip>
</React.Fragment>
);
};

export default CullingInformation;

32 changes: 32 additions & 0 deletions packages/module/src/CullingInfo/CullingInfoUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export type CullingDate = string | number | Date;

export interface CullingInfo {
isWarn?: boolean;
isError?: boolean;
msg: string;
}
export type CalculateTooltip = (culled: CullingDate, warning: CullingDate, currDate: CullingDate) => CullingInfo;

export const seconds = 1000;
export const minutes: number = seconds * 60;
export const hours: number = minutes * 60;
export const days: number = hours * 24;

export const calculateTooltip: CalculateTooltip = (culled, warning, currDate) => {
const culledDate: Date = new Date(culled);
const warningDate: Date = new Date(warning);
const diffTime: number = new Date(currDate).valueOf() - warningDate.valueOf();
const removeIn: number = Math.ceil((culledDate.valueOf() - new Date(currDate).valueOf()) / days);
const msg = `System scheduled for inventory removal in ${removeIn} days`;
if (diffTime >= 0) {
return {
isError: true,
msg,
};
}

return {
isWarn: true,
msg,
};
};
2 changes: 2 additions & 0 deletions packages/module/src/CullingInfo/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from './CullingInfo';
export * from './CullingInfo';
3 changes: 3 additions & 0 deletions packages/module/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export * from './ErrorStack';
export { default as ErrorBoundary } from './ErrorBoundary';
export * from './ErrorBoundary';

export { default as CullingInfo } from './CullingInfo';
export * from './CullingInfo';

export { default as ColumnManagementModal } from './ColumnManagementModal';
export * from './ColumnManagementModal';

Expand Down
Loading