Skip to content

Commit 1271f8a

Browse files
committed
feat: themes
1 parent 991b5fa commit 1271f8a

13 files changed

Lines changed: 730 additions & 285 deletions

File tree

examples/ai-conference-assistant/package-lock.json

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/ai-conference-assistant/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
"lint": "eslint"
1010
},
1111
"dependencies": {
12-
"@hackmd/api": "file:../../nodejs",
1312
"@ai-sdk/react": "^3.0.140",
13+
"@hackmd/api": "file:../../nodejs",
1414
"ai": "^6.0.138",
1515
"next": "16.2.1",
16+
"next-themes": "^0.4.6",
1617
"react": "19.2.4",
1718
"react-dom": "19.2.4",
1819
"zod": "^4.3.6"

examples/ai-conference-assistant/src/app/globals.css

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
@import "tailwindcss";
22

3+
@custom-variant dark (&:where(.dark, .dark *));
4+
35
:root {
4-
--background: #f9fafb;
5-
--foreground: #171717;
6+
--background: #fafafa;
7+
--foreground: #18181b;
8+
--surface: #ffffff;
9+
--surface-muted: #f4f4f5;
10+
--border: #e4e4e7;
11+
--accent: #2563eb;
12+
}
13+
14+
.dark {
15+
--background: #09090b;
16+
--foreground: #fafafa;
17+
--surface: #18181b;
18+
--surface-muted: #27272a;
19+
--border: #3f3f46;
20+
--accent: #3b82f6;
621
}
722

823
@theme inline {
@@ -15,5 +30,5 @@
1530
body {
1631
background: var(--background);
1732
color: var(--foreground);
18-
font-family: var(--font-sans), Arial, Helvetica, sans-serif;
33+
font-family: var(--font-sans), ui-sans-serif, system-ui, sans-serif;
1934
}
Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
import type { Metadata } from "next";
2-
import "./globals.css";
1+
import type { Metadata } from 'next'
2+
import { AppProviders } from '@/components/providers'
3+
import './globals.css'
34

45
export const metadata: Metadata = {
5-
title: "HackMD Conference Assistant",
6-
description: "AI-powered collaborative note generator for conferences using HackMD API",
7-
};
6+
title: 'HackMD Conference Assistant',
7+
description: 'AI-powered collaborative note generator for conferences using HackMD API',
8+
}
89

910
export default function RootLayout({
1011
children,
1112
}: Readonly<{
12-
children: React.ReactNode;
13+
children: React.ReactNode
1314
}>) {
1415
return (
15-
<html
16-
lang="en"
17-
className="h-full antialiased"
18-
>
19-
<body className="min-h-full flex flex-col font-sans">{children}</body>
16+
<html lang="en" className="h-full antialiased" suppressHydrationWarning>
17+
<body className="min-h-full flex flex-col font-sans text-foreground bg-background">
18+
<AppProviders>{children}</AppProviders>
19+
</body>
2020
</html>
21-
);
21+
)
2222
}

examples/ai-conference-assistant/src/app/page.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use client'
22

3-
import { useState, useRef, useEffect } from 'react'
3+
import { useState, useRef, useEffect, useCallback } from 'react'
44
import { SetupPanel } from '@/components/setup-panel'
55
import { ChatPanel } from '@/components/chat-panel'
66
import { PreviewPanel } from '@/components/preview-panel'
@@ -21,24 +21,25 @@ export interface GeneratedData {
2121
export default function Home() {
2222
const [config, setConfig] = useState<AppConfig | null>(null)
2323
const [sessionData, setSessionData] = useState<string>('')
24-
const [generatedData, setGeneratedData] = useState<GeneratedData | null>(null)
24+
const [generatedData, setGeneratedDataState] = useState<GeneratedData | null>(null)
2525
/** User explicitly confirms the preview before HackMD creation is allowed. */
2626
const [previewConfirmed, setPreviewConfirmed] = useState(false)
2727
const [showProgress, setShowProgress] = useState(false)
2828
const [previewPage, setPreviewPage] = useState<{ title: string; content: string } | null>(null)
2929
const sessionDataRef = useRef(sessionData)
3030
useEffect(() => { sessionDataRef.current = sessionData }, [sessionData])
3131

32-
useEffect(() => {
32+
const setGeneratedData = useCallback((data: GeneratedData | null) => {
33+
setGeneratedDataState(data)
3334
setPreviewConfirmed(false)
34-
}, [generatedData])
35+
}, [])
3536

3637
if (!config) {
3738
return <SetupPanel onConfigured={setConfig} />
3839
}
3940

4041
return (
41-
<div className="flex flex-col lg:flex-row h-screen bg-gray-50 min-h-0">
42+
<div className="flex flex-col lg:flex-row h-screen min-h-0 bg-zinc-50 dark:bg-zinc-950">
4243
{/* Chat */}
4344
<div className="flex-1 flex flex-col min-h-0 min-w-0 lg:max-w-[calc(100%-480px)]">
4445
<ChatPanel
@@ -54,7 +55,7 @@ export default function Home() {
5455
</div>
5556

5657
{/* Preview + confirm + create (always mounted so narrow screens can confirm before HackMD) */}
57-
<div className="h-[min(42vh,420px)] lg:h-auto lg:w-[480px] border-t lg:border-t-0 lg:border-l border-gray-200 bg-white flex-shrink-0 flex flex-col min-h-0">
58+
<div className="h-[min(42vh,420px)] lg:h-auto lg:w-[480px] flex-shrink-0 flex flex-col min-h-0 border-t border-zinc-200/80 bg-white shadow-[inset_0_1px_0_0_rgba(0,0,0,0.03)] dark:border-zinc-800 dark:bg-zinc-900 lg:border-t-0 lg:border-l">
5859
<PreviewPanel
5960
generatedData={generatedData}
6061
previewPage={previewPage}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
'use client'
2+
3+
import { useTheme } from 'next-themes'
4+
import type { Locale } from '@/i18n/dictionaries'
5+
import { useI18n } from '@/i18n/context'
6+
7+
export function AppSettingsBar({ className = '' }: { className?: string }) {
8+
const { locale, setLocale, t } = useI18n()
9+
const { theme, setTheme } = useTheme()
10+
11+
return (
12+
<div
13+
className={`inline-flex items-center gap-1 rounded-xl border border-zinc-200/80 bg-white/80 p-1 shadow-sm backdrop-blur-sm dark:border-zinc-700/80 dark:bg-zinc-900/80 ${className}`}
14+
role="toolbar"
15+
aria-label={t('settings.language')}
16+
>
17+
<div className="flex rounded-lg bg-zinc-100/90 p-0.5 dark:bg-zinc-800/90">
18+
{(['light', 'dark', 'system'] as const).map(key => (
19+
<button
20+
key={key}
21+
type="button"
22+
onClick={() => setTheme(key)}
23+
className={`rounded-md px-2 py-1 text-xs font-medium transition ${
24+
theme === key
25+
? 'bg-white text-zinc-900 shadow dark:bg-zinc-700 dark:text-zinc-100'
26+
: 'text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200'
27+
}`}
28+
title={
29+
key === 'light'
30+
? t('settings.themeLight')
31+
: key === 'dark'
32+
? t('settings.themeDark')
33+
: t('settings.themeSystem')
34+
}
35+
>
36+
{key === 'light' ? '☀' : key === 'dark' ? '☾' : '◐'}
37+
</button>
38+
))}
39+
</div>
40+
<span className="hidden h-4 w-px bg-zinc-200 dark:bg-zinc-700 sm:inline" aria-hidden />
41+
<label className="sr-only" htmlFor="app-locale">
42+
{t('settings.language')}
43+
</label>
44+
<select
45+
id="app-locale"
46+
value={locale}
47+
onChange={e => setLocale(e.target.value as Locale)}
48+
className="max-w-[8.5rem] rounded-lg border-0 bg-transparent py-1 pl-1 pr-6 text-xs font-medium text-zinc-700 outline-none ring-0 focus:ring-2 focus:ring-blue-500/40 dark:text-zinc-200"
49+
>
50+
<option value="en">{t('settings.english')}</option>
51+
<option value="zh-TW">{t('settings.traditionalChinese')}</option>
52+
</select>
53+
</div>
54+
)
55+
}

0 commit comments

Comments
 (0)