First Commit. Duplicate bunext-sqlite preset

This commit is contained in:
2026-10-18 16:47:11 +01:00
commit 81e4a8aecc
718 changed files with 41695 additions and 0 deletions
+28
View File
@@ -0,0 +1,28 @@
export type FooterColumnLink = {
title: string;
url: string;
};
export type FooterColumn = {
label: string;
links: FooterColumnLink[];
};
export const FooterColumns: FooterColumn[] = [
{
label: "Product",
links: [
{ title: "Features", url: "/#features" },
{ title: "About", url: "/about" },
{ title: "Contact", url: "/contact" },
{ title: "Log in", url: "/auth/login" },
],
},
{
label: "Legal",
links: [
{ title: "Terms and Conditions", url: "/terms" },
{ title: "Privacy Policy", url: "/privacy" },
],
},
];
+104
View File
@@ -0,0 +1,104 @@
import React from "react";
import ReactDOM from "react-dom";
import { Menu, X } from "lucide-react";
import Stack from "@moduletrace/twui/layout/Stack";
import { MainLinks } from "@/src/dict/main-links-dict";
import LinkList from "@moduletrace/twui/elements/LinkList";
import HeaderCTA from "./header-cta";
import Row from "@moduletrace/twui/layout/Row";
import Logo from "@/src/components/general/logo";
import { twMerge } from "tailwind-merge";
import MobileNavThemeRow from "@/src/components/general/mobile-nav-theme-row";
export default function MobileNav() {
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
if (!open) return;
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
document.addEventListener("keydown", onKey);
return () => {
document.body.style.overflow = prev;
document.removeEventListener("keydown", onKey);
};
}, [open]);
return (
<div className="flex xl:hidden relative">
<button
type="button"
aria-label="Open menu"
aria-expanded={open}
className="cursor-pointer p-1 text-dark dark:text-white"
onClick={() => setOpen(true)}
>
<Menu size={26} />
</button>
{open
? ReactDOM.createPortal(
<div
className={twMerge(
"fixed inset-0 z-200 flex flex-col",
"bg-light text-dark dark:bg-background-dark dark:text-white",
)}
role="dialog"
aria-modal="true"
aria-label="Mobile navigation"
onClick={(e) => {
if ((e.target as HTMLElement).closest("a")) {
setOpen(false);
}
}}
>
<Row
className={twMerge(
"w-full justify-between items-center flex-nowrap",
"px-4 sm:px-10 py-4 border-b",
"border-slate-200/70 dark:border-white/10",
)}
>
<Logo icon_size={28} />
<button
type="button"
aria-label="Close menu"
className="cursor-pointer p-1 text-dark dark:text-white"
onClick={() => setOpen(false)}
>
<X size={26} />
</button>
</Row>
<Stack className="grow w-full items-stretch px-4 sm:px-10 py-6 gap-8! overflow-y-auto">
<nav className="w-full">
<LinkList
links={MainLinks}
className="flex-col items-stretch gap-1 w-full"
linkProps={{
className: twMerge(
"px-0 py-4 w-full text-lg border-0",
"text-slate-600 hover:text-dark",
"dark:text-slate-300 dark:hover:text-white",
),
}}
/>
</nav>
<HeaderCTA className="flex flex-col w-full items-stretch" />
</Stack>
<MobileNavThemeRow className="px-4 sm:px-10 py-4" />
</div>,
document.body,
)
: null}
</div>
);
}
@@ -0,0 +1,21 @@
import Stack from "@moduletrace/twui/layout/Stack";
import Span from "@moduletrace/twui/layout/Span";
import LinkList from "@moduletrace/twui/elements/LinkList";
import type { FooterColumn } from "../(data)/footer-columns";
type Props = FooterColumn;
export default function FooterLinkColumn({ label, links }: Props) {
return (
<Stack className="gap-5! min-w-[160px]">
<Span className="text-xs text-slate-400 dark:text-slate-500">
{label}
</Span>
<LinkList
links={links}
className="flex-col items-start gap-3"
linkProps={{ className: "p-0 text-sm border-0" }}
/>
</Stack>
);
}
@@ -0,0 +1,33 @@
import Row from "@moduletrace/twui/layout/Row";
import Button from "@moduletrace/twui/layout/Button";
import { twMerge } from "tailwind-merge";
import { useAuthStatus } from "@/src/hooks/use-auth-status";
type Props = {
className?: string;
};
export default function HeaderCTA({ className }: Props) {
const { is_logged_in } = useAuthStatus();
if (is_logged_in) {
return null;
}
return (
<Row className={twMerge("flex-col", className)}>
<Button
title="Log in"
href="/auth/login"
color="gray"
variant="outlined"
className="w-full"
>
Log in
</Button>
<Button title="Get Started" href="/auth/signup" className="w-full">
Get Started
</Button>
</Row>
);
}
@@ -0,0 +1,59 @@
import Row from "@moduletrace/twui/layout/Row";
import Dropdown from "@moduletrace/twui/elements/Dropdown";
import Img from "@moduletrace/twui/layout/Img";
import Paper from "@moduletrace/twui/elements/Paper";
import LinkList from "@moduletrace/twui/elements/LinkList";
import Divider from "@moduletrace/twui/layout/Divider";
import { useLocalUser } from "@/src/hooks/use-local-user";
export default function HeaderUser() {
const { user } = useLocalUser();
if (!user?.logged_in_status) {
return null;
}
return (
<Row className="">
<Dropdown
target={
<Img
alt="Logged in user image"
src={user.image_thumbnail ?? "/icons/man.png"}
size={32}
circle
className="bg-slate-300 dark:bg-slate-700 ring-1 ring-slate-200 dark:ring-white/10 cursor-pointer"
title={
user.first_name +
(user.last_name ? ` ${user.last_name}` : "")
}
/>
}
position="bottom-right"
>
<Paper className="min-w-[200px] px-0! py-2 header-user-dropdown">
<LinkList
links={[
{
title: "Dashboard",
url: "/admin",
},
{
component: <Divider />,
},
{
title: "Settings",
url: "/admin/settings",
},
{
title: "Logout",
url: "/auth/logout",
},
]}
className="flex-col items-stretch w-full"
/>
</Paper>
</Dropdown>
</Row>
);
}
+63
View File
@@ -0,0 +1,63 @@
import React from "react";
import { AppContext } from "@/src/pages/__root";
import type { AppContextObject } from "@/src/types";
import Stack from "@moduletrace/twui/layout/Stack";
import Section from "@moduletrace/twui/layout/Section";
import Container from "@moduletrace/twui/layout/Container";
import Row from "@moduletrace/twui/layout/Row";
import Span from "@moduletrace/twui/layout/Span";
import Logo from "@/src/components/general/logo";
import ColorSchemeSelector from "@moduletrace/twui/elements/ColorSchemeSelector";
import { FooterColumns } from "../(data)/footer-columns";
import FooterLinkColumn from "../(partials)/footer-link-column";
export default function Footer() {
const appContext: AppContextObject = React.useContext(AppContext);
const { SITE_DATA } = appContext;
return (
<footer className="bg-white dark:bg-background-dark border-t border-slate-200 dark:border-white/10">
<Section className="pt-20! pb-0!">
<Container>
<Stack className="w-full gap-16! items-stretch">
<Row className="w-full items-start justify-between gap-14! flex-wrap">
<Stack className="gap-5! max-w-md">
<Logo icon_size={32} />
<Span className="text-slate-500 dark:text-slate-400 leading-relaxed">
A considered starting point for Bun, Bunext
and SQLite applications.
</Span>
</Stack>
<Row className="gap-16! justify-start flex-wrap items-start">
{FooterColumns.map((column) => (
<FooterLinkColumn
key={column.label}
{...column}
/>
))}
</Row>
</Row>
<Row className="w-full items-center justify-between flex-wrap py-8 gap-4! border-t border-slate-200 dark:border-white/10">
<Span className="text-xs text-slate-400 dark:text-slate-500">
&copy; {new Date().getFullYear()}{" "}
{SITE_DATA.siteName}. All rights reserved.
</Span>
<Row className="items-center gap-6!">
<Span className="text-xs text-slate-400 dark:text-slate-500">
Built with Bunext + SQLite
</Span>
<ColorSchemeSelector
className="text-slate-500 hover:text-foreground-light dark:text-slate-400 dark:hover:text-foreground-dark transition-colors"
size="small"
/>
</Row>
</Row>
</Stack>
</Container>
</Section>
</footer>
);
}
+56
View File
@@ -0,0 +1,56 @@
import MobileNav from "../(partials)/MobileNav";
import Section from "@moduletrace/twui/layout/Section";
import Container from "@moduletrace/twui/layout/Container";
import Row from "@moduletrace/twui/layout/Row";
import Logo from "@/src/components/general/logo";
import HeaderNav from "@moduletrace/twui/elements/HeaderNav";
import { MainLinks } from "@/src/dict/main-links-dict";
import Button from "@moduletrace/twui/layout/Button";
import HeaderUser from "../(partials)/header-user";
import { useAuthStatus } from "@/src/hooks/use-auth-status";
export default function Header() {
const { is_logged_in } = useAuthStatus();
return (
<header className="sticky top-0 z-50 w-full border-b border-slate-200/70 bg-light/80 backdrop-blur-md dark:border-white/10 dark:bg-background-dark/80">
<Section className="py-0!">
<Container className="w-full justify-between">
<Row className="w-full items-center justify-between flex-nowrap py-4">
<Logo icon_size={28} />
<div className="hidden xl:flex">
<HeaderNav headerLinks={MainLinks} />
</div>
<Row className="flex-nowrap items-center gap-2!">
{!is_logged_in ? (
<>
<Button
title="Log in"
href="/auth/login"
variant="ghost"
color="gray"
size="small"
className="hidden sm:flex"
>
Log in
</Button>
<Button
title="Get started"
href="/auth/signup"
size="small"
>
Get started
</Button>
</>
) : null}
<MobileNav />
<HeaderUser />
</Row>
</Row>
</Container>
</Section>
</header>
);
}
+24
View File
@@ -0,0 +1,24 @@
import React from "react";
import Header from "./(sections)/Header";
import Footer from "./(sections)/Footer";
import { AppContext } from "@/src/pages/__root";
import Stack from "@moduletrace/twui/layout/Stack";
type Props = {
children: React.ReactNode;
blank?: boolean;
};
export default function Layout({ children, blank }: Props): React.JSX.Element {
const {} = React.useContext(AppContext);
return (
<React.Fragment>
<Stack className="w-full items-stretch gap-0">
{blank ? null : <Header />}
<main>{children}</main>
{blank ? null : <Footer />}
</Stack>
</React.Fragment>
);
}