import { transparentize } from "polished"; import * as React from "react"; import styled, { useTheme, css } from "styled-components"; import breakpoint from "styled-components-breakpoint"; import EventBoundary from "~/components/EventBoundary"; import NudeButton from "~/components/NudeButton"; import { NavigationNode } from "~/types"; import NavLink, { Props as NavLinkProps } from "./NavLink"; export type DragObject = NavigationNode & { depth: number; active: boolean; collectionId: string; }; type Props = Omit & { to?: string | Record; href?: string | Record; innerRef?: (arg0: HTMLElement | null | undefined) => void; onClick?: React.MouseEventHandler; onMouseEnter?: React.MouseEventHandler; icon?: React.ReactNode; label?: React.ReactNode; menu?: React.ReactNode; showActions?: boolean; active?: boolean; isActiveDrop?: boolean; isDraft?: boolean; depth?: number; scrollIntoViewIfNeeded?: boolean; }; const activeDropStyle = { fontWeight: 600, }; function SidebarLink( { icon, onClick, onMouseEnter, to, label, active, isActiveDrop, isDraft, menu, showActions, exact, href, depth, className, ...rest }: Props, ref: React.RefObject ) { const theme = useTheme(); const style = React.useMemo( () => ({ paddingLeft: `${(depth || 0) * 16 + 12}px`, }), [depth] ); const activeStyle = React.useMemo( () => ({ fontWeight: 600, color: theme.text, background: theme.sidebarItemBackground, ...style, }), [theme, style] ); return ( <> {icon && {icon}} {menu && {menu}} ); } // accounts for whitespace around icon const IconWrapper = styled.span` margin-left: -4px; margin-right: 4px; height: 24px; overflow: hidden; flex-shrink: 0; `; const Actions = styled(EventBoundary)<{ showActions?: boolean }>` display: ${(props) => (props.showActions ? "inline-flex" : "none")}; position: absolute; top: 4px; right: 4px; color: ${(props) => props.theme.textTertiary}; transition: opacity 50ms; svg { color: ${(props) => props.theme.textSecondary}; fill: currentColor; opacity: 0.5; } &:hover { display: inline-flex; svg { opacity: 0.75; } } `; const Link = styled(NavLink)<{ $isActiveDrop?: boolean; $isDraft?: boolean }>` display: flex; position: relative; text-overflow: ellipsis; padding: 6px 16px; border-radius: 4px; transition: background 50ms, color 50ms; user-select: none; background: ${(props) => props.$isActiveDrop ? props.theme.slateDark : "inherit"}; color: ${(props) => props.$isActiveDrop ? props.theme.white : props.theme.sidebarText}; font-size: 16px; cursor: pointer; overflow: hidden; ${(props) => props.$isDraft && css` padding: 4px 14px; border: 1px dashed ${props.theme.sidebarDraftBorder}; `} svg { ${(props) => (props.$isActiveDrop ? `fill: ${props.theme.white};` : "")} transition: fill 50ms; } &:focus { color: ${(props) => props.theme.text}; background: ${(props) => transparentize("0.25", props.theme.sidebarItemBackground)}; } & + ${Actions} { ${NudeButton} { background: ${(props) => props.theme.sidebarBackground}; } } &:focus + ${Actions} { ${NudeButton} { background: ${(props) => transparentize("0.25", props.theme.sidebarItemBackground)}; } } &[aria-current="page"] + ${Actions} { ${NudeButton} { background: ${(props) => props.theme.sidebarItemBackground}; } } ${breakpoint("tablet")` padding: 4px 8px 4px 16px; font-size: 15px; `} @media (hover: hover) { &:hover + ${Actions}, &:active + ${Actions} { display: inline-flex; svg { opacity: 0.75; } } &:hover { color: ${(props) => props.$isActiveDrop ? props.theme.white : props.theme.text}; } } `; const Label = styled.div` position: relative; width: 100%; max-height: 4.8em; line-height: 1.6; * { unicode-bidi: plaintext; } `; export default React.forwardRef(SidebarLink);