Add Blocknote editor, Add theme switching
This commit is contained in:
3185
package-lock.json
generated
3185
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -46,6 +46,9 @@
|
|||||||
"vite": "^5.4.21"
|
"vite": "^5.4.21"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@blocknote/core": "^0.46.1",
|
||||||
|
"@blocknote/react": "^0.46.1",
|
||||||
|
"@blocknote/shadcn": "^0.46.1",
|
||||||
"@radix-ui/react-dialog": "^1.1.15",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||||
"@radix-ui/react-separator": "^1.1.8",
|
"@radix-ui/react-separator": "^1.1.8",
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { NoteProvider } from "../contexts/ActiveNoteContext";
|
|||||||
import { NotesStoreProvider } from "../contexts/NotesStore";
|
import { NotesStoreProvider } from "../contexts/NotesStore";
|
||||||
import NoteEditor from "./NoteEditor"
|
import NoteEditor from "./NoteEditor"
|
||||||
import AppLayout from "./AppLayout"
|
import AppLayout from "./AppLayout"
|
||||||
|
import { ThemeProvider } from "../contexts/ThemeContext";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
|
<ThemeProvider>
|
||||||
<NotesStoreProvider>
|
<NotesStoreProvider>
|
||||||
<NoteProvider>
|
<NoteProvider>
|
||||||
<AppLayout>
|
<AppLayout>
|
||||||
@@ -12,6 +14,7 @@ function App() {
|
|||||||
</AppLayout>
|
</AppLayout>
|
||||||
</NoteProvider>
|
</NoteProvider>
|
||||||
</NotesStoreProvider>
|
</NotesStoreProvider>
|
||||||
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,30 +9,21 @@ import ListItem from "./ListItem"
|
|||||||
|
|
||||||
import { useNote } from "../contexts/ActiveNoteContext"
|
import { useNote } from "../contexts/ActiveNoteContext"
|
||||||
import { useNotesStore } from "../contexts/NotesStore"
|
import { useNotesStore } from "../contexts/NotesStore"
|
||||||
import { useState } from "react"
|
import { useTheme } from "../contexts/ThemeContext"
|
||||||
|
|
||||||
function AppSidebar() {
|
function AppSidebar() {
|
||||||
const { notes, createNote } = useNotesStore()
|
const { notes, createNote } = useNotesStore()
|
||||||
const { setCurrentNote } = useNote()
|
const { setCurrentNote } = useNote()
|
||||||
const [isDark, setIsDark] = useState(
|
const { theme, toggleTheme } = useTheme()
|
||||||
() => document.documentElement.classList.contains("dark")
|
|
||||||
)
|
|
||||||
|
|
||||||
const toggleTheme = () => {
|
|
||||||
const html = document.documentElement
|
|
||||||
html.classList.toggle("dark")
|
|
||||||
setIsDark(html.classList.contains("dark"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
|
|
||||||
|
|
||||||
<SidebarHeader className="justify-between px-4 py-3 border-b-1 gap-3.5">
|
<SidebarHeader className="justify-between px-4 py-3 border-b-1 gap-3.5">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<span className="font-medium">Notes</span>
|
<span className="font-medium">Notes</span>
|
||||||
<Button variant="ghost" size="sm" onClick={toggleTheme} className="text-muted-foreground">
|
<Button variant="ghost" size="sm" onClick={toggleTheme} className="text-muted-foreground">
|
||||||
{isDark ? <Sun /> : <Moon />}
|
{theme === "dark" ? <Sun /> : <Moon />}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Input placeholder="Search Notes" className="shadow-none bg-background" />
|
<Input placeholder="Search Notes" className="shadow-none bg-background" />
|
||||||
|
|||||||
@@ -1,7 +1,17 @@
|
|||||||
|
import "@blocknote/core/fonts/inter.css";
|
||||||
|
import "@blocknote/shadcn/style.css";
|
||||||
|
import { BlockNoteView } from "@blocknote/shadcn";
|
||||||
|
import { useCreateBlockNote } from "@blocknote/react"
|
||||||
import { useNote } from "../contexts/ActiveNoteContext"
|
import { useNote } from "../contexts/ActiveNoteContext"
|
||||||
|
import { useTheme } from "../contexts/ThemeContext";
|
||||||
|
|
||||||
function NoteEditor() {
|
function NoteEditor() {
|
||||||
const { currentNote, setCurrentNote } = useNote();
|
const { currentNote } = useNote();
|
||||||
|
const { theme } = useTheme();
|
||||||
|
|
||||||
|
const editor = useCreateBlockNote({
|
||||||
|
initialContent: currentNote?.body
|
||||||
|
});
|
||||||
|
|
||||||
if (!currentNote) return (
|
if (!currentNote) return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
@@ -12,7 +22,7 @@ function NoteEditor() {
|
|||||||
return (
|
return (
|
||||||
<div className="p-6">
|
<div className="p-6">
|
||||||
<h1>{ currentNote.title }</h1>
|
<h1>{ currentNote.title }</h1>
|
||||||
<p>{ currentNote.body }</p>
|
<BlockNoteView editor={editor as any} theme={theme} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/contexts/ThemeContext.tsx
Normal file
37
src/contexts/ThemeContext.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { createContext, useContext, useState } from "react";
|
||||||
|
|
||||||
|
type Theme = "light" | "dark";
|
||||||
|
|
||||||
|
const ThemeContext = createContext<{
|
||||||
|
theme: Theme;
|
||||||
|
toggleTheme: () => void;
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
|
export function ThemeProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [theme, setTheme] = useState<Theme>(() =>
|
||||||
|
document.documentElement.classList.contains("dark")
|
||||||
|
? "dark"
|
||||||
|
: "light"
|
||||||
|
);
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
const html = document.documentElement;
|
||||||
|
html.classList.toggle("dark");
|
||||||
|
|
||||||
|
setTheme(
|
||||||
|
html.classList.contains("dark") ? "dark" : "light"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTheme() {
|
||||||
|
const ctx = useContext(ThemeContext);
|
||||||
|
if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
@@ -1,12 +1,13 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tw-animate-css";
|
@import "tw-animate-css";
|
||||||
|
@source "../node_modules/@blocknote/shadcn";
|
||||||
@custom-variant dark (&:is(.dark *));
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: "Fira Sans", sans-serif, monospace !important;
|
font-family: "Fira Sans", sans-serif, monospace !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@custom-variant dark (&:is(.dark *));
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
@@ -115,6 +116,14 @@ body {
|
|||||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
--sidebar-border: oklch(1 0 0 / 10%);
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
--sidebar-ring: oklch(0.556 0 0);
|
||||||
|
|
||||||
|
/* Blocknote tweaks */
|
||||||
|
--bn-colors-editor-background: #0a0a0a !important;
|
||||||
|
--bn-colors-block-background: #0a0a0a !important;
|
||||||
|
|
||||||
|
.bn-add-file-button {
|
||||||
|
background-color: var(--primary-foreground) !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -1,12 +1,23 @@
|
|||||||
|
import { PartialBlock } from "@blocknote/core";
|
||||||
|
|
||||||
export class Note {
|
export class Note {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
body: string;
|
body: PartialBlock<any, any, any>[];
|
||||||
|
|
||||||
constructor(data: Partial<Note> = {}) {
|
constructor(data: Partial<Note> = {}) {
|
||||||
this.id = data.id ?? this.randomNumber();
|
this.id = data.id ?? this.randomNumber();
|
||||||
this.title = data.title ?? "Untitled Note";
|
this.title = data.title ?? "Untitled Note";
|
||||||
this.body = data.body ?? "";
|
this.body = data.body ?? this.defaultBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
private defaultBody(): any {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
type: "paragraph",
|
||||||
|
content: ""
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replace with UUID
|
// TODO: replace with UUID
|
||||||
|
|||||||
Reference in New Issue
Block a user