diff --git a/app/about/page.tsx b/app/about/page.tsx
index bcfd410..069e2f1 100644
--- a/app/about/page.tsx
+++ b/app/about/page.tsx
@@ -1,13 +1,12 @@
import React from "react";
-import Base from "../components/layouts/base";
-import ProfileHeader from "../components/ui/header";
-import ContainerLayout from "../components/layouts/container";
+import Base from "@components/layouts/base";
+import ContainerLayout from "@components/layouts/container";
import InProgressImage from "../../public/aboutme-option12.webp";
import Image from "next/image";
const page = () => {
return (
-
+
About Me
diff --git a/app/components/layouts/base.tsx b/app/components/layouts/base.tsx
index 15b90e5..b53aed6 100644
--- a/app/components/layouts/base.tsx
+++ b/app/components/layouts/base.tsx
@@ -1,17 +1,18 @@
import React, { ReactNode } from "react";
-import NavBar from "../ui/nav-bar";
+import SeoBase from "@components/layouts/seo-base";
+import NavBar from "@components/ui/nav-bar/nav-bar";
-interface Props {
- children: ReactNode;
-}
-
-const Base = ({ children }: Props) => {
+export default function Base({
+ title,
+ subtitle,
+ pageSlug,
+ children,
+}: BaseLayoutProps) {
return (
<>
-
+
+
{children}
>
);
-};
-
-export default Base;
+}
diff --git a/app/components/layouts/seo-base.tsx b/app/components/layouts/seo-base.tsx
new file mode 100644
index 0000000..ac82367
--- /dev/null
+++ b/app/components/layouts/seo-base.tsx
@@ -0,0 +1,26 @@
+import Head from "next/head";
+
+// -----------------------------------------------------------------------------
+// Component
+// -----------------------------------------------------------------------------
+
+export default function SeoBase({
+ title = "Kent Miguel",
+ subtitle,
+}: SeoBaseProps) {
+ const fullTitle =
+ subtitle && subtitle.length > 0 ? `${title} | ${subtitle}` : title;
+
+ return (
+
+ {/*
+
+
+ */}
+
+ {fullTitle}
+
+
+
+ );
+}
diff --git a/app/components/ui/button/index.tsx b/app/components/ui/button/index.tsx
new file mode 100644
index 0000000..d580043
--- /dev/null
+++ b/app/components/ui/button/index.tsx
@@ -0,0 +1,77 @@
+import Link from 'next/link';
+import { type ForwardedRef, forwardRef } from 'react';
+
+import { buttonIconVariants, buttonVariants } from './styles';
+import type { ButtonProps } from './types';
+import { cx } from 'class-variance-authority';
+import { twMerge } from 'tailwind-merge';
+
+const Button = forwardRef(
+ (
+ {
+ className,
+ size = 'md',
+ variant = 'primary',
+ intent = 'none',
+ disabled = false,
+ href,
+ leftIcon,
+ rightIcon,
+ newTab,
+ title,
+ onClick,
+ children,
+ ...rest
+ }: ButtonProps,
+ ref: ForwardedRef,
+ ) => {
+ const props = {
+ className: twMerge(
+ cx(
+ buttonVariants({ size, variant, intent: !disabled ? intent : undefined, disabled }),
+ className,
+ ),
+ ),
+ title: title || href || undefined,
+ 'data-variant': variant,
+ 'data-disabled': disabled,
+ 'aria-disabled': disabled,
+ disabled,
+ ref,
+ onClick: newTab && href ? () => window.open(href, '_blank') : onClick,
+ ...rest,
+ };
+
+ if (href && !newTab) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ );
+ },
+);
+
+Button.displayName = 'Button';
+
+export default Button;
diff --git a/app/components/ui/button/styles.tsx b/app/components/ui/button/styles.tsx
new file mode 100644
index 0000000..dcfeec2
--- /dev/null
+++ b/app/components/ui/button/styles.tsx
@@ -0,0 +1,206 @@
+import { cva } from 'class-variance-authority';
+
+export const buttonIconVariants = cva(['flex', 'items-center', 'justify-center'], {
+ variants: {
+ size: {
+ sm: ['w-3', 'h-3'],
+ md: ['w-4', 'h-4'],
+ lg: ['w-4', 'h-4'],
+ xl: ['w-5', 'h-5'],
+ },
+ },
+});
+
+export const buttonVariants = cva(
+ [
+ 'rounded',
+ 'w-fit',
+ 'flex',
+ 'justify-center',
+ 'items-center',
+ 'font-medium',
+ 'transition-colors',
+ 'focus-visible:outline-none',
+ 'focus-visible:ring-2',
+ 'focus-visible:ring-blue-9',
+ ],
+ {
+ variants: {
+ size: {
+ sm: ['px-2', 'h-6', 'text-xs', 'space-x-1'],
+ md: ['px-3', 'h-8', 'text-sm', 'space-x-1.5'],
+ lg: ['px-3', 'h-10', 'text-sm', 'space-x-1.5'],
+ xl: ['px-4', 'h-12', 'text-base', 'space-x-2'],
+ },
+ variant: {
+ primary: ['border'],
+ secondary: [],
+ outline: ['border'],
+ ghost: [],
+ text: ['hover:underline'],
+ },
+ intent: {
+ none: [
+ // Primary
+ 'data-[variant=primary]:bg-gray-4',
+ 'data-[variant=primary]:text-gray-12',
+ 'data-[variant=primary]:border-gray-7',
+ 'data-[variant=primary]:hover:border-gray-8',
+ 'data-[variant=primary]:active:bg-gray-5',
+ // Secondary
+ 'data-[variant=secondary]:bg-gray-4',
+ 'data-[variant=secondary]:text-gray-11',
+ 'data-[variant=secondary]:hover:bg-gray-5',
+ 'data-[variant=secondary]:active:bg-gray-6',
+ // Outline
+ 'data-[variant=outline]:text-gray-11',
+ 'data-[variant=outline]:border-gray-7',
+ 'data-[variant=outline]:hover:border-gray-8',
+ 'data-[variant=outline]:active:bg-gray-3',
+ // Ghost
+ 'data-[variant=ghost]:text-gray-11',
+ 'data-[variant=ghost]:hover:bg-gray-4',
+ 'data-[variant=ghost]:active:bg-gray-5',
+ // Text
+ 'data-[variant=text]:text-gray-11',
+ ],
+ primary: [
+ // Primary
+ 'data-[variant=primary]:bg-blue-9',
+ 'data-[variant=primary]:dark:text-gray-12',
+ 'data-[variant=primary]:text-gray-1',
+ 'data-[variant=primary]:border-blue-7',
+ 'data-[variant=primary]:hover:border-blue-8',
+ 'data-[variant=primary]:active:bg-blue-10',
+ // Secondary
+ 'data-[variant=secondary]:bg-blue-4',
+ 'data-[variant=secondary]:text-blue-11',
+ 'data-[variant=secondary]:hover:bg-blue-5',
+ 'data-[variant=secondary]:active:bg-blue-6',
+ // Outline
+ 'data-[variant=outline]:text-blue-9',
+ 'data-[variant=outline]:border-blue-7',
+ 'data-[variant=outline]:hover:border-blue-8',
+ 'data-[variant=outline]:active:bg-blue-3',
+ // Ghost
+ 'data-[variant=ghost]:text-blue-9',
+ 'data-[variant=ghost]:hover:bg-blue-4',
+ 'data-[variant=ghost]:active:bg-blue-5',
+ // Text
+ 'data-[variant=text]:text-blue-9',
+ ],
+ success: [
+ // Primary
+ 'data-[variant=primary]:bg-green-9',
+ 'data-[variant=primary]:dark:text-gray-12',
+ 'data-[variant=primary]:text-gray-1',
+ 'data-[variant=primary]:border-green-7',
+ 'data-[variant=primary]:hover:border-green-8',
+ 'data-[variant=primary]:active:bg-green-10',
+ // Secondary
+ 'data-[variant=secondary]:bg-green-4',
+ 'data-[variant=secondary]:text-green-11',
+ 'data-[variant=secondary]:hover:bg-green-5',
+ 'data-[variant=secondary]:active:bg-green-6',
+ // Outline
+ 'data-[variant=outline]:text-green-9',
+ 'data-[variant=outline]:border-green-7',
+ 'data-[variant=outline]:hover:border-green-8',
+ 'data-[variant=outline]:active:bg-green-3',
+ // Ghost
+ 'data-[variant=ghost]:text-green-9',
+ 'data-[variant=ghost]:hover:bg-green-4',
+ 'data-[variant=ghost]:active:bg-green-5',
+ // Text
+ 'data-[variant=text]:text-green-9',
+ ],
+ fail: [
+ // Primary
+ 'data-[variant=primary]:bg-red-9',
+ 'data-[variant=primary]:dark:text-gray-12',
+ 'data-[variant=primary]:text-gray-1',
+ 'data-[variant=primary]:border-red-7',
+ 'data-[variant=primary]:hover:border-red-8',
+ 'data-[variant=primary]:active:bg-red-10',
+ // Secondary
+ 'data-[variant=secondary]:bg-red-4',
+ 'data-[variant=secondary]:text-red-11',
+ 'data-[variant=secondary]:hover:bg-red-5',
+ 'data-[variant=secondary]:active:bg-red-6',
+ // Outline
+ 'data-[variant=outline]:text-red-9',
+ 'data-[variant=outline]:border-red-7',
+ 'data-[variant=outline]:hover:border-red-8',
+ 'data-[variant=outline]:active:bg-red-3',
+ // Ghost
+ 'data-[variant=ghost]:text-red-9',
+ 'data-[variant=ghost]:hover:bg-red-4',
+ 'data-[variant=ghost]:active:bg-red-5',
+ // Text
+ 'data-[variant=text]:text-red-9',
+ ],
+ warning: [
+ // Primary
+ 'data-[variant=primary]:bg-yellow-9',
+ 'data-[variant=primary]:dark:text-gray-1',
+ 'data-[variant=primary]:text-gray-12',
+ 'data-[variant=primary]:border-yellow-7',
+ 'data-[variant=primary]:hover:border-yellow-8',
+ 'data-[variant=primary]:active:bg-yellow-10',
+ // Secondary
+ 'data-[variant=secondary]:bg-yellow-4',
+ 'data-[variant=secondary]:text-yellow-11',
+ 'data-[variant=secondary]:hover:bg-yellow-5',
+ 'data-[variant=secondary]:active:bg-yellow-6',
+ // Outline
+ 'data-[variant=outline]:text-yellow-9',
+ 'data-[variant=outline]:border-yellow-7',
+ 'data-[variant=outline]:hover:border-yellow-8',
+ 'data-[variant=outline]:active:bg-yellow-3',
+ // Ghost
+ 'data-[variant=ghost]:text-yellow-9',
+ 'data-[variant=ghost]:hover:bg-yellow-4',
+ 'data-[variant=ghost]:active:bg-yellow-5',
+ // Text
+ 'data-[variant=text]:text-yellow-9',
+ ],
+ orange: [
+ // Primary
+ 'data-[variant=primary]:bg-orange-9',
+ 'data-[variant=primary]:dark:text-gray-12',
+ 'data-[variant=primary]:text-gray-1',
+ 'data-[variant=primary]:border-orange-7',
+ 'data-[variant=primary]:hover:border-orange-8',
+ 'data-[variant=primary]:active:bg-orange-10',
+ // Secondary
+ 'data-[variant=secondary]:bg-orange-4',
+ 'data-[variant=secondary]:text-orange-11',
+ 'data-[variant=secondary]:hover:bg-orange-5',
+ 'data-[variant=secondary]:active:bg-orange-6',
+ // Outline
+ 'data-[variant=outline]:text-orange-9',
+ 'data-[variant=outline]:border-orange-7',
+ 'data-[variant=outline]:hover:border-orange-8',
+ 'data-[variant=outline]:active:bg-orange-3',
+ // Ghost
+ 'data-[variant=ghost]:text-orange-9',
+ 'data-[variant=ghost]:hover:bg-orange-4',
+ 'data-[variant=ghost]:active:bg-orange-5',
+ // Text
+ 'data-[variant=text]:text-orange-9',
+ ],
+ },
+ disabled: {
+ true: 'aria-disabled cursor-not-allowed',
+ false: '',
+ },
+ },
+ compoundVariants: [
+ { variant: 'primary', disabled: true, className: 'bg-gray-3 text-gray-7 border-gray-7' },
+ { variant: 'secondary', disabled: true, className: 'bg-gray-9 text-gray-11' },
+ { variant: 'outline', disabled: true, className: 'text-gray-7 border-gray-7' },
+ { variant: 'ghost', disabled: true, className: 'bg-gray-9 text-gray-11' },
+ { variant: 'text', disabled: true, className: 'bg-gray-9 text-gray-11' },
+ ],
+ },
+);
diff --git a/app/components/ui/button/types.tsx b/app/components/ui/button/types.tsx
new file mode 100644
index 0000000..665a3d6
--- /dev/null
+++ b/app/components/ui/button/types.tsx
@@ -0,0 +1,22 @@
+import type { ReactNode } from 'react';
+
+import { buttonVariants } from './styles';
+import type { VariantProps } from 'class-variance-authority';
+
+// -----------------------------------------------------------------------------
+// Variant props
+// -----------------------------------------------------------------------------
+
+export type ButtonVariantProps = VariantProps;
+
+// -----------------------------------------------------------------------------
+// Component props
+// -----------------------------------------------------------------------------
+
+export type ButtonProps = JSX.IntrinsicElements['button'] &
+ ButtonVariantProps & {
+ href?: string;
+ leftIcon?: ReactNode;
+ rightIcon?: ReactNode;
+ newTab?: boolean;
+ };
diff --git a/app/components/ui/nav-bar.tsx b/app/components/ui/nav-bar.tsx
deleted file mode 100644
index fc9fe14..0000000
--- a/app/components/ui/nav-bar.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import Link from "next/link";
-import React from "react";
-
-const NavBar = () => {
- return (
-
-
-
-
-
- -
- Home
-
-
- -
- Resume
-
- -
- About Me
-
-
-
-
- Kent Miguel
-
-
-
- -
- Home
-
-
- -
- Resume
-
- -
- About Me
-
-
-
-
-
- {/*
*/}
-
- );
-};
-
-export default NavBar;
diff --git a/app/components/ui/nav-bar/nav-bar-desktop.tsx b/app/components/ui/nav-bar/nav-bar-desktop.tsx
new file mode 100644
index 0000000..d5faabb
--- /dev/null
+++ b/app/components/ui/nav-bar/nav-bar-desktop.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+
+import { NAVBAR_PAGES } from "@/lib/constants/site";
+import Button from "@components/ui/button/index";
+import clsx from "clsx";
+
+export default function NavbarDesktop({ selected }: NavBarProps) {
+ return (
+
+ );
+}
diff --git a/app/components/ui/nav-bar/nav-bar-mobile.tsx b/app/components/ui/nav-bar/nav-bar-mobile.tsx
new file mode 100644
index 0000000..473f5f0
--- /dev/null
+++ b/app/components/ui/nav-bar/nav-bar-mobile.tsx
@@ -0,0 +1,36 @@
+"use client";
+
+import React from "react";
+
+import { NAVBAR_PAGES } from "@/lib/constants/site";
+import Tooltip from "@components/ui/tooltip";
+import IconButton from "@components/ui/icon-button";
+import clsx from "clsx";
+
+export default function NavbarMobile({ selected }: NavBarProps) {
+ return (
+
+ );
+}
diff --git a/app/components/ui/nav-bar/nav-bar.tsx b/app/components/ui/nav-bar/nav-bar.tsx
new file mode 100644
index 0000000..e597814
--- /dev/null
+++ b/app/components/ui/nav-bar/nav-bar.tsx
@@ -0,0 +1,15 @@
+"use client";
+
+import React from "react";
+import { Fragment } from "react";
+import NavbarDesktop from "./nav-bar-desktop";
+import NavbarMobile from "./nav-bar-mobile";
+
+export default function Navbar({ selected }: NavBarProps) {
+ return (
+
+
+
+
+ );
+}
diff --git a/app/global.d.ts b/app/global.d.ts
index 2a3545e..38cf63a 100644
--- a/app/global.d.ts
+++ b/app/global.d.ts
@@ -1,4 +1,6 @@
import { Database as DB } from "@/lib/database.types";
+import { PageSlug } from "@/lib/types/site";
+import type { ReactNode } from "react";
declare global {
type Database = DB;
@@ -31,4 +33,18 @@ declare global {
spaceBefore?: boolean;
description?: string;
};
+
+ type NavBarProps = {
+ selected?: PageSlug;
+ };
+
+ export type SeoBaseProps = {
+ title?: string;
+ subtitle?: string;
+ };
+
+ type BaseLayoutProps = SeoBaseProps & {
+ pageSlug?: PageSlug;
+ children?: ReactNode;
+ };
}
diff --git a/app/page.tsx b/app/page.tsx
index 1d6af75..503a2da 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,16 +1,15 @@
import Base from "./components/layouts/base";
-import ProfileHeader from "./components/ui/header";
-import ContainerLayout from "./components/layouts/container";
-import Contents from "./components/home/contents";
-import Running from "./components/home/running/running";
+import ProfileHeader from "@components/ui/header";
+import ContainerLayout from "@components/layouts/container";
+import Contents from "@components/home/contents";
export const dynamic = "force-dynamic";
export default async function Home() {
return (
<>
-
+
diff --git a/app/projects/page.tsx b/app/projects/page.tsx
new file mode 100644
index 0000000..0938031
--- /dev/null
+++ b/app/projects/page.tsx
@@ -0,0 +1,24 @@
+import React from "react";
+import Base from "@components/layouts/base";
+import ProfileHeader from "@components/ui/header";
+import ContainerLayout from "@components/layouts/container";
+import InProgressImage from "../../public/aboutme-option3.webp";
+import Image from "next/image";
+
+export default function AboutMe() {
+ return (
+
+
+
+ Projects
+
+ Work in progress.
+
+
+
+ );
+}
diff --git a/app/resume/components/skills.tsx b/app/resume/components/skills.tsx
index fa29c29..e8e1b0d 100644
--- a/app/resume/components/skills.tsx
+++ b/app/resume/components/skills.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import ContentDisplay from "../../components/layouts/content-display";
+import ContentDisplay from "@components/layouts/content-display";
import { Wand2 } from "lucide-react";
const Skills = () => {
@@ -10,7 +10,7 @@ const Skills = () => {
name="Skills"
description="Tech Stack I usually use"
>
-
+
-
Coding Languages : Solidity, Web3, Typescript, JavaScript (Next.js,
diff --git a/app/resume/page.tsx b/app/resume/page.tsx
index 627c1ae..5fc32f4 100644
--- a/app/resume/page.tsx
+++ b/app/resume/page.tsx
@@ -1,21 +1,17 @@
import React from "react";
-import ProfileHeader from "../components/ui/header";
-import Base from "../components/layouts/base";
-import ContainerLayout from "../components/layouts/container";
-import Image from "next/image";
-import profile from "../../public/profile.jpeg";
-import Contents from "../components/home/contents";
+import ProfileHeader from "@components/ui/header";
+import Base from "@components/layouts/base";
+import ContainerLayout from "@components/layouts/container";
import ResumeContent from "./components/resume-content";
import Skills from "./components/skills";
const page = () => {
return (
-
+
- {/** Accordion */}
);
diff --git a/lib/constants/site.tsx b/lib/constants/site.tsx
new file mode 100644
index 0000000..8811971
--- /dev/null
+++ b/lib/constants/site.tsx
@@ -0,0 +1,14 @@
+import { Home, FileText, PencilRuler, Ghost } from "lucide-react";
+
+import type { Page } from "@/lib/types/site";
+
+/**
+ * Pages displayed on personal website
+ * navigation bar.
+ */
+export const NAVBAR_PAGES: Page[] = [
+ { name: "Home", slug: "/", icon: },
+ { name: "Resume", slug: "/resume", icon: },
+ { name: "Projects", slug: "/projects", icon: },
+ { name: "About Me", slug: "/about", icon: },
+];
diff --git a/lib/types/site.ts b/lib/types/site.ts
new file mode 100644
index 0000000..611572b
--- /dev/null
+++ b/lib/types/site.ts
@@ -0,0 +1,46 @@
+import type { ReactNode } from "react";
+
+/**
+ * Type for an external link.
+ * @param name Name describing the link.
+ * @param href URL of the link.
+ * @param icon Optional icon to describe/represent the link.
+ */
+export type ExternalLink = {
+ name: string;
+ href: string;
+ icon?: ReactNode;
+};
+
+/**
+ * Type for a page slug
+ */
+export type PageSlug =
+ | "/"
+ | "/resume"
+ // Design pages
+ | "/projects"
+ | "/about";
+
+/**
+ * Type for an external page
+ * intended to be part of configuration files (e.g. for the navigation bar
+ * component).
+ */
+export type PageExternalLink =
+ | "https://twitter.com/fiveoutofnine"
+ | "https://github.com/fiveoutofnine";
+
+/**
+ * Type for a page
+ * intended to be part of configuration files (e.g. for the navigation bar
+ * component).
+ * @param name Name describing the page.
+ * @param slug Slug/URL of the page.
+ * @param icon Optional icon to describe/represent the page.
+ */
+export type Page = {
+ name: string;
+ slug: PageSlug | PageExternalLink;
+ icon?: ReactNode;
+};
diff --git a/prettier.config.js b/prettier.config.js
deleted file mode 100644
index f5661aa..0000000
--- a/prettier.config.js
+++ /dev/null
@@ -1,22 +0,0 @@
-// prettier.config.js
-module.exports = {
- bracketSpacing: true,
- semi: true,
- trailingComma: 'all',
- printWidth: 100,
- tabWidth: 2,
- singleQuote: true,
- importOrder: [
- '(^(react/(.*)$)|^(react$))|(^(next/(.*)$)|^(next$))',
- '',
- '^@/styles/(.*)$',
- '^@/public/(.*)$',
- '^@/lib/(.*)$',
- '^@/components/(.*)$',
- '^@/pages/(.*)$',
- ],
- importOrderSortSpecifiers: true,
- importOrderSeparation: true,
- importOrderCaseInsensitive: true,
- plugins: ['@trivago/prettier-plugin-sort-imports', require('prettier-plugin-tailwindcss')],
-};
diff --git a/tsconfig.json b/tsconfig.json
index beb39af..a20d921 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -21,7 +21,8 @@
"baseUrl": ".",
"paths": {
"@/*": ["./*"],
- "@components/*":["./app/components/*"]
+ "@components/*":["./app/components/*"],
+ "@/lib/*": ["lib/*"],
}
},