Skip to content

Commit

Permalink
Merge pull request #112 from pegasystems/feature/meter
Browse files Browse the repository at this point in the history
add support for the meter component
  • Loading branch information
ricmars authored Jan 3, 2025
2 parents 9fbb11f + 4e4d32c commit 63ab34d
Show file tree
Hide file tree
Showing 10 changed files with 675 additions and 0 deletions.
Binary file added .storybook/static/Meter_Configuration.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions src/Support.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@ When adding new components, make sure that:
- The component follow [best practices](https://pegasystems.github.io/constellation-ui-gallery/?path=/docs/getting-started--docs#implementing-a-constellation-dx-component).
- The component props are exposed as an interface type object with description and default values so that the table of properties is documented in storybook.
- Make sure that all files are linted correctly (npm run lint and npm run fix).
- Make sure that all styles follow best practices (no use for px, no hardcoded colors...)
- Make sure that you have a unit test.
- Make sure that end to end and accessibility test is passing (see [README.md](https://github.com/pegasystems/constellation-ui-gallery/blob/master/README.md#end-to-end-testing) for instructions).
- If using a 3rd party library, make sure that the library is open source and compatible with the React version and the Constellation component library - see [Library](https://pegasystems.github.io/constellation-ui-gallery/?path=/docs/libraries--docs).
- You are not using a 3rd party presentational component library like Material UI - All the components should use the Constellation presentational components.
- Do not include 3rd party icon library - there should be enough OOB icons in the Constellation presentational component library.
- Test your component with different themes using the Storybook toolbar - the theme branding color and other tokens should be correctly handled by your components
- The for right to left (rtl) using the Storybook toolbar.

Once your code is ready, create a PR to the main branch using the following [procedure](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork).
Send an email to [email protected] with a link to the github issue.
32 changes: 32 additions & 0 deletions src/components/Pega_Extensions_Meter/Docs.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Meta, Primary, Controls, Story } from '@storybook/blocks';
import * as DemoStories from './demo.stories';

<Meta of={DemoStories} />

# Overview

Meters are visual representations of a quantity or an achievement. Their progress is determined by user actions, rather than system actions. This component will use a percentage field type to render the value between 0 and 100. If the value that you want to render is an integer, create a declare expression to generate a percentage value.

The meter should not be used to indicate progress, such as loading or percent completion of a task. To communicate progress, use the Loading component in the presentational library.

<Primary />

## Props

<Controls />

## Samples

Example of using a multiple meter to display test coverage

<Story of={DemoStories.Grouped} />

Example of using a multiple meter to display the storage capacity

<Story of={DemoStories.Grouped1} />

## Configuration

Here is an example of using the meter in different views (Case Summary, Details and form view)

![Configuration](Meter_Configuration.jpg)
66 changes: 66 additions & 0 deletions src/components/Pega_Extensions_Meter/ElemDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import styled, { css, useTheme } from 'styled-components';
import { type Event } from './index';
import { readableColor, themeDefinition } from '@pega/cosmos-react-core';
import { rgba } from 'polished';

type ElemDisplayProps = {
type?: string;
event: Event;
totalValue?: number;
};

export const StyledElementDisplay = styled.div(
({
textColor,
valueColor,
theme
}: {
textColor: string;
valueColor: string;
theme: typeof themeDefinition;
}) => {
const {
base: { spacing }
} = theme;
return css`
display: inline-flex;
align-items: center;
gap: ${spacing};
& > i {
background-color: ${valueColor};
width: ${spacing};
height: ${spacing};
border-radius: 50%;
}
& > span {
color: ${textColor};
font-size: 0.75rem;
}
`;
}
);

const ElemDisplay = (props: ElemDisplayProps) => {
const { type, event, totalValue } = props;
const theme = useTheme();
const Element = type === 'li' ? 'li' : 'div';

const textColor = rgba(
readableColor(theme.base.palette['primary-background']),
theme.base.transparency['transparent-3']
);

const labelMsg = `${event.label} (${
totalValue && totalValue !== 100
? `${Math.floor((event.value * totalValue) / 100.0)} of ${totalValue}`
: `${Math.floor(event.value)}%`
})`;

return (
<StyledElementDisplay as={Element} textColor={textColor} valueColor={event.color} aria-hidden>
<i />
<span>{labelMsg}</span>
</StyledElementDisplay>
);
};
export default ElemDisplay;
42 changes: 42 additions & 0 deletions src/components/Pega_Extensions_Meter/ElemMeter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Tooltip, useElement } from '@pega/cosmos-react-core';
import { type Event } from './index';
import styled, { css } from 'styled-components';

type ElemMeterProps = {
id?: string;
event: Event;
totalValue?: number;
};

export const StyledElementMeter = styled.div(({ event }: { event: Event }) => {
return css`
background-color: ${event.color};
width: ${event.value}%;
`;
});

const ElemMeter = (props: ElemMeterProps) => {
const { event, totalValue } = props;
const [el, setEl] = useElement();
const labelMsg =
totalValue && totalValue !== 100
? `${Math.floor((event.value * totalValue) / 100.0)} of ${totalValue}`
: `${Math.floor(event.value)}%`;
return (
<>
<StyledElementMeter
id={props.id}
event={event}
ref={setEl}
role='meter'
aria-label={event.label}
aria-valuenow={event.value}
aria-valuemin={0}
aria-valuemax={100}
aria-valuetext={labelMsg}
/>
{el && <Tooltip target={el}>{`${event.label} (${labelMsg})`}</Tooltip>}
</>
);
};
export default ElemMeter;
60 changes: 60 additions & 0 deletions src/components/Pega_Extensions_Meter/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"name": "Pega_Extensions_Meter",
"label": "Meter",
"description": "Meter",
"organization": "Pega",
"version": "1.0.0",
"library": "Extensions",
"allowedApplications": [],
"componentKey": "Pega_Extensions_Meter",
"type": "Field",
"subtype": "Decimal-Percentage",
"properties": [
{
"name": "label",
"label": "Field label",
"format": "TEXT",
"required": true
},
{
"name": "additionalInfo",
"label": "Additional information",
"format": "TEXT"
},
{
"name": "dataPage",
"label": "Data Page name used to source the data",
"format": "TEXT"
},
{
"name": "showMetaData",
"label": "Show the meta data on the screen",
"format": "BOOLEAN"
},
{
"name": "totalTasks",
"label": "Total tasks",
"format": "NUMBER"
},
{
"name": "color",
"label": "Color or JSON string",
"format": "TEXT"
},
{
"label": "Conditions",
"format": "GROUP",
"properties": [
{
"name": "visibility",
"label": "Visibility",
"format": "VISIBILITY"
}
]
}
],
"defaultConfig": {
"label": "@L $this.label",
"detailFVLItem": true
}
}
165 changes: 165 additions & 0 deletions src/components/Pega_Extensions_Meter/demo.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import type { StoryObj } from '@storybook/react';
import { PegaExtensionsMeter, type MeterProps } from './index';

export default {
title: 'Fields/Meter',
argTypes: {
value: {
control: { type: 'number', min: 0, max: 100, step: 1 }
},
fieldMetadata: {
table: {
disable: true
}
},
displayMode: {
options: ['EDITABLE', 'LABELS_LEFT', 'DISPLAY_ONLY'],
control: { type: 'radio' }
},
variant: {
table: {
disable: true
}
},
getPConnect: {
table: {
disable: true
}
}
},
component: PegaExtensionsMeter
};

const setPCore = () => {
(window as any).PCore = {
getConstants: () => {
return {
CASE_INFO: {
CASE_INFO_ID: 'ID'
}
};
},
getComponentsRegistry: () => {
return {
getLazyComponent: (f: string) => f
};
},
getEnvironmentInfo: () => {
return {
getTimeZone: () => 'local'
};
},
getDataApiUtils: () => {
return {
getData: (dataPage: string) => {
if (dataPage === 'test') {
return Promise.resolve({
data: {
data: [
{ label: 'Pass', color: '#34a35d', value: 16 },
{ label: 'Failed', color: '#fb243d', value: 3 },
{ label: 'Blocked', color: '#f5fa60', value: 6 },
{ label: 'Not Executed', color: '#c084fc', value: 10 }
]
}
});
}
return Promise.resolve({
data: {
data: [
{ label: 'Apps', color: '#34d399', value: 16 },
{ label: 'Messages', color: '#fbbf24', value: 8 },
{ label: 'Media', color: '#60a5fa', value: 24 },
{ label: 'System', color: '#c084fc', value: 10 }
]
}
});
}
};
}
};
};

type Story = StoryObj<typeof PegaExtensionsMeter>;

const MeterDemo = (inputs: MeterProps) => {
return {
render: (args: MeterProps) => {
setPCore();
const props = {
...args,
getPConnect: () => {
return {
getValue: () => 'C-123',
getContextName: () => '',
getStateProps: () => {
return {
value: 'C-123'
};
},
getActionsApi: () => {
return {
openWorkByHandle: () => {
/* nothing */
},
createWork: () => {
/* nothing */
},
updateFieldValue: () => {
/* nothing */
},
triggerFieldChange: () => {
/* nothing */
},
showCasePreview: () => {
/* nothing */
}
};
},
ignoreSuggestion: () => {
/* nothing */
},
acceptSuggestion: () => {
/* nothing */
},
setInheritedProps: () => {
/* nothing */
},
resolveConfigProps: () => {
/* nothing */
}
};
}
};
return <PegaExtensionsMeter {...props} />;
},
args: inputs
};
};

export const Default: Story = MeterDemo({
displayMode: 'EDITABLE',
value: 50,
dataPage: '',
label: 'Tasks completed',
hideLabel: false,
color: '#34d399',
additionalInfo: 'Number of tasks completed out of total tasks',
showMetaData: false,
totalTasks: 10
});

export const Grouped: Story = MeterDemo({
dataPage: 'test',
label: 'Test coverage',
showMetaData: true,
displayMode: 'EDITABLE'
});

export const Grouped1: Story = MeterDemo({
dataPage: 'storage',
label: 'System storage',
showMetaData: true,
totalTasks: 100,
displayMode: 'EDITABLE'
});
10 changes: 10 additions & 0 deletions src/components/Pega_Extensions_Meter/demo.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { render, screen } from '@testing-library/react';
import { composeStories } from '@storybook/react';
import * as DemoStories from './demo.stories';

const { Default } = composeStories(DemoStories);

test('renders meter component with default args', async () => {
render(<Default />);
expect(await screen.findByText('Tasks completed')).toBeVisible();
});
Loading

0 comments on commit 63ab34d

Please sign in to comment.