Merge pull request #11 from Code-Victor/feat/light-theme
Feat/light theme
This commit is contained in:
commit
3b93090d97
@ -224,10 +224,11 @@ io.on("connection", async (socket) => {
|
|||||||
containers[data.sandboxId],
|
containers[data.sandboxId],
|
||||||
sendLoadedEvent
|
sendLoadedEvent
|
||||||
)
|
)
|
||||||
await fileManagers[data.sandboxId].initialize()
|
|
||||||
terminalManagers[data.sandboxId] = new TerminalManager(
|
terminalManagers[data.sandboxId] = new TerminalManager(
|
||||||
containers[data.sandboxId]
|
containers[data.sandboxId]
|
||||||
)
|
)
|
||||||
|
console.log(`terminal manager set up for ${data.sandboxId}`)
|
||||||
|
await fileManagers[data.sandboxId].initialize()
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileManager = fileManagers[data.sandboxId]
|
const fileManager = fileManagers[data.sandboxId]
|
||||||
@ -415,6 +416,12 @@ io.on("connection", async (socket) => {
|
|||||||
socket.on("createTerminal", async (id: string, callback) => {
|
socket.on("createTerminal", async (id: string, callback) => {
|
||||||
try {
|
try {
|
||||||
await lockManager.acquireLock(data.sandboxId, async () => {
|
await lockManager.acquireLock(data.sandboxId, async () => {
|
||||||
|
let terminalManager = terminalManagers[data.sandboxId]
|
||||||
|
if (!terminalManager) {
|
||||||
|
terminalManager = terminalManagers[data.sandboxId] =
|
||||||
|
new TerminalManager(containers[data.sandboxId])
|
||||||
|
}
|
||||||
|
|
||||||
await terminalManager.createTerminal(id, (responseString: string) => {
|
await terminalManager.createTerminal(id, (responseString: string) => {
|
||||||
socket.emit("terminalResponse", { id, data: responseString })
|
socket.emit("terminalResponse", { id, data: responseString })
|
||||||
const port = extractPortNumber(responseString)
|
const port = extractPortNumber(responseString)
|
||||||
|
@ -99,6 +99,29 @@
|
|||||||
); /* violet 900 -> bg */
|
); /* violet 900 -> bg */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.light .gradient-button-bg {
|
||||||
|
background: radial-gradient(
|
||||||
|
circle at top,
|
||||||
|
#262626 0%,
|
||||||
|
#f5f5f5 50%
|
||||||
|
); /* Dark gray -> Light gray */
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .gradient-button {
|
||||||
|
background: radial-gradient(
|
||||||
|
circle at bottom,
|
||||||
|
hsl(0, 10%, 25%) -10%,
|
||||||
|
#9d9d9d 50%
|
||||||
|
); /* Light gray -> Almost white */
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .gradient-button-bg > div:hover {
|
||||||
|
background: radial-gradient(
|
||||||
|
circle at bottom,
|
||||||
|
hsl(0, 10%, 25%) -10%,
|
||||||
|
#9d9d9d 80%
|
||||||
|
); /* Light gray -> Almost white */
|
||||||
|
}
|
||||||
.inline-decoration::before {
|
.inline-decoration::before {
|
||||||
content: "Generate";
|
content: "Generate";
|
||||||
color: #525252;
|
color: #525252;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ThemeProvider } from "@/components/layout/themeProvider"
|
|
||||||
import { Toaster } from "@/components/ui/sonner"
|
import { Toaster } from "@/components/ui/sonner"
|
||||||
|
import { ThemeProvider } from "@/components/ui/theme-provider"
|
||||||
import { PreviewProvider } from "@/context/PreviewContext"
|
import { PreviewProvider } from "@/context/PreviewContext"
|
||||||
import { SocketProvider } from "@/context/SocketContext"
|
import { SocketProvider } from '@/context/SocketContext'
|
||||||
import { ClerkProvider } from "@clerk/nextjs"
|
import { ClerkProvider } from "@clerk/nextjs"
|
||||||
import { Analytics } from "@vercel/analytics/react"
|
import { Analytics } from "@vercel/analytics/react"
|
||||||
import { GeistMono } from "geist/font/mono"
|
import { GeistMono } from "geist/font/mono"
|
||||||
@ -25,8 +25,7 @@ export default function RootLayout({
|
|||||||
<body>
|
<body>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute="class"
|
attribute="class"
|
||||||
defaultTheme="dark"
|
defaultTheme="system"
|
||||||
forcedTheme="dark"
|
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<SocketProvider>
|
<SocketProvider>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import Logo from "@/assets/logo.svg"
|
import Logo from "@/assets/logo.svg"
|
||||||
|
import { ThemeSwitcher } from "@/components/ui/theme-switcher"
|
||||||
import { User } from "@/lib/types"
|
import { User } from "@/lib/types"
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
@ -19,6 +20,7 @@ export default function DashboardNavbar({ userData }: { userData: User }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
<DashboardNavbarSearch />
|
<DashboardNavbarSearch />
|
||||||
|
<ThemeSwitcher />
|
||||||
<UserButton userData={userData} />
|
<UserButton userData={userData} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -288,7 +288,7 @@ function SearchInput({
|
|||||||
<form {...{ onSubmit }} className="w-40 h-8 ">
|
<form {...{ onSubmit }} className="w-40 h-8 ">
|
||||||
<label
|
<label
|
||||||
htmlFor="template-search"
|
htmlFor="template-search"
|
||||||
className="flex gap-2 rounded-sm transition-colors bg-[#2e2e2e] border border-[--s-color] [--s-color:hsl(var(--muted-foreground))] focus-within:[--s-color:#fff] h-full items-center px-2"
|
className="flex gap-2 rounded-sm transition-colors bg-gray-100 dark:bg-[#2e2e2e] border border-[--s-color] [--s-color:hsl(var(--muted-foreground))] focus-within:[--s-color:hsl(var(--muted-foreground),50%)] h-full items-center px-2"
|
||||||
>
|
>
|
||||||
<Search className="size-4 text-[--s-color] transition-colors" />
|
<Search className="size-4 text-[--s-color] transition-colors" />
|
||||||
<input
|
<input
|
||||||
@ -298,7 +298,7 @@ function SearchInput({
|
|||||||
placeholder="Search templates"
|
placeholder="Search templates"
|
||||||
value={value}
|
value={value}
|
||||||
onChange={(e) => onValueChange?.(e.target.value)}
|
onChange={(e) => onValueChange?.(e.target.value)}
|
||||||
className="bg-transparent placeholder:text-muted-foreground text-white w-full focus:outline-none text-xs"
|
className="bg-transparent placeholder:text-muted-foreground w-full focus:outline-none text-xs"
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
|
@ -39,6 +39,7 @@ import {
|
|||||||
Sparkles,
|
Sparkles,
|
||||||
TerminalSquare,
|
TerminalSquare,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
import React from "react"
|
import React from "react"
|
||||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||||
import { Button } from "../ui/button"
|
import { Button } from "../ui/button"
|
||||||
@ -61,7 +62,8 @@ export default function CodeEditor({
|
|||||||
}) {
|
}) {
|
||||||
//SocketContext functions and effects
|
//SocketContext functions and effects
|
||||||
const { socket, setUserAndSandboxId } = useSocket()
|
const { socket, setUserAndSandboxId } = useSocket()
|
||||||
|
// theme
|
||||||
|
const { theme } = useTheme()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Ensure userData.id and sandboxData.id are available before attempting to connect
|
// Ensure userData.id and sandboxData.id are available before attempting to connect
|
||||||
if (userData.id && sandboxData.id) {
|
if (userData.id && sandboxData.id) {
|
||||||
@ -105,6 +107,7 @@ export default function CodeEditor({
|
|||||||
|
|
||||||
// Editor state
|
// Editor state
|
||||||
const [editorLanguage, setEditorLanguage] = useState("plaintext")
|
const [editorLanguage, setEditorLanguage] = useState("plaintext")
|
||||||
|
console.log("editor language: ",editorLanguage)
|
||||||
const [cursorLine, setCursorLine] = useState(0)
|
const [cursorLine, setCursorLine] = useState(0)
|
||||||
const [editorRef, setEditorRef] =
|
const [editorRef, setEditorRef] =
|
||||||
useState<monaco.editor.IStandaloneCodeEditor>()
|
useState<monaco.editor.IStandaloneCodeEditor>()
|
||||||
@ -1118,7 +1121,7 @@ export default function CodeEditor({
|
|||||||
fixedOverflowWidgets: true,
|
fixedOverflowWidgets: true,
|
||||||
fontFamily: "var(--font-geist-mono)",
|
fontFamily: "var(--font-geist-mono)",
|
||||||
}}
|
}}
|
||||||
theme="vs-dark"
|
theme={theme === "light" ? "vs" : "vs-dark"}
|
||||||
value={activeFileContent}
|
value={activeFileContent}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -1148,7 +1151,7 @@ export default function CodeEditor({
|
|||||||
defaultSize={isPreviewCollapsed ? 4 : 20}
|
defaultSize={isPreviewCollapsed ? 4 : 20}
|
||||||
minSize={25}
|
minSize={25}
|
||||||
collapsedSize={isHorizontalLayout ? 20 : 4}
|
collapsedSize={isHorizontalLayout ? 20 : 4}
|
||||||
className="p-2 flex flex-col"
|
className="p-2 flex flex-col gap-2"
|
||||||
collapsible
|
collapsible
|
||||||
onCollapse={() => setIsPreviewCollapsed(true)}
|
onCollapse={() => setIsPreviewCollapsed(true)}
|
||||||
onExpand={() => setIsPreviewCollapsed(false)}
|
onExpand={() => setIsPreviewCollapsed(false)}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import Logo from "@/assets/logo.svg"
|
import Logo from "@/assets/logo.svg"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
import { ThemeSwitcher } from "@/components/ui/theme-switcher"
|
||||||
import UserButton from "@/components/ui/userButton"
|
import UserButton from "@/components/ui/userButton"
|
||||||
import { Sandbox, User } from "@/lib/types"
|
import { Sandbox, User } from "@/lib/types"
|
||||||
import { Pencil, Users } from "lucide-react"
|
import { Pencil, Users } from "lucide-react"
|
||||||
@ -79,6 +80,7 @@ export default function Navbar({
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
<ThemeSwitcher />
|
||||||
<UserButton userData={userData} />
|
<UserButton userData={userData} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,9 +6,9 @@ import "./xterm.css"
|
|||||||
|
|
||||||
import { debounce } from "@/lib/utils"
|
import { debounce } from "@/lib/utils"
|
||||||
import { Loader2 } from "lucide-react"
|
import { Loader2 } from "lucide-react"
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
import { ElementRef, useEffect, useRef } from "react"
|
import { ElementRef, useEffect, useRef } from "react"
|
||||||
import { Socket } from "socket.io-client"
|
import { Socket } from "socket.io-client"
|
||||||
|
|
||||||
export default function EditorTerminal({
|
export default function EditorTerminal({
|
||||||
socket,
|
socket,
|
||||||
id,
|
id,
|
||||||
@ -22,18 +22,17 @@ export default function EditorTerminal({
|
|||||||
setTerm: (term: Terminal) => void
|
setTerm: (term: Terminal) => void
|
||||||
visible: boolean
|
visible: boolean
|
||||||
}) {
|
}) {
|
||||||
const terminalRef = useRef<ElementRef<"div">>(null)
|
const { theme } = useTheme()
|
||||||
|
const terminalContainerRef = useRef<ElementRef<"div">>(null)
|
||||||
const fitAddonRef = useRef<FitAddon | null>(null)
|
const fitAddonRef = useRef<FitAddon | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!terminalRef.current) return
|
if (!terminalContainerRef.current) return
|
||||||
// console.log("new terminal", id, term ? "reusing" : "creating");
|
// console.log("new terminal", id, term ? "reusing" : "creating");
|
||||||
|
|
||||||
const terminal = new Terminal({
|
const terminal = new Terminal({
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
theme: {
|
theme: theme === "light" ? lightTheme : darkTheme,
|
||||||
background: "#262626",
|
|
||||||
},
|
|
||||||
fontFamily: "var(--font-geist-mono)",
|
fontFamily: "var(--font-geist-mono)",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: 1.5,
|
lineHeight: 1.5,
|
||||||
@ -47,14 +46,20 @@ export default function EditorTerminal({
|
|||||||
return dispose
|
return dispose
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (term) {
|
||||||
|
term.options.theme = theme === "light" ? lightTheme : darkTheme
|
||||||
|
}
|
||||||
|
}, [theme])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!term) return
|
if (!term) return
|
||||||
|
|
||||||
if (!terminalRef.current) return
|
if (!terminalContainerRef.current) return
|
||||||
if (!fitAddonRef.current) {
|
if (!fitAddonRef.current) {
|
||||||
const fitAddon = new FitAddon()
|
const fitAddon = new FitAddon()
|
||||||
term.loadAddon(fitAddon)
|
term.loadAddon(fitAddon)
|
||||||
term.open(terminalRef.current)
|
term.open(terminalContainerRef.current)
|
||||||
fitAddon.fit()
|
fitAddon.fit()
|
||||||
fitAddonRef.current = fitAddon
|
fitAddonRef.current = fitAddon
|
||||||
}
|
}
|
||||||
@ -69,7 +74,7 @@ export default function EditorTerminal({
|
|||||||
})
|
})
|
||||||
const resizeObserver = new ResizeObserver(
|
const resizeObserver = new ResizeObserver(
|
||||||
debounce((entries) => {
|
debounce((entries) => {
|
||||||
if (!fitAddonRef.current || !terminalRef.current) return
|
if (!fitAddonRef.current || !terminalContainerRef.current) return
|
||||||
|
|
||||||
const entry = entries[0]
|
const entry = entries[0]
|
||||||
if (!entry) return
|
if (!entry) return
|
||||||
@ -78,8 +83,8 @@ export default function EditorTerminal({
|
|||||||
|
|
||||||
// Only call fit if the size has actually changed
|
// Only call fit if the size has actually changed
|
||||||
if (
|
if (
|
||||||
width !== terminalRef.current.offsetWidth ||
|
width !== terminalContainerRef.current.offsetWidth ||
|
||||||
height !== terminalRef.current.offsetHeight
|
height !== terminalContainerRef.current.offsetHeight
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
fitAddonRef.current.fit()
|
fitAddonRef.current.fit()
|
||||||
@ -91,13 +96,13 @@ export default function EditorTerminal({
|
|||||||
)
|
)
|
||||||
|
|
||||||
// start observing for resize
|
// start observing for resize
|
||||||
resizeObserver.observe(terminalRef.current)
|
resizeObserver.observe(terminalContainerRef.current)
|
||||||
return () => {
|
return () => {
|
||||||
disposableOnData.dispose()
|
disposableOnData.dispose()
|
||||||
disposableOnResize.dispose()
|
disposableOnResize.dispose()
|
||||||
resizeObserver.disconnect()
|
resizeObserver.disconnect()
|
||||||
}
|
}
|
||||||
}, [term, terminalRef.current])
|
}, [term, terminalContainerRef.current])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!term) return
|
if (!term) return
|
||||||
@ -116,7 +121,7 @@ export default function EditorTerminal({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
ref={terminalRef}
|
ref={terminalContainerRef}
|
||||||
style={{ display: visible ? "block" : "none" }}
|
style={{ display: visible ? "block" : "none" }}
|
||||||
className="w-full h-full text-left"
|
className="w-full h-full text-left"
|
||||||
>
|
>
|
||||||
@ -130,3 +135,56 @@ export default function EditorTerminal({
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lightTheme = {
|
||||||
|
foreground: "#2e3436",
|
||||||
|
background: "#ffffff",
|
||||||
|
black: "#2e3436",
|
||||||
|
brightBlack: "#555753",
|
||||||
|
red: "#cc0000",
|
||||||
|
brightRed: "#ef2929",
|
||||||
|
green: "#4e9a06",
|
||||||
|
brightGreen: "#8ae234",
|
||||||
|
yellow: "#c4a000",
|
||||||
|
brightYellow: "#fce94f",
|
||||||
|
blue: "#3465a4",
|
||||||
|
brightBlue: "#729fcf",
|
||||||
|
magenta: "#75507b",
|
||||||
|
brightMagenta: "#ad7fa8",
|
||||||
|
cyan: "#06989a",
|
||||||
|
brightCyan: "#34e2e2",
|
||||||
|
white: "#d3d7cf",
|
||||||
|
brightWhite: "#eeeeec",
|
||||||
|
cursor: "#2e3436",
|
||||||
|
cursorAccent: "#ffffff",
|
||||||
|
selectionBackground: "#3465a4",
|
||||||
|
selectionForeground: "#ffffff",
|
||||||
|
selectionInactiveBackground: "#264973",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dark Theme
|
||||||
|
const darkTheme = {
|
||||||
|
foreground: "#f8f8f2",
|
||||||
|
background: "#0a0a0a",
|
||||||
|
black: "#21222c",
|
||||||
|
brightBlack: "#6272a4",
|
||||||
|
red: "#ff5555",
|
||||||
|
brightRed: "#ff6e6e",
|
||||||
|
green: "#50fa7b",
|
||||||
|
brightGreen: "#69ff94",
|
||||||
|
yellow: "#f1fa8c",
|
||||||
|
brightYellow: "#ffffa5",
|
||||||
|
blue: "#bd93f9",
|
||||||
|
brightBlue: "#d6acff",
|
||||||
|
magenta: "#ff79c6",
|
||||||
|
brightMagenta: "#ff92df",
|
||||||
|
cyan: "#8be9fd",
|
||||||
|
brightCyan: "#a4ffff",
|
||||||
|
white: "#f8f8f2",
|
||||||
|
brightWhite: "#ffffff",
|
||||||
|
cursor: "#f8f8f2",
|
||||||
|
cursorAccent: "#0a0a0a",
|
||||||
|
selectionBackground: "#264973",
|
||||||
|
selectionForeground: "#ffffff",
|
||||||
|
selectionInactiveBackground: "#1a3151",
|
||||||
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import Logo from "@/assets/logo.svg"
|
import Logo from "@/assets/logo.svg"
|
||||||
import XLogo from "@/assets/x.svg"
|
import CustomButton from "@/components/ui/customButton"
|
||||||
import Button from "@/components/ui/customButton"
|
|
||||||
import { ChevronRight } from "lucide-react"
|
import { ChevronRight } from "lucide-react"
|
||||||
import Image from "next/image"
|
import Image from "next/image"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
import { Button } from "../ui/button"
|
||||||
|
import { ThemeSwitcher } from "../ui/theme-switcher"
|
||||||
|
|
||||||
export default function Landing() {
|
export default function Landing() {
|
||||||
return (
|
return (
|
||||||
@ -20,21 +21,37 @@ export default function Landing() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
|
<Button variant="outline" size="icon" asChild>
|
||||||
<a href="https://www.x.com/ishaandey_" target="_blank">
|
<a href="https://www.x.com/ishaandey_" target="_blank">
|
||||||
<Image src={XLogo} alt="X Logo" width={18} height={18} />
|
<svg
|
||||||
|
width="1200"
|
||||||
|
height="1227"
|
||||||
|
viewBox="0 0 1200 1227"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="size-[1.125rem] text-muted-foreground"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-medium text-center mt-16">
|
<h1 className="text-2xl font-medium text-center mt-16">
|
||||||
A Collaborative + AI-Powered Code Environment
|
A Collaborative + AI-Powered Code Environment
|
||||||
</h1>
|
</h1>
|
||||||
<div className="text-muted-foreground mt-4 text-center ">
|
<p className="text-muted-foreground mt-4 text-center ">
|
||||||
Sandbox is an open-source cloud-based code editing environment with
|
Sandbox is an open-source cloud-based code editing environment with
|
||||||
custom AI code autocompletion and real-time collaboration.
|
custom AI code autocompletion and real-time collaboration.
|
||||||
</div>
|
</p>
|
||||||
<div className="mt-8 flex space-x-4">
|
<div className="mt-8 flex space-x-4">
|
||||||
<Link href="/sign-up">
|
<Link href="/sign-up">
|
||||||
<Button>Go To App</Button>
|
<CustomButton>Go To App</CustomButton>
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/ishaan1013/sandbox"
|
href="https://github.com/ishaan1013/sandbox"
|
||||||
|
@ -24,7 +24,7 @@ const Button = ({
|
|||||||
`gradient-button-bg p-[1px] inline-flex group rounded-md text-sm font-medium focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50`
|
`gradient-button-bg p-[1px] inline-flex group rounded-md text-sm font-medium focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="rounded-[6px] w-full gradient-button flex items-center justify-center whitespace-nowrap px-4 py-2 h-9">
|
<div className="rounded-[6px] w-full gradient-button transition-colors flex items-center justify-center whitespace-nowrap px-4 py-2 h-9">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
@ -28,7 +28,7 @@ export default function Tab({
|
|||||||
variant={"secondary"}
|
variant={"secondary"}
|
||||||
className={`font-normal select-none ${
|
className={`font-normal select-none ${
|
||||||
selected
|
selected
|
||||||
? "bg-neutral-700 hover:bg-neutral-600 text-foreground"
|
? "bg-muted-foreground/50 hover:bg-muted-foreground/40 text-foreground"
|
||||||
: "text-muted-foreground"
|
: "text-muted-foreground"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
|
@ -6,3 +6,4 @@ import { type ThemeProviderProps } from "next-themes/dist/types"
|
|||||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||||
}
|
}
|
||||||
|
|
39
frontend/components/ui/theme-switcher.tsx
Normal file
39
frontend/components/ui/theme-switcher.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Moon, Sun } from "lucide-react"
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
|
||||||
|
export function ThemeSwitcher() {
|
||||||
|
const { setTheme } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" size="icon" className="text-muted-foreground">
|
||||||
|
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||||
|
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||||
|
<span className="sr-only">Toggle theme</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||||
|
Light
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||||
|
Dark
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||||
|
System
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
@ -38,6 +38,7 @@
|
|||||||
"csl": "xml",
|
"csl": "xml",
|
||||||
"cson": "coffeescript",
|
"cson": "coffeescript",
|
||||||
"csproj": "xml",
|
"csproj": "xml",
|
||||||
|
"css":"css",
|
||||||
"ct": "xml",
|
"ct": "xml",
|
||||||
"ctp": "php",
|
"ctp": "php",
|
||||||
"cxx": "cpp",
|
"cxx": "cpp",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user