Skip to content

Commit

Permalink
Colored menu.
Browse files Browse the repository at this point in the history
  • Loading branch information
Leshe4ka committed May 17, 2024
1 parent 42c236d commit 3c89d07
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 14 deletions.
10 changes: 8 additions & 2 deletions frontend/src/components/Nav/ClusterMenu/ClusterMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
clusterTopicsRelativePath,
} from 'lib/paths';
import { useLocation } from 'react-router-dom';
import { useLocalStorage } from 'lib/hooks/useLocalStorage';

interface ClusterMenuProps {
name: Cluster['name'];
Expand All @@ -38,17 +39,22 @@ const ClusterMenu: FC<ClusterMenuProps> = ({
features?.includes(key);
const [isOpen, setIsOpen] = useState(!!singleMode);
const location = useLocation();
const [colorKey, setColorKey] = useLocalStorage(
`clusterColor-${name}`,
'transparent'
);

const getIsMenuItemActive = (path: string) =>
location.pathname.includes(path);

return (
<ul role="menu">
<S.ClusterList $colorKey={colorKey}>
<MenuTab
title={name}
status={status}
isOpen={isOpen}
toggleClusterMenu={() => setIsOpen((prev) => !prev)}
setColorKey={setColorKey}
/>
{isOpen && (
<S.List>
Expand Down Expand Up @@ -98,7 +104,7 @@ const ClusterMenu: FC<ClusterMenuProps> = ({
)}
</S.List>
)}
</ul>
</S.ClusterList>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import styled from 'styled-components';
import { ClusterColorKey } from 'theme/theme';

export const Container = styled.div`
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-template-rows: repeat(2, auto);
padding: 0 8px;
gap: 8px;
border-radius: 8px;
cursor: auto;
`;

export const ColorCircle = styled.div<{ $colorKey: ClusterColorKey }>`
width: 16px;
height: 16px;
border-radius: 50%;
margin: 4px;
background-color: ${({ $colorKey, theme }) =>
theme.clusterColorPicker.backgroundColor[$colorKey]};
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
&:hover {
outline: ${({ theme }) => `1px solid ${theme.clusterColorPicker.outline}`};
}
&:active {
outline: ${({ theme }) => `2px solid ${theme.clusterColorPicker.outline}`};
}
${({ $colorKey, theme }) =>
$colorKey === 'transparent' &&
`
border: 1px solid ${theme.clusterColorPicker.transparentCircle.border};
&:before {
content: '\\2716';
font-size: 8px;
color: ${theme.clusterColorPicker.transparentCircle.cross};
}
&:hover {
border: none;
}
&:active {
border: none;
}
`}
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { Dropdown } from 'components/common/Dropdown';
import ColorPickerIcon from 'components/common/Icons/ColorPickerIcon';
import { ClusterColorKey } from 'theme/theme';

import * as S from './MenuColorPicker.styled';

interface MenuColorPickerProps {
setColorKey: (key: ClusterColorKey) => void;
}

const COLOR_KEYS: ClusterColorKey[] = [
'transparent',
'gray',
'red',
'orange',
'lettuce',
'green',
'turquoise',
'blue',
'violet',
'pink',
];

const MenuColorPicker = ({ setColorKey }: MenuColorPickerProps) => {
const handleCircleCLick = (colorKey: ClusterColorKey) => () => {
setColorKey(colorKey);
};

return (
<Dropdown offsetY={5} label={<ColorPickerIcon />}>
<S.Container>
{COLOR_KEYS.map((key) => (
<S.ColorCircle
onClick={handleCircleCLick(key)}
$colorKey={key}
key={key}
/>
))}
</S.Container>
</Dropdown>
);
};

export default MenuColorPicker;
15 changes: 12 additions & 3 deletions frontend/src/components/Nav/Menu/MenuTab.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { type FC } from 'react';
import { ServerStatus } from 'generated-sources';
import MenuColorPicker from 'components/Nav/Menu/MenuColorPicker/MenuColorPicker';

import * as S from './styled';

Expand All @@ -8,13 +9,15 @@ export interface MenuTabProps {
status: ServerStatus;
isOpen: boolean;
toggleClusterMenu: () => void;
setColorKey: () => void;
}

const MenuTab: FC<MenuTabProps> = ({
title,
toggleClusterMenu,
status,
isOpen,
setColorKey,
}) => (
<S.MenuItem $variant="primary" onClick={toggleClusterMenu}>
<S.ContentWrapper>
Expand All @@ -27,9 +30,15 @@ const MenuTab: FC<MenuTabProps> = ({
<S.Title title={title}>{title}</S.Title>
</S.ContentWrapper>

<S.ChevronWrapper>
<S.ChevronIcon $isOpen={isOpen} />
</S.ChevronWrapper>
<S.ActionsWrapper>
<S.ColorPickerWrapper>
<MenuColorPicker setColorKey={setColorKey} />
</S.ColorPickerWrapper>

<S.ChevronWrapper>
<S.ChevronIcon $isOpen={isOpen} />
</S.ChevronWrapper>
</S.ActionsWrapper>
</S.MenuItem>
);

Expand Down
14 changes: 14 additions & 0 deletions frontend/src/components/Nav/Menu/styled.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import styled, { css } from 'styled-components';
import { ServerStatus } from 'generated-sources';

export const ColorPickerWrapper = styled.div`
display: flex;
visibility: hidden;
`;

export const MenuItem = styled('li').attrs({ role: 'menuitem' })<{
$variant: 'primary' | 'secondary';
$isActive?: boolean;
Expand Down Expand Up @@ -28,6 +33,10 @@ export const MenuItem = styled('li').attrs({ role: 'menuitem' })<{
&:hover {
background-color: ${theme.menu[$variant].backgroundColor.hover};
color: ${theme.menu[$variant].color.hover};
${ColorPickerWrapper} {
visibility: visible;
}
}
&:active {
Expand Down Expand Up @@ -96,3 +105,8 @@ export const ChevronIcon = styled.path.attrs<ChevronIconProps>(
)<ChevronIconProps>`
stroke: ${({ theme }) => theme.menu.primary.chevronIconColor};
`;

export const ActionsWrapper = styled.div`
display: flex;
align-items: center;
`;
13 changes: 12 additions & 1 deletion frontend/src/components/Nav/Nav.styled.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import styled from 'styled-components';
import { ClusterColorKey } from 'theme/theme';

export const List = styled.ul.attrs({ role: 'menu' })`
padding: 2px 4px 6px 4px;
padding: 2px 4px 6px 8px;
& > & {
padding: 0 0 0 8px;
Expand All @@ -11,3 +12,13 @@ export const List = styled.ul.attrs({ role: 'menu' })`
margin-bottom: 2px;
}
`;

export const ClusterList = styled.ul.attrs<{ $colorKey: ClusterColorKey }>({
role: 'menu',
})`
border-radius: 8px;
padding: 4px 4px 4px 4px;
margin-bottom: 8px;
background-color: ${({ theme, $colorKey }) =>
theme.clusterMenu.backgroundColor[$colorKey]};
`;
7 changes: 6 additions & 1 deletion frontend/src/components/common/Dropdown/Dropdown.styled.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import styled, { css, keyframes } from 'styled-components';
import { ControlledMenu } from '@szhsin/react-menu';
import { menuSelector, menuItemSelector } from '@szhsin/react-menu/style-utils';
import { menuItemSelector, menuSelector } from '@szhsin/react-menu/style-utils';

import '@szhsin/react-menu/dist/core.css';

Expand All @@ -18,6 +18,7 @@ const menuHide = keyframes`
export const Dropdown = styled(ControlledMenu)(
({ theme: { dropdown } }) => css`
// container for the menu items
${menuSelector.name} {
border: 1px solid ${dropdown.borderColor};
box-shadow: 0 4px 16px ${dropdown.shadow};
Expand All @@ -26,6 +27,7 @@ export const Dropdown = styled(ControlledMenu)(
font-size: 14px;
background-color: ${dropdown.backgroundColor};
text-align: left;
cursor: auto;
}
${menuSelector.stateOpening} {
Expand All @@ -34,6 +36,7 @@ export const Dropdown = styled(ControlledMenu)(
// NOTE: animation-fill-mode: forwards is required to
// prevent flickering with React 18 createRoot()
${menuSelector.stateClosing} {
animation: ${menuHide} 0.2s ease-out forwards;
}
Expand Down Expand Up @@ -62,6 +65,8 @@ export const DropdownButton = styled.button`
display: flex;
cursor: pointer;
align-self: center;
padding: 0;
margin: 0 4px;
&:disabled {
opacity: 0.5;
Expand Down
13 changes: 11 additions & 2 deletions frontend/src/components/common/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ interface DropdownProps extends PropsWithChildren<Partial<MenuProps>> {
disabled?: boolean;
}

const Dropdown: React.FC<DropdownProps> = ({ label, disabled, children }) => {
const Dropdown: React.FC<DropdownProps> = ({
label,
disabled,
children,
offsetY,
}) => {
const ref = useRef(null);
const { value: isOpen, setFalse, setTrue } = useBoolean(false);

Expand All @@ -37,8 +42,12 @@ const Dropdown: React.FC<DropdownProps> = ({ label, disabled, children }) => {
onClose={setFalse}
align="end"
direction="bottom"
offsetY={10}
offsetY={offsetY ?? 10}
viewScroll="auto"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
}}
>
{children}
</S.Dropdown>
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/components/common/Icons/ColorPickerIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';

const ColorPickerIcon: React.FC = () => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<circle
cx="8"
cy="8"
r="5.5"
fill="url(#paint0_linear_6232_2411)"
stroke="white"
/>
<defs>
<linearGradient
id="paint0_linear_6232_2411"
x1="3"
y1="8"
x2="13"
y2="8"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FFA012" />
<stop offset="0.485" stopColor="#00D5A2" />
<stop offset="1" stopColor="#127FFF" />
</linearGradient>
</defs>
</svg>
);
};

export default ColorPickerIcon;
2 changes: 1 addition & 1 deletion frontend/src/lib/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { LOCAL_STORAGE_KEY_PREFIX } from 'lib/constants';
import { useState, useEffect } from 'react';
import { useEffect, useState } from 'react';

export const useLocalStorage = (featureKey: string, defaultValue: string) => {
const key = `${LOCAL_STORAGE_KEY_PREFIX}-${featureKey}`;
Expand Down
Loading

0 comments on commit 3c89d07

Please sign in to comment.