Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@
"@tiptap/starter-kit": "^3.14.0",
"check-password-strength": "^2.0.10",
"fetch-event-stream": "^0.1.5",
"framer-motion": "^12.23.25",
"graphql-ws": "^5.5.5",
"jotai": "^2.12.2",
"lottie-react": "^2.4.1",
Expand All @@ -140,6 +141,7 @@
"recharts": "^3.1.2",
"remark-gfm": "^3.0.1",
"uuid": "^8.3.2",
"vaul": "^1.1.2",
"web-vitals": "^3.5.0",
"webextension-polyfill": "^0.12.0",
"zod": "4.1.8"
Expand Down
23 changes: 7 additions & 16 deletions packages/shared/src/components/Feed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import {
useFeedLayout,
useFeedVotePost,
useMutationSubscription,
useViewSize,
ViewSize,
} from '../hooks';
import { useProfileCompletionCard } from '../hooks/profile/useProfileCompletionCard';
import type { AllFeedPages } from '../lib/query';
Expand Down Expand Up @@ -188,6 +190,8 @@ export default function Feed<T>({
const { user } = useContext(AuthContext);
const { isFallback, query: routerQuery } = useRouter();
const { openNewTab, spaciness, loadedSettings } = useContext(SettingsContext);
const isLaptopView = useViewSize(ViewSize.Laptop);
const isLaptop = isNullOrUndefined(isLaptopView) || isLaptopView;
const { isListMode } = useFeedLayout();
const numCards = currentSettings.numCards[spaciness ?? 'eco'];
const isSquadFeed = feedName === OtherFeedPage.Squad;
Expand Down Expand Up @@ -430,18 +434,6 @@ export default function Feed<T>({
}
}, [canFetchMore, isFetching, trackFinishFeed]);

useEffect(() => {
return () => {
document.body.classList.remove('hidden-scrollbar');
};
}, []);

useEffect(() => {
if (!selectedPost) {
document.body.classList.remove('hidden-scrollbar');
}
}, [selectedPost]);

const onShareClick = useCallback(
(post: Post, row?: number, column?: number) =>
openSharePost({ post, columns: virtualizedNumCards, column, row }),
Expand All @@ -463,7 +455,6 @@ export default function Feed<T>({
row?: number;
column?: number;
}) => {
document.body.classList.add('hidden-scrollbar');
callback?.();
setPostModalIndex({ index, row, column });
onOpenModal(index);
Expand All @@ -484,7 +475,7 @@ export default function Feed<T>({
await onPostClick(post, index, row, column, {
skipPostUpdate: true,
});
if (!isAuxClick && !shouldUseListFeedLayout) {
if (!isAuxClick && (!shouldUseListFeedLayout || !isLaptop)) {
onPostModalOpen({ index, row, column });
}
};
Expand Down Expand Up @@ -515,7 +506,7 @@ export default function Feed<T>({
is_ad: isAd,
}),
);
if (!shouldUseListFeedLayout) {
if (!shouldUseListFeedLayout || !isLaptop) {
onPostModalOpen({ index, row, column });
}
};
Expand Down Expand Up @@ -627,7 +618,7 @@ export default function Feed<T>({
{!isFetching && !isInitialLoading && !isHorizontal && (
<InfiniteScrollScreenOffset ref={infiniteScrollRef} />
)}
{!shouldUseListFeedLayout && selectedPost && PostModal && (
{(!shouldUseListFeedLayout || !isLaptop) && selectedPost && PostModal && (
<PostModal
isOpen={!!selectedPost}
id={selectedPost.id}
Expand Down
48 changes: 27 additions & 21 deletions packages/shared/src/components/cards/article/ArticleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { HIGH_PRIORITY_IMAGE_PROPS } from '../../image/Image';
import { ClickbaitShield } from '../common/ClickbaitShield';
import { useSmartTitle } from '../../../hooks/post/useSmartTitle';
import { isSourceUserSource } from '../../../graphql/sources';
import { motion } from 'framer-motion';

export const ArticleList = forwardRef(function ArticleList(
{
Expand Down Expand Up @@ -103,7 +104,9 @@ export const ArticleList = forwardRef(function ArticleList(
...domProps,
style,
className,
}}
layoutId: `post-card-${post.id}`,
transition: { type: 'spring', stiffness: 400, damping: 35 },
} as any}
ref={ref}
flagProps={{ pinnedAt, trending, type }}
linkProps={
Expand Down Expand Up @@ -144,6 +147,7 @@ export const ArticleList = forwardRef(function ArticleList(
<CardContent>
<div className="mr-4 flex flex-1 flex-col">
<CardTitle
layoutId={`post-title-${post.id}`}
lineClamp={undefined}
className={!!post.read && 'text-text-tertiary'}
>
Expand All @@ -160,26 +164,28 @@ export const ArticleList = forwardRef(function ArticleList(
{!isMobile && actionButtons}
</div>

<CardCoverList
data-testid="postImage"
isVideoType={isVideoType}
onShare={onShare}
post={post}
imageProps={{
alt: 'Post Cover image',
className: classNames(
'mobileXXL:self-start',
!isVideoType && 'mt-4',
),
...(eagerLoadImage
? HIGH_PRIORITY_IMAGE_PROPS
: { loading: 'lazy' }),
src: post.image,
}}
videoProps={{
className: 'mt-4 mobileXL:w-40 mobileXXL:w-56 !h-fit',
}}
/>
<motion.div layoutId={`post-cover-${post.id}`} transition={{ type: 'spring', stiffness: 400, damping: 35 }} className="w-full mobileXL:w-auto">
<CardCoverList
data-testid="postImage"
isVideoType={isVideoType}
onShare={onShare}
post={post}
imageProps={{
alt: 'Post Cover image',
className: classNames(
'mobileXXL:self-start',
!isVideoType && 'mt-4',
),
...(eagerLoadImage
? HIGH_PRIORITY_IMAGE_PROPS
: { loading: 'lazy' }),
src: post.image,
}}
videoProps={{
className: 'mt-4 mobileXL:w-40 mobileXXL:w-56 !h-fit',
}}
/>
</motion.div>
</CardContent>
</CardContainer>
{isMobile && actionButtons}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export const PostCardHeader = ({
<>
{highlightBookmarkedPost && <BookmakProviderHeader />}
<CardHeader
layoutId={`post-author-${post.id}`}
transition={{ type: 'spring', stiffness: 400, damping: 35 }}
className={classNames(
className,
highlightBookmarkedPost && headerHiddenClassName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ function FeedItemContainer(
<Link href={linkProps.href}>
<CardLink
{...linkProps}
onClick={(event) => {
if (
event.ctrlKey ||
event.metaKey ||
event.shiftKey ||
event.altKey
) {
linkProps.onClick?.(event);
return;
}

event.preventDefault();

const card = (event.currentTarget as HTMLElement).closest(
'article',
);

linkProps.onClick?.(event);
}}
onFocus={() => setFocus(true)}
onBlur={() => setFocus(false)}
/>
Expand Down
17 changes: 12 additions & 5 deletions packages/shared/src/components/cards/common/list/ListCard.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
import type { HTMLAttributes, ReactNode } from 'react';
import React from 'react';
import classNames from 'classnames';
import { motion } from 'framer-motion';
import type { ReactElement } from 'react-markdown/lib/react-markdown';
import classed from '../../../../lib/classed';
import { Image } from '../../../image/Image';

type TitleProps = HTMLAttributes<HTMLHeadingElement> & {
lineClamp?: `line-clamp-${number}`;
children: ReactNode;
layoutId?: string;
};

const SHARED_TRANSITION = { type: 'spring', stiffness: 400, damping: 35 };

const Title = ({
className,
lineClamp = 'line-clamp-3',
children,
layoutId,
...rest
}: TitleProps): ReactElement => {
return (
<h3
<motion.h3
{...rest}
transition={SHARED_TRANSITION}
layoutId={layoutId}
className={classNames(
'multi-truncate font-bold text-text-primary typo-title3',
lineClamp,
className,
)}
>
{children}
</h3>
</motion.h3>
);
};

Expand All @@ -37,7 +44,7 @@ export const CardContainer = classed('div', 'flex flex-col');
export const CardContent = classed('div', 'flex flex-col mobileXL:flex-row');

export const CardImage = classed(
Image,
motion(Image) as any,
'rounded-12 min-h-[10rem] max-h-[12.5rem] object-cover w-full h-auto mobileXL:max-h-40 mobileXL:w-40 mobileXXL:max-h-56 mobileXXL:w-56',
);

Expand All @@ -50,10 +57,10 @@ const clickableCardClasses = classNames(
export const CardLink = classed('a', clickableCardClasses);

export const ListCard = classed(
'article',
motion.article as any,
`group relative w-full flex flex-col py-6 px-4 border-t border-border-subtlest-tertiary rounded-16
hover:bg-surface-float
`,
);

export const CardHeader = classed('div', 'flex flex-row items-center mb-2');
export const CardHeader = classed(motion.div as any, 'flex flex-row items-center mb-2');
36 changes: 36 additions & 0 deletions packages/shared/src/components/modals/BasePostModal.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,41 @@
padding: 0;
overflow-y: auto;
z-index: 100;
opacity: 0;
transition: opacity 250ms ease-out;
}

& :global(.post-modal-overlay.ReactModal__Overlay--after-open) {
opacity: 1;
}

& :global(.post-modal-overlay.ReactModal__Overlay--before-close) {
opacity: 0;
transition: opacity 150ms ease-in;
}

& :global(.post-modal-overlay .modal) {
opacity: 1;
}

/* Laptop: subtle slide-up animation since there's no morph overlay */
@media (min-width: 64rem) {
& :global(.post-modal-overlay .modal) {
opacity: 0;
transform: translateY(0.75rem) scale(0.99);
transition:
transform 260ms cubic-bezier(0.22, 1, 0.36, 1),
opacity 260ms cubic-bezier(0.22, 1, 0.36, 1);
}

& :global(.post-modal-overlay.ReactModal__Overlay--after-open .modal) {
opacity: 1;
transform: none;
}

& :global(.post-modal-overlay.ReactModal__Overlay--before-close .modal) {
opacity: 0;
transform: translateY(0.75rem) scale(0.99);
}
}
}
13 changes: 13 additions & 0 deletions packages/shared/src/components/modals/BasePostModal.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ReactElement } from 'react';
import React, { useState, useCallback } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import classNames from 'classnames';
import type { ModalProps } from './common/Modal';
import { Modal } from './common/Modal';
Expand Down Expand Up @@ -28,6 +29,8 @@ interface BasePostModalProps extends ModalProps {
post: Post;
}

const SHARED_TRANSITION = { type: 'spring', stiffness: 400, damping: 35 };

function BasePostModal({
className,
children,
Expand Down Expand Up @@ -81,10 +84,20 @@ function BasePostModal({
<Modal
size={Modal.Size.XLarge}
kind={Modal.Kind.FlexibleTop}
closeTimeoutMS={300}
portalClassName={styles.postModal}
id="post-modal"
overlayRef={setScrollNode}
{...props}
contentElement={(contentProps, contentChildren) => (
<AnimatePresence>
{props.isOpen && (
<motion.div {...contentProps as any} layoutId={`post-card-${post?.id}`} transition={SHARED_TRANSITION}>
{contentChildren}
</motion.div>
)}
</AnimatePresence>
)}
overlayClassName="post-modal-overlay bg-overlay-quaternary-onion"
className={classNames(
className,
Expand Down
4 changes: 4 additions & 0 deletions packages/shared/src/components/post/BasePostContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import PostEngagements from './PostEngagements';
import type { BasePostContentProps } from './common';
import { PostHeaderActions } from './PostHeaderActions';
import { ButtonSize } from '../buttons/common';
import { PostBottomAction } from './PostBottomAction';

const Custom404 = dynamic(
() => import(/* webpackChunkName: "custom404" */ '../Custom404'),
Expand Down Expand Up @@ -64,6 +65,9 @@ export function BasePostContent({
shouldOnboardAuthor={shouldOnboardAuthor}
/>
)}
{!isPostPage && !!navigationProps?.onClose && (
<PostBottomAction onAction={() => navigationProps.onClose?.(null as any)} />
)}
</>
);
}
Loading