feat(settings): add settings modal with sidebar trigger
This commit is contained in:
parent
6ac1abfa86
commit
a730220aef
@ -1,6 +1,7 @@
|
|||||||
import { AppSidebar } from "@/components/app-sidebar"
|
import { AppSidebar } from "@/components/app-sidebar"
|
||||||
import { SiteHeader } from "@/components/site-header"
|
import { SiteHeader } from "@/components/site-header"
|
||||||
import { CommandMenuProvider } from "@/components/command-menu-provider"
|
import { CommandMenuProvider } from "@/components/command-menu-provider"
|
||||||
|
import { SettingsProvider } from "@/components/settings-provider"
|
||||||
import {
|
import {
|
||||||
SidebarInset,
|
SidebarInset,
|
||||||
SidebarProvider,
|
SidebarProvider,
|
||||||
@ -15,6 +16,7 @@ export default async function DashboardLayout({
|
|||||||
const projectList = await getProjects()
|
const projectList = await getProjects()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<SettingsProvider>
|
||||||
<CommandMenuProvider>
|
<CommandMenuProvider>
|
||||||
<SidebarProvider
|
<SidebarProvider
|
||||||
defaultOpen={false}
|
defaultOpen={false}
|
||||||
@ -36,5 +38,6 @@ export default async function DashboardLayout({
|
|||||||
</SidebarInset>
|
</SidebarInset>
|
||||||
</SidebarProvider>
|
</SidebarProvider>
|
||||||
</CommandMenuProvider>
|
</CommandMenuProvider>
|
||||||
|
</SettingsProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import { NavFiles } from "@/components/nav-files"
|
|||||||
import { NavProjects } from "@/components/nav-projects"
|
import { NavProjects } from "@/components/nav-projects"
|
||||||
import { NavUser } from "@/components/nav-user"
|
import { NavUser } from "@/components/nav-user"
|
||||||
import { useCommandMenu } from "@/components/command-menu-provider"
|
import { useCommandMenu } from "@/components/command-menu-provider"
|
||||||
|
import { useSettings } from "@/components/settings-provider"
|
||||||
import {
|
import {
|
||||||
Sidebar,
|
Sidebar,
|
||||||
SidebarContent,
|
SidebarContent,
|
||||||
@ -80,6 +81,7 @@ function SidebarNav({
|
|||||||
const pathname = usePathname()
|
const pathname = usePathname()
|
||||||
const { state } = useSidebar()
|
const { state } = useSidebar()
|
||||||
const { open: openSearch } = useCommandMenu()
|
const { open: openSearch } = useCommandMenu()
|
||||||
|
const { open: openSettings } = useSettings()
|
||||||
const isExpanded = state === "expanded"
|
const isExpanded = state === "expanded"
|
||||||
const isFilesMode = pathname?.startsWith("/dashboard/files")
|
const isFilesMode = pathname?.startsWith("/dashboard/files")
|
||||||
const isProjectMode = /^\/dashboard\/projects\/[^/]+/.test(
|
const isProjectMode = /^\/dashboard\/projects\/[^/]+/.test(
|
||||||
@ -94,7 +96,11 @@ function SidebarNav({
|
|||||||
: "main"
|
: "main"
|
||||||
|
|
||||||
const secondaryItems = [
|
const secondaryItems = [
|
||||||
...data.navSecondary,
|
...data.navSecondary.map((item) =>
|
||||||
|
item.title === "Settings"
|
||||||
|
? { ...item, onClick: openSettings }
|
||||||
|
: item
|
||||||
|
),
|
||||||
{ title: "Search", icon: IconSearch, onClick: openSearch },
|
{ title: "Search", icon: IconSearch, onClick: openSearch },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
166
src/components/settings-modal.tsx
Executable file
166
src/components/settings-modal.tsx
Executable file
@ -0,0 +1,166 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
} from "@/components/ui/dialog"
|
||||||
|
import { Label } from "@/components/ui/label"
|
||||||
|
import { Switch } from "@/components/ui/switch"
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select"
|
||||||
|
import {
|
||||||
|
Tabs,
|
||||||
|
TabsContent,
|
||||||
|
TabsList,
|
||||||
|
TabsTrigger,
|
||||||
|
} from "@/components/ui/tabs"
|
||||||
|
import { Separator } from "@/components/ui/separator"
|
||||||
|
|
||||||
|
export function SettingsModal({
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: {
|
||||||
|
open: boolean
|
||||||
|
onOpenChange: (open: boolean) => void
|
||||||
|
}) {
|
||||||
|
const { theme, setTheme } = useTheme()
|
||||||
|
const [emailNotifs, setEmailNotifs] = React.useState(true)
|
||||||
|
const [pushNotifs, setPushNotifs] = React.useState(true)
|
||||||
|
const [weeklyDigest, setWeeklyDigest] = React.useState(false)
|
||||||
|
const [timezone, setTimezone] = React.useState("America/New_York")
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogContent className="sm:max-w-lg">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Settings</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Manage your app preferences.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
|
||||||
|
<Tabs defaultValue="general" className="mt-2">
|
||||||
|
<TabsList>
|
||||||
|
<TabsTrigger value="general">General</TabsTrigger>
|
||||||
|
<TabsTrigger value="notifications">
|
||||||
|
Notifications
|
||||||
|
</TabsTrigger>
|
||||||
|
<TabsTrigger value="appearance">Appearance</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value="general" className="space-y-4 pt-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="timezone">Timezone</Label>
|
||||||
|
<Select value={timezone} onValueChange={setTimezone}>
|
||||||
|
<SelectTrigger id="timezone" className="w-full">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="America/New_York">
|
||||||
|
Eastern (ET)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="America/Chicago">
|
||||||
|
Central (CT)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="America/Denver">
|
||||||
|
Mountain (MT)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="America/Los_Angeles">
|
||||||
|
Pacific (PT)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Europe/London">
|
||||||
|
London (GMT)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="Europe/Berlin">
|
||||||
|
Berlin (CET)
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<Label>Weekly digest</Label>
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
Receive a summary of activity each week.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={weeklyDigest}
|
||||||
|
onCheckedChange={setWeeklyDigest}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent
|
||||||
|
value="notifications"
|
||||||
|
className="space-y-4 pt-4"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<Label>Email notifications</Label>
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
Get notified about project updates via email.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={emailNotifs}
|
||||||
|
onCheckedChange={setEmailNotifs}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<Label>Push notifications</Label>
|
||||||
|
<p className="text-muted-foreground text-sm">
|
||||||
|
Receive push notifications in your browser.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
checked={pushNotifs}
|
||||||
|
onCheckedChange={setPushNotifs}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent
|
||||||
|
value="appearance"
|
||||||
|
className="space-y-4 pt-4"
|
||||||
|
>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="theme">Theme</Label>
|
||||||
|
<Select
|
||||||
|
value={theme ?? "light"}
|
||||||
|
onValueChange={setTheme}
|
||||||
|
>
|
||||||
|
<SelectTrigger id="theme" className="w-full">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="light">Light</SelectItem>
|
||||||
|
<SelectItem value="dark">Dark</SelectItem>
|
||||||
|
<SelectItem value="system">System</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
32
src/components/settings-provider.tsx
Executable file
32
src/components/settings-provider.tsx
Executable file
@ -0,0 +1,32 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { SettingsModal } from "@/components/settings-modal"
|
||||||
|
|
||||||
|
const SettingsContext = React.createContext<{
|
||||||
|
open: () => void
|
||||||
|
}>({ open: () => {} })
|
||||||
|
|
||||||
|
export function useSettings() {
|
||||||
|
return React.useContext(SettingsContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsProvider({
|
||||||
|
children,
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode
|
||||||
|
}) {
|
||||||
|
const [isOpen, setIsOpen] = React.useState(false)
|
||||||
|
|
||||||
|
const value = React.useMemo(
|
||||||
|
() => ({ open: () => setIsOpen(true) }),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
<SettingsModal open={isOpen} onOpenChange={setIsOpen} />
|
||||||
|
</SettingsContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user