fix: hover card styling
This commit is contained in:
@@ -1,9 +1,16 @@
|
||||
import { transparentize } from "polished";
|
||||
import * as React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import styled from "styled-components";
|
||||
import { s } from "@shared/styles";
|
||||
import Text from "~/components/Text";
|
||||
|
||||
export const CARD_PADDING = 16;
|
||||
|
||||
export const CARD_WIDTH = 375;
|
||||
|
||||
export const THUMBNAIL_HEIGHT = 200;
|
||||
|
||||
const StyledText = styled(Text)`
|
||||
margin-bottom: 0;
|
||||
padding-top: 0.125em;
|
||||
@@ -11,7 +18,11 @@ const StyledText = styled(Text)`
|
||||
|
||||
export const Preview = styled(Link)`
|
||||
cursor: var(--pointer);
|
||||
margin-bottom: 0;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 30px 90px -20px rgba(0, 0, 0, 0.3),
|
||||
0 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
position: absolute;
|
||||
${(props) => (!props.to ? "pointer-events: none;" : "")}
|
||||
`;
|
||||
|
||||
@@ -21,7 +32,7 @@ export const Title = styled.h2`
|
||||
color: ${s("text")};
|
||||
`;
|
||||
|
||||
export const Info: React.FC = ({ children }) => (
|
||||
export const Info: React.FC = ({ children }: { children: string }) => (
|
||||
<StyledText type="tertiary" size="xsmall">
|
||||
{children}
|
||||
</StyledText>
|
||||
@@ -34,3 +45,57 @@ export const Description: React.FC = styled(StyledText)`
|
||||
export const DescriptionContainer = styled.div`
|
||||
margin-top: 0.5em;
|
||||
`;
|
||||
|
||||
export const CardContent = styled.div`
|
||||
overflow: hidden;
|
||||
max-height: 20.5em;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
// &:after — gradient mask for overflow text
|
||||
export const Card = styled.div<{ fadeOut?: boolean; $borderRadius?: string }>`
|
||||
backdrop-filter: blur(10px);
|
||||
background: ${(props) => props.theme.menuBackground};
|
||||
padding: ${CARD_PADDING}px;
|
||||
width: ${CARD_WIDTH}px;
|
||||
font-size: 0.9em;
|
||||
position: relative;
|
||||
|
||||
.placeholder,
|
||||
.heading-anchor {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// fills the gap between the card and pointer to avoid a dead zone
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.fadeOut !== false
|
||||
? `&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
${transparentize(1, props.theme.menuBackground)} 0%,
|
||||
${transparentize(1, props.theme.menuBackground)} 75%,
|
||||
${props.theme.menuBackground} 90%
|
||||
);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1.7em;
|
||||
border-bottom: 16px solid ${props.theme.menuBackground};
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}`
|
||||
: ""}
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { m } from "framer-motion";
|
||||
import { transparentize } from "polished";
|
||||
import * as React from "react";
|
||||
import { Portal } from "react-portal";
|
||||
import styled from "styled-components";
|
||||
@@ -12,14 +11,13 @@ import useOnClickOutside from "~/hooks/useOnClickOutside";
|
||||
import useRequest from "~/hooks/useRequest";
|
||||
import useStores from "~/hooks/useStores";
|
||||
import { client } from "~/utils/ApiClient";
|
||||
import { CARD_WIDTH, CARD_PADDING } from "./Components";
|
||||
import HoverPreviewDocument from "./HoverPreviewDocument";
|
||||
import HoverPreviewLink from "./HoverPreviewLink";
|
||||
import HoverPreviewMention from "./HoverPreviewMention";
|
||||
|
||||
const DELAY_OPEN = 300;
|
||||
const DELAY_CLOSE = 600;
|
||||
const CARD_PADDING = 16;
|
||||
const CARD_MAX_WIDTH = 375;
|
||||
|
||||
type Props = {
|
||||
/* The HTML element that is being hovered over */
|
||||
@@ -123,10 +121,7 @@ function HoverPreviewInternal({ element, onClose }: Props) {
|
||||
const elemBounds = element.getBoundingClientRect();
|
||||
const cardBounds = cardRef.current?.getBoundingClientRect();
|
||||
const left = cardBounds
|
||||
? Math.min(
|
||||
elemBounds.left,
|
||||
window.innerWidth - CARD_PADDING - CARD_MAX_WIDTH
|
||||
)
|
||||
? Math.min(elemBounds.left, window.innerWidth - CARD_PADDING - CARD_WIDTH)
|
||||
: elemBounds.left;
|
||||
const leftOffset = elemBounds.left - left;
|
||||
|
||||
@@ -151,33 +146,29 @@ function HoverPreviewInternal({ element, onClose }: Props) {
|
||||
initial={{ opacity: 0, y: -20, pointerEvents: "none" }}
|
||||
animate={{ opacity: 1, y: 0, pointerEvents: "auto" }}
|
||||
>
|
||||
<Card fadeOut={data.type !== UnfurlType.Mention}>
|
||||
<CardContent>
|
||||
{data.type === UnfurlType.Mention ? (
|
||||
<HoverPreviewMention
|
||||
url={data.thumbnailUrl}
|
||||
title={data.title}
|
||||
info={data.meta.info}
|
||||
color={data.meta.color}
|
||||
/>
|
||||
) : data.type === UnfurlType.Document ? (
|
||||
<HoverPreviewDocument
|
||||
id={data.meta.id}
|
||||
url={data.url}
|
||||
title={data.title}
|
||||
description={data.description}
|
||||
info={data.meta.info}
|
||||
/>
|
||||
) : (
|
||||
<HoverPreviewLink
|
||||
url={data.url}
|
||||
thumbnailUrl={data.thumbnailUrl}
|
||||
title={data.title}
|
||||
description={data.description}
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
{data.type === UnfurlType.Mention ? (
|
||||
<HoverPreviewMention
|
||||
url={data.thumbnailUrl}
|
||||
title={data.title}
|
||||
info={data.meta.info}
|
||||
color={data.meta.color}
|
||||
/>
|
||||
) : data.type === UnfurlType.Document ? (
|
||||
<HoverPreviewDocument
|
||||
id={data.meta.id}
|
||||
url={data.url}
|
||||
title={data.title}
|
||||
description={data.description}
|
||||
info={data.meta.info}
|
||||
/>
|
||||
) : (
|
||||
<HoverPreviewLink
|
||||
url={data.url}
|
||||
thumbnailUrl={data.thumbnailUrl}
|
||||
title={data.title}
|
||||
description={data.description}
|
||||
/>
|
||||
)}
|
||||
<Pointer offset={leftOffset + elemBounds.width / 2} />
|
||||
</Animate>
|
||||
) : null}
|
||||
@@ -202,64 +193,6 @@ const Animate = styled(m.div)`
|
||||
}
|
||||
`;
|
||||
|
||||
const CardContent = styled.div`
|
||||
overflow: hidden;
|
||||
max-height: 20.5em;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
// &:after — gradient mask for overflow text
|
||||
const Card = styled.div<{ fadeOut?: boolean }>`
|
||||
backdrop-filter: blur(10px);
|
||||
background: ${(props) => props.theme.menuBackground};
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 30px 90px -20px rgba(0, 0, 0, 0.3),
|
||||
0 0 1px 1px rgba(0, 0, 0, 0.05);
|
||||
padding: ${CARD_PADDING}px;
|
||||
min-width: 350px;
|
||||
max-width: ${CARD_MAX_WIDTH}px;
|
||||
font-size: 0.9em;
|
||||
position: relative;
|
||||
|
||||
.placeholder,
|
||||
.heading-anchor {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// fills the gap between the card and pointer to avoid a dead zone
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
${(props) =>
|
||||
props.fadeOut !== false
|
||||
? `&:after {
|
||||
content: "";
|
||||
display: block;
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
${transparentize(1, props.theme.menuBackground)} 0%,
|
||||
${transparentize(1, props.theme.menuBackground)} 75%,
|
||||
${props.theme.menuBackground} 90%
|
||||
);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1.7em;
|
||||
border-bottom: 16px solid ${props.theme.menuBackground};
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
}`
|
||||
: ""}
|
||||
`;
|
||||
|
||||
const Position = styled.div<{ fixed?: boolean; top?: number; left?: number }>`
|
||||
margin-top: 10px;
|
||||
position: ${({ fixed }) => (fixed ? "fixed" : "absolute")};
|
||||
|
||||
@@ -1,7 +1,14 @@
|
||||
import * as React from "react";
|
||||
import Editor from "~/components/Editor";
|
||||
import Flex from "~/components/Flex";
|
||||
import { Preview, Title, Info, DescriptionContainer } from "./Components";
|
||||
import {
|
||||
Preview,
|
||||
Title,
|
||||
Info,
|
||||
DescriptionContainer,
|
||||
Card,
|
||||
CardContent,
|
||||
} from "./Components";
|
||||
|
||||
type Props = {
|
||||
/** Document id associated with the editor, if any */
|
||||
@@ -19,20 +26,24 @@ type Props = {
|
||||
function HoverPreviewDocument({ id, url, title, info, description }: Props) {
|
||||
return (
|
||||
<Preview to={url}>
|
||||
<Flex column>
|
||||
<Title>{title}</Title>
|
||||
<Info>{info}</Info>
|
||||
<DescriptionContainer>
|
||||
<React.Suspense fallback={<div />}>
|
||||
<Editor
|
||||
key={id}
|
||||
defaultValue={description}
|
||||
embedsDisabled
|
||||
readOnly
|
||||
/>
|
||||
</React.Suspense>
|
||||
</DescriptionContainer>
|
||||
</Flex>
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Flex column>
|
||||
<Title>{title}</Title>
|
||||
<Info>{info}</Info>
|
||||
<DescriptionContainer>
|
||||
<React.Suspense fallback={<div />}>
|
||||
<Editor
|
||||
key={id}
|
||||
defaultValue={description}
|
||||
embedsDisabled
|
||||
readOnly
|
||||
/>
|
||||
</React.Suspense>
|
||||
</DescriptionContainer>
|
||||
</Flex>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Preview>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import Img from "@shared/editor/components/Img";
|
||||
import Flex from "~/components/Flex";
|
||||
import { Preview, Title, Description } from "./Components";
|
||||
import {
|
||||
Preview,
|
||||
Title,
|
||||
Description,
|
||||
Card,
|
||||
CardContent,
|
||||
CARD_WIDTH,
|
||||
THUMBNAIL_HEIGHT,
|
||||
} from "./Components";
|
||||
|
||||
type Props = {
|
||||
/** Link url */
|
||||
@@ -16,16 +25,26 @@ type Props = {
|
||||
|
||||
function HoverPreviewLink({ url, thumbnailUrl, title, description }: Props) {
|
||||
return (
|
||||
<Preview to={url}>
|
||||
<Flex gap={12} column>
|
||||
<Img src={thumbnailUrl} alt={""} />
|
||||
<Flex column>
|
||||
<Title>{title}</Title>
|
||||
<Description>{description}</Description>
|
||||
</Flex>
|
||||
<Preview to={{ pathname: url }} target="_blank" rel="noopener noreferrer">
|
||||
<Flex column>
|
||||
{thumbnailUrl ? <Thumbnail src={thumbnailUrl} alt={""} /> : null}
|
||||
<Card>
|
||||
<CardContent>
|
||||
<Flex column>
|
||||
<Title>{title}</Title>
|
||||
<Description>{description}</Description>
|
||||
</Flex>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Flex>
|
||||
</Preview>
|
||||
);
|
||||
}
|
||||
|
||||
const Thumbnail = styled(Img)`
|
||||
object-fit: cover;
|
||||
max-width: ${CARD_WIDTH}px;
|
||||
height: ${THUMBNAIL_HEIGHT}px;
|
||||
`;
|
||||
|
||||
export default HoverPreviewLink;
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as React from "react";
|
||||
import Avatar from "~/components/Avatar";
|
||||
import { AvatarSize } from "~/components/Avatar/Avatar";
|
||||
import Flex from "~/components/Flex";
|
||||
import { Preview, Title, Info } from "./Components";
|
||||
import { Preview, Title, Info, Card, CardContent } from "./Components";
|
||||
|
||||
type Props = {
|
||||
/** Resource url, avatar url in case of user mention */
|
||||
@@ -18,20 +18,24 @@ type Props = {
|
||||
function HoverPreviewMention({ url, title, info, color }: Props) {
|
||||
return (
|
||||
<Preview to="">
|
||||
<Flex gap={12}>
|
||||
<Avatar
|
||||
model={{
|
||||
avatarUrl: url,
|
||||
initial: title ? title[0] : "?",
|
||||
color,
|
||||
}}
|
||||
size={AvatarSize.XLarge}
|
||||
/>
|
||||
<Flex column>
|
||||
<Title>{title}</Title>
|
||||
<Info>{info}</Info>
|
||||
</Flex>
|
||||
</Flex>
|
||||
<Card fadeOut={false}>
|
||||
<CardContent>
|
||||
<Flex gap={12}>
|
||||
<Avatar
|
||||
model={{
|
||||
avatarUrl: url,
|
||||
initial: title ? title[0] : "?",
|
||||
color,
|
||||
}}
|
||||
size={AvatarSize.XLarge}
|
||||
/>
|
||||
<Flex column>
|
||||
<Title>{title}</Title>
|
||||
<Info>{info}</Info>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Preview>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user