Skip to content

Commit 51e440c

Browse files
author
tkokhing
committed
maj refactor, post list ui. make reuse more-stories-concise, hk usePostSort
1 parent 1cb7aee commit 51e440c

9 files changed

Lines changed: 96 additions & 66 deletions

File tree

src/app/_components/language_handler/language-display.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
'use client';
1+
"use client";
22

33
import { type ElementType } from 'react';
44
import LanguageSwitcher from "@/app/_components/language_handler/language-switcher";

src/app/_components/main_frame/dismissible-box.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
'use client';
1+
"use client";
22

33
import { useState } from 'react';
44

src/app/_components/post_gen/more-stories-concise.tsx

Lines changed: 5 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,16 @@
1-
"use client";
1+
"use client"
22

3-
import { useEffect, useState } from "react";
4-
import { useRouter, useSearchParams } from "next/navigation";
53
import { Post } from "@/interfaces/post";
64
import { TitlePreview } from "@/app/_components/post_gen/title-preview";
5+
import { usePostSort } from "@/app/_hooks/usePostSort";
76

87
type Props = {
98
posts: Post[];
9+
skipURLParam?: boolean; // for MDX usage
1010
};
1111

12-
export function MoreStoriesConcise({ posts }: Props) {
13-
const router = useRouter();
14-
const searchParams = useSearchParams();
15-
16-
const defaultSort = searchParams.get("sort") === "date" ? "date" : "title";
17-
const [sortBy, setSortBy] = useState<"title" | "date">(defaultSort);
18-
19-
// Sync localStorage on sort change
20-
useEffect(() => {
21-
localStorage.setItem("preferredSort", sortBy);
22-
}, [sortBy]);
23-
24-
// If no URL param, check localStorage once on load
25-
useEffect(() => {
26-
const saved = localStorage.getItem("preferredSort");
27-
if (!searchParams.get("sort") && saved) {
28-
setSortBy(saved === "date" ? "date" : "title");
29-
router.replace(`?sort=${saved}`);
30-
}
31-
}, []);
32-
33-
// Apply sorting
34-
const sortedPosts = [...posts].sort((a, b) => {
35-
if (sortBy === "title") return a.title.localeCompare(b.title);
36-
return new Date(b.date).getTime() - new Date(a.date).getTime();
37-
});
38-
39-
// Handle button click
40-
const handleSortChange = (mode: "title" | "date") => {
41-
setSortBy(mode);
42-
router.replace(`?sort=${mode}`);
43-
};
12+
export function MoreStoriesConcise({ posts, skipURLParam }: Props) {
13+
const { sortedPosts, sortBy, handleSortChange } = usePostSort(posts, skipURLParam);
4414

4515
return (
4616
<section>

src/app/_components/post_gen/post-body-client.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
'use client';
1+
"use client";
22

33
import { useFontSize } from '@/app/_components/main_frame/font-size-ctrl';
44

src/app/_components/post_gen/title-preview.tsx

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,16 @@ export function TitlePreview({
1717
subPath,
1818
excerpt,
1919
postStatus,
20-
}: Props)
21-
{
20+
}: Props) {
2221
return (
23-
<div>
24-
<h3 className="shadow-sm w-full dark:shadow-sky-900/50 hover:shadow-2xl hover:dark:shadow-zinc-50/100 hover:dark:shadow-lg py-3 px-4 text-xl mb-3 leading-snug">
25-
<Link href={`/${subPath}/${slug}`} className="hover:underline">
26-
{title}
27-
<p className="text-pretty text-xs mb-4 italic">
28-
{excerpt}
29-
</p>
30-
<p className="text-right text-xs mb-4 italic">
31-
{postStatus} <DateFormatter dateString={date} />
32-
</p>
33-
</Link>
34-
</h3>
22+
<div className="shadow-sm w-full dark:shadow-sky-900/50 hover:shadow-2xl hover:dark:shadow-zinc-50/100 hover:dark:shadow-lg py-3 px-4 text-xl mb-3 leading-snug">
23+
<Link href={`/${subPath}/${slug}`}>
24+
<h3 className="text-xl font-medium hover:underline">{title}</h3>
25+
</Link>
26+
<p className="text-pretty text-xs mb-1 italic">{excerpt}</p>
27+
<p className="text-right text-xs italic">
28+
{postStatus} <DateFormatter dateString={date} />
29+
</p>
3530
</div>
3631
);
3732
}

src/app/_components/preference/counter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
'use client';
1+
"use client";
22

33
import { useState } from 'react';
44

src/app/_components/preference/data-exporter.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// for content injections and
22
// export to different page.tsx for dynamic language switching
3-
'use client';
3+
"use client";
44
import enFrontierData from '@/lib/_data_exporter/frontier_data_en.mdx';
55
import zhFrontierData from '@/lib/_data_exporter/frontier_data_zh.mdx';
66
import enCyberDomainData from '@/lib/_data_exporter/strategic_cyber_domains_en.mdx';
Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,45 @@
1-
// app/_components/toggle-frame.tsx
2-
31
"use client";
42

5-
import { useState, ReactNode } from "react";
3+
import { useRef, useState, useEffect } from "react";
64

75
type Props = {
8-
label?: string;
9-
children: ReactNode;
6+
label: string;
7+
children: React.ReactNode;
8+
defaultOpen?: boolean;
109
};
1110

12-
export function ToggleFrame({ label, children }: Props) {
13-
const [open, setOpen] = useState(false);
11+
export function ToggleFrame({ label, children, defaultOpen = false }: Props) {
12+
const [isOpen, setIsOpen] = useState(defaultOpen);
13+
const ref = useRef<HTMLDivElement>(null);
14+
15+
const handleToggle = () => {
16+
setIsOpen((prev) => !prev);
17+
};
18+
19+
// Scroll into view when opened
20+
useEffect(() => {
21+
if (isOpen && ref.current) {
22+
setTimeout(() => {
23+
ref.current?.scrollIntoView({ behavior: "smooth", block: "start" });
24+
}, 50);
25+
}
26+
}, [isOpen]);
1427

1528
return (
16-
<div className="my-4">
29+
<div ref={ref} className="my-6">
1730
<button
18-
onClick={() => setOpen(!open)}
19-
className="underline text-blue-800 dark:text-blue-300 hover:text-tkokhing-blue hover:dark:text-tkokhing-dark transition"
31+
type="button"
32+
onClick={handleToggle}
33+
className="text-left text-lg font-semibold underline text-blue-800 dark:text-blue-300 hover:text-tkokhing-blue hover:dark:text-tkokhing-dark transition"
2034
>
21-
{open ? `Hide ${label}` : `Show ${label}`}
35+
{label}&nbsp;{isOpen ? "▼" : "▶"}
2236
</button>
23-
{open && <div className="mt-2">{children}</div>}
37+
38+
{isOpen && (
39+
<div className="mt-4">
40+
{children}
41+
</div>
42+
)}
2443
</div>
2544
);
2645
}

src/app/_hooks/usePostSort.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// app/_hooks/usePostSort.ts
2+
3+
"use client";
4+
5+
import { useEffect, useState } from "react";
6+
import { useRouter, useSearchParams } from "next/navigation";
7+
import { Post } from "@/interfaces/post";
8+
9+
type SortMode = "title" | "date";
10+
11+
export function usePostSort(posts: Post[], skipURLParam = false) {
12+
const router = useRouter();
13+
const searchParams = useSearchParams();
14+
const defaultSort: SortMode = searchParams.get("sort") === "date" ? "date" : "title";
15+
const [sortBy, setSortBy] = useState<SortMode>(defaultSort);
16+
17+
// Sync localStorage
18+
useEffect(() => {
19+
localStorage.setItem("preferredSort", sortBy);
20+
}, [sortBy]);
21+
22+
// Load preferred sort if no query param and not skipping
23+
useEffect(() => {
24+
const saved = localStorage.getItem("preferredSort");
25+
if (!skipURLParam && !searchParams.get("sort") && saved) {
26+
const savedMode = saved === "date" ? "date" : "title";
27+
setSortBy(savedMode);
28+
router.replace(`?sort=${savedMode}`, { scroll: false });
29+
}
30+
}, []);
31+
32+
// Apply sorting
33+
const sortedPosts = [...posts].sort((a, b) => {
34+
if (sortBy === "title") return a.title.localeCompare(b.title);
35+
return new Date(b.date).getTime() - new Date(a.date).getTime();
36+
});
37+
38+
// handle sort change
39+
const handleSortChange = (mode: SortMode) => {
40+
setSortBy(mode);
41+
if (!skipURLParam) {
42+
router.replace(`?sort=${mode}`, { scroll: false });
43+
}
44+
};
45+
return { sortedPosts, sortBy, handleSortChange };
46+
}

0 commit comments

Comments
 (0)