diff --git a/package.json b/package.json index 18bb0484d..0c312eeb8 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,12 @@ "storybook": "storybook dev -p 6006", "start": "npm run storybook", "build-storybook": "storybook build", - "build:dev": "webpack --config webpack.dev.ts", - "build:dev:jetbrains": "webpack --config webpack.dev.ts --env platform=JetBrains", + "build:dev": "webpack --progress --config webpack.dev.ts", + "build:dev:jetbrains": "webpack --progress --config webpack.dev.ts --env platform=JetBrains", "build:dev:web": "webpack --config webpack.dev.ts --env platform=Web", - "build:prod": "webpack --config webpack.prod.ts", - "build:prod:jetbrains": "webpack --config webpack.prod.ts --env platform=JetBrains", - "build:prod:web": "webpack --config webpack.prod.ts --env platform=Web", + "build:prod": "webpack --progress --config webpack.prod.ts", + "build:prod:jetbrains": "webpack --progress --config webpack.prod.ts --env platform=JetBrains", + "build:prod:web": "webpack --progress --config webpack.prod.ts --env platform=Web", "precommit": "lint-staged", "prepare": "husky" }, diff --git a/src/components/Dashboard/MetricsReport/Chart/index.tsx b/src/components/Dashboard/MetricsReport/Chart/index.tsx index a744eec28..5a0c952ec 100644 --- a/src/components/Dashboard/MetricsReport/Chart/index.tsx +++ b/src/components/Dashboard/MetricsReport/Chart/index.tsx @@ -1,4 +1,6 @@ +import { UIEvent, useEffect, useState } from "react"; import useDimensions from "react-cool-dimensions"; +import useScrollbarSize from "react-scrollbar-size"; import { Input } from "squarify"; import { sendUserActionTrackingEvent } from "../../../../utils/actions/sendUserActionTrackingEvent"; import { TreeMap } from "../../../common/TreeMap"; @@ -16,7 +18,21 @@ export const Chart = ({ timeMode, viewLevel }: ChartProps) => { - const { width, height, observe } = useDimensions(); + const { width, height, entry, observe } = useDimensions(); + const [isLeftOverlayVisible, setIsLeftOverlayVisible] = useState(false); + const [isRightOverlayVisible, setIsRightOverlayVisible] = useState(false); + const scrollbar = useScrollbarSize(); + + useEffect(() => { + if (entry) { + setIsLeftOverlayVisible(entry.target.scrollLeft > 0); + + setIsRightOverlayVisible( + entry.target.scrollWidth > width && + entry.target.scrollLeft + width !== entry.target.scrollWidth + ); + } + }, [width, entry]); const chartData: Input[] = data.map((x) => { const score = x.score; @@ -56,9 +72,39 @@ export const Chart = ({ }; }); + const handleContainerScroll = (e: UIEvent) => { + setIsLeftOverlayVisible(e.currentTarget.scrollLeft > 0); + setIsRightOverlayVisible( + e.currentTarget.scrollWidth > width && + e.currentTarget.scrollLeft + e.currentTarget.clientWidth !== + e.currentTarget.scrollWidth + ); + }; + return ( - - + + + + + + ); }; diff --git a/src/components/Dashboard/MetricsReport/Chart/styles.ts b/src/components/Dashboard/MetricsReport/Chart/styles.ts index cb5735fd4..0fcdf38e9 100644 --- a/src/components/Dashboard/MetricsReport/Chart/styles.ts +++ b/src/components/Dashboard/MetricsReport/Chart/styles.ts @@ -1,6 +1,39 @@ -import styled from "styled-components"; +import styled, { css } from "styled-components"; export const Container = styled.div` width: 100%; height: 100%; + position: relative; +`; + +export const ContentContainer = styled.div` + width: 100%; + height: 100%; + overflow-x: auto; +`; + +export const Overlay = styled.div<{ + $placement: "left" | "right"; + $visible: boolean; +}>` + position: absolute; + top: 0; + bottom: 0; + ${({ $placement }) => + $placement === "left" + ? css` + left: 0; + ` + : css` + right: 0; + `} + width: 70px; + background: linear-gradient( + ${({ $placement }) => ($placement === "left" ? "90" : "270")}deg, + #1a1b1e 0%, + rgb(26 27 30 / 0%) 100% + ); + opacity: ${({ $visible }) => ($visible ? 1 : 0)}; + transition: opacity 300ms; + pointer-events: none; `; diff --git a/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/types.ts b/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/types.ts index 88cfa2e69..5001d62e5 100644 --- a/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/types.ts +++ b/src/components/Documentation/pages/EnvironmentTypes/EnvironmentTypeCard/types.ts @@ -1,3 +1,4 @@ +import { MemoExoticComponent } from "react"; import { ThemeableIconProps } from "../../../../common/icons/types"; import { InsightCardType } from "../InsightCard/types"; @@ -5,7 +6,7 @@ export type EnvironmentStatus = "active" | "waiting-for-data"; export interface EnvironmentTypeCardProps { name: string; - icon: React.MemoExoticComponent< + icon: MemoExoticComponent< (props: ThemeableIconProps & { height: number }) => JSX.Element >; description?: string; diff --git a/src/components/Documentation/pages/EnvironmentTypes/InsightCard/types.ts b/src/components/Documentation/pages/EnvironmentTypes/InsightCard/types.ts index a5ad9da6a..c1d66fcda 100644 --- a/src/components/Documentation/pages/EnvironmentTypes/InsightCard/types.ts +++ b/src/components/Documentation/pages/EnvironmentTypes/InsightCard/types.ts @@ -1,3 +1,4 @@ +import { MemoExoticComponent } from "react"; import { IconProps } from "../../../../common/icons/types"; export interface InsightCardProps { @@ -15,7 +16,7 @@ export interface CountChipProps { } export interface InsightCardTypeData { - icon?: React.MemoExoticComponent<(props: IconProps) => JSX.Element>; + icon?: MemoExoticComponent<(props: IconProps) => JSX.Element>; name: string; description: string; } diff --git a/src/components/Documentation/pages/EnvironmentTypes/types.ts b/src/components/Documentation/pages/EnvironmentTypes/types.ts index 6361b72a5..dee6e3db2 100644 --- a/src/components/Documentation/pages/EnvironmentTypes/types.ts +++ b/src/components/Documentation/pages/EnvironmentTypes/types.ts @@ -1,10 +1,11 @@ +import { MemoExoticComponent } from "react"; import { ThemeableIconProps } from "../../../common/icons/types"; import { EnvironmentStatus } from "./EnvironmentTypeCard/types"; import { InsightCardType } from "./InsightCard/types"; export interface EnvironmentTypeData { id: string; - icon: React.MemoExoticComponent< + icon: MemoExoticComponent< (props: ThemeableIconProps & { height: number }) => JSX.Element >; name: string; diff --git a/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx b/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx index fea7db066..ff6cb3a5b 100644 --- a/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx +++ b/src/components/Errors/NewErrorCard/OccurrenceChart/index.tsx @@ -1,5 +1,5 @@ import { format } from "date-fns"; -import { useEffect, useMemo, useState } from "react"; +import { SVGProps, useEffect, useMemo, useState } from "react"; import { Bar, BarChart, @@ -45,18 +45,18 @@ export const OccurrenceChart = ({ const { environment } = useConfigSelector(); const environmentId = environment?.id; - const tickLabelStyles: React.SVGProps = { + const tickLabelStyles: SVGProps = { fill: theme.colors.v3.text.secondary, opacity: 0.5 }; - const YAxisTickLabelStyles: React.SVGProps = { + const YAxisTickLabelStyles: SVGProps = { ...tickLabelStyles, fontSize: theme.typographies.footNote.fontSize, fontWeight: theme.typographies.footNote.fontWeight.regular }; - const XAxisTickLabelStyles: React.SVGProps = { + const XAxisTickLabelStyles: SVGProps = { ...tickLabelStyles, fontSize: theme.typographies.captionOne.fontSize, fontWeight: theme.typographies.captionOne.fontWeight.regular diff --git a/src/components/Highlights/EmptyStateCard/types.ts b/src/components/Highlights/EmptyStateCard/types.ts index 422c7b80c..ec0b51cb7 100644 --- a/src/components/Highlights/EmptyStateCard/types.ts +++ b/src/components/Highlights/EmptyStateCard/types.ts @@ -1,4 +1,4 @@ -import { ComponentType } from "react"; +import { ComponentType, ReactNode } from "react"; import { IconProps } from "../../common/icons/types"; type EmptyStateType = "default" | "success" | "lowSeverity"; @@ -8,7 +8,7 @@ export interface EmptyStateCardProps { icon: ComponentType; title?: string; text?: string; - customContent?: React.ReactNode; + customContent?: ReactNode; blurredContent?: JSX.Element; } diff --git a/src/components/Highlights/TopIssues/common/HighlightCard/types.ts b/src/components/Highlights/TopIssues/common/HighlightCard/types.ts index 0492cdcfa..c98bc9a27 100644 --- a/src/components/Highlights/TopIssues/common/HighlightCard/types.ts +++ b/src/components/Highlights/TopIssues/common/HighlightCard/types.ts @@ -1,6 +1,7 @@ +import { ReactNode } from "react"; import { GenericMetrics, HighlightData } from "../../types"; export interface HighlightCardProps { - content: React.ReactNode; + content: ReactNode; highlight: HighlightData; } diff --git a/src/components/Navigation/CodeButton/GlowingIconButton/types.ts b/src/components/Navigation/CodeButton/GlowingIconButton/types.ts index ce3ad7321..3bb36636f 100644 --- a/src/components/Navigation/CodeButton/GlowingIconButton/types.ts +++ b/src/components/Navigation/CodeButton/GlowingIconButton/types.ts @@ -1,7 +1,9 @@ +import { ReactNode } from "react"; + type GlowingIconButtonType = "default" | "error"; export interface GlowingIconButtonProps { - icon: React.ReactNode; + icon: ReactNode; onClick?: () => void; type?: GlowingIconButtonType; } diff --git a/src/components/Navigation/common/IconButton/types.ts b/src/components/Navigation/common/IconButton/types.ts index 0954c599e..8b19a5c69 100644 --- a/src/components/Navigation/common/IconButton/types.ts +++ b/src/components/Navigation/common/IconButton/types.ts @@ -1,7 +1,7 @@ -import React from "react"; +import { ReactNode } from "react"; export interface IconButtonProps { - icon: React.ReactNode; + icon: ReactNode; isDisabled?: boolean; className?: string; onClick?: () => void; diff --git a/src/components/Navigation/common/MenuList/types.ts b/src/components/Navigation/common/MenuList/types.ts index 69d4cfb19..787e5f3a7 100644 --- a/src/components/Navigation/common/MenuList/types.ts +++ b/src/components/Navigation/common/MenuList/types.ts @@ -21,10 +21,10 @@ export interface ListItemIconContainerProps { } export interface MenuItem { - customContent?: React.ReactNode; + customContent?: ReactNode; id: string; label?: string; - icon?: React.ReactNode; + icon?: ReactNode; onClick?: () => void; isDisabled?: boolean; groupName?: string; diff --git a/src/components/RecentActivity/Badge/index.tsx b/src/components/RecentActivity/Badge/index.tsx index 14f354297..3666f3ea0 100644 --- a/src/components/RecentActivity/Badge/index.tsx +++ b/src/components/RecentActivity/Badge/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import { memo } from "react"; import * as s from "./styles"; import { BadgeProps } from "./types"; @@ -6,4 +6,4 @@ const BadgeComponent = ({ size = "small", className }: BadgeProps) => ( ); -export const Badge = React.memo(BadgeComponent); +export const Badge = memo(BadgeComponent); diff --git a/src/components/RecentActivity/LiveView/index.tsx b/src/components/RecentActivity/LiveView/index.tsx index 57f0ce7e5..eaa81237c 100644 --- a/src/components/RecentActivity/LiveView/index.tsx +++ b/src/components/RecentActivity/LiveView/index.tsx @@ -1,5 +1,6 @@ import { format } from "date-fns"; import { + MouseEvent, UIEvent, useCallback, useEffect, @@ -253,10 +254,7 @@ export const LiveView = ({ data, onClose }: LiveViewProps) => { ); }; - const handleAreaMouseMove = ( - props: unknown, - e: React.MouseEvent - ) => { + const handleAreaMouseMove = (props: unknown, e: MouseEvent) => { setAreaTooltip({ x: e.clientX, y: e.clientY }); }; @@ -266,7 +264,7 @@ export const LiveView = ({ data, onClose }: LiveViewProps) => { const handleDotMouseOver = ( props: unknown, - e: React.MouseEvent + e: MouseEvent ) => { setDotTooltip({ coordinates: { x: e.clientX, y: e.clientY }, diff --git a/src/components/common/Badge/index.tsx b/src/components/common/Badge/index.tsx index 6ac495ba7..f4ff067ab 100644 --- a/src/components/common/Badge/index.tsx +++ b/src/components/common/Badge/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import { memo } from "react"; import * as s from "./styles"; import { BadgeProps } from "./types"; @@ -8,4 +8,4 @@ const BadgeComponent = ({ customStyles }: BadgeProps) => ( ); -export const Badge = React.memo(BadgeComponent); +export const Badge = memo(BadgeComponent); diff --git a/src/components/common/Badge/types.ts b/src/components/common/Badge/types.ts index 4a5ddf317..2841d9cbb 100644 --- a/src/components/common/Badge/types.ts +++ b/src/components/common/Badge/types.ts @@ -1,10 +1,12 @@ +import { CSSProperties } from "react"; + export interface BadgeProps { customStyles?: { - main: React.CSSProperties; - outline: React.CSSProperties; + main: CSSProperties; + outline: CSSProperties; }; } export interface CustomStylesProps { - $customStyles?: React.CSSProperties; + $customStyles?: CSSProperties; } diff --git a/src/components/common/Button/index.tsx b/src/components/common/Button/index.tsx index 97bdd755b..00c7ab41c 100644 --- a/src/components/common/Button/index.tsx +++ b/src/components/common/Button/index.tsx @@ -1,4 +1,4 @@ -import React, { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, MouseEvent } from "react"; import * as s from "./styles"; import { ButtonProps } from "./types"; @@ -14,7 +14,7 @@ export const ButtonComponent = ( }: ButtonProps, ref: ForwardedRef ) => { - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: MouseEvent) => { if (onClick) { onClick(e); } diff --git a/src/components/common/EmptyState/types.ts b/src/components/common/EmptyState/types.ts index 7b8b0a102..32395d8c7 100644 --- a/src/components/common/EmptyState/types.ts +++ b/src/components/common/EmptyState/types.ts @@ -1,8 +1,8 @@ -import { ReactNode } from "react"; +import { ComponentType, ReactNode } from "react"; import { ThemeableIconProps } from "../../common/icons/types"; export interface EmptyStateProps { - icon?: React.ComponentType; + icon?: ComponentType; title?: string; content?: ReactNode; } diff --git a/src/components/common/IconButton/index.tsx b/src/components/common/IconButton/index.tsx index db64c724b..5e6e0cfc9 100644 --- a/src/components/common/IconButton/index.tsx +++ b/src/components/common/IconButton/index.tsx @@ -1,3 +1,4 @@ +import { MouseEvent } from "react"; import * as s from "./styles"; import { IconButtonProps } from "./types"; @@ -6,7 +7,7 @@ export const IconButton = ({ disabled, icon: Icon }: IconButtonProps) => { - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: MouseEvent) => { onClick(e); }; diff --git a/src/components/common/IconButton/types.ts b/src/components/common/IconButton/types.ts index fc136d1b0..76b7c790d 100644 --- a/src/components/common/IconButton/types.ts +++ b/src/components/common/IconButton/types.ts @@ -1,7 +1,8 @@ +import { ComponentType, MouseEventHandler } from "react"; import { IconProps } from "../icons/types"; export interface IconButtonProps { - icon: React.ComponentType; - onClick: React.MouseEventHandler; + icon: ComponentType; + onClick: MouseEventHandler; disabled?: boolean; } diff --git a/src/components/common/JiraTicket/TicketLinkButton/index.tsx b/src/components/common/JiraTicket/TicketLinkButton/index.tsx index 3ef1082d9..9f17d1e9d 100644 --- a/src/components/common/JiraTicket/TicketLinkButton/index.tsx +++ b/src/components/common/JiraTicket/TicketLinkButton/index.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { ChangeEvent, useEffect, useState } from "react"; import { isValidHttpUrl } from "../../../../utils/isValidUrl"; import { Button } from "../../Button"; import { ActionableTextField } from "../ActionableTextField"; @@ -13,7 +13,7 @@ export const TicketLinkButton = ({ ticketLink?.errorMessage ?? null ); const [link, setLink] = useState(ticketLink?.link ?? null); - const onTicketLinkChange = (event: React.ChangeEvent) => { + const onTicketLinkChange = (event: ChangeEvent) => { const ticketLink = event.target.value; setLink(ticketLink); if (!ticketLink || isValidHttpUrl(ticketLink)) { diff --git a/src/components/common/JiraTicket/TicketLinkButton/types.ts b/src/components/common/JiraTicket/TicketLinkButton/types.ts index 8554f29ef..83df5c088 100644 --- a/src/components/common/JiraTicket/TicketLinkButton/types.ts +++ b/src/components/common/JiraTicket/TicketLinkButton/types.ts @@ -1,9 +1,11 @@ +import { ChangeEvent } from "react"; + export interface TicketLinkButtonProps { ticketLink?: { link?: string | null; errorMessage?: string | null; }; - onTicketLinkChange?: (event: React.ChangeEvent) => void; + onTicketLinkChange?: (event: ChangeEvent) => void; unlinkTicket?: () => void; linkTicket?: (value: string) => void; } diff --git a/src/components/common/JiraTicket/types.ts b/src/components/common/JiraTicket/types.ts index 5b02af8a1..98d21189c 100644 --- a/src/components/common/JiraTicket/types.ts +++ b/src/components/common/JiraTicket/types.ts @@ -1,4 +1,4 @@ -import { ReactNode } from "react"; +import { ChangeEvent, ReactNode } from "react"; export interface JiraTicketThemeColors { background: string; @@ -27,7 +27,7 @@ export interface JiraTicketProps { link?: string | null; errorMessage?: string | null; }; - onTicketLinkChange?: (event: React.ChangeEvent) => void; + onTicketLinkChange?: (event: ChangeEvent) => void; unlinkTicket?: () => void; linkTicket?: (link: string) => void; } diff --git a/src/components/common/Loader/index.tsx b/src/components/common/Loader/index.tsx index c6e6c5316..78f5f4ce1 100644 --- a/src/components/common/Loader/index.tsx +++ b/src/components/common/Loader/index.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-curly-brace-presence */ -import React from "react"; +import { memo } from "react"; import { CircleLoader } from "../CircleLoader"; import { LoaderProps } from "./types"; @@ -365,4 +365,4 @@ const LoaderComponent = ({ size = 20, status, themeKind }: LoaderProps) => { } }; -export const Loader = React.memo(LoaderComponent); +export const Loader = memo(LoaderComponent); diff --git a/src/components/common/NewButton/types.ts b/src/components/common/NewButton/types.ts index 9e8fce05a..b6a9baa78 100644 --- a/src/components/common/NewButton/types.ts +++ b/src/components/common/NewButton/types.ts @@ -1,4 +1,4 @@ -import { ButtonHTMLAttributes } from "react"; +import { ButtonHTMLAttributes, ComponentType } from "react"; import { IconProps } from "../../common/icons/types"; export interface ButtonThemeColors { @@ -32,7 +32,7 @@ export type ButtonType = "primary" | "secondary" | "tertiary"; export type ButtonSize = "small" | "large"; export interface NewButtonProps { - icon?: React.ComponentType; + icon?: ComponentType; label?: string; title?: string; onClick?: () => void; diff --git a/src/components/common/Popover/index.tsx b/src/components/common/Popover/index.tsx index 9b3b3127e..2ea1b16d8 100644 --- a/src/components/common/Popover/index.tsx +++ b/src/components/common/Popover/index.tsx @@ -1,5 +1,6 @@ // Source: https://floating-ui.com/docs/popover#reusable-popover-component +import { ReactNode } from "react"; import { PopoverContext, usePopover } from "./hooks"; import { PopoverProps } from "./types"; @@ -9,7 +10,7 @@ export function Popover({ modal = false, ...restOptions }: { - children: React.ReactNode; + children: ReactNode; } & PopoverProps) { // This can accept any props as options, e.g. `placement`, // or other positioning options. diff --git a/src/components/common/Popover/types.ts b/src/components/common/Popover/types.ts index a50d019f0..a4234a46c 100644 --- a/src/components/common/Popover/types.ts +++ b/src/components/common/Popover/types.ts @@ -1,12 +1,11 @@ import { Placement } from "@floating-ui/react"; +import { Dispatch, SetStateAction } from "react"; import { usePopover } from "./hooks"; export type ContextType = | (ReturnType & { - setLabelId: React.Dispatch>; - setDescriptionId: React.Dispatch< - React.SetStateAction - >; + setLabelId: Dispatch>; + setDescriptionId: Dispatch>; }) | null; diff --git a/src/components/common/RegisterForm/index.tsx b/src/components/common/RegisterForm/index.tsx index a8fbb0d77..cc585212a 100644 --- a/src/components/common/RegisterForm/index.tsx +++ b/src/components/common/RegisterForm/index.tsx @@ -1,4 +1,4 @@ -import { useContext, useMemo } from "react"; +import { FormEventHandler, useContext, useMemo } from "react"; import { Controller, useForm } from "react-hook-form"; import { actions as globalActions } from "../../../actions"; @@ -71,7 +71,7 @@ export const RegisterForm = ({ onNext(true); }; - const handleOnSubmit: React.FormEventHandler = (e) => { + const handleOnSubmit: FormEventHandler = (e) => { e.preventDefault(); void handleSubmit(onSubmit)(e); }; diff --git a/src/components/common/RegistrationDialog/TextField/types.ts b/src/components/common/RegistrationDialog/TextField/types.ts index 5b76d7929..0ec9dd99f 100644 --- a/src/components/common/RegistrationDialog/TextField/types.ts +++ b/src/components/common/RegistrationDialog/TextField/types.ts @@ -1,9 +1,9 @@ -import { ChangeEventHandler, FocusEventHandler } from "react"; +import { ChangeEventHandler, ComponentType, FocusEventHandler } from "react"; import { IconProps } from "../../icons/types"; export interface TextFieldProps { placeholder?: string; - icon?: React.ComponentType; + icon?: ComponentType; value: string; onChange: ChangeEventHandler; className?: string; diff --git a/src/components/common/Tooltip/types.ts b/src/components/common/Tooltip/types.ts index f20bb78b9..00e7c9bbb 100644 --- a/src/components/common/Tooltip/types.ts +++ b/src/components/common/Tooltip/types.ts @@ -1,5 +1,5 @@ import { Placement } from "@floating-ui/react"; -import { ReactElement, ReactNode } from "react"; +import { CSSProperties, ReactElement, ReactNode } from "react"; export interface TooltipThemeColors { background: string; @@ -10,6 +10,6 @@ export interface TooltipProps { children: ReactElement; title: ReactNode; placement?: Placement; - style?: React.CSSProperties; + style?: CSSProperties; isOpen?: boolean; } diff --git a/src/components/common/TreeMap/TreeMap.stories.tsx b/src/components/common/TreeMap/TreeMap.stories.tsx index 7d4f625c8..43230a22a 100644 --- a/src/components/common/TreeMap/TreeMap.stories.tsx +++ b/src/components/common/TreeMap/TreeMap.stories.tsx @@ -21,6 +21,10 @@ export const Default: Story = { width: 800, height: 800, padding: 40, + minTileDimensions: { + width: 100, + height: 100 + }, data: [ { id: "payment", diff --git a/src/components/common/TreeMap/index.tsx b/src/components/common/TreeMap/index.tsx index fe4bbad35..f99e9ea28 100644 --- a/src/components/common/TreeMap/index.tsx +++ b/src/components/common/TreeMap/index.tsx @@ -1,29 +1,85 @@ -import squarify from "squarify"; +import squarify, { ILayoutRect, Input } from "squarify"; import { isNull } from "../../../typeGuards/isNull"; -import { TreeMapProps } from "./types"; +import { TileData, TreeMapProps } from "./types"; -export const TreeMap = ({ padding = 0, data, width, height }: TreeMapProps) => { - const container = { x0: 0, y0: 0, x1: width, y1: height }; +const normalizeData = (data: Input[]) => { + const MIN_MAX_RATIO = 5; + const NORMALIZED_MIN = 1; + const NORMALIZED_MAX = MIN_MAX_RATIO; + const dataMin = Math.min(...data.map((item) => item.value)) || 1; const dataMax = Math.max(...data.map((item) => item.value)); - const minNormalizedValue = dataMax > 0 ? dataMax * 0.05 : 1; - const normalizedData = data.map((item, index) => { - return { - id: index, - value: item.value < minNormalizedValue ? minNormalizedValue : item.value, - content: item.content + const dataMinMaxRatio = dataMax / dataMin; + + return dataMinMaxRatio > MIN_MAX_RATIO + ? data.map((item) => { + return { + id: item.id, + value: + ((item.value - dataMin) * (NORMALIZED_MAX - NORMALIZED_MIN)) / + (dataMax - dataMin) + + NORMALIZED_MIN, // min-max normalization + content: item.content + }; + }) + : data; +}; + +const calculateTiles = ( + data: Input[], + container: { x0: number; y0: number; x1: number; y1: number }, + minTileDimensions?: { + width: number; + height: number; + } +) => { + let tiles: ILayoutRect[] = []; + let areTilesValid = false; + const MAX_ITERATIONS = 100; + let iterations = 0; + + while (!areTilesValid && iterations < MAX_ITERATIONS) { + tiles = squarify(data, container); + + areTilesValid = + !minTileDimensions || + (minTileDimensions && + tiles.every( + (tile) => + tile.x1 - tile.x0 >= minTileDimensions.width && + tile.y1 - tile.y0 >= minTileDimensions.height + )); + + container = { + ...container, + x1: Math.trunc(container.x1 * 1.1) }; - }); + + iterations++; + } + + return { tiles, container }; +}; + +export const TreeMap = ({ + padding = 0, + data, + width, + height, + minTileDimensions +}: TreeMapProps) => { + const container = { x0: 0, y0: 0, x1: width, y1: height }; + const normalizedData = normalizeData(data); const sortedData = [...normalizedData].sort((a, b) => b.value - a.value); - const tiles = squarify(sortedData, container); + const tilesData = calculateTiles(sortedData, container, minTileDimensions); // Transform coordinates to add paddings between tiles const transformedTiles = padding - ? tiles.map((tile) => { + ? tilesData.tiles.map((tile) => { const isLeftEdge = tile.x0 === 0; const isTopEdge = tile.y0 === 0; - const isRightEdge = tile.x1 - width < 1; - const isBottomEdge = tile.y1 - height < 1; + const isRightEdge = tile.x1 - tilesData.container.x1 < 1; + const isBottomEdge = tile.y1 - tilesData.container.y1 < 1; return { ...tile, @@ -33,7 +89,7 @@ export const TreeMap = ({ padding = 0, data, width, height }: TreeMapProps) => { y1: isBottomEdge ? tile.y1 : tile.y1 - padding }; }) - : tiles; + : tilesData.tiles; return (
[]; width: number; height: number; + minTileDimensions?: { + width: number; + height: number; + }; } diff --git a/src/components/common/v3/Button/types.ts b/src/components/common/v3/Button/types.ts index b26712bf4..74cdfca3d 100644 --- a/src/components/common/v3/Button/types.ts +++ b/src/components/common/v3/Button/types.ts @@ -1,10 +1,10 @@ -import { ButtonHTMLAttributes } from "react"; +import { ButtonHTMLAttributes, ComponentType } from "react"; import { IconProps } from "../../icons/types"; export type ButtonType = "primary" | "secondary" | "tertiary"; export interface BaseButtonProps { - icon?: React.ComponentType; + icon?: ComponentType; label?: string; buttonType?: ButtonType; } diff --git a/src/components/common/v3/IconButton/index.tsx b/src/components/common/v3/IconButton/index.tsx index 3c95bc04d..9dc898bb6 100644 --- a/src/components/common/v3/IconButton/index.tsx +++ b/src/components/common/v3/IconButton/index.tsx @@ -1,4 +1,4 @@ -import React, { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, MouseEvent } from "react"; import * as s from "./styles"; import { IconButtonProps } from "./types"; @@ -7,7 +7,7 @@ const IconButtonComponent = ( { onClick, className, disabled, icon }: IconButtonProps, ref: ForwardedRef ) => { - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: MouseEvent) => { if (onClick) { onClick(e); } diff --git a/src/components/common/v3/NewButton/types.ts b/src/components/common/v3/NewButton/types.ts index 2abde8024..74fab581c 100644 --- a/src/components/common/v3/NewButton/types.ts +++ b/src/components/common/v3/NewButton/types.ts @@ -1,4 +1,4 @@ -import { ButtonHTMLAttributes } from "react"; +import { ButtonHTMLAttributes, ComponentType } from "react"; import { IconProps } from "../../icons/types"; export type ButtonType = @@ -8,7 +8,7 @@ export type ButtonType = | "secondaryBorderless"; export interface BaseButtonProps { - icon?: React.ComponentType; + icon?: ComponentType; label?: string; buttonType?: ButtonType; } diff --git a/src/components/common/v3/NewEmptyState/types.ts b/src/components/common/v3/NewEmptyState/types.ts index 8f93726d0..c49dde65b 100644 --- a/src/components/common/v3/NewEmptyState/types.ts +++ b/src/components/common/v3/NewEmptyState/types.ts @@ -1,8 +1,8 @@ -import { ReactNode } from "react"; +import { ComponentType, ReactNode } from "react"; import { IconProps } from "../../../common/icons/types"; export interface EmptyStateProps { - icon?: React.ComponentType; + icon?: ComponentType; title?: string; content?: ReactNode; } diff --git a/src/components/common/v3/NewIconButton/index.tsx b/src/components/common/v3/NewIconButton/index.tsx index 1a07b1721..ffa111383 100644 --- a/src/components/common/v3/NewIconButton/index.tsx +++ b/src/components/common/v3/NewIconButton/index.tsx @@ -1,4 +1,4 @@ -import React, { ForwardedRef, forwardRef } from "react"; +import { ForwardedRef, forwardRef, MouseEvent } from "react"; import * as s from "./styles"; import { ButtonType, NewIconButtonProps } from "./types"; @@ -25,7 +25,7 @@ const NewIconButtonComponent = ( }: NewIconButtonProps, ref: ForwardedRef ) => { - const handleClick = (e: React.MouseEvent) => { + const handleClick = (e: MouseEvent) => { if (onClick) { onClick(e); } diff --git a/src/components/common/v3/TextField/types.ts b/src/components/common/v3/TextField/types.ts index e4241c761..672c1ad53 100644 --- a/src/components/common/v3/TextField/types.ts +++ b/src/components/common/v3/TextField/types.ts @@ -1,4 +1,9 @@ -import { ChangeEventHandler, HTMLInputTypeAttribute, ReactNode } from "react"; +import { + ChangeEventHandler, + ComponentType, + HTMLInputTypeAttribute, + ReactNode +} from "react"; import { IconProps } from "../../icons/types"; export interface TextFieldProps { @@ -11,7 +16,7 @@ export interface TextFieldProps { isInvalid?: boolean; error?: string; type?: HTMLInputTypeAttribute; - icon?: React.ComponentType; + icon?: ComponentType; alwaysRenderError?: boolean; } diff --git a/src/components/common/v3/Tooltip/types.ts b/src/components/common/v3/Tooltip/types.ts index 85df28817..6a84b0cfe 100644 --- a/src/components/common/v3/Tooltip/types.ts +++ b/src/components/common/v3/Tooltip/types.ts @@ -1,5 +1,5 @@ import { Placement } from "@floating-ui/react"; -import { ReactElement, ReactNode } from "react"; +import { CSSProperties, ReactElement, ReactNode } from "react"; export interface TooltipThemeColors { background: string; @@ -10,7 +10,7 @@ export interface TooltipProps { children: ReactElement; title: ReactNode; placement?: Placement; - style?: React.CSSProperties; + style?: CSSProperties; isOpen?: boolean; fullWidth?: boolean; isDisabled?: boolean; diff --git a/src/hooks/usePagination.ts b/src/hooks/usePagination.ts index eeb5d9679..e6a05c124 100644 --- a/src/hooks/usePagination.ts +++ b/src/hooks/usePagination.ts @@ -1,10 +1,10 @@ -import { useEffect, useState } from "react"; +import { Dispatch, SetStateAction, useEffect, useState } from "react"; export const usePagination = ( items: T[], pageSize: number, key?: string -): [T[], number, React.Dispatch>] => { +): [T[], number, Dispatch>] => { const [page, setPage] = useState(0); const pageCount = Math.ceil(items.length / pageSize) || 1;