diff --git a/src/components/Assets/AssetList/AssetEntry/index.tsx b/src/components/Assets/AssetList/AssetEntry/index.tsx index c17655e08..b71f766d8 100644 --- a/src/components/Assets/AssetList/AssetEntry/index.tsx +++ b/src/components/Assets/AssetList/AssetEntry/index.tsx @@ -1,14 +1,26 @@ -import { useTheme } from "styled-components"; +import { DefaultTheme, useTheme } from "styled-components"; import { getInsightImportanceColor } from "../../../../utils/getInsightImportanceColor"; import { getInsightTypeInfo } from "../../../../utils/getInsightTypeInfo"; import { getInsightTypeOrderPriority } from "../../../../utils/getInsightTypeOrderPriority"; import { timeAgo } from "../../../../utils/timeAgo"; +import { GlobeIcon } from "../../../common/icons/GlobeIcon"; import { getAssetTypeInfo } from "../../utils"; import * as s from "./styles"; import { AssetEntryProps } from "./types"; +const getServiceIconColor = (theme: DefaultTheme) => { + switch (theme.mode) { + case "light": + return "#4d668a"; + case "dark": + case "dark-jetbrains": + return "#dadada"; + } +}; + export const AssetEntry = (props: AssetEntryProps) => { const theme = useTheme(); + const serviceIconColor = getServiceIconColor(theme); const handleLinkClick = () => { props.onAssetLinkClick(props.entry); @@ -69,10 +81,11 @@ export const AssetEntry = (props: AssetEntryProps) => { - + Services + {props.entry.serviceName} @@ -83,15 +96,6 @@ export const AssetEntry = (props: AssetEntryProps) => { )} - - Last - - {timeAgo(lastSeenDateTime)} - ago - - - - Performance @@ -101,7 +105,15 @@ export const AssetEntry = (props: AssetEntryProps) => { )} - + + + + Last + + {timeAgo(lastSeenDateTime)} + ago + + Slowest 5% @@ -113,7 +125,7 @@ export const AssetEntry = (props: AssetEntryProps) => { )} - + ); diff --git a/src/components/Assets/AssetList/AssetEntry/styles.ts b/src/components/Assets/AssetList/AssetEntry/styles.ts index b45aa6634..bb0a04c45 100644 --- a/src/components/Assets/AssetList/AssetEntry/styles.ts +++ b/src/components/Assets/AssetList/AssetEntry/styles.ts @@ -74,7 +74,7 @@ export const InsightIconContainer = styled(AssetTypeIconContainer)` export const StatsContainer = styled.div` display: flex; - gap: 15px 12px; + gap: 16px 12px; flex-wrap: wrap; font-size: 10px; line-height: 12px; @@ -84,20 +84,12 @@ export const Stats = styled.div` display: flex; flex-direction: column; gap: 4px; + width: 120px; `; -export const StatsColumn = styled.div` +export const StatsRow = styled.div` display: flex; - flex-direction: column; - gap: 12px; - - &:first-child { - width: 35%; - } - - &:nth-child(2) { - width: 35%; - } + gap: 16px; `; export const ServicesContainer = styled.div` @@ -107,7 +99,7 @@ export const ServicesContainer = styled.div` `; export const ServiceName = styled.div` - padding: 4px 6px; + padding: 4px 0; border-radius: 23px; line-height: 8px; overflow: hidden; @@ -122,15 +114,6 @@ export const ServiceName = styled.div` return "#dadada"; } }}; - background: ${({ theme }) => { - switch (theme.mode) { - case "light": - return "#e9eef4"; - case "dark": - case "dark-jetbrains": - return "#2e2e2e"; - } - }}; `; export const ValueContainer = styled.div` diff --git a/src/components/Assets/AssetList/index.tsx b/src/components/Assets/AssetList/index.tsx index 52afab239..c681771c1 100644 --- a/src/components/Assets/AssetList/index.tsx +++ b/src/components/Assets/AssetList/index.tsx @@ -6,6 +6,7 @@ import { PopoverContent } from "../../common/Popover/PopoverContent"; import { PopoverTrigger } from "../../common/Popover/PopoverTrigger"; import { ChevronIcon } from "../../common/icons/ChevronIcon"; import { MagnifierIcon } from "../../common/icons/MagnifierIcon"; +import { SortIcon } from "../../common/icons/SortIcon"; import { Direction } from "../../common/icons/types"; import { getAssetTypeInfo } from "../utils"; import { AssetEntry as AssetEntryComponent } from "./AssetEntry"; @@ -14,6 +15,7 @@ import { AssetListProps, ExtendedAssetEntryWithServices, SORTING_CRITERION, + SORTING_ORDER, Sorting } from "./types"; @@ -23,6 +25,8 @@ const sortEntries = ( ): ExtendedAssetEntryWithServices[] => { entries = [...entries]; + const isDesc = sorting.order === SORTING_ORDER.DESC; + const sortByName = ( a: ExtendedAssetEntryWithServices, b: ExtendedAssetEntryWithServices, @@ -83,39 +87,31 @@ const sortEntries = ( ).length; return ( - (sorting.isDesc + (isDesc ? aHighestImportance - bHighestImportance : bHighestImportance - aHighestImportance) || - (sorting.isDesc + (isDesc ? bMostImportantInsightCount - aMostImportantInsightCount : aMostImportantInsightCount - bMostImportantInsightCount) || - sortByName(a, b, sorting.isDesc) + sortByName(a, b, isDesc) ); }); case SORTING_CRITERION.PERFORMANCE: - return entries.sort((a, b) => - sortByPercentile(a, b, 0.5, sorting.isDesc) - ); + return entries.sort((a, b) => sortByPercentile(a, b, 0.5, isDesc)); case SORTING_CRITERION.SLOWEST_FIVE_PERCENT: - return entries.sort((a, b) => - sortByPercentile(a, b, 0.95, sorting.isDesc) - ); + return entries.sort((a, b) => sortByPercentile(a, b, 0.95, isDesc)); case SORTING_CRITERION.LATEST: return entries.sort((a, b) => { const aDateTime = new Date(a.lastSpanInstanceInfo.startTime).valueOf(); const bDateTime = new Date(b.lastSpanInstanceInfo.startTime).valueOf(); return ( - (sorting.isDesc ? bDateTime - aDateTime : aDateTime - bDateTime) || - sortByName(a, b, sorting.isDesc) + (isDesc ? bDateTime - aDateTime : aDateTime - bDateTime) || + sortByName(a, b, isDesc) ); }); case SORTING_CRITERION.NAME: - return entries.sort((a, b) => - sorting.isDesc - ? sortByName(b, a, sorting.isDesc) - : sortByName(a, b, sorting.isDesc) - ); + return entries.sort((a, b) => sortByName(a, b, isDesc)); default: return entries; } @@ -151,13 +147,44 @@ const getSortingMenuChevronColor = (theme: DefaultTheme) => { } }; +const getSortIconColor = (theme: DefaultTheme, selected: boolean) => { + if (selected) { + switch (theme.mode) { + case "light": + return "#f1f5fa"; + case "dark": + case "dark-jetbrains": + return "#dadada"; + } + } + switch (theme.mode) { + case "light": + return "#828797"; + case "dark": + case "dark-jetbrains": + return "#dadada"; + } +}; + +const getDefaultSortingOrder = ( + criterion: SORTING_CRITERION +): SORTING_ORDER => { + switch (criterion) { + case SORTING_CRITERION.NAME: + return SORTING_ORDER.ASC; + case SORTING_CRITERION.PERFORMANCE: + case SORTING_CRITERION.SLOWEST_FIVE_PERCENT: + case SORTING_CRITERION.CRITICAL_INSIGHTS: + case SORTING_CRITERION.LATEST: + default: + return SORTING_ORDER.DESC; + } +}; + export const AssetList = (props: AssetListProps) => { - const [sorting, setSorting] = useState<{ - criterion: SORTING_CRITERION; - isDesc: boolean; - }>({ + const [sorting, setSorting] = useState({ criterion: SORTING_CRITERION.CRITICAL_INSIGHTS, - isDesc: true + order: SORTING_ORDER.DESC }); const [isSortingMenuOpen, setIsSortingMenuOpen] = useState(false); const [searchInputValue, setSearchInputValue] = useState(""); @@ -184,12 +211,15 @@ export const AssetList = (props: AssetListProps) => { if (sorting.criterion === value) { setSorting({ ...sorting, - isDesc: !sorting.isDesc + order: + sorting.order === SORTING_ORDER.DESC + ? SORTING_ORDER.ASC + : SORTING_ORDER.DESC }); } else { setSorting({ criterion: value as SORTING_CRITERION, - isDesc: false + order: getDefaultSortingOrder(value as SORTING_CRITERION) }); } handleSortingMenuToggle(); @@ -199,6 +229,13 @@ export const AssetList = (props: AssetListProps) => { props.onAssetLinkClick(entry); }; + const handleSortingOrderToggleOptionButtonClick = (order: SORTING_ORDER) => { + setSorting({ + ...sorting, + order + }); + }; + const assetTypeInfo = getAssetTypeInfo(props.assetTypeId); const entries: ExtendedAssetEntryWithServices[] = useMemo( @@ -260,14 +297,14 @@ export const AssetList = (props: AssetListProps) => { placement={"bottom-start"} > - + Sort by {sorting.criterion} - + { + + {[SORTING_ORDER.DESC, SORTING_ORDER.ASC].map((order) => { + const isSelected = sorting.order === order; + const iconColor = getSortIconColor(theme, isSelected); + + return ( + handleSortingOrderToggleOptionButtonClick(order)} + > + + + + + ); + })} + {sortedEntries.length > 0 ? ( diff --git a/src/components/Assets/AssetList/styles.ts b/src/components/Assets/AssetList/styles.ts index f2a726753..67042bec0 100644 --- a/src/components/Assets/AssetList/styles.ts +++ b/src/components/Assets/AssetList/styles.ts @@ -1,4 +1,9 @@ import styled from "styled-components"; +import { + SORTING_ORDER, + SortingMenuButtonProps, + SortingOrderOptionProps +} from "./types"; export const Container = styled.div` display: flex; @@ -45,6 +50,7 @@ export const Toolbar = styled.div` display: flex; justify-content: space-between; padding: 8px; + gap: 4px; `; export const PopoverContainer = styled.div` @@ -70,7 +76,7 @@ export const SearchInput = styled.input` font-size: 10px; padding: 4px 4px 4px 18px; border-radius: 4px; - width: 140px; + width: 70px; outline: none; caret-color: ${({ theme }) => { switch (theme.mode) { @@ -132,14 +138,17 @@ export const SearchInput = styled.input` } `; -export const SortingMenuContainer = styled.div` +export const SortingMenuButton = styled.button` + background: none; + cursor: pointer; display: flex; gap: 2px; font-weight: 500; font-size: 10px; line-height: 12px; align-items: center; - height: 20px; + border-radius: 4px; + padding: 4px 8px; color: ${({ theme }) => { switch (theme.mode) { case "light": @@ -149,6 +158,41 @@ export const SortingMenuContainer = styled.div` return "#9b9b9b"; } }}; + border: 1px solid + ${({ theme, isOpen }) => { + if (isOpen) { + switch (theme.mode) { + case "light": + return "#7891d0"; + case "dark": + case "dark-jetbrains": + return "#9b9b9b"; + } + } + + switch (theme.mode) { + case "light": + return "#d0d6eb"; + case "dark": + case "dark-jetbrains": + return "#49494d"; + } + }}; + + &:hover, + &:focus { + outline: none; + border: 1px solid + ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#7891d0"; + case "dark": + case "dark-jetbrains": + return "#9b9b9b"; + } + }}; + } `; export const SortingLabel = styled.span` @@ -167,6 +211,41 @@ export const SortingLabel = styled.span` }}; `; +export const SortingOrderToggle = styled.div` + display: flex; + border-radius: 4px; + padding: 4px; + gap: 4px; + border: 1px solid + ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#b9c0d4"; + case "dark": + case "dark-jetbrains": + return "#49494d"; + } + }}; +`; + +export const SortingOrderToggleOptionButton = styled.button` + border: none; + outline: none; + padding: 0; + border-radius: 2px; + cursor: pointer; + background: ${({ selected }) => (selected ? "#3538cd" : "transparent")}; +`; + +export const SortingOrderIconContainer = styled.div<{ + sortingOrder: SORTING_ORDER; +}>` + display: flex; + transform: scaleY( + ${({ sortingOrder }) => (sortingOrder === SORTING_ORDER.DESC ? -1 : 1)} + ); +`; + export const ItemsCount = styled.span` margin-left: auto; color: ${({ theme }) => { diff --git a/src/components/Assets/AssetList/types.ts b/src/components/Assets/AssetList/types.ts index a993f6192..606681505 100644 --- a/src/components/Assets/AssetList/types.ts +++ b/src/components/Assets/AssetList/types.ts @@ -19,7 +19,20 @@ export enum SORTING_CRITERION { NAME = "Name" } +export enum SORTING_ORDER { + ASC = "asc", + DESC = "desc" +} + export interface Sorting { criterion: SORTING_CRITERION; - isDesc: boolean; + order: SORTING_ORDER; +} + +export interface SortingMenuButtonProps { + isOpen: boolean; +} + +export interface SortingOrderOptionProps { + selected: boolean; } diff --git a/src/components/InstallationWizard/InstallStep/index.tsx b/src/components/InstallationWizard/InstallStep/index.tsx index db8713352..99f72cadd 100644 --- a/src/components/InstallationWizard/InstallStep/index.tsx +++ b/src/components/InstallationWizard/InstallStep/index.tsx @@ -132,7 +132,7 @@ export const InstallStep = (props: InstallStepProps) => { openLinkInDefaultBrowser(DOCKER_DESKTOP_URL)}> Docker Desktop {" "} - 4.10.0 or later installed) + 4.10.0 or higher installed) { + switch (theme.mode) { + case "light": + return "#f1f5fa"; + case "dark": + case "dark-jetbrains": + return "#383838"; + } + }}; + } + + &:active { + color: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#4d668a"; + case "dark": + case "dark-jetbrains": + return "#e2e7ff"; + } + }}; + background: ${({ theme }) => { + switch (theme.mode) { + case "light": + return "#e9eef4"; + case "dark": + case "dark-jetbrains": + return "#49494d"; + } + }}; + } `; diff --git a/src/components/common/Popover/PopoverContent/index.tsx b/src/components/common/Popover/PopoverContent/index.tsx index 0e56bb390..dbc8b8319 100644 --- a/src/components/common/Popover/PopoverContent/index.tsx +++ b/src/components/common/Popover/PopoverContent/index.tsx @@ -5,7 +5,7 @@ import { FloatingPortal, useMergeRefs } from "@floating-ui/react"; -import { CSSProperties, ForwardedRef, forwardRef, HTMLProps } from "react"; +import { CSSProperties, ForwardedRef, HTMLProps, forwardRef } from "react"; import { usePopoverContext } from "../hooks"; const PopoverContentComponent = ( @@ -26,7 +26,9 @@ const PopoverContentComponent = ( position: context.strategy, top: context.y ?? 0, left: context.x ?? 0, - width: "max-content", + width: + context.elements.reference?.getBoundingClientRect().width, + outline: "none", ...props.style } as CSSProperties } diff --git a/src/components/common/Popover/PopoverTrigger/index.tsx b/src/components/common/Popover/PopoverTrigger/index.tsx index 260d427b8..77f294323 100644 --- a/src/components/common/Popover/PopoverTrigger/index.tsx +++ b/src/components/common/Popover/PopoverTrigger/index.tsx @@ -10,7 +10,6 @@ import { Ref } from "react"; import { usePopoverContext } from "../hooks"; -import * as s from "./styles"; import { PopoverTriggerProps } from "./types"; const PopoverTriggerComponent = ( @@ -43,15 +42,14 @@ const PopoverTriggerComponent = ( } return ( - {children} - + ); }; diff --git a/src/components/common/Popover/PopoverTrigger/styles.ts b/src/components/common/Popover/PopoverTrigger/styles.ts deleted file mode 100644 index f228829d5..000000000 --- a/src/components/common/Popover/PopoverTrigger/styles.ts +++ /dev/null @@ -1,9 +0,0 @@ -import styled from "styled-components"; - -export const Button = styled.button` - background: none; - border: none; - display: flex; - padding: 0; - cursor: pointer; -`; diff --git a/src/components/common/icons/GlobeIcon.tsx b/src/components/common/icons/GlobeIcon.tsx new file mode 100644 index 000000000..ebd616304 --- /dev/null +++ b/src/components/common/icons/GlobeIcon.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const GlobeIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + + + + + + + + ); +}; + +export const GlobeIcon = React.memo(GlobeIconComponent); diff --git a/src/components/common/icons/SortIcon.tsx b/src/components/common/icons/SortIcon.tsx new file mode 100644 index 000000000..aae2fe52f --- /dev/null +++ b/src/components/common/icons/SortIcon.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import { useIconProps } from "./hooks"; +import { IconProps } from "./types"; + +const SortIconComponent = (props: IconProps) => { + const { size, color } = useIconProps(props); + + return ( + + + + ); +}; + +export const SortIcon = React.memo(SortIconComponent);