Merge pull request #1 from nexa-sol/nav-and-markets-ui

Nav and markets UI
This commit is contained in:
2026-02-11 20:39:17 +01:00
committed by GitHub
16 changed files with 6882 additions and 19 deletions
+23
View File
@@ -0,0 +1,23 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/app/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"iconLibrary": "lucide",
"rtl": false,
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"registries": {}
}
+6316 -4
View File
File diff suppressed because it is too large Load Diff
+8 -1
View File
@@ -8,16 +8,23 @@
"start": "next start" "start": "next start"
}, },
"dependencies": { "dependencies": {
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"lucide-react": "^0.563.0",
"next": "16.1.6", "next": "16.1.6",
"radix-ui": "^1.4.3",
"react": "19.2.3", "react": "19.2.3",
"react-dom": "19.2.3" "react-dom": "19.2.3",
"tailwind-merge": "^3.4.0"
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"shadcn": "^3.8.4",
"tailwindcss": "^4", "tailwindcss": "^4",
"tw-animate-css": "^1.4.0",
"typescript": "^5" "typescript": "^5"
} }
} }
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+14
View File
@@ -0,0 +1,14 @@
<svg width="42" height="9" viewBox="0 0 42 9" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_9_13)">
<path d="M6.732 0.995999C6.732 0.843999 6.788 0.715999 6.9 0.611999C7.012 0.499999 7.148 0.443999 7.308 0.443999H7.884V9H6.708C6.588 9 6.492 8.952 6.42 8.856L0.972 1.836V4.704C0.972 4.856 0.916 4.988 0.804 5.1C0.692 5.204 0.556 5.256 0.396 5.256H-0.18V0.443999H0.984C1.104 0.443999 1.2 0.491999 1.272 0.588L6.732 7.608V0.995999ZM19.4827 8.484V9H11.0107V8.208C11.0107 8.128 11.0387 8.064 11.0947 8.016C11.1507 7.96 11.2147 7.932 11.2867 7.932H18.9067C19.0667 7.932 19.2027 7.988 19.3147 8.1C19.4267 8.204 19.4827 8.332 19.4827 8.484ZM19.3507 0.96C19.3507 1.112 19.2947 1.244 19.1827 1.356C19.0707 1.46 18.9347 1.512 18.7747 1.512H11.2987C11.2187 1.512 11.1507 1.484 11.0947 1.428C11.0387 1.372 11.0107 1.304 11.0107 1.224V0.443999H19.3507V0.96ZM11.0107 4.464C11.0107 4.392 11.0387 4.328 11.0947 4.272C11.1507 4.216 11.2147 4.188 11.2867 4.188H18.5707V4.704C18.5707 4.856 18.5147 4.988 18.4027 5.1C18.2907 5.204 18.1547 5.256 17.9947 5.256H11.0107V4.464ZM30.4834 0.443999L26.3914 4.716L30.4834 9H29.5114C29.2634 9 29.0474 8.904 28.8634 8.712L25.7194 5.424L22.5754 8.712C22.3914 8.904 22.1754 9 21.9274 9H20.8594C21.8434 7.968 24.4794 5.212 28.7674 0.732C28.9514 0.539999 29.1674 0.443999 29.4154 0.443999H30.4834ZM24.2194 3.168C24.1234 3.264 24.0114 3.312 23.8834 3.312C23.7554 3.312 23.6434 3.264 23.5474 3.168L20.9554 0.443999H21.9274C22.1754 0.443999 22.3914 0.539999 22.5754 0.732L24.5554 2.808L24.2194 3.168ZM36.3944 1.656C33.6104 6.36 32.1904 8.756 32.1344 8.844C32.0784 8.948 31.9904 9 31.8704 9H30.8024C32.9704 5.28 34.5304 2.596 35.4824 0.948C35.5624 0.796 35.6784 0.675999 35.8304 0.588C35.9904 0.491999 36.1624 0.443999 36.3464 0.443999H37.0184C37.7064 1.732 39.2304 4.584 41.5904 9H40.7144C40.4504 9 40.2544 8.884 40.1264 8.652C39.7904 8.012 39.3224 7.136 38.7224 6.024L36.3944 1.656Z" fill="url(#paint0_linear_9_13)"/>
</g>
<defs>
<linearGradient id="paint0_linear_9_13" x1="-3" y1="7" x2="44" y2="7" gradientUnits="userSpaceOnUse">
<stop stop-color="#6D45F9"/>
<stop offset="1" stop-color="#00C688"/>
</linearGradient>
<clipPath id="clip0_9_13">
<rect width="42" height="9" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

+112 -10
View File
@@ -1,26 +1,128 @@
@import "tailwindcss"; @import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";
:root { @custom-variant dark (&:is(.dark *));
--background: #ffffff;
--foreground: #171717;
}
@theme inline { @theme inline {
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans); --font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);
--radius-2xl: calc(var(--radius) + 8px);
--radius-3xl: calc(var(--radius) + 12px);
--radius-4xl: calc(var(--radius) + 16px);
} }
@media (prefers-color-scheme: dark) {
:root { :root {
--background: #0a0a0a; --radius: 0.425rem;
--foreground: #ededed;
/* Base surfaces */
--background: oklch(0.13 0.01 260); /* deep charcoal */
--foreground: oklch(0.98 0 0);
--card: oklch(0.18 0.015 260);
--card-foreground: oklch(0.98 0 0);
--popover: oklch(0.18 0.015 260);
--popover-foreground: oklch(0.98 0 0);
/* Primary – Solana purple */
--primary: oklch(0.55 0.25 285);
--primary-foreground: oklch(0.98 0 0);
/* Secondary – muted surface */
--secondary: oklch(0.24 0.015 260);
--secondary-foreground: oklch(0.96 0 0);
/* Muted / subtle text */
--muted: oklch(0.24 0.015 260);
--muted-foreground: oklch(0.72 0.01 260);
/* Accent – SOL mint */
--accent: oklch(0.72 0.18 165);
--accent-foreground: oklch(0.13 0.01 260);
/* Destructive */
--destructive: oklch(0.68 0.2 25);
/* Inputs / borders */
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 14%);
--ring: oklch(0.6 0.12 285);
/* Charts – crypto-native */
--chart-1: oklch(0.55 0.25 285); /* purple */
--chart-2: oklch(0.72 0.18 165); /* mint */
--chart-3: oklch(0.65 0.22 310); /* violet */
--chart-4: oklch(0.7 0.17 140); /* green */
--chart-5: oklch(0.62 0.2 30); /* orange/red */
/* Sidebar */
--sidebar: oklch(0.17 0.015 260);
--sidebar-foreground: oklch(0.96 0 0);
--sidebar-primary: oklch(0.55 0.25 285);
--sidebar-primary-foreground: oklch(0.98 0 0);
--sidebar-accent: oklch(0.24 0.015 260);
--sidebar-accent-foreground: oklch(0.98 0 0);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.6 0.12 285);
}
@layer base {
* {
@apply border-border outline-ring/50;
}
body {
@apply bg-background text-foreground;
} }
} }
body { body {
background: var(--background); @apply bg-background text-foreground;
color: var(--foreground); }
font-family: Arial, Helvetica, sans-serif;
@font-face {
font-family: 'Researcher';
src: url('/font/researcher-regular-400.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
} }
+2 -2
View File
@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "Nexa - Solana trading supercharged by AI",
description: "Generated by create next app", description: "The solana trading terminal supercharged by AI",
}; };
export default function RootLayout({ export default function RootLayout({
+43
View File
@@ -0,0 +1,43 @@
import { SolanaLogo } from "@/components/logos";
import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardTitle } from "@/components/ui/card";
import { Settings2Icon, ZapIcon } from "lucide-react";
export default function PairsCard({ title }: { title: string }) {
return (
<Card className="py-3">
<CardHeader className="px-4 pb-3! border-b flex justify-between items-center">
<CardTitle>{title}</CardTitle>
<div className="flex items-center gap-2">
<div className="border border-border animate-colors hover:bg-input/20 px-1.5 py-1.5 rounded-full flex items-center gap-1">
<div className="flex items-center">
<ZapIcon className="w-3 h-3" />
<input
type="text"
placeholder="0.0"
className="px-1 w-10 focus:outline-0 text-xs"
/>
<SolanaLogo className="w-3 h-3" />
</div>
<div className="border-l border-border pl-1 flex items-center text-xs gap-1.5">
{["P1", "P2", "P3"].map((p) => (
<div
key={p}
className="text-foreground/90 cursor-pointer hover:text-primary"
>
{p}
</div>
))}
</div>
</div>
<Button
variant="ghost"
className="p-1! h-max hover:bg-accent/20 hover:text-accent"
>
<Settings2Icon />
</Button>
</div>
</CardHeader>
</Card>
);
}
+21
View File
@@ -0,0 +1,21 @@
"use client";
import Navbar from "@/components/layout/Navbar";
import Statusbar from "@/components/layout/Statusbar";
import PairsCard from "./components/PairsCard";
export default function Markets() {
return (
<main className="h-svh flex flex-col">
<Navbar />
<section className="w-full h-full pt-10 pb-5 px-5">
<div className="w-full h-full grid grid-cols-3 gap-5">
<PairsCard title="New Pairs" />
<PairsCard title="Final Stretch" />
<PairsCard title="Migrated" />
</div>
</section>
<Statusbar />
</main>
);
}
+75
View File
@@ -0,0 +1,75 @@
import Link from "next/link";
import { Button } from "../ui/button";
import {
BellIcon,
ChevronDownIcon,
StarIcon,
UserIcon,
WalletIcon,
} from "lucide-react";
import { SolanaLogo } from "../logos";
const links = [
{
path: "/markets",
name: "Markets",
},
{
path: "/trackers",
name: "Trackers",
},
];
export default function Navbar() {
const personalItemClass =
"rounded-full bg-card p-2! aspect-square group hover:bg-accent/20 hover:text-accent";
return (
<nav className="border-b border-b-border py-4 px-5 flex gap-5 items-center">
<a href="/markets" className="flex gap-1 items-center">
<img src="/nexa.svg" alt="Nexa" className="h-7" />
<img src="/nexa-text.svg" alt="NEXA" className="h-5" />
</a>
<div>
<ul className="flex">
{links.map((l) => (
<li key={l.path}>
<Link
href={l.path}
className="px-3 py-2 rounded-lg hover:bg-primary/20 hover:text-primary transition-colors duration-300"
>
{l.name}
</Link>
</li>
))}
</ul>
</div>
<div className="ml-auto flex gap-4">
<Button variant="outline" className={personalItemClass}>
<StarIcon />
</Button>
<Button variant="outline" className={personalItemClass}>
<BellIcon />
</Button>
</div>
<div className="flex gap-4">
<Button variant="outline" className={personalItemClass}>
<div className="flex gap-1 items-center h-full">
<WalletIcon />
<div className="flex items-center">
<SolanaLogo className="p-0.5" />
3.13
</div>
<span className="w-[1.5px] h-full ml-1 mr-1 bg-border group-hover:bg-accent/60 animate-colors duration-300" />
$248
<ChevronDownIcon />
</div>
</Button>
<Button variant="outline" className={personalItemClass}>
<UserIcon />
</Button>
</div>
</nav>
);
}
+3
View File
@@ -0,0 +1,3 @@
export default function Statusbar() {
return <footer></footer>;
}
+101
View File
@@ -0,0 +1,101 @@
import { SVGProps } from "react";
export const SolanaLogo = (props: SVGProps<SVGSVGElement>) => (
<svg
id="Layer_1"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 397.7 311.7"
style={{
background: "new 0 0 397.7 311.7",
}}
xmlSpace="preserve"
{...props}
>
<style type="text/css">
{
"\n\t.st0{fill:url(#SVGID_1_);}\n\t.st1{fill:url(#SVGID_2_);}\n\t.st2{fill:url(#SVGID_3_);}\n"
}
</style>
<linearGradient
id="SVGID_1_"
gradientUnits="userSpaceOnUse"
x1={360.8791}
y1={351.4553}
x2={141.213}
y2={-69.2936}
gradientTransform="matrix(1 0 0 -1 0 314)"
>
<stop
offset={0}
style={{
stopColor: "#00FFA3",
}}
/>
<stop
offset={1}
style={{
stopColor: "#DC1FFF",
}}
/>
</linearGradient>
<path
className="st0"
d="M64.6,237.9c2.4-2.4,5.7-3.8,9.2-3.8h317.4c5.8,0,8.7,7,4.6,11.1l-62.7,62.7c-2.4,2.4-5.7,3.8-9.2,3.8H6.5 c-5.8,0-8.7-7-4.6-11.1L64.6,237.9z"
/>
<linearGradient
id="SVGID_2_"
gradientUnits="userSpaceOnUse"
x1={264.8291}
y1={401.6014}
x2={45.163}
y2={-19.1475}
gradientTransform="matrix(1 0 0 -1 0 314)"
>
<stop
offset={0}
style={{
stopColor: "#00FFA3",
}}
/>
<stop
offset={1}
style={{
stopColor: "#DC1FFF",
}}
/>
</linearGradient>
<path
className="st1"
d="M64.6,3.8C67.1,1.4,70.4,0,73.8,0h317.4c5.8,0,8.7,7,4.6,11.1l-62.7,62.7c-2.4,2.4-5.7,3.8-9.2,3.8H6.5 c-5.8,0-8.7-7-4.6-11.1L64.6,3.8z"
/>
<linearGradient
id="SVGID_3_"
gradientUnits="userSpaceOnUse"
x1={312.5484}
y1={376.688}
x2={92.8822}
y2={-44.061}
gradientTransform="matrix(1 0 0 -1 0 314)"
>
<stop
offset={0}
style={{
stopColor: "#00FFA3",
}}
/>
<stop
offset={1}
style={{
stopColor: "#DC1FFF",
}}
/>
</linearGradient>
<path
className="st2"
d="M333.1,120.1c-2.4-2.4-5.7-3.8-9.2-3.8H6.5c-5.8,0-8.7,7-4.6,11.1l62.7,62.7c2.4,2.4,5.7,3.8,9.2,3.8h317.4 c5.8,0,8.7-7,4.6-11.1L333.1,120.1z"
/>
</svg>
);
+64
View File
@@ -0,0 +1,64 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { Slot } from "radix-ui"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
outline:
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost:
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2 has-[>svg]:px-3",
xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
icon: "size-9",
"icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
"icon-sm": "size-8",
"icon-lg": "size-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
function Button({
className,
variant = "default",
size = "default",
asChild = false,
...props
}: React.ComponentProps<"button"> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean
}) {
const Comp = asChild ? Slot.Root : "button"
return (
<Comp
data-slot="button"
data-variant={variant}
data-size={size}
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
)
}
export { Button, buttonVariants }
+92
View File
@@ -0,0 +1,92 @@
import * as React from "react"
import { cn } from "@/lib/utils"
function Card({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card"
className={cn(
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className
)}
{...props}
/>
)
}
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-header"
className={cn(
"@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className
)}
{...props}
/>
)
}
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-title"
className={cn("leading-none font-semibold", className)}
{...props}
/>
)
}
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-description"
className={cn("text-muted-foreground text-sm", className)}
{...props}
/>
)
}
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-action"
className={cn(
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className
)}
{...props}
/>
)
}
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-content"
className={cn("px-6", className)}
{...props}
/>
)
}
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return (
<div
data-slot="card-footer"
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props}
/>
)
}
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardAction,
CardDescription,
CardContent,
}
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}