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

styled-components.dropdown menu items #616

Open
wants to merge 11 commits into
base: feature/config-provider
Choose a base branch
from
Open
1,154 changes: 766 additions & 388 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@types/react-plotly.js": "^2.2.4",
"@types/react-router-dom": "^5.1.5",
"@types/sinon": "7.0.6",
"@types/styled-components": "^5.1.34",
"@types/uuid": "^9.0.7",
"@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1",
Expand Down Expand Up @@ -97,8 +98,8 @@
"dependencies": {
"@aics/simularium-viewer": "^3.8.4",
"@ant-design/css-animation": "^1.7.3",
"@ant-design/icons": "^4.0.6",
"antd": "^5.21.6",
"@ant-design/icons": "^4.8.3",
"antd": "^5.22.3",
"axios": "^1.7.4",
"bowser": "^2.11.0",
"classnames": "2.2.5",
Expand All @@ -120,6 +121,7 @@
"redux": "4.2.0",
"redux-logic": "^3.0.3",
"reselect": "4.0.0",
"styled-components": "^6.1.13",
"use-debounce": "^9.0.4",
"uuid": "^9.0.1"
}
Expand Down
144 changes: 144 additions & 0 deletions src/components/CustomDropdown/DropdownMenuItems.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import React from "react";
import { Link, LinkProps } from "react-router-dom";
import styled, { css } from "styled-components";
import { ArrowRight } from "../Icons";

// Dropdown items can be of a few component
// varieties, including buttons, router links, and
// anchor tags. This file unifies their styling,
// and then divides them into three component exports
// for semantically explicit usage in dropdowns.

// Common styles
const baseStyles = css`
font-family: ${(props) => props.theme.typography};
Copy link
Contributor Author

@interim17 interim17 Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is our only styled component for now, but we will probably want to add a GlobalStyles provider to get things like basic fonts into all styled components, and it could potentially replace some or all of the global style rules in style.css if we do that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does TS pick up that props is of a specific type, or is it any? Are you able to specify the type of theme?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to intellisense the type isn't any it's ExecutionContext & object which I'm not entirely sure about, I can type theme as DefaultTheme from styled-components in the file where I export it, but that type is already picked up here in this file.

I'm not getting a typescript error, do you type props more explicitly in your implementation of styled-components in TFE?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't used the props.theme before in StyledComponents, I usually am getting my theme variables out of a context. Just curious if Intellisense/TS picked up the Defaulttheme type!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It did, and the ThemeProvider is a essentially a context

background: none;
border: 2px solid ${({ theme }) => theme.colors.dropdown.background};
border-radius: 3px;
color: ${({ theme }) => theme.colors.dropdown.text};
cursor: pointer;
height: 28px;
padding: 6px;
width: 100%;
min-width: 177px;
font-size: 14px;

&&& {
Copy link
Contributor Author

@interim17 interim17 Dec 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

&& (or here &&&) This is a nice way in sass/styled-components to do a "precedence boost" without chasing down lots of antd class names, and without using !important which overlooks specificity entirely.

https://styled-components.com/docs/basics#adapting-based-on-props

&:focus-visible,
&:focus-visible:hover {
outline: 1.5px solid ${({ theme }) => theme.colors.dropdown.active};
border: 2px solid ${({ theme }) => theme.colors.dropdown.background};
color: ${({ theme }) => theme.colors.dropdown.activeTextColor};
background-color: ${({ theme }) => theme.colors.dropdown.active};

svg {
fill: ${({ theme }) => theme.colors.dropdown.background};
}
}

&:hover:not(:focus-visible) {
background-color: ${({ theme }) => theme.colors.dropdown.active};
color: ${({ theme }) => theme.colors.dropdown.activeTextColor};
border-color: ${({ theme }) => theme.colors.dropdown.active};

svg {
fill: ${({ theme }) => theme.colors.dropdown.background};
}
}
}
`;

const contentStyles = css`
display: flex;
align-items: center;
gap: 8px;
justify-content: space-between;
color: inherit;

svg {
font-size: 10px;
}
`;

// Styled components
const StyledDropdownButton = styled.button`
${baseStyles}
${contentStyles}
`;

const StyledRouterLink = styled(Link)`
${baseStyles}
${contentStyles}
`;

const StyledExternalLink = styled.a`
${baseStyles}
${contentStyles}
`;
Comment on lines +64 to +77
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✨✨✨✨✨✨


// Typing for the props of each variant
interface BaseDropdownItemProps {
children: React.ReactNode;
onClick?: () => void;
className?: string;
}

interface ButtonProps extends BaseDropdownItemProps {
isSubmenuTrigger?: boolean;
}

interface RouterProps extends BaseDropdownItemProps {
to: LinkProps["to"];
newTab?: boolean;
}

interface AnchorProps extends Omit<BaseDropdownItemProps, "onClick"> {
href: string;
newTab?: boolean;
}

const getNewTabAttributes = (newTab?: boolean) =>
newTab ? { target: "_blank", rel: "noopener noreferrer" } : {};

// Components for use in dropdown menus:
export const DropdownButton: React.FC<ButtonProps> = ({
children,
className,
onClick,
isSubmenuTrigger,
}) => (
<StyledDropdownButton type="button" onClick={onClick} className={className}>
{children}
{isSubmenuTrigger && ArrowRight}
</StyledDropdownButton>
);

export const DropdownRouterLink: React.FC<RouterProps> = ({
children,
className,
to,
newTab,
}) => (
<StyledRouterLink
to={to}
className={className}
{...getNewTabAttributes(newTab)}
>
{children}
</StyledRouterLink>
);

export const DropdownAnchor: React.FC<AnchorProps> = ({
children,
className,
href,
newTab,
}) => (
<StyledExternalLink
href={href}
className={className}
{...getNewTabAttributes(newTab)}
>
{children}
</StyledExternalLink>
);
3 changes: 3 additions & 0 deletions src/components/CustomDropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ const CustomDropdown: React.FC<CustomDropdownProps> = ({
menu={{ items, theme: "dark", className: styles.menu }}
placement={placement}
disabled={disabled}
dropdownRender={(menu) => (
<div className={styles.menuWrapper}>{menu}</div>
)}
>
<NavButton
titleText={titleText}
Expand Down
44 changes: 9 additions & 35 deletions src/components/CustomDropdown/style.css
Original file line number Diff line number Diff line change
@@ -1,44 +1,18 @@
.menu {
.menu-wrapper {
width: fit-content;
background-color: var(--dark-theme-dropdown-menu-bg);
border-radius: 3px;
padding: 8px;
}

.menu svg {
fill: var(--dark-theme-menu-text-color);
padding: 4px;
}

.menu :global(.ant-dropdown-menu-item),
.menu :global(.ant-dropdown-menu-submenu) {
border: 2px solid var(--dark-theme-dropdown-menu-item-bg);
border-radius: 4px;
height: 32px;
.menu {
background-color: inherit;
display: flex;
flex-direction: column;
gap: 4px;
}

.menu :global(.ant-dropdown-menu-item) :global(.ant-btn) {
color: var(--dark-theme-dropdown-menu-item-color);
.menu-wrapper .menu :global(.ant-dropdown-menu-submenu-title),
.menu-wrapper .menu :global(.ant-dropdown-menu-item-only-child) {
padding: 0px;
}

.menu :global(.ant-dropdown-menu-item):hover,
.menu :global(.ant-dropdown-menu-submenu):hover {
background-color: var(--dark-theme-dropdown-menu-item-hover-bg);
}

.menu :global(.ant-dropdown-menu-item):hover *,
.menu :global(.ant-dropdown-menu-submenu):hover * {
color: var(--dark-theme-dropdown-menu-item-hover-color);
fill: var(--dark-theme-dropdown-menu-item-hover-color);
}

.menu :global(.ant-dropdown-menu-item):focus-within,
.menu :global(.ant-dropdown-menu-submenu):focus-within {
z-index: 1000;
background-color: var(--dark-theme-dropdown-menu-item-focus-bg);
outline: 1px solid var(--dark-theme-dropdown-menu-item-focus-outline);
}

.menu :global(.ant-dropdown-menu-item) *:focus-visible {
color: var(--dark-theme-dropdown-menu-item-focus-color);
}
61 changes: 28 additions & 33 deletions src/components/HelpMenu/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from "react";
import { useLocation, Link } from "react-router-dom";
import { useLocation } from "react-router-dom";
import { MenuProps } from "antd";

import { TUTORIAL_PATHNAME } from "../../routes";
Expand All @@ -12,24 +12,26 @@ import {
import { ButtonClass } from "../../constants/interfaces";
import { DownArrow } from "../Icons";
import VersionModal from "../VersionModal";
import NavButton from "../NavButton";
import CustomDropdown from "../CustomDropdown";
import {
DropdownAnchor,
DropdownButton,
DropdownRouterLink,
} from "../CustomDropdown/DropdownMenuItems";

const HelpMenu = (): JSX.Element => {
const [modalVisible, setModalVisible] = React.useState(false);

const location = useLocation();
const tutorialLink =
location.pathname === "/viewer" ? (
<Link
to={TUTORIAL_PATHNAME}
target="_blank"
rel="noopener noreferrer"
>
<DropdownRouterLink to={TUTORIAL_PATHNAME} newTab={true}>
Quick start
</Link>
</DropdownRouterLink>
) : (
<Link to={TUTORIAL_PATHNAME}>Quick start</Link>
<DropdownRouterLink to={TUTORIAL_PATHNAME}>
Quick start
</DropdownRouterLink>
);

const items: MenuProps["items"] = [
Expand All @@ -40,60 +42,53 @@ const HelpMenu = (): JSX.Element => {
{
key: "forum",
label: (
<a href={FORUM_URL} target="_blank" rel="noopener noreferrer">
<DropdownAnchor href={FORUM_URL} newTab={true}>
Forum
</a>
</DropdownAnchor>
),
},
{
key: "github",
label: (
<a href={GITHUB_URL} target="_blank" rel="noopener noreferrer">
<DropdownAnchor href={GITHUB_URL} newTab={true}>
GitHub
</a>
</DropdownAnchor>
),
},
{
key: "submit-issue",
label: "Submit issue",
label: (
<DropdownButton isSubmenuTrigger={true}>
Submit issue
</DropdownButton>
),
expandIcon: false,
popupOffset: [-0.45, -4],
children: [
{
key: "via-github",
label: (
<a
href={ISSUE_URL}
target="_blank"
rel="noopener noreferrer"
>
<DropdownAnchor href={ISSUE_URL}>
via GitHub (preferred)
</a>
</DropdownAnchor>
),
},
{
key: "via-forum",
label: (
<a
href={FORUM_BUG_REPORT_URL}
target="_blank"
rel="noopener noreferrer"
>
<DropdownAnchor href={FORUM_BUG_REPORT_URL}>
via Forum (for non-GitHub users)
</a>
</DropdownAnchor>
),
},
],
},
{
key: "version",
label: (
<NavButton
titleText={"Version info"}
clickHandler={() => {
setModalVisible(!modalVisible);
}}
buttonType={ButtonClass.DropdownItem}
/>
<DropdownButton onClick={() => setModalVisible(!modalVisible)}>
Version info
</DropdownButton>
),
},
];
Expand Down
2 changes: 2 additions & 0 deletions src/components/Icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
CaretDownFilled,
FullscreenExitOutlined,
FullscreenOutlined,
RightOutlined,
} from "@ant-design/icons";

import PurpleArrowPointingRight from "../../assets/open-arrow.svg";
Expand Down Expand Up @@ -57,6 +58,7 @@ export const DownCaret = (
export const FilledCaret = <CaretDownFilled />;
export const FullScreen = <FullscreenOutlined />;
export const ExitFullScreen = <FullscreenExitOutlined />;
export const ArrowRight = <RightOutlined />;

export default {
Play,
Expand Down
Loading
Loading