diff --git a/.prettierrc b/.prettierrc index ef2ab10..2beaf65 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,6 @@ { "semi": false, "singleQuote": true, + "jsxSingleQuote": true, "tabWidth": 4 } diff --git a/app/globals.css b/app/globals.css index 9a58d01..b5c61c9 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,21 +1,3 @@ @tailwind base; @tailwind components; @tailwind utilities; - -/* :root { - --background: #ffffff; - --foreground: #171717; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -body { - color: var(--foreground); - background: var(--background); - font-family: Arial, Helvetica, sans-serif; -} */ diff --git a/app/layout.tsx b/app/layout.tsx index 15e196b..40057ac 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,3 +1,4 @@ +import { ThemeProvider } from 'next-themes' import { FloatingMenu } from '@/components/FloatingMenu' import type { Metadata } from 'next' import { Geist, Geist_Mono } from 'next/font/google' @@ -5,32 +6,34 @@ import './globals.css' const geistSans = Geist({ variable: '--font-geist-sans', - subsets: ['latin'] + subsets: ['latin'], }) const geistMono = Geist_Mono({ variable: '--font-geist-mono', - subsets: ['latin'] + subsets: ['latin'], }) export const metadata: Metadata = { - title: 'Create Next App', - description: 'Generated by create next app' + title: 'co-coding', + description: 'Co-coding with your friends...?', } export default function RootLayout({ - children + children, }: Readonly<{ - children: React.ReactNode; + children: React.ReactNode }>) { return ( - - - { children } - - + + + + {children} + + + ) } diff --git a/app/page.tsx b/app/page.tsx index ed6d1cf..fc4d97f 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -2,34 +2,50 @@ import { useState } from 'react' import { useRouter } from 'next/navigation' -import SettingPanel from '@/components/SettingPanel' +import { SettingDialog } from '@/components/SettingDialog' +import { Button, Input } from 'react-daisyui' export default function Home() { const [roomId, setRoomId] = useState('') + const [isSettingDialogOpen, setIsSettingDialogOpen] = useState(false) + const [isJoining, setIsJoining] = useState(false) const router = useRouter() const handleJoinRoom = () => { if (roomId) { router.push(`/room/${roomId}`) + setIsJoining(true) } } return ( -
-

co-coding

-
- +
+

co-coding

+ setRoomId(e.target.value)} - placeholder="Enter room id" - className="input input-bordered w-64" + placeholder='Enter room id' + className='w-64' /> - - +
+ + +
+ setIsSettingDialogOpen(false)} + />
) } diff --git a/common/icons.tsx b/common/icons.tsx index 92c19f6..1e0e63f 100644 --- a/common/icons.tsx +++ b/common/icons.tsx @@ -1,19 +1,5 @@ +import { FaPalette, FaGear } from 'react-icons/fa6' export const Icons = { - Settings: (props: React.ComponentProps<'svg'>) => ( - - - - ), + Settings: FaGear, + Theme: FaPalette, } diff --git a/components/CollaborativeEditor.tsx b/components/CollaborativeEditor.tsx index 64885e3..b43a423 100644 --- a/components/CollaborativeEditor.tsx +++ b/components/CollaborativeEditor.tsx @@ -8,10 +8,12 @@ import CodeMirror, { Extension, keymap, ReactCodeMirrorRef, - ViewUpdate + ViewUpdate, } from '@uiw/react-codemirror' // import { vscodeDark } from '@uiw/codemirror-theme-vscode' import { insertNewlineAndIndent } from '@codemirror/commands' +import { Button, Select } from 'react-daisyui' +import { useSettingsStore } from './SettingPanel' // 引入 useSettingsStore const BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:3001' @@ -20,9 +22,8 @@ interface CollaborativeEditorProps { roomId: string | undefined } - export default function CollaborativeEditor({ - roomId + roomId, }: CollaborativeEditorProps) { const [editorContent, setEditorContent] = useState('') const [isReadOnly, setIsReadOnly] = useState(false) @@ -30,7 +31,7 @@ export default function CollaborativeEditor({ const [editingUser, setEditingUser] = useState(null) const [isJoined, setIsJoined] = useState(false) const [error, setError] = useState(null) - const [selectedLanguage, setSelectedLanguage] = useState(languages[0]) + const { settings } = useSettingsStore() // 使用 useSettingsStore 获取语言设置 const [languageExtension, setLanguageExtension] = useState(null) const socketRef = useRef(null) const editorRef = useRef(null) @@ -76,10 +77,13 @@ export default function CollaborativeEditor({ }, [roomId, userId]) useEffect(() => { - selectedLanguage.extension().then((ext) => { - setLanguageExtension(ext) - }) - }, [selectedLanguage]) + languages + .find((lang) => lang['name'] === settings.language) // 使用 settings.language + ?.extension() + .then((ext) => { + setLanguageExtension(ext) + }) + }, [settings.language]) // 依赖 settings.language const handleEditorChange = (value: string) => { if (roomId && !isReadOnly) { @@ -88,7 +92,7 @@ export default function CollaborativeEditor({ socketRef.current?.emit('contentChange', { roomId, content: value, - lastChar + lastChar, }) } } @@ -110,7 +114,7 @@ export default function CollaborativeEditor({ if (!isJoined) { return (
-

Joining room { roomId }...

+

Joining room {roomId}...

) } @@ -118,7 +122,7 @@ export default function CollaborativeEditor({ if (error) { return (
-

{ error }

+

{error}

) } @@ -131,7 +135,7 @@ export default function CollaborativeEditor({ if (head !== docLength || anchor !== docLength) { view.dispatch({ - selection: { anchor: docLength, head: docLength } + selection: { anchor: docLength, head: docLength }, }) } } @@ -146,39 +150,20 @@ export default function CollaborativeEditor({ // 禁止粘贴 EditorView.domEventHandlers({ paste: () => true }), // 快捷键仅保留换行 - keymap.of([{ key: 'Enter', run: insertNewlineAndIndent }]) + keymap.of([{ key: 'Enter', run: insertNewlineAndIndent }]), ] return ( -
e.preventDefault() }> -
- -
+
e.preventDefault()}>

- 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 && ( - - ) } + + )}
) diff --git a/components/FloatingMenu.tsx b/components/FloatingMenu.tsx index 2e9b0f9..0c79cdc 100644 --- a/components/FloatingMenu.tsx +++ b/components/FloatingMenu.tsx @@ -1,55 +1,68 @@ 'use client' import React, { useState } from 'react' -import { Button, Modal } from 'react-daisyui' -import SettingPanel from './SettingPanel' -// import ChangeThemePanel from './ChangeThemePanel'; +import { Button, Modal, Menu, Tooltip } from 'react-daisyui' import { Icons } from '@/common/icons' import { ThemePanel } from './ThemePanel' +import { SettingDialog } from './SettingDialog' 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 [isSettingDialogOpen, setIsSettingDialogOpen] = useState(false) const { Dialog: ThemeDialog, handleShow: themeHandleShow } = Modal.useDialog() - // const { , } = Modal.useDialog() return (
- + + + setIsSettingDialogOpen(true)} + > + + + + + + + + + - - 设置 - - - - -
- -
-
-
+ setIsSettingDialogOpen(false)} + /> - - - - 设置 + + + Theme +
+ +
+
-
+
diff --git a/components/SettingDialog.tsx b/components/SettingDialog.tsx new file mode 100644 index 0000000..976b1bb --- /dev/null +++ b/components/SettingDialog.tsx @@ -0,0 +1,40 @@ +import React from 'react' +import { Modal, Button } from 'react-daisyui' +import SettingPanel from './SettingPanel' + +interface SettingDialogProps { + isOpen: boolean + onClose: () => void +} + +export const SettingDialog: React.FC = ({ + isOpen, + onClose, +}) => { + return ( + + + Settings +
+ +
+
+ + + + +
+ +
+
+
+ ) +} diff --git a/components/SettingPanel.tsx b/components/SettingPanel.tsx index 6feefcb..44b94e0 100644 --- a/components/SettingPanel.tsx +++ b/components/SettingPanel.tsx @@ -1,20 +1,35 @@ -import { Select, Card } from 'react-daisyui' +import { Select } from 'react-daisyui' import React from 'react' import { create } from 'zustand' +import { persist } from 'zustand/middleware' // 引入 persist 中间件 +import { languages } from '@/common/lang' + interface Settings { language: string - theme: string } interface SettingsStore { settings: Settings setSettings: (settings: Settings) => void + setSettingsItem: (settingItem: string, newValue: any) => void } -export const useSettingsStore = create((set) => ({ - settings: { language: 'javascript', theme: 'light' }, - setSettings: (newSettings) => set({ settings: newSettings }), -})) +// 使用 persist 中间件来持久化 settings +export const useSettingsStore = create()( + persist( + (set) => ({ + settings: { language: languages[0].name }, + setSettings: (newSettings) => set({ settings: newSettings }), + setSettingsItem: (settingItem, newValue) => + set((state) => ({ + settings: { ...state.settings, [settingItem]: newValue }, + })), + }), + { + name: 'settings-storage', // 本地存储的 key + }, + ), +) interface SettingPanelProps { onSettingChange?: (settings: Settings) => void @@ -25,7 +40,7 @@ export default function SettingPanel({ }: SettingPanelProps) { const { settings, setSettings } = useSettingsStore() - const handleSettingChange = (key: 'language' | 'theme', value: string) => { + const handleSettingChange = (key: string, value: string) => { const newSettings = { ...settings, [key]: value } setSettings(newSettings) onSettingChange(newSettings) @@ -33,21 +48,22 @@ export default function SettingPanel({ return (
-
-
diff --git a/components/ThemeItem.tsx b/components/ThemeItem.tsx index d2b387b..ecb1e80 100644 --- a/components/ThemeItem.tsx +++ b/components/ThemeItem.tsx @@ -3,57 +3,69 @@ import clsx from 'clsx' import { twMerge } from 'tailwind-merge' export type ThemeItemProps = React.HTMLAttributes & { - dataTheme: string - selected?: boolean + dataTheme: string + selected?: boolean + displayName?: string } const ThemeItem = ({ - selected, - children, - dataTheme, - className, - ...props -}: ThemeItemProps) => { - const classes = twMerge( + dataTheme, + selected, + displayName, + children, className, - 'border-base-content/20 hover:border-base-content/40 outline-base-content\ + ...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, - }) - ) + clsx({ + outline: selected, + }), + ) - return ( -
-
-
-
-
-
-
{dataTheme}
-
-
-
A
-
+ return ( +
+
+
+
+
+
+
+ {displayName || dataTheme} +
+
+
+
+ A +
+
-
-
A
-
+
+
+ A +
+
-
-
A
-
+
+
+ A +
+
-
-
A
-
+
+
+ A +
+
+
+ {children &&
{children}
} +
+
- {children &&
{children}
} -
-
-
- ) + ) } export default ThemeItem diff --git a/components/ThemePanel.tsx b/components/ThemePanel.tsx index 3b908cd..a758768 100644 --- a/components/ThemePanel.tsx +++ b/components/ThemePanel.tsx @@ -1,68 +1,55 @@ -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 -} +'use client' -interface ThemeStore { - theme: Theme - setTheme: (newTheme: string) => void -} - -export const useThemeStore = create((set) => ({ - theme: { light: 'ca', dark: 'dark', mode: 'follow-system' }, - setTheme: (newTheme: string) => {}, -})) +import { useTheme } from 'next-themes' +import tailwindConfig from '@/tailwind.config' +import ThemeItem from './ThemeItem' +import { useEffect, useState } from 'react' -function getTheme(theme: Theme) { - // 根据传入的 theme 配置返回具体的主题 - if (theme.mode === 'light') { - return theme.light - } +export const ThemePanel: React.FC = (props: React.ComponentProps<'div'>) => { + const { theme, setTheme } = useTheme() + const [mounted, setMounted] = useState(false) - if (theme.mode === 'dark') { - return theme.dark - } + // 确保组件在客户端渲染后再显示主题 + useEffect(() => { + setMounted(true) + }, []) - // follow-system 模式下根据系统主题返回 - if (typeof window !== 'undefined') { - const prefersDark = window.matchMedia( - '(prefers-color-scheme: dark)', - ).matches - return prefersDark ? theme.dark : theme.light + if (!mounted) { + return null } - // 默认返回 light 主题 - return theme.light -} + const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') + .matches + ? 'dark' + : 'light' -export const ThemePanel: React.FC = (props: React.ComponentProps<'div'>) => { - const { theme, setTheme } = useThemeStore() return (
-

Theme

+

Current Theme: {theme}

+ { + setTheme('system') + }} + > {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 851c823..ed26c30 100644 --- a/package.json +++ b/package.json @@ -17,14 +17,17 @@ "clsx": "^2.1.1", "codemirror-lang-elixir": "^4.0.0", "next": "15.1.2", + "next-themes": "^0.4.4", "react": "^19.0.0", "react-daisyui": "^5.0.5", "react-dom": "^19.0.0", + "react-icons": "^5.4.0", + "react-use": "^17.6.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" + "zustand": "^5.0.3" }, "devDependencies": { "@eslint/eslintrc": "^3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66015dc..455e83f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,9 @@ importers: next: specifier: 15.1.2 version: 15.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + next-themes: + specifier: ^0.4.4 + version: 0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) react: specifier: ^19.0.0 version: 19.0.0 @@ -44,6 +47,12 @@ importers: react-dom: specifier: ^19.0.0 version: 19.0.0(react@19.0.0) + react-icons: + specifier: ^5.4.0 + version: 5.4.0(react@19.0.0) + react-use: + specifier: ^17.6.0 + version: 17.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0) socket.io: specifier: ^4.8.1 version: 4.8.1 @@ -57,8 +66,8 @@ importers: specifier: ^10.9.2 version: 10.9.2(@types/node@20.17.10)(typescript@5.7.2) zustand: - specifier: ^5.0.2 - version: 5.0.2(@types/react@19.0.2)(react@19.0.0) + specifier: ^5.0.3 + version: 5.0.3(@types/react@19.0.2)(react@19.0.0) devDependencies: '@eslint/eslintrc': specifier: ^3 @@ -465,6 +474,9 @@ packages: '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} + '@types/js-cookie@2.2.7': + resolution: {integrity: sha512-aLkWa0C0vO5b4Sr798E26QgOkss68Un0bLjs7u9qxzPT5CG+8DuNTffWES58YzJs3hrVAOs1wonycqEBqNJubA==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -561,6 +573,9 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' + '@xobotyi/scrollbar-width@1.9.5': + resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -759,6 +774,9 @@ packages: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + copy-to-clipboard@3.3.3: + resolution: {integrity: sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==} + cors@2.8.5: resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} engines: {node: '>= 0.10'} @@ -773,9 +791,16 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-in-js-utils@3.1.0: + resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} + css-selector-tokenizer@0.8.0: resolution: {integrity: sha512-Jd6Ig3/pe62/qe5SBPTN8h8LeUg/pT4lLgtavPf7updwwHpvFzxvOQBHYj2LZDMjUnBzgvIUSjRcf6oT5HzHFg==} + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -890,6 +915,9 @@ packages: resolution: {integrity: sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==} engines: {node: '>=10.13.0'} + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + es-abstract@1.23.6: resolution: {integrity: sha512-Ifco6n3yj2tMZDWNLyloZrytt9lqqlwvS83P3HtaETR0NUOYnIULGGHpktqYGObGy+8wc1okO25p8TjemhImvA==} engines: {node: '>= 0.4'} @@ -1058,6 +1086,12 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-shallow-equal@1.0.0: + resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} + + fastest-stable-stringify@2.0.2: + resolution: {integrity: sha512-bijHueCGd0LqqNK9b5oCMHc0MluJAx0cwqASgbWMvkO01lCYgIhacVRLcaDz3QnyYIRNJRDwMb41VuT6pHJ91Q==} + fastparse@1.1.2: resolution: {integrity: sha512-483XLLxTVIwWK3QTrMGRqUfUpoOs/0hbQrl2oz4J0pAcm3A3bu84wxTFqGqkJzewCLdME38xJLJAxBABfQT8sQ==} @@ -1173,6 +1207,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hyphenate-style-name@1.1.0: + resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1185,6 +1222,9 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + inline-style-prefixer@7.0.1: + resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==} + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -1320,6 +1360,9 @@ packages: resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true + js-cookie@2.2.1: + resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -1389,6 +1432,9 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1425,6 +1471,12 @@ packages: mz@2.7.0: resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + nano-css@5.6.2: + resolution: {integrity: sha512-+6bHaC8dSDGALM1HJjOHVXpuastdu2xFoZlC77Jh4cg+33Zcgm+Gxd+1xsnpZK14eyHObSp82+ll5y3SX75liw==} + peerDependencies: + react: '*' + react-dom: '*' + nanoid@3.3.8: resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -1437,6 +1489,12 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + next-themes@0.4.4: + resolution: {integrity: sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + next@15.1.2: resolution: {integrity: sha512-nLJDV7peNy+0oHlmY2JZjzMfJ8Aj0/dd3jCwSZS8ZiO5nkQfcZRqDrRN3U5rJtqVTQneIOGZzb6LCNrk7trMCQ==} engines: {node: ^18.18.0 || ^19.8.0 || >= 20.0.0} @@ -1623,9 +1681,26 @@ packages: peerDependencies: react: ^19.0.0 + react-icons@5.4.0: + resolution: {integrity: sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==} + peerDependencies: + react: '*' + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-universal-interface@0.6.2: + resolution: {integrity: sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw==} + peerDependencies: + react: '*' + tslib: '*' + + react-use@17.6.0: + resolution: {integrity: sha512-OmedEScUMKFfzn1Ir8dBxiLLSOzhKe/dPZwVxcujweSj45aNM7BEGPb9BEVIgVEqEXx6f3/TsXzwIktNgUR02g==} + peerDependencies: + react: '*' + react-dom: '*' + react@19.0.0: resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} engines: {node: '>=0.10.0'} @@ -1648,6 +1723,9 @@ packages: resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} engines: {node: '>= 0.4'} + resize-observer-polyfill@1.5.1: + resolution: {integrity: sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1668,6 +1746,9 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rtl-css-js@1.16.1: + resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} + run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} @@ -1682,6 +1763,10 @@ packages: scheduler@0.25.0: resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + screenfull@5.2.0: + resolution: {integrity: sha512-9BakfsO2aUQN2K9Fdbj87RJIEZ82Q9IGim7FqM5OsebfoFC6ZHXgDq/KvniuLTPdeM8wY2o6Dj3WQ7KeQCj3cA==} + engines: {node: '>=0.10.0'} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -1699,6 +1784,10 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + set-harmonic-interval@1.0.1: + resolution: {integrity: sha512-AhICkFV84tBP1aWqPwLZqFvAwqEoVA9kxNMniGEUvzOlm4vLmOFLiTT3UZ6bziJTy4bOVpzWGTfSCbmaayGx8g==} + engines: {node: '>=6.9'} + sharp@0.33.5: resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -1753,9 +1842,29 @@ packages: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + source-map@0.5.6: + resolution: {integrity: sha512-MjZkVp0NHr5+TPihLcadqnlVoGIoWo4IBHptutGh9wI3ttUYvCG26HkSuDi+K6lsZ25syXJXcctwgyVCt//xqA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} + stack-generator@2.0.10: + resolution: {integrity: sha512-mwnua/hkqM6pF4k8SnmZ2zfETsRUpWXREfA/goT8SLCV4iOFa4bzOX2nDipWAZFPTjLvQB82f5yaodMVhK0yJQ==} + + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + stacktrace-gps@3.1.2: + resolution: {integrity: sha512-GcUgbO4Jsqqg6RxfyTHFiPxdPqF+3LFmQhm7MgCuYQOYuWyqxo5pwRPz5d/u6/WYJdEnWfK4r+jGbyD8TSggXQ==} + + stacktrace-js@2.0.2: + resolution: {integrity: sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg==} + streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -1823,6 +1932,9 @@ packages: babel-plugin-macros: optional: true + stylis@4.3.5: + resolution: {integrity: sha512-K7npNOKGRYuhAFFzkzMGfxFDpN6gDwf8hcMiE+uveTVbBgm93HrNP3ZDUpKqzZ4pG7TP6fmb+EMAQPjq9FqqvA==} + sucrase@3.35.0: resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} engines: {node: '>=16 || 14 >=14.17'} @@ -1855,16 +1967,26 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + throttle-debounce@3.0.1: + resolution: {integrity: sha512-dTEWWNu6JmeVXY0ZYoPuH5cRIwc0MeGbJwah9KUNYSJwommQpCzTySTpEe8Gs1J23aeWEuAobe4Ag7EHVt/LOg==} + engines: {node: '>=10'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toggle-selection@1.0.6: + resolution: {integrity: sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==} + ts-api-utils@1.4.3: resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' + ts-easing@0.2.0: + resolution: {integrity: sha512-Z86EW+fFFh/IFB1fqQ3/+7Zpf9t2ebOAxNI/V6Wo7r5gqiqtxmgTlQ1qbqQcjLKYeSHPTsEmvlJUDg/EuL0uHQ==} + ts-interface-checker@0.1.13: resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} @@ -1998,8 +2120,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zustand@5.0.2: - resolution: {integrity: sha512-8qNdnJVJlHlrKXi50LDqqUNmUbuBjoKLrYQBnoChIbVph7vni+sY+YpvdjXG9YLd/Bxr6scMcR+rm5H3aSqPaw==} + zustand@5.0.3: + resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=18.0.0' @@ -2398,6 +2520,8 @@ snapshots: '@types/estree@1.0.6': {} + '@types/js-cookie@2.2.7': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -2532,6 +2656,8 @@ snapshots: - '@codemirror/lint' - '@codemirror/search' + '@xobotyi/scrollbar-width@1.9.5': {} + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -2759,6 +2885,10 @@ snapshots: cookie@0.7.2: {} + copy-to-clipboard@3.3.3: + dependencies: + toggle-selection: 1.0.6 + cors@2.8.5: dependencies: object-assign: 4.1.1 @@ -2774,11 +2904,20 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-in-js-utils@3.1.0: + dependencies: + hyphenate-style-name: 1.1.0 + css-selector-tokenizer@0.8.0: dependencies: cssesc: 3.0.0 fastparse: 1.1.2 + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + cssesc@3.0.0: {} csstype@3.1.3: {} @@ -2901,6 +3040,10 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.2.1 + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + es-abstract@1.23.6: dependencies: array-buffer-byte-length: 1.0.2 @@ -3215,6 +3358,10 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-shallow-equal@1.0.0: {} + + fastest-stable-stringify@2.0.2: {} + fastparse@1.1.2: {} fastq@1.17.1: @@ -3341,6 +3488,8 @@ snapshots: dependencies: function-bind: 1.1.2 + hyphenate-style-name@1.1.0: {} + ignore@5.3.2: {} import-fresh@3.3.0: @@ -3350,6 +3499,10 @@ snapshots: imurmurhash@0.1.4: {} + inline-style-prefixer@7.0.1: + dependencies: + css-in-js-utils: 3.1.0 + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -3490,6 +3643,8 @@ snapshots: jiti@1.21.7: {} + js-cookie@2.2.1: {} + js-tokens@4.0.0: {} js-yaml@4.1.0: @@ -3553,6 +3708,8 @@ snapshots: math-intrinsics@1.1.0: {} + mdn-data@2.0.14: {} + merge2@1.4.1: {} micromatch@4.0.8: @@ -3586,12 +3743,30 @@ snapshots: object-assign: 4.1.1 thenify-all: 1.6.0 + nano-css@5.6.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@jridgewell/sourcemap-codec': 1.5.0 + css-tree: 1.1.3 + csstype: 3.1.3 + fastest-stable-stringify: 2.0.2 + inline-style-prefixer: 7.0.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + rtl-css-js: 1.16.1 + stacktrace-js: 2.0.2 + stylis: 4.3.5 + nanoid@3.3.8: {} natural-compare@1.4.0: {} negotiator@0.6.3: {} + next-themes@0.4.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + next@15.1.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@next/env': 15.1.2 @@ -3774,8 +3949,36 @@ snapshots: react: 19.0.0 scheduler: 0.25.0 + react-icons@5.4.0(react@19.0.0): + dependencies: + react: 19.0.0 + react-is@16.13.1: {} + react-universal-interface@0.6.2(react@19.0.0)(tslib@2.8.1): + dependencies: + react: 19.0.0 + tslib: 2.8.1 + + react-use@17.6.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0): + dependencies: + '@types/js-cookie': 2.2.7 + '@xobotyi/scrollbar-width': 1.9.5 + copy-to-clipboard: 3.3.3 + fast-deep-equal: 3.1.3 + fast-shallow-equal: 1.0.0 + js-cookie: 2.2.1 + nano-css: 5.6.2(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-universal-interface: 0.6.2(react@19.0.0)(tslib@2.8.1) + resize-observer-polyfill: 1.5.1 + screenfull: 5.2.0 + set-harmonic-interval: 1.0.1 + throttle-debounce: 3.0.1 + ts-easing: 0.2.0 + tslib: 2.8.1 + react@19.0.0: {} read-cache@1.0.0: @@ -3806,6 +4009,8 @@ snapshots: es-errors: 1.3.0 set-function-name: 2.0.2 + resize-observer-polyfill@1.5.1: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -3824,6 +4029,10 @@ snapshots: reusify@1.0.4: {} + rtl-css-js@1.16.1: + dependencies: + '@babel/runtime': 7.26.0 + run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 @@ -3844,6 +4053,8 @@ snapshots: scheduler@0.25.0: {} + screenfull@5.2.0: {} + semver@6.3.1: {} semver@7.6.3: {} @@ -3864,6 +4075,8 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + set-harmonic-interval@1.0.1: {} + sharp@0.33.5: dependencies: color: 4.2.3 @@ -3975,8 +4188,29 @@ snapshots: source-map-js@1.2.1: {} + source-map@0.5.6: {} + + source-map@0.6.1: {} + stable-hash@0.0.4: {} + stack-generator@2.0.10: + dependencies: + stackframe: 1.3.4 + + stackframe@1.3.4: {} + + stacktrace-gps@3.1.2: + dependencies: + source-map: 0.5.6 + stackframe: 1.3.4 + + stacktrace-js@2.0.2: + dependencies: + error-stack-parser: 2.1.4 + stack-generator: 2.0.10 + stacktrace-gps: 3.1.2 + streamsearch@1.1.0: {} string-width@4.2.3: @@ -4060,6 +4294,8 @@ snapshots: client-only: 0.0.1 react: 19.0.0 + stylis@4.3.5: {} + sucrase@3.35.0: dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -4115,14 +4351,20 @@ snapshots: dependencies: any-promise: 1.3.0 + throttle-debounce@3.0.1: {} + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 + toggle-selection@1.0.6: {} + ts-api-utils@1.4.3(typescript@5.7.2): dependencies: typescript: 5.7.2 + ts-easing@0.2.0: {} + ts-interface-checker@0.1.13: {} ts-node@10.9.2(@types/node@20.17.10)(typescript@5.7.2): @@ -4280,7 +4522,7 @@ snapshots: yocto-queue@0.1.0: {} - zustand@5.0.2(@types/react@19.0.2)(react@19.0.0): + zustand@5.0.3(@types/react@19.0.2)(react@19.0.0): optionalDependencies: '@types/react': 19.0.2 react: 19.0.0 diff --git a/tailwind.config.ts b/tailwind.config.ts index fd4a56f..77e6e3f 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -20,9 +20,9 @@ export default { plugins: [daisyui], daisyui: { themes: [ - 'cupcake', - 'dark', 'light', + 'dark', + 'cupcake', 'bumblebee', 'emerald', 'corporate',