Skip to content

Commit

Permalink
feat: home page redesign
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Apr 16, 2024
1 parent 7fce5c4 commit 5630729
Show file tree
Hide file tree
Showing 6 changed files with 603 additions and 386 deletions.
157 changes: 157 additions & 0 deletions src/app/(app)/(home)/components/ActivityCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
'use client'

import React, { useMemo } from 'react'
import clsx from 'clsx'
import Link from 'next/link'
import RemoveMarkdown from 'remove-markdown'
import type { ReactActivityType } from './types'

import {
FaSolidFeatherAlt,
IcTwotoneSignpost,
MdiLightbulbOn20,
} from '~/components/icons/menu-collection'
import { routeBuilder, Routes } from '~/lib/route-builder'
import { useAggregationSelector } from '~/providers/root/aggregation-data-provider'

export const iconClassName =
'rounded-full border shrink-0 border-accent/30 text-xs center inline-flex size-6 text-accent'

export const ActivityCard = ({ activity }: { activity: ReactActivityType }) => {
const siteOwner = useAggregationSelector((state) => state.user)
const Content = useMemo(() => {
switch (activity.bizType) {
case 'comment': {
return (
<div className="relative flex flex-col justify-center gap-2">
<div
className={clsx(
'absolute left-0 top-1/2 -translate-y-1/4',
iconClassName,
)}
>
<i className="icon-[mingcute--comment-line]" />
</div>
<div className="flex items-center gap-2 pl-8">
<div className="space-x-2">
{activity.avatar && (
<img
src={activity.avatar}
className="inline size-[16px] rounded-full ring-2 ring-slate-200 dark:ring-zinc-800"
/>
)}
<span className="font-medium">{activity.author}</span>{' '}
<small></small>{' '}
<Link
className="shiro-link--underline"
href={
activity.slug
? `/posts/${activity.slug}`
: `/notes/${activity.nid}`
}
>
<b>{activity.title}</b>
</Link>{' '}
<small>说:</small>
</div>
</div>
<div className="flex pl-8">
<div
className={clsx(
'relative inline-block rounded-xl p-3 text-zinc-800 dark:text-zinc-200',
'rounded-tl-sm bg-zinc-600/5 dark:bg-zinc-500/20',
'max-w-full overflow-auto',
)}
>
{RemoveMarkdown(activity.text)}
</div>
</div>
</div>
)
}
case 'note': {
return (
<div className="flex translate-y-1/4 gap-2">
<div className={clsx(iconClassName)}>
<FaSolidFeatherAlt />
</div>
<div className="space-x-2">
<small>发布了</small>{' '}
<Link href={routeBuilder(Routes.Note, { id: activity.nid })}>
<b>{activity.title}</b>
</Link>
</div>
</div>
)
}
case 'post': {
return (
<div className="flex translate-y-1/4 gap-2">
<div className={clsx(iconClassName)}>
<IcTwotoneSignpost />
</div>
<div className="space-x-2">
<small>发布了</small>{' '}
<Link href={`/posts/${activity.slug}`}>
<b>{activity.title}</b>
</Link>
</div>
</div>
)
}
case 'recent': {
return (
<div className="relative flex flex-col justify-center gap-2">
<div
className={clsx(
'absolute left-0 top-1/2 -translate-y-1/4',
iconClassName,
)}
>
<MdiLightbulbOn20 />
</div>

<div className="flex gap-2 pl-8">
<img
src={siteOwner?.avatar}
className="mt-4 hidden size-6 rounded-full lg:inline"
/>
<div
className={clsx(
'relative inline-block rounded-xl p-3 text-zinc-800 dark:text-zinc-200',
'rounded-tl-sm bg-zinc-600/5 dark:bg-zinc-500/20',
'max-w-full overflow-auto',
)}
>
{RemoveMarkdown(activity.content)}
</div>
</div>
</div>
)
}
case 'like': {
return (
<div className="flex translate-y-1/4 items-start gap-2">
<span className={clsx(iconClassName)}>
<i className="icon-[mingcute--heart-line]" />
</span>
<div className="space-x-2">
<small>有人点赞了</small>{' '}
<Link
href={
activity.slug
? `/posts/${activity.slug}`
: `/notes/${activity.nid}`
}
>
<b>{activity.title}</b>
</Link>
</div>
</div>
)
}
}
}, [activity, siteOwner?.avatar])

return <div className="pb-4 text-base">{Content}</div>
}
92 changes: 92 additions & 0 deletions src/app/(app)/(home)/components/ActivityPostList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
'use client'

import React from 'react'
import { m } from 'framer-motion'
import Link from 'next/link'

import { Divider } from '~/components/ui/divider'
import { RelativeTime } from '~/components/ui/relative-time'
import { softBouncePreset } from '~/constants/spring'
import { routeBuilder, Routes } from '~/lib/route-builder'

import { useHomeQueryData } from '../query'

export const ActivityPostList = () => {
const { notes, posts } = useHomeQueryData()
return (
<m.section
initial={{ opacity: 0.0001, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={softBouncePreset}
className="mt-8 flex flex-col gap-4 lg:mt-0"
>
<h2 className="text-2xl font-medium leading-loose">最近更新的文稿</h2>
<ul className="shiro-timeline mt-4">
{posts.map((post) => {
return (
<li key={post.id} className="flex min-w-0 justify-between">
<Link
prefetch
className="min-w-0 shrink truncate"
href={routeBuilder(Routes.Post, {
category: post.category.slug,
slug: post.slug,
})}
>
{post.title}
</Link>

<span className="ml-2 shrink-0 self-end text-xs opacity-70">
<RelativeTime
date={post.created}
displayAbsoluteTimeAfterDay={180}
/>
</span>
</li>
)
})}
</ul>

<Link
className="flex items-center justify-end opacity-70 duration-200 hover:text-accent"
href={routeBuilder(Routes.Posts, {})}
>
<i className="icon-[mingcute--arrow-right-circle-line]" />
<span className="ml-2">还有更多</span>
</Link>

<Divider />
<h2 className="text-2xl font-medium leading-loose">最近更新的手记</h2>
<ul className="shiro-timeline mt-4">
{notes.map((note, i) => {
return (
<li key={note.id} className="flex min-w-0 justify-between">
<Link
className="min-w-0 shrink truncate"
href={routeBuilder(Routes.Note, {
id: note.nid,
})}
>
{note.title}
</Link>

<span className="ml-2 shrink-0 self-end text-xs opacity-70">
<RelativeTime
date={note.created}
displayAbsoluteTimeAfterDay={180}
/>
</span>
</li>
)
})}
</ul>
<Link
className="flex items-center justify-end opacity-70 duration-200 hover:text-accent"
href={routeBuilder(Routes.Timelime, { type: 'note' })}
>
<i className="icon-[mingcute--arrow-right-circle-line]" />
<span className="ml-2">还有更多</span>
</Link>
</m.section>
)
}
90 changes: 90 additions & 0 deletions src/app/(app)/(home)/components/ActivityRecent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
'use client'

import { useQuery } from '@tanstack/react-query'
import React, { useMemo } from 'react'
import clsx from 'clsx'
import { m } from 'framer-motion'
import type { ReactActivityType } from './types'

import { ScrollArea } from '~/components/ui/scroll-area'
import { softBouncePreset } from '~/constants/spring'
import { apiClient } from '~/lib/request'

import { ActivityCard, iconClassName } from './ActivityCard'

export const ActivityRecent = () => {
const { data, isLoading } = useQuery({
queryKey: ['home-activity-recent'],
queryFn: async () => {
return (await apiClient.activity.getRecentActivities()).$serialized
},
refetchOnMount: true,
meta: {
persist: true,
},
})

const flatData = useMemo(() => {
return [...Object.entries(data || {})]
.map(([type, items]) => {
if (!Array.isArray(items)) return []
return items.map((item: any) => {
return { ...item, bizType: type }
})
})
.flat()
.sort((a, b) => {
return new Date(b.created).getTime() - new Date(a.created).getTime()
}) as ReactActivityType[]
// .slice(0, 6) as ReactActivityType[]
}, [data])

return (
<m.div
initial={{ opacity: 0.0001, y: 50 }}
animate={{ opacity: 1, y: 0 }}
transition={softBouncePreset}
className="mt-8 w-full text-lg lg:mt-0"
>
<m.h2 className="mb-8 text-2xl font-medium leading-loose lg:ml-14">
最近发生的事
</m.h2>

{isLoading ? (
<div className="relative h-[400px] max-h-[80vh]">
<ul className="shiro-timeline mt-4 flex animate-pulse flex-col pb-4 pl-2 text-slate-200 dark:!text-neutral-700">
{new Array(6).fill(null).map((_, i) => {
return (
<li key={i} className="flex w-full items-center gap-2">
<div
className={clsx(
iconClassName,
'border-0 bg-current text-inherit',
)}
/>

<div className="mb-4 box-content h-16 w-full rounded-md bg-current" />
</li>
)
})}
</ul>
</div>
) : (
<ScrollArea.ScrollArea rootClassName="h-[400px] relative max-h-[80vh]">
<ul className="shiro-timeline mt-4 flex flex-col pb-8 pl-2">
{flatData.map((activity) => {
return (
<li
key={`${activity.bizType}-${activity.id}-${activity.created}`}
className="flex min-w-0 justify-between"
>
<ActivityCard activity={activity} />
</li>
)
})}
</ul>
</ScrollArea.ScrollArea>
)}
</m.div>
)
}
54 changes: 54 additions & 0 deletions src/app/(app)/(home)/components/Screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
'use client'

import React, { forwardRef, useRef } from 'react'
import { useInView } from 'framer-motion'
import type { PropsWithChildren } from 'react'

import { isDev } from '~/lib/env'
import { clsxm } from '~/lib/helper'

const debugStyle = {
outline: '1px solid #0088cc',
}
export const Screen = forwardRef<
HTMLDivElement,
PropsWithChildren<{
className?: string
}>
>((props, ref) => {
return (
<InViewScreen
ref={ref}
className={clsxm(
'h-dvh min-h-[800px] min-w-0 max-w-screen overflow-hidden',
props.className,
)}
>
{props.children}
</InViewScreen>
)
})

export const InViewScreen = forwardRef<
HTMLDivElement,
PropsWithChildren<{
className?: string
}>
>((props, ref) => {
const inViewRef = useRef<HTMLSpanElement>(null)
const inView = useInView(inViewRef, { once: true })

return (
<div
ref={ref}
style={isDev ? debugStyle : undefined}
className={clsxm('relative flex flex-col center', props.className)}
>
<span ref={inViewRef} />
{inView && props.children}
</div>
)
})

InViewScreen.displayName = 'InViewScreen'
Screen.displayName = 'Screen'
Loading

0 comments on commit 5630729

Please sign in to comment.