e.preventDefault() }>
+
+ value={ selectedLanguage.name }
+ onChange={ (e) =>
setSelectedLanguage(
languages.find(
- (lang) => lang.name === e.target.value,
- ) || languages[0],
+ (lang) => lang.name === e.target.value
+ ) || languages[0]
)
}
- className="px-4 py-2 border rounded"
+ className='px-4 py-2 border rounded'
>
- {languages.map((lang) => (
-
- {lang.name}
+ { languages.map((lang) => (
+
+ { lang.name }
- ))}
+ )) }
-
+
- You are User {userId} in Room {roomId}.
- {isReadOnly
- ? `User ${editingUser} is currently editing.`
- : 'You can edit now. Press Enter to switch control.'}
+ You are User { userId } in Room { roomId }.
+ { isReadOnly
+ ? `User ${ editingUser } is currently editing.`
+ : 'You can edit now. Press Enter to switch control.' }
- {isReadOnly && (
+ { isReadOnly && (
Request Edit Permission
- )}
+ ) }
)
diff --git a/components/FloatingMenu.tsx b/components/FloatingMenu.tsx
new file mode 100644
index 0000000..2e9b0f9
--- /dev/null
+++ b/components/FloatingMenu.tsx
@@ -0,0 +1,59 @@
+'use client'
+
+import React, { useState } from 'react'
+import { Button, Modal } from 'react-daisyui'
+import SettingPanel from './SettingPanel'
+// import ChangeThemePanel from './ChangeThemePanel';
+import { Icons } from '@/common/icons'
+import { ThemePanel } from './ThemePanel'
+
+export const FloatingMenu: React.FC = (
+ props: React.ComponentProps<'div'> | { className: string },
+) => {
+ // const [isSettingsOpen, setIsSettingsOpen] = useState(false)
+ // const [isThemeOpen, setIsThemeOpen] = useState(false)
+ const { Dialog: SettingDialog, handleShow: settingHandleShow } =
+ Modal.useDialog()
+ const { Dialog: ThemeDialog, handleShow: themeHandleShow } =
+ Modal.useDialog()
+ // const { , } = Modal.useDialog()
+
+ return (
+
+
+
+
+
+
+ 设置
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 设置
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/components/SettingPanel.tsx b/components/SettingPanel.tsx
index 5610c5f..6feefcb 100644
--- a/components/SettingPanel.tsx
+++ b/components/SettingPanel.tsx
@@ -1,7 +1,6 @@
import { Select, Card } from 'react-daisyui'
import React from 'react'
import { create } from 'zustand'
-
interface Settings {
language: string
theme: string
@@ -9,12 +8,12 @@ interface Settings {
interface SettingsStore {
settings: Settings
- updateSettings: (settings: Settings) => void
+ setSettings: (settings: Settings) => void
}
export const useSettingsStore = create
((set) => ({
settings: { language: 'javascript', theme: 'light' },
- updateSettings: (newSettings) => set({ settings: newSettings }),
+ setSettings: (newSettings) => set({ settings: newSettings }),
}))
interface SettingPanelProps {
@@ -24,59 +23,33 @@ interface SettingPanelProps {
export default function SettingPanel({
onSettingChange = () => {},
}: SettingPanelProps) {
- const { settings, updateSettings } = useSettingsStore()
+ const { settings, setSettings } = useSettingsStore()
const handleSettingChange = (key: 'language' | 'theme', value: string) => {
const newSettings = { ...settings, [key]: value }
- updateSettings(newSettings)
+ setSettings(newSettings)
onSettingChange(newSettings)
}
return (
-
-
-
-
- 编程语言
-
- ) =>
- handleSettingChange('language', e.target.value)
- }
- className="w-full max-w-xs"
- >
-
- JavaScript
-
-
- TypeScript
-
- Python
- Java
-
-
-
-
-
- 主题
-
-
- handleSettingChange('theme', e.target.value)
- }
- className="w-full max-w-xs"
- >
- Light
- Dark
- Cupcake
- Dracula
- Night
- Coffee
-
-
-
-
+
+
+
+ 编程语言
+
+ ) =>
+ handleSettingChange('language', e.target.value)
+ }
+ className="w-full max-w-xs"
+ >
+ JavaScript
+ TypeScript
+ Python
+ Java
+
+
+
)
}
diff --git a/components/ThemeItem.tsx b/components/ThemeItem.tsx
new file mode 100644
index 0000000..d2b387b
--- /dev/null
+++ b/components/ThemeItem.tsx
@@ -0,0 +1,59 @@
+import React from 'react'
+import clsx from 'clsx'
+import { twMerge } from 'tailwind-merge'
+
+export type ThemeItemProps = React.HTMLAttributes & {
+ dataTheme: string
+ selected?: boolean
+}
+
+const ThemeItem = ({
+ selected,
+ children,
+ dataTheme,
+ className,
+ ...props
+}: ThemeItemProps) => {
+ const classes = twMerge(
+ className,
+ 'border-base-content/20 hover:border-base-content/40 outline-base-content\
+ overflow-hidden rounded-lg border outline-2 outline-offset-2',
+ clsx({
+ outline: selected,
+ })
+ )
+
+ return (
+
+
+
+
+
+
+
{dataTheme}
+
+ {children &&
{children}
}
+
+
+
+
+ )
+}
+
+export default ThemeItem
diff --git a/components/ThemePanel.tsx b/components/ThemePanel.tsx
new file mode 100644
index 0000000..3b908cd
--- /dev/null
+++ b/components/ThemePanel.tsx
@@ -0,0 +1,73 @@
+import tailwindConfig from '@/tailwind.config.ts'
+import ThemeItem from './ThemeItem'
+import { create } from 'zustand'
+
+interface Theme {
+ mode: 'light' | 'dark' | 'follow-system'
+ light: string
+ dark: string
+}
+
+interface ThemeStore {
+ theme: Theme
+ setTheme: (newTheme: string) => void
+}
+
+export const useThemeStore = create((set) => ({
+ theme: { light: 'ca', dark: 'dark', mode: 'follow-system' },
+ setTheme: (newTheme: string) => {},
+}))
+
+function getTheme(theme: Theme) {
+ // 根据传入的 theme 配置返回具体的主题
+ if (theme.mode === 'light') {
+ return theme.light
+ }
+
+ if (theme.mode === 'dark') {
+ return theme.dark
+ }
+
+ // follow-system 模式下根据系统主题返回
+ if (typeof window !== 'undefined') {
+ const prefersDark = window.matchMedia(
+ '(prefers-color-scheme: dark)',
+ ).matches
+ return prefersDark ? theme.dark : theme.light
+ }
+
+ // 默认返回 light 主题
+ return theme.light
+}
+
+export const ThemePanel: React.FC = (props: React.ComponentProps<'div'>) => {
+ const { theme, setTheme } = useThemeStore()
+ return (
+
+
Theme
+
+ {tailwindConfig.daisyui.themes.map((t, i) => (
+ {
+ document
+ .getElementsByTagName('html')[0]
+ .setAttribute('data-theme', t)
+ window.localStorage.setItem(
+ 'sb-react-daisyui-preview-theme',
+ t,
+ )
+ setTheme(t)
+ }}
+ />
+ ))}
+
+
+ )
+}
diff --git a/package.json b/package.json
index 472b23c..851c823 100644
--- a/package.json
+++ b/package.json
@@ -14,6 +14,7 @@
"@codemirror/legacy-modes": "^6.4.2",
"@uiw/codemirror-theme-vscode": "^4.23.7",
"@uiw/react-codemirror": "^4.23.7",
+ "clsx": "^2.1.1",
"codemirror-lang-elixir": "^4.0.0",
"next": "15.1.2",
"react": "^19.0.0",
@@ -21,6 +22,7 @@
"react-dom": "^19.0.0",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1",
+ "tailwind-merge": "^2.6.0",
"ts-node": "^10.9.2",
"zustand": "^5.0.2"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 2075b47..66015dc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -26,6 +26,9 @@ importers:
'@uiw/react-codemirror':
specifier: ^4.23.7
version: 4.23.7(@babel/runtime@7.26.0)(@codemirror/autocomplete@6.18.4)(@codemirror/language@6.10.8)(@codemirror/lint@6.8.4)(@codemirror/search@6.5.8)(@codemirror/state@6.5.0)(@codemirror/theme-one-dark@6.1.2)(@codemirror/view@6.36.1)(codemirror@6.0.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ clsx:
+ specifier: ^2.1.1
+ version: 2.1.1
codemirror-lang-elixir:
specifier: ^4.0.0
version: 4.0.0
@@ -47,6 +50,9 @@ importers:
socket.io-client:
specifier: ^4.8.1
version: 4.8.1
+ tailwind-merge:
+ specifier: ^2.6.0
+ version: 2.6.0
ts-node:
specifier: ^10.9.2
version: 10.9.2(@types/node@20.17.10)(typescript@5.7.2)
@@ -718,6 +724,10 @@ packages:
client-only@0.0.1:
resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==}
+ clsx@2.1.1:
+ resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
+ engines: {node: '>=6'}
+
codemirror-lang-elixir@4.0.0:
resolution: {integrity: sha512-mzFesxo/t6KOxwnkqVd34R/q7yk+sMtHh6vUKGAvjwHmpL7bERHB+vQAsmU/nqrndkwVeJEHWGw/z/ybfdiudA==}
@@ -1826,6 +1836,9 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
+ tailwind-merge@2.6.0:
+ resolution: {integrity: sha512-P+Vu1qXfzediirmHOC3xKGAYeZtPcV9g76X+xg2FD4tYgR71ewMA35Y3sCz3zhiN/dwefRpJX0yBcgwi1fXNQA==}
+
tailwindcss@3.4.17:
resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==}
engines: {node: '>=14.0.0'}
@@ -2705,6 +2718,8 @@ snapshots:
client-only@0.0.1: {}
+ clsx@2.1.1: {}
+
codemirror-lang-elixir@4.0.0:
dependencies:
'@codemirror/language': 6.10.8
@@ -4061,6 +4076,8 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
+ tailwind-merge@2.6.0: {}
+
tailwindcss@3.4.17(ts-node@10.9.2(@types/node@20.17.10)(typescript@5.7.2)):
dependencies:
'@alloc/quick-lru': 5.2.0
diff --git a/run.fish b/run.fish
new file mode 100644
index 0000000..8d4513d
--- /dev/null
+++ b/run.fish
@@ -0,0 +1,10 @@
+function run_dev
+ pnpm run dev &
+ pnpm next dev &
+ function cleanup --on-signal INT
+ kill (jobs -p)
+ exit
+ end
+ wait
+end
+run_dev
diff --git a/tailwind.config.ts b/tailwind.config.ts
index f608c30..fd4a56f 100644
--- a/tailwind.config.ts
+++ b/tailwind.config.ts
@@ -6,6 +6,8 @@ export default {
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
+ 'node_modules/daisyui/dist/**/*.js',
+ 'node_modules/react-daisyui/dist/**/*.js',
],
theme: {
extend: {