From a113604cb1cd65b79e2609d9796abbfba6583715 Mon Sep 17 00:00:00 2001 From: Daniel Karski Date: Fri, 6 Dec 2024 10:31:52 +0100 Subject: [PATCH] [CP-3325] Memory Usage Bar Component developed --- libs/generic-view/models/src/index.ts | 3 ++ .../models/src/lib/segment-bar.ts | 30 +++++++++++ libs/generic-view/ui/src/index.ts | 3 ++ .../compute-segment-bar-items.helper.test.ts | 16 ++++-- .../compute-segment-bar-items.helper.ts | 18 +++---- .../ui/src/lib/segment-bar/index.ts | 11 ++++ .../src/lib/segment-bar/segment-bar-item.tsx | 42 ++++++++++++++++ .../ui/src/lib/segment-bar/segment-bar.tsx | 50 +++++++++++++++++++ .../segment-bar/use-container-width.hook.ts | 19 +++++++ 9 files changed, 176 insertions(+), 16 deletions(-) create mode 100644 libs/generic-view/models/src/lib/segment-bar.ts create mode 100644 libs/generic-view/ui/src/lib/segment-bar/index.ts create mode 100644 libs/generic-view/ui/src/lib/segment-bar/segment-bar-item.tsx create mode 100644 libs/generic-view/ui/src/lib/segment-bar/segment-bar.tsx create mode 100644 libs/generic-view/ui/src/lib/segment-bar/use-container-width.hook.ts diff --git a/libs/generic-view/models/src/index.ts b/libs/generic-view/models/src/index.ts index a34ab27a13..680015d838 100644 --- a/libs/generic-view/models/src/index.ts +++ b/libs/generic-view/models/src/index.ts @@ -35,6 +35,7 @@ import { formRadioInput } from "./lib/form-radio-input" import { formCheckboxInput } from "./lib/form-checkbox-input" import { tooltip } from "./lib/tooltip" import { progressBar } from "./lib/progress-bar" +import { segmentBar } from "./lib/segment-bar" import { tooltipAnchor } from "./lib/tooltip-anchor" import { overviewOsVersion } from "./lib/overview-os-version" import { tooltipContent } from "./lib/tooltip-content" @@ -103,6 +104,7 @@ export * from "./lib/form-search-input" export * from "./lib/form-search-input-results" export * from "./lib/form-checkbox-input" export * from "./lib/progress-bar" +export * from "./lib/segment-bar" export * from "./lib/tooltip" export * from "./lib/tooltip-anchor" export * from "./lib/tooltip-content" @@ -169,6 +171,7 @@ export default { [formSearchInputResults.key]: formSearchInputResults, [formCheckboxInput.key]: formCheckboxInput, [progressBar.key]: progressBar, + [segmentBar.key]: segmentBar, [tooltip.key]: tooltip, [tooltipAnchor.key]: tooltipAnchor, [tooltipContent.key]: tooltipContent, diff --git a/libs/generic-view/models/src/lib/segment-bar.ts b/libs/generic-view/models/src/lib/segment-bar.ts new file mode 100644 index 0000000000..eb606e167f --- /dev/null +++ b/libs/generic-view/models/src/lib/segment-bar.ts @@ -0,0 +1,30 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import { z } from "zod" + +const dataValidator = z.undefined() + +const segmentBarItemSchema = z.object({ + color: z.string(), + label: z.string(), + value: z.number(), + minWidth: z.number(), +}) + +export type SegmentBarItem = z.infer + +const configValidator = z.object({ + segments: z.array(segmentBarItemSchema), + segmentBorderRadius: z.string().optional(), +}) + +export type SegmentBarConfig = z.infer + +export const segmentBar = { + key: "segment-bar", + dataValidator, + configValidator, +} as const diff --git a/libs/generic-view/ui/src/index.ts b/libs/generic-view/ui/src/index.ts index dd847179a5..abac7d3527 100644 --- a/libs/generic-view/ui/src/index.ts +++ b/libs/generic-view/ui/src/index.ts @@ -6,6 +6,7 @@ import { blocks } from "./lib/blocks/blocks" import { rows } from "./lib/data-rows/data-rows" import { predefinedComponents } from "./lib/predefined/predefined" +import { segmentBar } from "./lib/segment-bar" import { helpers } from "./lib/helpers/helpers" import { interactive } from "./lib/interactive/interactive" import { labels } from "./lib/labels" @@ -31,6 +32,7 @@ export * from "./lib/predefined/backup-restore/backup-restore-error" export * from "./lib/predefined/import-contacts/import-contacts-error" export * from "./lib/predefined/data-migration/components/transfer-error-modal" export { DataMigrationPage } from "./lib/predefined/data-migration/data-migration" +export * from "./lib/segment-bar" export * from "./lib/buttons/button-text" export * from "./lib/buttons/button-primary" export * from "./lib/texts/paragraphs" @@ -40,6 +42,7 @@ export * from "./lib/entities" const apiComponents = { ...predefinedComponents, + ...segmentBar, ...blocks, ...rows, ...helpers, diff --git a/libs/generic-view/ui/src/lib/segment-bar/compute-segment-bar-items.helper.test.ts b/libs/generic-view/ui/src/lib/segment-bar/compute-segment-bar-items.helper.test.ts index 2b961e034d..880a086ec3 100644 --- a/libs/generic-view/ui/src/lib/segment-bar/compute-segment-bar-items.helper.test.ts +++ b/libs/generic-view/ui/src/lib/segment-bar/compute-segment-bar-items.helper.test.ts @@ -3,10 +3,8 @@ * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md */ -import { - computeSegmentBarItems, - SegmentBarItem, -} from "./compute-segment-bar-items.helper" +import { SegmentBarItem } from "generic-view/models" +import { computeSegmentBarItems } from "./compute-segment-bar-items.helper" describe("Single segments", () => { test("handles a single segment without minWidth", () => { @@ -148,6 +146,16 @@ describe("Multiple segments - with minWidth", () => { }) describe("Edge cases", () => { + test("returns an empty array when containerWidth is 0", () => { + const segments: SegmentBarItem[] = [ + { color: "#000", label: "Segment 1", value: 100, minWidth: 50 }, + { color: "#111", label: "Segment 2", value: 50, minWidth: 25 }, + ] + const result = computeSegmentBarItems(segments, 0) + + expect(result).toEqual([]) + }) + test("ignores segments with value = 0", () => { const segments: SegmentBarItem[] = [ { color: "#000", label: "Segment 1", value: 0, minWidth: 50 }, diff --git a/libs/generic-view/ui/src/lib/segment-bar/compute-segment-bar-items.helper.ts b/libs/generic-view/ui/src/lib/segment-bar/compute-segment-bar-items.helper.ts index 6856cc334f..880fbd74e2 100644 --- a/libs/generic-view/ui/src/lib/segment-bar/compute-segment-bar-items.helper.ts +++ b/libs/generic-view/ui/src/lib/segment-bar/compute-segment-bar-items.helper.ts @@ -3,27 +3,18 @@ * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md */ -export interface SegmentBarItem { - color: string - label: string - value: number - minWidth: number -} +import { SegmentBarItem } from "generic-view/models" interface AdjustedSegment extends SegmentBarItem { usesMinWidth: boolean } -interface ComputedSegmentBarItem extends SegmentBarItem { +export interface ComputedSegmentBarItem extends SegmentBarItem { width: string left: string zIndex: number } -export interface SegmentBarProps { - segments: SegmentBarItem[] -} - const calculateTotalSegmentValue = (segments: SegmentBarItem[]): number => segments.reduce((sum, segment) => sum + segment.value, 0) @@ -81,7 +72,6 @@ const calculateWidth = ( : (segment.value / remainingSegmentValue) * remainingWidth } - const calculateSegmentPositions = ( segments: AdjustedSegment[], totalSegmentValue: number, @@ -128,6 +118,10 @@ export const computeSegmentBarItems = ( segments: SegmentBarItem[], containerWidth: number ): ComputedSegmentBarItem[] => { + if(containerWidth === 0) { + return [] + } + const filteredSegments = segments.filter((segment) => segment.value > 0) const totalSegmentValue = calculateTotalSegmentValue(filteredSegments) diff --git a/libs/generic-view/ui/src/lib/segment-bar/index.ts b/libs/generic-view/ui/src/lib/segment-bar/index.ts new file mode 100644 index 0000000000..76e7261901 --- /dev/null +++ b/libs/generic-view/ui/src/lib/segment-bar/index.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import { segmentBar as segmentBarWrapper } from "generic-view/models" +import { SegmentBar } from "./segment-bar" + +export const segmentBar = { + [segmentBarWrapper.key]: SegmentBar, +} diff --git a/libs/generic-view/ui/src/lib/segment-bar/segment-bar-item.tsx b/libs/generic-view/ui/src/lib/segment-bar/segment-bar-item.tsx new file mode 100644 index 0000000000..d9374172ba --- /dev/null +++ b/libs/generic-view/ui/src/lib/segment-bar/segment-bar-item.tsx @@ -0,0 +1,42 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import React from "react" +import styled from "styled-components" +import { ComputedSegmentBarItem } from "./compute-segment-bar-items.helper" +import { BaseGenericComponent } from "generic-view/utils" + +interface SegmentBarItemProps extends ComputedSegmentBarItem { + borderRadius: string + isFirst: boolean +} + +export const SegmentBarItem: BaseGenericComponent< + undefined, + undefined, + SegmentBarItemProps +> = React.memo(({ color, width, left, zIndex, label, ...props }) => ( + +)) + +const Wrapper = styled.div<{ + borderRadius: string + isFirst: boolean +}>` + position: absolute; + height: 100%; + border-radius: ${(props) => + props.isFirst + ? `${props.borderRadius}` + : `0 ${props.borderRadius} ${props.borderRadius} 0`}; +` diff --git a/libs/generic-view/ui/src/lib/segment-bar/segment-bar.tsx b/libs/generic-view/ui/src/lib/segment-bar/segment-bar.tsx new file mode 100644 index 0000000000..a4f26a7278 --- /dev/null +++ b/libs/generic-view/ui/src/lib/segment-bar/segment-bar.tsx @@ -0,0 +1,50 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import React from "react" +import styled from "styled-components" +import { APIFC } from "generic-view/utils" +import { SegmentBarConfig } from "generic-view/models" +import { computeSegmentBarItems } from "./compute-segment-bar-items.helper" +import { SegmentBarItem } from "./segment-bar-item" +import { useContainerWidth } from "./use-container-width.hook" + +export const SegmentBar: APIFC = ({ + config, + data, + ...props +}) => { + const { ref, containerWidth } = useContainerWidth() + + const computeSegments = React.useCallback(() => { + return computeSegmentBarItems(config.segments, containerWidth) + }, [config.segments, containerWidth]) + + const computedSegments = computeSegments() + + const segmentBorderRadius = config.segmentBorderRadius || "56px" + + return ( + + {computedSegments.map((segment, index) => ( + + ))} + + ) +} + +const Wrapper = styled.div<{ + width: string + height: string +}>` + position: relative; + width: ${(props) => props.width}; + height: ${(props) => props.height}; +` diff --git a/libs/generic-view/ui/src/lib/segment-bar/use-container-width.hook.ts b/libs/generic-view/ui/src/lib/segment-bar/use-container-width.hook.ts new file mode 100644 index 0000000000..c00b1f060f --- /dev/null +++ b/libs/generic-view/ui/src/lib/segment-bar/use-container-width.hook.ts @@ -0,0 +1,19 @@ +/** + * Copyright (c) Mudita sp. z o.o. All rights reserved. + * For licensing, see https://github.com/mudita/mudita-center/blob/master/LICENSE.md + */ + +import { useLayoutEffect, useRef, useState } from "react" + +export const useContainerWidth = () => { + const ref = useRef(null) + const [containerWidth, setContainerWidth] = useState(0) + + useLayoutEffect(() => { + if (ref.current) { + setContainerWidth(ref.current.offsetWidth) + } + }, [ref]) + + return { ref, containerWidth } +}