瀏覽代碼

Extract blog data

Put blog data to dummy data JSON, render other blog controls.
master
TheoryOfNekomata 1 月之前
父節點
當前提交
433224806d
共有 11 個文件被更改,包括 262 次插入67 次删除
  1. +30
    -0
      packages/web/data.json
  2. +111
    -0
      packages/web/src/components/molecules/BlogItem/index.tsx
  3. +28
    -0
      packages/web/src/components/molecules/ClockIcon/index.tsx
  4. +1
    -1
      packages/web/src/components/molecules/EnvisionSection/index.tsx
  5. +1
    -1
      packages/web/src/components/molecules/Icon/index.tsx
  6. +31
    -0
      packages/web/src/components/molecules/IconText/index.tsx
  7. +3
    -8
      packages/web/src/components/molecules/MainLandingSection/index.tsx
  8. +15
    -15
      packages/web/src/components/molecules/MakeSection/index.tsx
  9. +18
    -39
      packages/web/src/components/organisms/BlogLayout/index.tsx
  10. +22
    -1
      packages/web/src/pages/blog/index.tsx
  11. +2
    -2
      packages/web/src/styles/globals.css

+ 30
- 0
packages/web/data.json 查看文件

@@ -1,4 +1,34 @@
{
"blog": [
{
"id": 1,
"title": "Article Title",
"createdAt": "2024-03-14T13:37:00.000Z",
"slug": "article-title",
"content": "Excerpt lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam."
},
{
"id": 2,
"title": "New Article Title",
"createdAt": "2024-02-14T06:09:00.000Z",
"slug": "new-article-title",
"content": "Excerpt lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam."
},
{
"id": 3,
"title": "Another Article Title",
"createdAt": "2024-01-25T04:20:00.000Z",
"slug": "another-article-title",
"content": "Excerpt lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam."
},
{
"id": 4,
"title": "Yet Another Article Title",
"createdAt": "2024-01-03T10:10:00.000Z",
"slug": "yet-another-article-title",
"content": "Excerpt lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam. Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam."
}
],
"make": [
{
"id": 1,


+ 111
- 0
packages/web/src/components/molecules/BlogItem/index.tsx 查看文件

@@ -0,0 +1,111 @@
import {IconText} from '@/components/molecules/IconText';
import {ClockIcon} from '@/components/molecules/ClockIcon';
import * as WebNavigationReact from '@tesseract-design/web-navigation-react';
import Link from 'next/link';
import * as React from 'react';

export interface BlogItemProps {
title: string;
createdAt: number | string | Date;
slug: string;
content: string;
}

export const BlogItem: React.FC<BlogItemProps> = ({
title,
createdAt,
slug,
content,
}) => {
const dateObj = new Date(createdAt);
const dateFormat = new Intl.DateTimeFormat('en-PH', {
month: 'short',
day: 'numeric',
year: 'numeric',
}).formatToParts(dateObj);
const month = dateFormat.find((f) => f.type === 'month')?.value;
const date = dateFormat.find((f) => f.type === 'day')?.value;
const year = dateFormat.find((f) => f.type === 'year')?.value;

const timeFormat = new Intl.DateTimeFormat('en-PH', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
}).formatToParts(dateObj);

const hour = timeFormat.find((v) => v.type === 'hour')?.value;
const minute = timeFormat.find((v) => v.type === 'minute')?.value;

const timeZoneFormat = new Intl.DateTimeFormat('en-PH', {
timeZoneName: 'shortOffset',
}).formatToParts(dateObj);

const timeZone = timeZoneFormat.find((v) => v.type === 'timeZoneName')?.value?.replace('GMT', 'UTC');

return (
<article
className="rounded overflow-hidden relative flex flex-col gap-8 before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border-2 before:rounded-inherit before:pointer-events-none before:opacity-10"
>
<header className="flex flex-col gap-4">
<h1 className="m-0 h-24 flex items-end px-8">
<Link
className="no-underline"
href={{
pathname: '/blog/articles/[slug]',
query: {
slug,
}
} as unknown as string}
>
{title}
</Link>
</h1>
<time className="px-8 tabular-nums font-headings lowercase font-extralight text-5xl flex gap-4">
{month && date && year && (
<IconText
icon={date}
topText={month.slice(0, 3)}
bottomText={year}
/>
)}
{(month && date && year) && (hour && minute) && ' '}
{hour && minute && (
<IconText
icon={
<span className="text-[0.6667em]">
<ClockIcon
hour={Number(hour)}
minute={Number(minute)}
/>
</span>
}
topText={timeFormat.map((f) => f.value).join('')}
bottomText={timeZone}
/>
)}
</time>
</header>
<div className="px-8">
{content}
</div>
<footer className="flex justify-end px-8 py-4 relative before:absolute before:border-t-2 before:top-0 before:left-0 before:w-full before:opacity-10">
<div className="w-48">
<WebNavigationReact.LinkButton
component={Link as unknown as 'a'}
href={{
pathname: '/blog/articles/[slug]',
query: {
slug,
}
} as unknown as string}
menuItem
block
subtext={title}
>
Read more
</WebNavigationReact.LinkButton>
</div>
</footer>
</article>
);
}

+ 28
- 0
packages/web/src/components/molecules/ClockIcon/index.tsx 查看文件

@@ -0,0 +1,28 @@
import * as React from 'react';

export const ClockIconDerivedElementComponent = 'svg' as const;

export type ClockIconDerivedElement = SVGElementTagNameMap[typeof ClockIconDerivedElementComponent];

export interface ClockIconProps extends React.SVGProps<ClockIconDerivedElement> {
hour: number;
minute: number;
}

export const ClockIcon = React.forwardRef<ClockIconDerivedElement, ClockIconProps>(({
hour,
minute,
className,
...etcProps
}, forwardedRef) => (
<ClockIconDerivedElementComponent
{...etcProps}
ref={forwardedRef}
viewBox="0 0 24 24"
className={`w-[1.5em] h-[1.5em] linejoin-round linecap-round stroke-2 stroke-current fill-none ${className ?? ''}`.trim()}
>
<circle r="40%" cx="50%" cy="50%" />
<line x1="50%" x2="50%" y1="50%" y2="30%" transform={`rotate(${(hour + (minute / 60)) / 12 * 360}, 12, 12)`} />
<line x1="50%" x2="50%" y1="50%" y2="20%" transform={`rotate(${minute / 60 * 360}, 12, 12)`} />
</ClockIconDerivedElementComponent>
));

+ 1
- 1
packages/web/src/components/molecules/EnvisionSection/index.tsx 查看文件

@@ -21,7 +21,7 @@ export const EnvisionSection: React.FC<EnvisionSectionProps> = ({
span="wide"
>
<div className="pb-8">
<h2 className="text-base leading-none m-0">
<h2 className="text-base leading-none m-0 font-thin">
<span className="text-7xl sm:text-8xl">
I
{' '}


+ 1
- 1
packages/web/src/components/molecules/Icon/index.tsx 查看文件

@@ -35,7 +35,7 @@ export const Icon = React.forwardRef<IconDerivedElement, IconProps>(({
{...etcProps}
ref={forwardedRef}
viewBox="0 0 24 24"
className={`w-6 h-6 linejoin-round linecap-round stroke-2 stroke-current fill-none ${className ?? ''}`.trim()}
className={`w-[1.5em] h-[1.5em] linejoin-round linecap-round stroke-2 stroke-current fill-none ${className ?? ''}`.trim()}
>
{ICONS[name]}
</IconDerivedElementComponent>


+ 31
- 0
packages/web/src/components/molecules/IconText/index.tsx 查看文件

@@ -0,0 +1,31 @@
import * as React from 'react';

export interface IconTextProps {
icon: React.ReactNode;
topText?: React.ReactNode;
bottomText?: React.ReactNode;
}

export const IconText: React.FC<IconTextProps> = ({
icon,
topText,
bottomText,
}) => (
<span className="font-sans align-middle inline-flex flex-col items-center justify-center leading-none w-[1em] h-[1em]">
<span className="order-2 text-[0.5em] font-light">
{icon}
</span>
{' '}
{topText && (
<span className="order-1 uppercase text-[0.25em] font-semibold">
{topText}
</span>
)}
{topText && bottomText && ' '}
{bottomText && (
<span className="order-3 uppercase text-[0.25em] font-semibold">
{bottomText}
</span>
)}
</span>
);

+ 3
- 8
packages/web/src/components/molecules/MainLandingSection/index.tsx 查看文件

@@ -3,16 +3,11 @@ import Link from 'next/link';
import {Layouts} from '@tesseract-design/viewfinder-react';
import {Brand} from '@/components/molecules/Brand';
import {Pillar} from '@/components/molecules/Pillar';
import {BackgroundGrid} from '@/components/molecules/BackgroundGrid';
import {Icon} from '@/components/molecules/Icon';

export interface MainLandingSectionProps {
backgroundImages: string[];
}
export interface MainLandingSectionProps {}

export const MainLandingSection: React.FC<MainLandingSectionProps> = ({
backgroundImages
}) => {
export const MainLandingSection: React.FC<MainLandingSectionProps> = () => {
const scrollDown: React.MouseEventHandler<HTMLAnchorElement> = React.useCallback((event) => {
event.preventDefault();

@@ -38,7 +33,7 @@ export const MainLandingSection: React.FC<MainLandingSectionProps> = ({
className="relative py-24"
>
<div className="flex flex-col sm:flex-row sm:items-center gap-8 sm:gap-16">
<h1 className="text-center m-0 before:text-7xl sm:before:text-8xl before:block before:content-['I_am.'] text-base leading-none">
<h1 className="text-center m-0 before:text-7xl before:font-thin sm:before:text-8xl before:block before:content-['I_am.'] text-base leading-none text-4xl">
<span className="text-4xl">
<Brand/>
</span>


+ 15
- 15
packages/web/src/components/molecules/MakeSection/index.tsx 查看文件

@@ -43,21 +43,21 @@ export const MakeSection: React.FC<MakeSectionProps> = ({
className="relative"
>
<div className="pb-8">
<h2 className="text-base leading-none m-0">
<span className="text-7xl sm:text-8xl">
I
{' '}
<Link
className="no-underline px-1 -mx-1"
href={{
query: {
feed: 'all',
},
}}
>
make.
</Link>
</span>
<h2 className="text-base leading-none m-0 font-thin">
<span className="text-7xl sm:text-8xl">
I
{' '}
<Link
className="no-underline px-1 -mx-1"
href={{
query: {
feed: 'all',
},
}}
>
make.
</Link>
</span>
</h2>
<div className="flex gap-2 font-headings lowercase text-3xl sm:text-4xl leading-none font-light">
<div><Link href={{ query: { feed: 'software' }}} className="p-1 -m-1 after:content-['.']">Software</Link></div>


+ 18
- 39
packages/web/src/components/organisms/BlogLayout/index.tsx 查看文件

@@ -1,11 +1,18 @@
import * as React from 'react';
import {Layouts, Widgets} from '@tesseract-design/viewfinder-react';
import Link from 'next/link';
import * as WebNavigationReact from '@tesseract-design/web-navigation-react';
import {BlogItem, BlogItemProps} from '@/components/molecules/BlogItem';

export interface BlogLayoutProps {}
interface SingleBlogItem extends BlogItemProps {
id: string;
}

export const BlogLayout: React.FC<BlogLayoutProps> = () => (
export interface BlogLayoutProps {
blogItems: SingleBlogItem[];
}

export const BlogLayout: React.FC<BlogLayoutProps> = ({
blogItems,
}) => (
<Layouts.LeftSidebar.Root
sidebarBaseWidget={
<Widgets.LeftSidebarBase>
@@ -23,41 +30,13 @@ export const BlogLayout: React.FC<BlogLayoutProps> = () => (
}
>
<Layouts.LeftSidebar.MainContentContainer>
<main className="my-16 flex gap-8">
<article
className="rounded overflow-hidden relative flex flex-col gap-4 before:absolute before:top-0 before:left-0 before:w-full before:h-full before:border-2 before:rounded-inherit px-4 py-3 before:pointer-events-none before:opacity-10"
>
<header>
<h1 className="m-0">
Article title
</h1>
<time className="font-headings lowercase font-extralight text-3xl">
2024 March 5 23:58
</time>
</header>
<div>
Excerpt lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam.
Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam.
Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam.
Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam.
Lorem ipsum dolor sit amet consectetur adipisicing elit sed do eiusmod aliquam.
</div>
<footer className="flex justify-end">
<div className="w-48">
<WebNavigationReact.LinkButton
component={Link as unknown as 'a'}
href={{
pathname: '/feed',
} as unknown as string}
menuItem
block
subtext="Article Title"
>
Read more
</WebNavigationReact.LinkButton>
</div>
</footer>
</article>
<main className="py-16 flex flex-col gap-8">
{blogItems.map((blogItem) => (
<BlogItem
key={blogItem.id}
{...blogItem}
/>
))}
</main>
</Layouts.LeftSidebar.MainContentContainer>
</Layouts.LeftSidebar.Root>


+ 22
- 1
packages/web/src/pages/blog/index.tsx 查看文件

@@ -2,14 +2,35 @@ import {NextPage} from 'next';
import {useHuePulsate} from '@/hooks/effects';
import * as config from '@/config';
import {BlogLayout} from '@/components/organisms/BlogLayout';
import * as Iceform from '@modal-sh/iceform-next';

const BlogPage: NextPage = () => {
interface BlogPageProps {
data: any;
}

const BlogPage: Iceform.NextPage<BlogPageProps> = ({
data,
}) => {
useHuePulsate(config.effects.huePulsate);

return (
<BlogLayout
blogItems={data.blog}
/>
);
};

export const getServerSideProps = Iceform.destination.getServerSideProps({
fn: async (actionReq, actionRes, context) => {
const { readFile } = await import('fs/promises');
const dataJson = await readFile('data.json', 'utf-8');
const data = JSON.parse(dataJson);
return {
props: {
data,
},
};
},
});

export default BlogPage;

+ 2
- 2
packages/web/src/styles/globals.css 查看文件

@@ -465,11 +465,11 @@ a {
}

h1 {
@apply font-headings lowercase text-5xl font-thin leading-none my-8;
@apply font-headings lowercase text-5xl font-extralight leading-none my-8;
}

h2 {
@apply font-headings lowercase text-4xl font-thin leading-none my-8;
@apply font-headings lowercase text-4xl font-extralight leading-none my-8;
}

h3 {


Loading…
取消
儲存