From ff2894e76781517af58cd519d4d8e41e01ef15ce Mon Sep 17 00:00:00 2001 From: rimar1337 <132627503+rimar1337@users.noreply.github.com> Date: Sat, 18 Oct 2025 08:09:04 +0000 Subject: [PATCH] material 3 initial design --- package-lock.json | 12 ++++++++++++ package.json | 1 + vite.config.ts | 3 ++- src/auto-imports.d.ts | 1 + src/components/Header.tsx | 29 +++++++++++++++++++++++++++++ src/components/InfiniteCustomFeed.tsx | 9 +++++---- src/components/Login.tsx | 200 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------- src/routes/__root.tsx | 527 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------------------------------------------------------------------------------------------------------------- src/routes/index.tsx | 36 +++++++++++++++++++++++------------- src/styles/app.css | 8 ++++++++ src/routes/profile.$did/index.tsx | 17 ++++++++++++++--- src/routes/profile.$did/post.$rkey.tsx | 45 ++++++++++++++------------------------------- 12 file(s) changed, 656 insertion(s)(+), 232 deletion(s)(-) diff --git a/package-lock.json b/package-lock.json --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "@iconify-icon/react": "^3.0.1", "@iconify-json/material-symbols": "^1.2.42", "@iconify-json/mdi": "^1.2.3", + "@iconify/json": "^2.2.396", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", "@testing-library/dom": "^10.4.0", @@ -1681,6 +1682,17 @@ "license": "Apache-2.0", "dependencies": { "@iconify/types": "*" + } + }, + "node_modules/@iconify/json": { + "version": "2.2.396", + "resolved": "https://registry.npmjs.org/@iconify/json/-/json-2.2.396.tgz", + "integrity": "sha512-tijg77JFuYIt32S9N8p7La8C0zp9zKZsX6UP8ip5GVB1F6Mp3pZA5Vc5eAquTY50NoDJX58U6z4Qn3d6Wyossg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@iconify/types": "*", + "pathe": "^2.0.0" } }, "node_modules/@iconify/types": { diff --git a/package.json b/package.json --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@iconify-icon/react": "^3.0.1", "@iconify-json/material-symbols": "^1.2.42", "@iconify-json/mdi": "^1.2.3", + "@iconify/json": "^2.2.396", "@svgr/core": "^8.1.0", "@svgr/plugin-jsx": "^8.1.0", "@testing-library/dom": "^10.4.0", diff --git a/vite.config.ts b/vite.config.ts --- a/vite.config.ts +++ b/vite.config.ts @@ -39,12 +39,13 @@ IconsResolver({ prefix: 'Icon', extension: 'jsx', + enabledCollections: ['mdi','material-symbols'], }), ], dts: 'src/auto-imports.d.ts', }), Icons({ - autoInstall: true, + //autoInstall: true, compiler: 'jsx', jsx: 'react' }), diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -8,6 +8,7 @@ declare global { const IconMaterialSymbolsAccountCircle: typeof import('~icons/material-symbols/account-circle.jsx').default const IconMaterialSymbolsAccountCircleOutline: typeof import('~icons/material-symbols/account-circle-outline.jsx').default + const IconMaterialSymbolsArrowBack: typeof import('~icons/material-symbols/arrow-back.jsx').default const IconMaterialSymbolsHome: typeof import('~icons/material-symbols/home.jsx').default const IconMaterialSymbolsHomeOutline: typeof import('~icons/material-symbols/home-outline.jsx').default const IconMaterialSymbolsNotifications: typeof import('~icons/material-symbols/notifications.jsx').default diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,29 @@ +import { Link, useRouter } from "@tanstack/react-router"; + +export function Header({ + backButtonCallback, + title +}: { + backButtonCallback?: () => void; + title?: string; +}) { + const router = useRouter(); + //const what = router.history. + return ( +
+ {backButtonCallback ? ( { + e.preventDefault(); + backButtonCallback(); + }} + aria-label="Go back" + > + + ) : (
)} + {title} +
+ ); +} diff --git a/src/components/InfiniteCustomFeed.tsx b/src/components/InfiniteCustomFeed.tsx --- a/src/components/InfiniteCustomFeed.tsx +++ b/src/components/InfiniteCustomFeed.tsx @@ -1,11 +1,12 @@ import * as React from "react"; + //import { useInView } from "react-intersection-observer"; import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; import { useAuth } from "~/providers/UnifiedAuthProvider"; import { - useQueryArbitrary, - useQueryIdentity, useInfiniteQueryFeedSkeleton, + // useQueryArbitrary, + // useQueryIdentity, } from "~/utils/useQuery"; interface InfiniteCustomFeedProps { @@ -112,10 +113,10 @@ ); diff --git a/src/components/Login.tsx b/src/components/Login.tsx --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -1,10 +1,17 @@ // src/components/Login.tsx -import React, { useEffect, useState, useRef } from "react"; -import { useAuth } from "~/providers/UnifiedAuthProvider"; import { Agent } from "@atproto/api"; +import React, { useEffect, useRef, useState } from "react"; + +import { useAuth } from "~/providers/UnifiedAuthProvider"; // --- 1. The Main Component (Orchestrator with `compact` prop) --- -export default function Login({ compact = false }: { compact?: boolean }) { +export default function Login({ + compact = false, + popup = false, +}: { + compact?: boolean; + popup?: boolean; +}) { const { status, agent, logout } = useAuth(); // Loading state can be styled differently based on the prop @@ -33,7 +40,7 @@ // Large view if (!compact) { return ( -
+

You are logged in! @@ -41,7 +48,7 @@ @@ -67,14 +74,14 @@ if (!compact) { // Large view renders the form directly in the card return ( -

+
); } // Compact view renders a button that toggles the form in a dropdown - return ; + return ; } // --- 2. The Reusable, Self-Contained Login Form Component --- @@ -83,7 +90,7 @@ return (
-
+
{ +const CompactLoginButton = ({popup}:{popup?: boolean}) => { const [showForm, setShowForm] = useState(false); const formRef = useRef(null); @@ -125,12 +132,12 @@
{showForm && ( -
+
)} @@ -138,13 +145,21 @@ ); }; -const TabButton = ({ label, active, onClick }: { label: string; active: boolean; onClick: () => void; }) => ( +const TabButton = ({ + label, + active, + onClick, +}: { + label: string; + active: boolean; + onClick: () => void; +}) => ( +

+ Sign in with AT. Your password is never shared. +

+ setHandle(e.target.value)} + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" + /> + ); }; @@ -201,51 +229,109 @@ return (
-

Warning: Less secure. Use an App Password.

- setUser(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" autoComplete="username" /> - setPassword(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" autoComplete="current-password" /> - setServiceURL(e.target.value)} className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" /> +

+ Warning: Less secure. Use an App Password. +

+ setUser(e.target.value)} + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" + autoComplete="username" + /> + setPassword(e.target.value)} + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" + autoComplete="current-password" + /> + setServiceURL(e.target.value)} + className="px-3 py-2 rounded border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 text-sm focus:outline-none focus:ring-2 focus:ring-gray-500" + /> {error &&

{error}

} - +
); }; // --- Profile Component (now supports a `large` prop for styling) --- -export const ProfileThing = ({ agent, large = false }: { agent: Agent | null; large?: boolean }) => { - const [profile, setProfile] = useState(null); +export const ProfileThing = ({ + agent, + large = false, +}: { + agent: Agent | null; + large?: boolean; +}) => { + const [profile, setProfile] = useState(null); - useEffect(() => { - const fetchUser = async () => { - const did = (agent as any)?.session?.did ?? (agent as any)?.assertDid; - if (!did) return; - try { - const res = await agent!.getProfile({ actor: did }); - setProfile(res.data); - } catch (e) { console.error("Failed to fetch profile", e); } - }; - if (agent) fetchUser(); - }, [agent]); - - if (!profile) { - return ( // Skeleton loader -
-
-
-
-
-
-
- ); + useEffect(() => { + const fetchUser = async () => { + const did = (agent as any)?.session?.did ?? (agent as any)?.assertDid; + if (!did) return; + try { + const res = await agent!.getProfile({ actor: did }); + setProfile(res.data); + } catch (e) { + console.error("Failed to fetch profile", e); } - - return ( -
- avatar -
-
{profile?.displayName}
-
@{profile?.handle}
-
+ }; + if (agent) fetchUser(); + }, [agent]); + + if (!profile) { + return ( + // Skeleton loader +
+
+
+
+
- ); -}; \ No newline at end of file +
+ ); + } + + return ( +
+ avatar +
+
+ {profile?.displayName} +
+
+ @{profile?.handle} +
+
+
+ ); +}; diff --git a/src/routes/__root.tsx b/src/routes/__root.tsx --- a/src/routes/__root.tsx +++ b/src/routes/__root.tsx @@ -5,7 +5,7 @@ import type { QueryClient } from "@tanstack/react-query"; import { createRootRouteWithContext, - Link, + // Link, // Outlet, Scripts, useLocation, @@ -176,7 +176,7 @@ )}
-
- + ) : ( +
+
+ Red Dwarf Logo + + Red Dwarf{" "} + {/* + lite + */} + +
+
+ +
- + )} - + + ); +} + +function MaterialNavItem({ + InactiveIcon, + ActiveIcon, + text, + active, + onClickCallbback, + small, +}: { + InactiveIcon: React.ReactElement; + ActiveIcon: React.ReactElement; + text: string; + active: boolean; + onClickCallbback: () => void; + small?: boolean; +}) { + if (small) + return ( + + ); + + return ( + + ); +} + +function MaterialPillButton({ + InactiveIcon, + ActiveIcon, + text, + //active, + onClickCallbback, + small, +}: { + InactiveIcon: React.ReactElement; + ActiveIcon: React.ReactElement; + text: string; + //active: boolean; + onClickCallbback: () => void; + small?: boolean; +}) { + const active = false; + return ( + ); } diff --git a/src/routes/index.tsx b/src/routes/index.tsx --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -3,6 +3,7 @@ import * as React from "react"; import { useEffect, useLayoutEffect } from "react"; +import { Header } from "~/components/Header"; import { InfiniteCustomFeed } from "~/components/InfiniteCustomFeed"; import { useAuth } from "~/providers/UnifiedAuthProvider"; import { @@ -353,9 +354,9 @@
-
- {savedFeeds.length > 0 ? ( - savedFeeds.map((item: any, idx: number) => { + {savedFeeds.length > 0 ? ( +
+ {savedFeeds.map((item: any, idx: number) => { const label = item.value.split("/").pop() || item.value; const isActive = selectedFeed === item.value; return ( @@ -363,27 +364,36 @@ key={item.value || idx} className={`px-3 py-1 rounded-full whitespace-nowrap font-medium transition-colors ${ isActive - ? "bg-gray-500 text-white" - : item.pinned - ? "bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200" - : "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-200" + ? "text-gray-900 dark:text-gray-100 hover:bg-gray-300 dark:bg-gray-700 bg-gray-200 hover:dark:bg-gray-600" + : "text-gray-600 dark:text-gray-400 hover:bg-gray-100 hover:dark:bg-gray-800" + // ? "bg-gray-500 text-white" + // : item.pinned + // ? "bg-gray-200 text-gray-700 dark:bg-gray-700 dark:text-gray-200" + // : "bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-200" }`} onClick={() => setSelectedFeed(item.value)} title={item.value} > {label} {item.pinned && ( - + )} ); - }) - ) : ( - Home - )} -
+ })} +
+ ) : ( + // Home +
+ )} {/* {isFeedLoading &&
Loading...
} {feedError &&
{feedError.message}
} {!isFeedLoading && !feedError && feed.length === 0 && ( diff --git a/src/styles/app.css b/src/styles/app.css --- a/src/styles/app.css +++ b/src/styles/app.css @@ -1,3 +1,4 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&family=Spectral+SC:wght@500&display=swap'); @import "tailwindcss"; /* @theme { @@ -78,4 +79,11 @@ color: rgb(29, 122, 242); word-break: break-all; } +} + +.font-inter { + font-family: "Inter", sans-serif; +} +.font-roboto { + font-family: "Roboto", sans-serif; } \ No newline at end of file diff --git a/src/routes/profile.$did/index.tsx b/src/routes/profile.$did/index.tsx --- a/src/routes/profile.$did/index.tsx +++ b/src/routes/profile.$did/index.tsx @@ -1,7 +1,8 @@ import { useQueryClient } from "@tanstack/react-query"; -import { createFileRoute, Link } from "@tanstack/react-router"; +import { createFileRoute } from "@tanstack/react-router"; import React from "react"; +import { Header } from "~/components/Header"; import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; import { useAuth } from "~/providers/UnifiedAuthProvider"; import { toggleFollow, useGetFollowState } from "~/utils/followState"; @@ -104,7 +105,17 @@ return ( <> -
+
{ + if (window.history.length > 1) { + window.history.back(); + } else { + window.location.assign("/"); + } + }} + /> + {/*
Profile -
+
*/} {/* Profile Header */}
diff --git a/src/routes/profile.$did/post.$rkey.tsx b/src/routes/profile.$did/post.$rkey.tsx --- a/src/routes/profile.$did/post.$rkey.tsx +++ b/src/routes/profile.$did/post.$rkey.tsx @@ -2,6 +2,7 @@ import { createFileRoute } from "@tanstack/react-router"; import React, { useLayoutEffect } from "react"; +import { Header } from "~/components/Header"; import { UniversalPostRendererATURILoader } from "~/components/UniversalPostRenderer"; //import { usePersistentStore } from '~/providers/PersistentStoreProvider'; import { @@ -296,38 +297,20 @@ return ( <> -
- {!nopics ? ( - - ) : ( - - )} - Post -
+ } + /> {parentsLoading && (
-- tangled.sh