aboutsummaryrefslogtreecommitdiffhomepage
path: root/frontend/src/components
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2025-12-03 03:51:29 +0900
committernsfisis <nsfisis@gmail.com>2025-12-04 23:27:28 +0900
commit58d132139ba8d5fa17c8681a0275047ce4cca809 (patch)
tree7dd18f5de496483b7d318e12e43379a51f3a6056 /frontend/src/components
parentaac4e9ccdebe52c156506d1899d5a38e99366f69 (diff)
downloadfeedaka-58d132139ba8d5fa17c8681a0275047ce4cca809.tar.gz
feedaka-58d132139ba8d5fa17c8681a0275047ce4cca809.tar.zst
feedaka-58d132139ba8d5fa17c8681a0275047ce4cca809.zip
feat(frontend): design update
Diffstat (limited to 'frontend/src/components')
-rw-r--r--frontend/src/components/AddFeedForm.tsx26
-rw-r--r--frontend/src/components/ArticleItem.tsx24
-rw-r--r--frontend/src/components/ArticleList.tsx16
-rw-r--r--frontend/src/components/FeedItem.tsx37
-rw-r--r--frontend/src/components/FeedList.tsx20
-rw-r--r--frontend/src/components/Layout.tsx4
-rw-r--r--frontend/src/components/MenuItem.tsx6
-rw-r--r--frontend/src/components/Navigation.tsx19
8 files changed, 86 insertions, 66 deletions
diff --git a/frontend/src/components/AddFeedForm.tsx b/frontend/src/components/AddFeedForm.tsx
index 6d18318..9a56574 100644
--- a/frontend/src/components/AddFeedForm.tsx
+++ b/frontend/src/components/AddFeedForm.tsx
@@ -48,43 +48,43 @@ export function AddFeedForm({ onFeedAdded }: Props) {
const isUrlValid = !url || isValidUrl(url);
return (
- <form onSubmit={handleSubmit} className="space-y-4 p-4">
- <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm">
- <h3 className="text-lg font-semibold text-gray-900 mb-4">
+ <form onSubmit={handleSubmit}>
+ <div className="rounded-xl border border-stone-200 bg-white p-5">
+ <h3 className="mb-4 text-sm font-semibold uppercase tracking-wide text-stone-900">
Subscribe to New Feed
</h3>
- <div className="flex gap-2">
+ <div className="flex gap-3">
<div className="flex-1">
<input
type="url"
value={url}
onChange={(e) => setUrl(e.target.value)}
placeholder="https://example.com/feed.xml"
- className={`w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-2 ${
+ className={`w-full rounded-lg border bg-white px-4 py-2.5 text-sm text-stone-900 placeholder-stone-400 transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-offset-1 ${
isUrlValid
- ? "border-gray-300 focus:border-blue-500 focus:ring-blue-500"
- : "border-red-300 focus:border-red-500 focus:ring-red-500"
+ ? "border-stone-200 focus:border-sky-500 focus:ring-sky-500/20"
+ : "border-red-300 focus:border-red-500 focus:ring-red-500/20"
}`}
disabled={fetching}
/>
{!isUrlValid && (
- <p className="mt-1 text-sm text-red-600">
+ <p className="mt-2 text-sm text-red-600">
Please enter a valid URL (http:// or https://)
</p>
)}
- {error && <p className="mt-1 text-sm text-red-600">{error}</p>}
+ {error && <p className="mt-2 text-sm text-red-600">{error}</p>}
</div>
<button
type="submit"
disabled={fetching || !url.trim() || !isUrlValid}
- className="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:bg-gray-400 disabled:cursor-not-allowed"
+ className="inline-flex items-center gap-2 rounded-lg bg-sky-600 px-5 py-2.5 text-sm font-medium text-white transition-all duration-200 hover:bg-sky-700 focus:outline-none focus:ring-2 focus:ring-sky-500 focus:ring-offset-2 disabled:cursor-not-allowed disabled:bg-stone-200 disabled:text-stone-400"
>
{fetching ? (
- <FontAwesomeIcon icon={faSpinner} spin className="mr-2" />
+ <FontAwesomeIcon icon={faSpinner} spin />
) : (
- <FontAwesomeIcon icon={faPlus} className="mr-2" />
+ <FontAwesomeIcon icon={faPlus} />
)}
- Subscribe
+ <span>Subscribe</span>
</button>
</div>
</div>
diff --git a/frontend/src/components/ArticleItem.tsx b/frontend/src/components/ArticleItem.tsx
index c61923a..f8fac24 100644
--- a/frontend/src/components/ArticleItem.tsx
+++ b/frontend/src/components/ArticleItem.tsx
@@ -51,38 +51,36 @@ export function ArticleItem({ article, onReadChange }: Props) {
return (
<div
- className={`group flex items-center gap-3 rounded-lg border p-3 hover:bg-gray-50 ${
+ className={`group flex items-center gap-3 rounded-lg p-3 transition-all duration-200 ${
article.isRead
- ? "border-gray-200 bg-white"
- : "border-blue-200 bg-blue-50"
+ ? "bg-white hover:bg-stone-50"
+ : "border-l-2 border-l-sky-500 bg-sky-50/50"
}`}
>
<button
type="button"
onClick={() => handleToggleRead(article.id, article.isRead)}
- className={`flex-shrink-0 rounded p-1 transition-colors ${
+ className={`flex-shrink-0 rounded-md p-1.5 transition-all duration-150 ${
article.isRead
- ? "text-gray-400 hover:text-gray-600"
- : "text-blue-600 hover:text-blue-700"
+ ? "text-stone-300 hover:bg-stone-100 hover:text-stone-500"
+ : "text-sky-500 hover:bg-sky-100 hover:text-sky-600"
}`}
title={article.isRead ? "Mark as unread" : "Mark as read"}
>
<FontAwesomeIcon
icon={article.isRead ? faCheck : faCircle}
- className="w-4 h-4"
+ className="h-4 w-4"
/>
</button>
- <div className="flex-1 min-w-0">
+ <div className="min-w-0 flex-1">
<button
type="button"
onClick={() => handleArticleClick(article)}
- className={`text-left w-full group-hover:text-blue-600 transition-colors ${
- article.isRead ? "text-gray-700" : "text-gray-900 font-medium"
+ className={`w-full text-left transition-colors duration-150 group-hover:text-sky-700 ${
+ article.isRead ? "text-stone-500" : "font-medium text-stone-900"
}`}
>
- <div className="flex items-center gap-2 break-words">
- {article.title}
- </div>
+ <span className="break-words">{article.title}</span>
</button>
</div>
</div>
diff --git a/frontend/src/components/ArticleList.tsx b/frontend/src/components/ArticleList.tsx
index 574f529..afadb25 100644
--- a/frontend/src/components/ArticleList.tsx
+++ b/frontend/src/components/ArticleList.tsx
@@ -30,7 +30,9 @@ export function ArticleList({ articles, isReadView }: Props) {
if (visibleArticles.length === 0) {
return (
- <div className="p-4 text-center text-gray-500">No articles found.</div>
+ <div className="py-8 text-center">
+ <p className="text-sm text-stone-400">No articles found.</p>
+ </div>
);
}
@@ -54,14 +56,14 @@ export function ArticleList({ articles, isReadView }: Props) {
);
return (
- <div className="space-y-6 p-4">
+ <div className="space-y-8">
{Object.values(articlesByFeed).map(({ feed, articles: feedArticles }) => (
- <div key={feed.id} className="space-y-2">
- <h3 className="text-lg font-semibold text-gray-900 border-b border-gray-200 pb-2">
+ <div key={feed.id} className="space-y-3">
+ <h3 className="border-b border-stone-200 pb-2 text-sm font-semibold uppercase tracking-wide text-stone-900">
{feed.title}
- <span className="ml-2 text-sm font-normal text-gray-500">
- ({feedArticles.length} article
- {feedArticles.length !== 1 ? "s" : ""})
+ <span className="ml-2 text-xs font-normal normal-case tracking-normal text-stone-400">
+ {feedArticles.length} article
+ {feedArticles.length !== 1 ? "s" : ""}
</span>
</h3>
<div className="space-y-1">
diff --git a/frontend/src/components/FeedItem.tsx b/frontend/src/components/FeedItem.tsx
index 80c8992..8333f75 100644
--- a/frontend/src/components/FeedItem.tsx
+++ b/frontend/src/components/FeedItem.tsx
@@ -41,26 +41,29 @@ export function FeedItem({ feed, onFeedUnsubscribed }: Props) {
};
return (
- <div className="rounded-lg border border-gray-200 bg-white p-4 shadow-sm">
- <div className="flex items-start justify-between">
- <div className="flex-1">
- <h3 className="text-lg font-semibold text-gray-900">{feed.title}</h3>
- <p className="mt-1 text-sm text-gray-500">
- <a href={feed.url} target="_blank" rel="noreferrer">
- {feed.url}
- </a>
+ <div className="group rounded-xl border border-stone-200 bg-white p-5 transition-all duration-200 hover:border-stone-300 hover:shadow-sm">
+ <div className="flex items-start justify-between gap-4">
+ <div className="min-w-0 flex-1">
+ <h3 className="truncate text-base font-semibold text-stone-900">
+ {feed.title}
+ </h3>
+ <a
+ href={feed.url}
+ target="_blank"
+ rel="noreferrer"
+ className="mt-1 block truncate text-sm text-stone-400 transition-colors hover:text-sky-600"
+ >
+ {feed.url}
+ </a>
+ <p className="mt-2 text-xs text-stone-400">
+ Last fetched: {formatDateTime(new Date(feed.fetchedAt))}
</p>
- <div className="mt-2 flex items-center gap-4 text-sm">
- <span className="text-gray-400">
- Last fetched: {formatDateTime(new Date(feed.fetchedAt))}
- </span>
- </div>
</div>
- <div className="flex items-center gap-2">
+ <div className="flex items-center gap-1">
<button
type="button"
onClick={() => handleMarkAllRead(feed.id)}
- className="rounded p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900"
+ className="rounded-lg p-2 text-stone-400 transition-all duration-150 hover:bg-stone-100 hover:text-stone-600"
title="Mark all as read"
>
<FontAwesomeIcon icon={faCheck} />
@@ -68,7 +71,7 @@ export function FeedItem({ feed, onFeedUnsubscribed }: Props) {
<button
type="button"
onClick={() => handleMarkAllUnread(feed.id)}
- className="rounded p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-900"
+ className="rounded-lg p-2 text-stone-400 transition-all duration-150 hover:bg-stone-100 hover:text-stone-600"
title="Mark all as unread"
>
<FontAwesomeIcon icon={faCircle} />
@@ -76,7 +79,7 @@ export function FeedItem({ feed, onFeedUnsubscribed }: Props) {
<button
type="button"
onClick={() => handleUnsubscribeFeed(feed.id)}
- className="rounded p-2 text-red-600 hover:bg-red-50 hover:text-red-700"
+ className="rounded-lg p-2 text-stone-400 transition-all duration-150 hover:bg-red-50 hover:text-red-600"
title="Unsubscribe from feed"
>
<FontAwesomeIcon icon={faTrash} />
diff --git a/frontend/src/components/FeedList.tsx b/frontend/src/components/FeedList.tsx
index 6081293..24bcfc7 100644
--- a/frontend/src/components/FeedList.tsx
+++ b/frontend/src/components/FeedList.tsx
@@ -15,17 +15,29 @@ export function FeedList({ onFeedUnsubscribed }: Props) {
});
if (fetching) {
- return <div className="p-4">Loading feeds...</div>;
+ return (
+ <div className="py-8 text-center">
+ <p className="text-sm text-stone-400">Loading feeds...</p>
+ </div>
+ );
}
if (error) {
- return <div className="p-4 text-red-600">Error: {error.message}</div>;
+ return (
+ <div className="rounded-lg bg-red-50 p-4 text-sm text-red-600">
+ Error: {error.message}
+ </div>
+ );
}
if (!data?.feeds || data.feeds.length === 0) {
- return <div className="p-4 text-gray-500">No feeds added yet.</div>;
+ return (
+ <div className="py-8 text-center">
+ <p className="text-sm text-stone-400">No feeds added yet.</p>
+ </div>
+ );
}
return (
- <div className="space-y-4 p-4">
+ <div className="space-y-3">
{data.feeds.map((feed) => (
<FeedItem
key={feed.id}
diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx
index 09a0eb4..5f30de5 100644
--- a/frontend/src/components/Layout.tsx
+++ b/frontend/src/components/Layout.tsx
@@ -7,9 +7,9 @@ interface Props {
export function Layout({ children }: Props) {
return (
- <div className="min-h-screen bg-gray-50">
+ <div className="min-h-screen bg-stone-50">
<Navigation />
- <main className="container mx-auto px-4 py-8">{children}</main>
+ <main className="mx-auto max-w-5xl px-6 py-10">{children}</main>
</div>
);
}
diff --git a/frontend/src/components/MenuItem.tsx b/frontend/src/components/MenuItem.tsx
index 29e397c..42202c9 100644
--- a/frontend/src/components/MenuItem.tsx
+++ b/frontend/src/components/MenuItem.tsx
@@ -15,10 +15,10 @@ export function MenuItem({ path, label, icon }: Props) {
return (
<Link
href={path}
- className={`flex items-center sm:space-x-2 px-3 py-2 rounded-md text-sm font-medium transition-colors ${
+ className={`flex items-center gap-2 px-3 py-1.5 rounded-lg text-sm font-medium transition-all duration-200 ${
isActive
- ? "bg-blue-100 text-blue-700"
- : "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
+ ? "bg-sky-50 text-sky-700"
+ : "text-stone-500 hover:text-stone-900 hover:bg-stone-100"
}`}
>
<FontAwesomeIcon icon={icon} />
diff --git a/frontend/src/components/Navigation.tsx b/frontend/src/components/Navigation.tsx
index c4af36a..1f99cd6 100644
--- a/frontend/src/components/Navigation.tsx
+++ b/frontend/src/components/Navigation.tsx
@@ -17,13 +17,16 @@ export function Navigation() {
};
return (
- <nav className="bg-white shadow-sm border-b border-gray-200">
- <div className="container mx-auto px-4">
- <div className="flex items-center justify-between h-16">
- <Link href="/" className="text-xl font-bold text-gray-900">
+ <nav className="sticky top-0 z-50 bg-white/80 backdrop-blur-sm border-b border-stone-200/60">
+ <div className="mx-auto max-w-5xl px-6">
+ <div className="flex items-center justify-between h-14">
+ <Link
+ href="/"
+ className="text-lg font-semibold tracking-tight text-stone-900"
+ >
feedaka
</Link>
- <div className="flex items-center space-x-6">
+ <div className="flex items-center gap-1">
<MenuItem path="/unread" label="Unread" icon={faBookOpen} />
<MenuItem path="/read" label="Read" icon={faCircleCheck} />
<MenuItem path="/settings" label="Settings" icon={faGear} />
@@ -31,11 +34,13 @@ export function Navigation() {
<button
type="button"
onClick={handleLogout}
- className="flex items-center space-x-2 text-gray-600 hover:text-gray-900"
+ className="flex items-center gap-2 px-3 py-1.5 rounded-lg text-stone-500 hover:text-stone-900 hover:bg-stone-100 transition-all duration-200"
title="Logout"
>
<FontAwesomeIcon icon={faRightFromBracket} />
- <span className="hidden sm:inline">Logout</span>
+ <span className="hidden sm:inline text-sm font-medium">
+ Logout
+ </span>
</button>
)}
</div>