Browse Source

Extract blog data

Put blog data to dummy data JSON, render other blog controls.
master
TheoryOfNekomata 1 month ago
parent
commit
433224806d
11 changed files with 262 additions and 67 deletions
  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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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 View File

@@ -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…
Cancel
Save