Skip to content

Commit

Permalink
feat: proj page
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Jul 1, 2023
1 parent e6ba8b2 commit c32a5a0
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 14 deletions.
2 changes: 1 addition & 1 deletion src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const generateMetadata = defineMetadata(async (_, getData) => {
return {
metadataBase: new URL(url.webUrl),
title: {
template: `%s | ${seo.title}`,
template: `%s - ${seo.title}`,
default: `${seo.title} - ${seo.description}`,
},
description: seo.description,
Expand Down
8 changes: 8 additions & 0 deletions src/app/projects/[id]/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { PropsWithChildren } from 'react'

export const metadata = {
title: '项目详情',
}
export default function Page(props: PropsWithChildren) {
return props.children
}
38 changes: 38 additions & 0 deletions src/app/projects/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
'use client'

import { useQuery } from '@tanstack/react-query'
import { useEffect } from 'react'
import { useParams, useRouter } from 'next/navigation'

import { NotFound404 } from '~/components/common/404'
import { Loading } from '~/components/ui/loading'
import { apiClient } from '~/utils/request'

export default function Page() {
const { id } = useParams()

const { data, isLoading } = useQuery({
queryKey: [id, 'project'],
queryFn: async ({ queryKey }) => {
const [id] = queryKey
return apiClient.project.getById(id)
},
})
const router = useRouter()
useEffect(() => {
if (data?.projectUrl) {
window.open(data.projectUrl)
router.back()
}
}, [data?.projectUrl])

if (isLoading) {
return <Loading useDefaultLoadingText />
}

if (!data) {
return <NotFound404 />
}

return null
}
11 changes: 11 additions & 0 deletions src/app/projects/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Metadata } from 'next'
import type { PropsWithChildren } from 'react'

import { WiderContainer } from '~/components/layout/container/Wider'

export const metadata: Metadata = {
title: '项目',
}
export default async function Layout(props: PropsWithChildren) {
return <WiderContainer>{props.children}</WiderContainer>
}
60 changes: 60 additions & 0 deletions src/app/projects/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
'use client'

import { useQuery } from '@tanstack/react-query'

import { CodiconGithubInverted } from '~/components/icons/menu-collection'
import { Loading } from '~/components/ui/loading'
import { BottomToUpTransitionView } from '~/components/ui/transition/BottomToUpTransitionView'
import { ProjectList } from '~/components/widgets/project/ProjectList'
import { NothingFound } from '~/components/widgets/shared/NothingFound'
import { noopArr } from '~/lib/noop'
import { useAggregationSelector } from '~/providers/root/aggregation-data-provider'
import { apiClient } from '~/utils/request'

export default function Page() {
const { data, isLoading } = useQuery({
queryKey: ['projects'],
queryFn: async () => {
const data = await apiClient.project.getAll()
return data.data
},
})

const githubUsername = useAggregationSelector(
(state) => state.user?.socialIds?.github,
)

if (isLoading) {
return <Loading useDefaultLoadingText />
}

if (!data) return <NothingFound />

return (
<div>
<header className="prose">
<h1>项目们</h1>
</header>

<main className="mt-10">
<div className="my-12 inline-flex items-center text-3xl font-medium">
项目{' '}
{githubUsername && (
<a
href={`https://github.com/${githubUsername}`}
className="ml-2 inline-flex !text-inherit"
target="_blank"
aria-label="view on GitHub"
rel="noopener noreferrer"
>
<CodiconGithubInverted />
</a>
)}
</div>
<BottomToUpTransitionView>
<ProjectList projects={data || noopArr} />
</BottomToUpTransitionView>
</main>
</div>
)
}
57 changes: 44 additions & 13 deletions src/components/layout/header/internal/HeaderContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

import React, { memo } from 'react'
import clsx from 'clsx'
import { AnimatePresence, m, useMotionValue } from 'framer-motion'
import {
AnimatePresence,
m,
useMotionTemplate,
useMotionValue,
} from 'framer-motion'
import Link from 'next/link'
import { usePathname } from 'next/navigation'
import type { IHeaderMenu } from '../config'
Expand All @@ -12,7 +17,11 @@ import { usePageScrollDirectionSelector } from '~/providers/root/page-scroll-inf
import { clsxm } from '~/utils/helper'

import { useHeaderConfig } from './HeaderDataConfigureProvider'
import { useHeaderBgOpacity, useMenuOpacity } from './hooks'
import {
useHeaderBgOpacity,
useHeaderHasMetaInfo,
useMenuOpacity,
} from './hooks'
import { MenuPopover } from './MenuPopover'

export const HeaderContent = () => {
Expand All @@ -28,12 +37,13 @@ export const HeaderContent = () => {

const AccessibleMenu: Component = () => {
const headerOpacity = useHeaderBgOpacity()
const hasMetaInfo = useHeaderHasMetaInfo()

const showShow = usePageScrollDirectionSelector(
(d) => {
return d === 'up' && headerOpacity > 0.8
return d === 'up' && headerOpacity > 0.8 && hasMetaInfo
},
[headerOpacity],
[headerOpacity, hasMetaInfo],
)
return (
<RootPortal>
Expand All @@ -56,20 +66,25 @@ const AccessibleMenu: Component = () => {
const AnimatedMenu: Component = ({ children }) => {
const opacity = useMenuOpacity()

const hasMetaInfo = useHeaderHasMetaInfo()
const shouldHideNavBg = !hasMetaInfo && opacity === 0
return (
<m.div
className="duration-[100ms]"
style={{
opacity,
visibility: opacity === 0 ? 'hidden' : 'visible',
opacity: hasMetaInfo ? opacity : 1,
visibility: opacity === 0 && hasMetaInfo ? 'hidden' : 'visible',
}}
>
{children}
{/* @ts-ignore */}
{React.cloneElement(children, { shouldHideNavBg })}
</m.div>
)
}

const ForDesktop: Component = ({ className }) => {
const ForDesktop: Component<{
shouldHideNavBg?: boolean
}> = ({ className, shouldHideNavBg }) => {
const mouseX = useMotionValue(0)
const mouseY = useMotionValue(0)
const radius = useMotionValue(0)
Expand All @@ -86,6 +101,8 @@ const ForDesktop: Component = ({ className }) => {
const { config: headerMenuConfig } = useHeaderConfig()
const pathname = usePathname()

const background = useMotionTemplate`radial-gradient(${radius}px circle at ${mouseX}px ${mouseY}px, var(--spotlight-color) 0%, transparent 65%)`

return (
<m.nav
layout="size"
Expand All @@ -95,19 +112,32 @@ const ForDesktop: Component = ({ className }) => {
'rounded-full bg-gradient-to-b from-zinc-50/70 to-white/90',
'shadow-lg shadow-zinc-800/5 ring-1 ring-zinc-900/5 backdrop-blur-md',
'dark:from-zinc-900/70 dark:to-zinc-800/90 dark:ring-zinc-100/10',

'group [--spotlight-color:hsl(var(--a)_/_0.05)]',
'duration-200',
shouldHideNavBg && 'bg-none shadow-none ring-transparent',
className,
)}
>
{/* Spotlight overlay */}
<m.div
className="pointer-events-none absolute -inset-px rounded-full opacity-0 transition-opacity duration-500 group-hover:opacity-100"
style={{ background }}
aria-hidden="true"
/>
<div className="flex px-4 font-medium text-zinc-800 dark:text-zinc-200 ">
{headerMenuConfig.map((section) => {
const subItemActive =
section.subMenu?.findIndex((item) => item.path === pathname) || -1
return (
<HeaderMenuItem
section={section}
key={section.path}
subItemActive={section.subMenu?.[subItemActive]}
isActive={
pathname === section.path ||
pathname.startsWith(`${section.path}/`)
pathname.startsWith(`${section.path}/`) ||
subItemActive > -1 ||
false
}
/>
)
Expand All @@ -120,7 +150,8 @@ const ForDesktop: Component = ({ className }) => {
const HeaderMenuItem = memo<{
section: IHeaderMenu
isActive: boolean
}>(({ section, isActive }) => {
subItemActive?: IHeaderMenu
}>(({ section, isActive, subItemActive }) => {
const href = section.path

return (
Expand All @@ -136,10 +167,10 @@ const HeaderMenuItem = memo<{
layoutId="header-menu-icon"
className={clsxm('mr-2 flex items-center')}
>
{section.icon}
{subItemActive?.icon ?? section.icon}
</m.span>
)}
<m.span layout>{section.title}</m.span>
<m.span layout>{subItemActive?.title ?? section.title}</m.span>
</span>
</AnimatedItem>
</MenuPopover>
Expand Down
10 changes: 10 additions & 0 deletions src/components/layout/header/internal/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,13 @@ export const useHeaderMetaInfo = () => {
slug: useAtomValue(headerMetaSlugAtom),
}
}

const headerHasMetaInfoAtom = atom((get) => {
const title = get(headerMetaTitleAtom)
const description = get(headerMetaDescriptionAtom)

return title !== '' && description !== ''
})
export const useHeaderHasMetaInfo = () => {
return useAtomValue(headerHasMetaInfoAtom)
}
2 changes: 2 additions & 0 deletions src/components/ui/text/FlexText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ export const FlexText: FC<{ text: string; scale: number }> = memo((props) => {
</span>
)
})

FlexText.displayName = 'FlexText'
30 changes: 30 additions & 0 deletions src/components/widgets/project/ProjectIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ImageLazy } from '~/components/ui/image'
import { FlexText } from '~/components/ui/text'
import { clsxm } from '~/utils/helper'

export const ProjectIcon: Component<{ avatar?: string; name?: string }> = (
props,
) => {
const { avatar, name, className } = props
return (
<div
className={clsxm(
'project-icon flex flex-shrink-0 flex-grow items-center justify-center rounded-xl',
avatar
? 'ring-2 ring-slate-200 dark:ring-neutral-800'
: 'bg-slate-300 text-white dark:bg-neutral-800',
'aspect-square rounded-md transition-all duration-300',
className,
)}
>
{avatar ? (
<ImageLazy
className="aspect-square rounded-xl transition-shadow duration-300"
src={avatar}
/>
) : (
<FlexText text={name?.charAt(0).toUpperCase() || ''} scale={0.5} />
)}
</div>
)
}
44 changes: 44 additions & 0 deletions src/components/widgets/project/ProjectList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Link from 'next/link'
import type { FC } from 'react'

import { routeBuilder, Routes } from '~/lib/route-builder'

import { ProjectIcon } from './ProjectIcon'

export type Project = {
id: string
avatar?: string
name: string
description?: string
}
export const ProjectList: FC<{ projects: Project[] }> = (props) => {
const projects = props.projects

return (
<section key="list" className="text-center">
<div className="grid grid-cols-1 gap-12 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{projects.map((project) => {
return (
<Link
href={routeBuilder(Routes.Project, { id: project.id })}
key={project.id}
className="group grid grid-cols-[1fr_2fr] gap-4"
>
<ProjectIcon
className="group-hover:-translate-y-2 group-hover:shadow-out-sm"
avatar={project.avatar}
name={project.name}
/>
<span className="flex flex-shrink-0 flex-grow flex-col gap-2 text-left">
<h4 className="font-2xl m-0 p-0 font-medium">{project.name}</h4>
<span className="line-clamp-5 text-sm md:line-clamp-4 lg:line-clamp-2">
{project.description}
</span>
</span>
</Link>
)
})}
</div>
</section>
)
}
Loading

1 comment on commit c32a5a0

@vercel
Copy link

@vercel vercel bot commented on c32a5a0 Jul 1, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

shiro – ./

springtide.vercel.app
innei.in
shiro-innei.vercel.app
shiro-git-main-innei.vercel.app

Please sign in to comment.