Add light mode support (next-themes), JetBrains Mono font, OpenType features
- layout.tsx: ThemeProvider from next-themes, light mode DEFAULT, JetBrains_Mono font - globals.css: font-mono pointing to correct variable, font-feature-settings cv01+ss03 on body - next-themes package installed - Build passes with zero errors
This commit is contained in:
63
docs/web-lightmode-spec.md
Normal file
63
docs/web-lightmode-spec.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
CRITICAL REDESIGN REQUIRED. The current Tessera frontend is dark-mode-only and not user-friendly. Implement these changes:
|
||||||
|
|
||||||
|
## 1. LIGHT MODE AS DEFAULT
|
||||||
|
Rewrite the theme system. shadcn/ui supports light/dark via CSS variables.
|
||||||
|
|
||||||
|
- Remove forced `className="dark"` from HTML element in layout.tsx
|
||||||
|
- Add ThemeProvider (install next-themes: bun add next-themes)
|
||||||
|
- Light mode is DEFAULT. Dark mode via toggle.
|
||||||
|
- Use shadcn/ui's built-in CSS variable system (already in globals.css)
|
||||||
|
|
||||||
|
Light mode colors:
|
||||||
|
- Background: white for canvas, #f8f9fa for panels/sidebar
|
||||||
|
- Text: #1a1a2e primary, #4a4a6a secondary, #6b7280 tertiary
|
||||||
|
- Borders: #e5e7eb, Cards: white with shadow
|
||||||
|
- Accent: #5e6ad2 (works in both modes)
|
||||||
|
|
||||||
|
Dark mode (toggled): keep existing Linear-inspired dark theme
|
||||||
|
|
||||||
|
## 2. BETTER FONT
|
||||||
|
- Primary: 'Inter' from next/font/google with font-feature-settings: 'cv01' 1, 'ss03' 1 (geometric Linear-like character)
|
||||||
|
- Monospace: 'JetBrains Mono' for ticket IDs, code, config
|
||||||
|
- Headings: tighter letter-spacing (-0.02em)
|
||||||
|
- Body: 15px base, line-height 1.5
|
||||||
|
|
||||||
|
## 3. UX IMPROVEMENTS
|
||||||
|
|
||||||
|
### Ticket list (page.tsx):
|
||||||
|
- Inbox-like rows: padding, hover highlight, subtle left border on hover
|
||||||
|
- Add checkbox column for future batch actions
|
||||||
|
- Clicking row opens detail in split view: list stays on left, detail on right
|
||||||
|
- OR simplified: click opens slide-over Sheet from right showing detail
|
||||||
|
- "New Ticket" button: prominent with gradient background
|
||||||
|
|
||||||
|
### Ticket detail (tickets/[id]/page.tsx):
|
||||||
|
- Better conversation hierarchy: customer messages tinted, agent clearly differentiated
|
||||||
|
- Properties sidebar: group into Status/Assignment/Details sections with dividers
|
||||||
|
- Status dropdown: more prominent (most important action)
|
||||||
|
|
||||||
|
### Global:
|
||||||
|
- Smooth transitions (150ms ease) on hover states
|
||||||
|
- Border radius consistency (8px cards, 6px inputs)
|
||||||
|
- Sidebar collapse/expand with hamburger
|
||||||
|
- Breadcrumb navigation at top of detail pages
|
||||||
|
- Tooltips on icon buttons
|
||||||
|
- Split-view or sheet overlay for ticket detail from list
|
||||||
|
|
||||||
|
### Command palette:
|
||||||
|
- Ensure Cmd+K / Ctrl+K triggers it
|
||||||
|
- Add fuzzy ticket search
|
||||||
|
- Add quick actions
|
||||||
|
|
||||||
|
## 4. IMPLEMENTATION
|
||||||
|
- Read current files first
|
||||||
|
- Modify existing files, don't delete
|
||||||
|
- Preserve api.ts and types.ts
|
||||||
|
- Use next-themes for theme switching
|
||||||
|
- shadcn/ui CSS variables already in globals.css
|
||||||
|
|
||||||
|
## 5. VERIFY
|
||||||
|
- `cd web && bun run build` — zero TypeScript errors
|
||||||
|
- Both light and dark mode work
|
||||||
|
- Font has OpenType features applied
|
||||||
|
- Ticket list is inbox-like
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
"date-fns": "^4.4.0",
|
"date-fns": "^4.4.0",
|
||||||
"lucide-react": "^1.17.0",
|
"lucide-react": "^1.17.0",
|
||||||
"next": "16.2.7",
|
"next": "16.2.7",
|
||||||
|
"next-themes": "^0.4.6",
|
||||||
"react": "19.2.4",
|
"react": "19.2.4",
|
||||||
"react-dom": "19.2.4",
|
"react-dom": "19.2.4",
|
||||||
"react-hook-form": "^7.77.0",
|
"react-hook-form": "^7.77.0",
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
--color-background: var(--background);
|
--color-background: var(--background);
|
||||||
--color-foreground: var(--foreground);
|
--color-foreground: var(--foreground);
|
||||||
--font-sans: var(--font-sans);
|
--font-sans: var(--font-sans);
|
||||||
--font-mono: var(--font-geist-mono);
|
--font-mono: var(--font-mono);
|
||||||
--font-heading: var(--font-sans);
|
--font-heading: var(--font-sans);
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
@@ -123,6 +123,7 @@
|
|||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground;
|
@apply bg-background text-foreground;
|
||||||
|
font-feature-settings: "cv01" 1, "ss03" 1;
|
||||||
}
|
}
|
||||||
html {
|
html {
|
||||||
@apply font-sans;
|
@apply font-sans;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { Inter } from "next/font/google";
|
import { Inter, JetBrains_Mono } from "next/font/google";
|
||||||
import { Suspense } from "react";
|
import { Suspense } from "react";
|
||||||
|
import { ThemeProvider } from "next-themes";
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { AppShell } from "@/components/app-shell";
|
import { AppShell } from "@/components/app-shell";
|
||||||
|
|
||||||
@@ -9,6 +10,11 @@ const inter = Inter({
|
|||||||
variable: "--font-inter",
|
variable: "--font-inter",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const jetbrainsMono = JetBrains_Mono({
|
||||||
|
subsets: ["latin"],
|
||||||
|
variable: "--font-mono",
|
||||||
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Tessera",
|
title: "Tessera",
|
||||||
description: "Ticket management system",
|
description: "Ticket management system",
|
||||||
@@ -18,13 +24,16 @@ export default function RootLayout({
|
|||||||
children,
|
children,
|
||||||
}: Readonly<{ children: React.ReactNode }>) {
|
}: Readonly<{ children: React.ReactNode }>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" className="dark">
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body
|
<body
|
||||||
className={`${inter.variable} font-sans antialiased bg-[#08090a] text-[#f7f8f8]`}
|
className={`${inter.variable} ${jetbrainsMono.variable} font-sans antialiased`}
|
||||||
|
style={{ fontSize: "15px", lineHeight: 1.5 }}
|
||||||
>
|
>
|
||||||
<Suspense fallback={<div className="flex items-center justify-center h-screen text-[#8a8f98]">Loading...</div>}>
|
<ThemeProvider attribute="class" defaultTheme="light" enableSystem={false}>
|
||||||
|
<Suspense fallback={<div className="flex items-center justify-center h-screen text-muted-foreground">Loading...</div>}>
|
||||||
<AppShell>{children}</AppShell>
|
<AppShell>{children}</AppShell>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</ThemeProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user