Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Feature: Enhanced Progress Control #1676

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
6b30fb9
Progress title + color attribute
namnguyen20999 Aug 17, 2024
58890d4
Add title anchor as none
namnguyen20999 Aug 17, 2024
f417fd8
Adding color property to Progress
namnguyen20999 Aug 21, 2024
57dba8e
Adding style apply to circular progress
namnguyen20999 Aug 21, 2024
49f3168
Merge branch 'develop' into 1626-add-dynamic-label-to-progress-visual…
namnguyen20999 Aug 21, 2024
f5b5a35
Merge branch 'develop' into 1626-add-dynamic-label-to-progress-visual…
namnguyen20999 Sep 4, 2024
9386508
remove sx from components
namnguyen20999 Sep 9, 2024
e06aed4
Merge branch 'develop' into 1626-add-dynamic-label-to-progress-visual…
namnguyen20999 Sep 9, 2024
7901097
update file to return CR
namnguyen20999 Sep 9, 2024
ccdf2b6
Merge branch '1626-add-dynamic-label-to-progress-visual-element' of g…
namnguyen20999 Sep 9, 2024
ba04767
Merge branch 'develop' into 1626-add-dynamic-label-to-progress-visual…
namnguyen20999 Sep 10, 2024
fbf6d70
Merge branch 'develop' into 1626-add-dynamic-label-to-progress-visual…
namnguyen20999 Sep 13, 2024
77e93cd
Merge branch 'develop' into 1626-add-dynamic-label-to-progress-visual…
namnguyen20999 Sep 16, 2024
bcd05ba
per Fab
namnguyen20999 Sep 16, 2024
0a114f4
update per Fred
namnguyen20999 Sep 16, 2024
a4f037e
Merge branch '1626-add-dynamic-label-to-progress-visual-element' of g…
namnguyen20999 Sep 16, 2024
3fb77d4
Merge branch 'develop' into 1626-add-dynamic-label-to-progress-visual…
namnguyen20999 Sep 16, 2024
270ed24
update per Fred & add more test cases
namnguyen20999 Sep 16, 2024
26c25ca
Merge branch '1626-add-dynamic-label-to-progress-visual-element' of g…
namnguyen20999 Sep 16, 2024
27dc808
Merge branch 'develop' into 1626-add-dynamic-label-to-progress-visual…
namnguyen20999 Sep 16, 2024
2cee9cb
typo
namnguyen20999 Sep 16, 2024
fcbedd2
Merge remote-tracking branch 'origin/1626-add-dynamic-label-to-progre…
namnguyen20999 Sep 16, 2024
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
93 changes: 91 additions & 2 deletions frontend/taipy-gui/src/components/Taipy/Progress.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@
* specific language governing permissions and limitations under the License.
*/

import React from "react";

import { render } from "@testing-library/react";
import "@testing-library/jest-dom";
import Progress, { getFlexDirection, getBoxWidth } from "./Progress";

import Progress from "./Progress";

describe("Progress component", () => {
it("renders circular progress without value (indeterminate)", () => {
Expand Down Expand Up @@ -48,4 +47,94 @@ describe("Progress component", () => {
expect(elt).toHaveClass("MuiLinearProgress-root");
expect(valueText).toBeInTheDocument();
});
it('should return null when render is false', async () => {
const { container } = render(<Progress render={false} />);
expect(container.firstChild).toBeNull();
});
it('should render the title when title is defined', () => {
const { getByText } = render(<Progress title="Title" />);
const title = getByText("Title");
expect(title).toBeInTheDocument();
})
it('renders Typography with correct sx and variant', () => {
const { getByText } = render(<Progress title="Title" />);
const typographyElement = getByText("Title");
expect(typographyElement).toBeInTheDocument();
expect(typographyElement).toHaveStyle('margin: 8px');
expect(typographyElement.tagName).toBe('SPAN');
});
it('renders determinate progress correctly', () => {
const { getByRole } = render(<Progress value={50} />);
const progressBar = getByRole('progressbar');
expect(progressBar).toBeInTheDocument();
expect(progressBar).toHaveAttribute('aria-valuenow', '50');
});
it('renders determinate progress with linear progress bar', () => {
const { getByRole } = render(<Progress value={50} linear />);
const progressBar = getByRole('progressbar');
expect(progressBar).toBeInTheDocument();
expect(progressBar).toHaveAttribute('aria-valuenow', '50');
})
it('renders title and linear progress bar correctly', () => {
const { getByText, getByRole } = render(<Progress title="Title" value={50} linear showValue={true} />);
const title = getByText("Title");
const progressBar = getByRole('progressbar');
expect(title).toBeInTheDocument();
expect(progressBar).toBeInTheDocument();
})
it('renders title and linear progress bar without showing value', () => {
const { getByText, queryByText } = render(<Progress title="Title" value={50} linear />);
const title = getByText("Title");
const value = queryByText("50%");
expect(title).toBeInTheDocument();
expect(value).toBeNull();
})
it('renders title and circular progress bar correctly', () => {
const { getByText, getByRole } = render(<Progress title="Title" value={50} showValue={true} />);
const title = getByText("Title");
const progressBar = getByRole('progressbar');
expect(title).toBeInTheDocument();
expect(progressBar).toBeInTheDocument();
})
});

describe('Progress functions', () => {
it('should return "column" when titleAnchor is "top"', () => {
expect(getFlexDirection("top")).toBe("column");
});

it('should return "column-reverse" when titleAnchor is "bottom"', () => {
expect(getFlexDirection("bottom")).toBe("column-reverse");
});

it('should return "row" when titleAnchor is "left"', () => {
expect(getFlexDirection("left")).toBe("row");
});

it('should return "row-reverse" when titleAnchor is "right"', () => {
expect(getFlexDirection("right")).toBe("row-reverse");
});

it('should return "row" when titleAnchor is not recognized', () => {
expect(getFlexDirection("unknown")).toBe("row");
});
it('should return "100%" when both title and titleAnchor are truthy', () => {
const result = getBoxWidth("Title", "Anchor");
expect(result).toBe("100%");
});

it('should return an empty string when title is truthy and titleAnchor is falsy', () => {
const result = getBoxWidth("Title", undefined);
expect(result).toBe("");
});

it('should return an empty string when title is falsy and titleAnchor is truthy', () => {
const result = getBoxWidth(undefined, "Anchor");
expect(result).toBe("");
});

it('should return an empty string when both title and titleAnchor are falsy', () => {
const result = getBoxWidth(undefined, undefined);
expect(result).toBe("");
});
});
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
119 changes: 91 additions & 28 deletions frontend/taipy-gui/src/components/Taipy/Progress.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ import { useClassNames, useDynamicProperty } from "../../utils/hooks";
import { TaipyBaseProps } from "./utils";

interface ProgressBarProps extends TaipyBaseProps {
linear?: boolean; //by default - false
showValue?: boolean; //by default - false
value?: number; //progress value
defaultValue?: number; //default progress value
color?: string;
linear?: boolean;
showValue?: boolean;
value?: number;
defaultValue?: number;
render?: boolean;
defaultRender?: boolean;
title?: string;
defaultTitle?: string;
titleAnchor?: "top" | "bottom" | "left" | "right" | "none";
}

const linearSx = { display: "flex", alignItems: "center" };
Expand All @@ -45,51 +49,110 @@ const circularPrgSx = {
justifyContent: "center",
};

export const getFlexDirection = (titleAnchor: string) => {
switch (titleAnchor) {
case "top":
return "column";
case "bottom":
return "column-reverse";
case "left":
return "row";
case "right":
return "row-reverse";
default:
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
return "row";
}
};

export const getBoxWidth = (title?: string, titleAnchor?: string) => (title && titleAnchor ? "100%" : "");

const Progress = (props: ProgressBarProps) => {
const { linear = false, showValue = false } = props;
const { linear = false, showValue = false, titleAnchor = "bottom" } = props;

const className = useClassNames(props.libClassName, props.dynamicClassName, props.className);
const value = useDynamicProperty(props.value, props.defaultValue, undefined, "number", true);
const render = useDynamicProperty(props.render, props.defaultRender, true);
const title = useDynamicProperty(props.title, props.defaultTitle, undefined);

if (!render) {
return null;
}

return showValue && value !== undefined ? (
linear ? (
<Box sx={linearSx} className={className} id={props.id}>
<Box sx={linearPrgSx}>
<LinearProgress variant="determinate" value={value} />
</Box>
<Box sx={linearTxtSx}>
<Typography variant="body2" color="text.secondary">{`${Math.round(value)}%`}</Typography>
<Box sx={{ ...linearSx, flexDirection: getFlexDirection(titleAnchor) }}>
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
{title && titleAnchor !== "none" ? (
<Typography sx={{ margin: 1 }} variant="caption">
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
{title}
</Typography>
) : null}
<Box sx={{ ...linearSx, width: getBoxWidth(title, titleAnchor) }} className={className} id={props.id}>
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
<Box sx={linearPrgSx}>
<LinearProgress variant="determinate" value={value} />
</Box>
<Box sx={linearTxtSx}>
<Typography variant="body2" color="text.secondary">{`${Math.round(value)}%`}</Typography>
</Box>
</Box>
</Box>
) : (
<Box sx={circularSx} className={className} id={props.id}>
<CircularProgress variant="determinate" value={value} />
<Box sx={circularPrgSx}>
<Typography variant="caption" component="div" color="text.secondary">
{`${Math.round(value)}%`}
<Box
sx={{
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
...circularSx,
flexDirection: getFlexDirection(titleAnchor),
alignItems: title && titleAnchor ? "center" : "",
}}
>
{title && titleAnchor !== "none" ? (
<Typography sx={{ margin: 1 }} variant="caption">
{title}
</Typography>
) : null}
<Box sx={{ ...circularSx, width: getBoxWidth(title, titleAnchor) }} className={className} id={props.id}>
<CircularProgress variant="determinate" value={value} />
<Box sx={circularPrgSx}>
<Typography variant="caption" component="div" color="text.secondary">
{`${Math.round(value)}%`}
</Typography>
</Box>
</Box>
</Box>
)
) : linear ? (
<LinearProgress
id={props.id}
variant={value === undefined ? "indeterminate" : "determinate"}
value={value}
className={className}
/>
<Box sx={{ ...linearSx, flexDirection: getFlexDirection(titleAnchor) }}>
{title && titleAnchor !== "none" ? (
<Typography sx={{ margin: 1 }} variant="caption">
{title}
</Typography>
) : null}
<LinearProgress
id={props.id}
sx={{ width: "100%" }}
variant={value === undefined ? "indeterminate" : "determinate"}
value={value}
className={className}
/>
</Box>
) : (
<CircularProgress
id={props.id}
variant={value === undefined ? "indeterminate" : "determinate"}
value={value}
className={className}
/>
<Box
sx={{
...circularSx,
flexDirection: getFlexDirection(titleAnchor),
alignItems: title && titleAnchor ? "center" : "",
}}
>
{title && titleAnchor !== "none" ? (
<Typography sx={{ margin: 1 }} variant="caption">
{title}
</Typography>
) : null}
<CircularProgress
id={props.id}
variant={value === undefined ? "indeterminate" : "determinate"}
value={value}
className={className}
/>
</Box>
);
};

Expand Down
3 changes: 3 additions & 0 deletions taipy/gui/_renderers/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,8 +608,11 @@ class _Factory:
.set_value_and_default(var_type=PropertyType.dynamic_number, native_type=True)
.set_attributes(
[
("color", PropertyType.string),
("linear", PropertyType.boolean, False),
("show_value", PropertyType.boolean, False),
("title", PropertyType.dynamic_string),
("title_anchor", PropertyType.string, "bottom"),
("render", PropertyType.dynamic_boolean, True),
]
)
Expand Down
16 changes: 16 additions & 0 deletions taipy/gui/viselements.json
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,11 @@
"doc": "If set, then the value represents the progress percentage that is shown.TODO - if unset?",
"default_property": true
},
{
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
"name": "color",
"type": "str",
"doc": "The color of the progress indicator."
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
},
{
"name": "linear",
"type": "bool",
Expand All @@ -1353,6 +1358,17 @@
"default_value": "False",
"doc": "If set to True, the progress value is shown."
},
{
"name": "title",
"type": "dynamic(str)",
"doc": "The title of the progress indicator."
namnguyen20999 marked this conversation as resolved.
Show resolved Hide resolved
},
{
"name": "title_anchor",
"type": "str",
"default_value": "\"bottom\"",
"doc": "The anchor of the title."
},
{
"name": "render",
"type": "dynamic(bool)",
Expand Down
Loading