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,36 @@
import React from 'react';
import CullingInformation from '@patternfly/react-component-groups/dist/dynamic/CullingInfo';
import { Stack, StackItem } from '@patternfly/react-core';


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 <>
<Stack>
<StackItem>
<CullingInformation
stale={staleDate}
currDate={new Date()}
culled={cullingDate}
staleWarning={warningDate}
render={({ msg }) => (<React.Fragment>{msg}</React.Fragment>)}>
</CullingInformation>
</StackItem>

<StackItem>
<CullingInformation
stale={staleDate}
currDate={new Date()}
culled={new Date('Fri Feb 07 2024')}
staleWarning={new Date('Mon Feb 03 2024')}
render={() => (<React.Fragment>This is an error message. Item is past due</React.Fragment>)}>
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
render={() => (<React.Fragment>This is an error message. Item is past due</React.Fragment>)}>
render={() => (<React.Fragment>This is an error message where the item is overdue</React.Fragment>)}>

</CullingInformation>
</StackItem>
</Stack>



</>
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import CullingInformation from '@patternfly/react-component-groups/dist/dynamic/CullingInfo';
import { Stack, StackItem } from '@patternfly/react-core';

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 <>
<Stack>
<StackItem>
<CullingInformation
stale={staleDate}
currDate={new Date()}
culled={cullingDate}
staleWarning={warningDate}>
</CullingInformation>
</StackItem>

<StackItem>
<CullingInformation
stale={staleDate}
currDate={new Date()}
culled={new Date('Fri Feb 07 2024')}
staleWarning={new Date('Mon Feb 03 2024')}
>
</CullingInformation>
</StackItem>
</Stack>


</>
};
134 changes: 134 additions & 0 deletions packages/module/src/CullingInfo/CullingInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React from 'react';
import { ExclamationCircleIcon, ExclamationTriangleIcon } from '@patternfly/react-icons';
import { Button, Tooltip, TooltipProps } from '@patternfly/react-core';
import clsx from 'clsx';
import { createUseStyles } from 'react-jss';

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

Check warning on line 7 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 7 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
type CullingDate = string | number | Date;

interface CullingInfo {
isWarn?: boolean;
isError?: boolean;
msg: string;
}

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

type CalculateTooltip = (culled: CullingDate, warning: CullingDate, currDate: CullingDate) => CullingInfo;

const useStyles = createUseStyles({
inventoryCullingWarning: {
color: 'var(--pf-t--global--icon--color--status--warning--default)',
},
inventoryCullingDanger: {
color: 'var(--pf-t--global--icon--color--status--danger--default)',
},
iconMargin: {
marginRight: 'var(--pf-t--global--spacer--sm)'
},
messageFont: {
fontWeight: 'var(--pf-t--global--font--weight--body--bold)',
},
});

/** extends TooltipProps */
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 51 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 51 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;
/** Optional custom warning message */
message?: string;
}

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

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 = message ? message : `System scheduled for inventory removal in ${removeIn} days`;
if (diffTime >= 0) {
return {
isError: true,
msg,
};
}

return {
isWarn: true,
msg,
};
};

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 &&
<Button variant="plain" aria-label="Action" role="tooltip" icon={<ExclamationTriangleIcon className={clsx( classes.iconMargin, classes.inventoryCullingWarning )}/>} />
}
{isError &&
<Button variant="plain" aria-label="Action" role="tooltip" icon={<ExclamationCircleIcon className={clsx( classes.iconMargin, classes.inventoryCullingDanger )}/>} />
}
<span className={clsx( classes.messageFont )}>
{render({ msg })}
</span>

</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;

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