From bc1910fdcacfb43f0bab84fc71b261cd2af115d4 Mon Sep 17 00:00:00 2001
From: SomeCodecat <88855796+SomeCodecat@users.noreply.github.com>
Date: Mon, 23 Jun 2025 11:20:09 +0200
Subject: [PATCH] feat: Implement settings dropdown and page components
- Added `SettingsDropdown` component for selecting settings sections with icons and descriptions.
- Created `SettingsPage` component to manage user settings, including account details, notifications, calendar availability, privacy, and appearance.
- Introduced `SettingsSwitcher` for selecting options within settings.
- Integrated command and dialog components for improved user interaction.
- Updated `UserDropdown` to include links for settings and logout.
- Refactored button styles and card footer layout for consistency.
- Added popover functionality for dropdown menus.
- Updated dependencies in `yarn.lock` for new components.
---
package.json | 2 +
src/app/settings/page.tsx | 483 +-------------------
src/components/misc/settings-dropdown.tsx | 158 +++++++
src/components/misc/settings-page.tsx | 467 +++++++++++++++++++
src/components/misc/settings-switcher.tsx | 123 +++++
src/components/misc/user-card.tsx | 2 +-
src/components/misc/user-dropdown.tsx | 12 +-
src/components/ui/button.tsx | 2 +-
src/components/ui/card.tsx | 2 +-
src/components/ui/command.tsx | 184 ++++++++
src/components/ui/dialog.tsx | 143 ++++++
src/components/ui/popover.tsx | 48 ++
src/components/wrappers/settings-scroll.tsx | 20 +-
yarn.lock | 58 ++-
14 files changed, 1202 insertions(+), 502 deletions(-)
create mode 100644 src/components/misc/settings-dropdown.tsx
create mode 100644 src/components/misc/settings-page.tsx
create mode 100644 src/components/misc/settings-switcher.tsx
create mode 100644 src/components/ui/command.tsx
create mode 100644 src/components/ui/dialog.tsx
create mode 100644 src/components/ui/popover.tsx
diff --git a/package.json b/package.json
index a9aa812..e3e8883 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-hover-card": "^1.1.13",
"@radix-ui/react-label": "^2.1.6",
+ "@radix-ui/react-popover": "^1.1.14",
"@radix-ui/react-scroll-area": "^1.2.8",
"@radix-ui/react-select": "^2.2.4",
"@radix-ui/react-separator": "^1.1.7",
@@ -44,6 +45,7 @@
"bcryptjs": "^3.0.2",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
+ "cmdk": "^1.1.1",
"lucide-react": "^0.515.0",
"next": "15.4.0-canary.92",
"next-auth": "^5.0.0-beta.25",
diff --git a/src/app/settings/page.tsx b/src/app/settings/page.tsx
index 563ebab..e0ad2a5 100644
--- a/src/app/settings/page.tsx
+++ b/src/app/settings/page.tsx
@@ -1,482 +1,5 @@
-import { Button } from '@/components/ui/button';
-import {
- Card,
- CardContent,
- CardDescription,
- CardFooter,
- CardHeader,
- CardTitle,
-} from '@/components/ui/card';
-import { Input } from '@/components/ui/input';
-import { Label } from '@/components/ui/label';
-import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
-import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
-import { Switch } from '@/components/ui/switch';
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from '@/components/ui/select';
+import SettingsPage from '@/components/misc/settings-page';
-export default function SettingsPage() {
- return (
-
-
-
-
- Account
- Notifications
- Calendar
- Privacy
- Appearance
-
-
-
-
-
-
- Account Settings
-
- Manage your account details and preferences.
-
-
-
-
- Display Name
-
-
-
-
Email Address
-
-
- Email is managed by your SSO provider.
-
-
-
-
Profile Picture
-
-
- Upload a new profile picture.
-
-
-
- Timezone
-
-
-
-
- Language
-
-
-
-
-
- English
- German
-
-
-
-
-
Delete Account
-
- Permanently delete your account and all associated data.
-
-
-
-
-
- Exit
- Save Changes
-
-
-
-
-
-
-
-
- Notification Preferences
-
- Choose how you want to be notified.
-
-
-
-
-
- Enable All Email Notifications
-
-
-
-
-
-
- New Meeting Bookings
-
-
-
-
-
- Meeting Confirmations/Cancellations
-
-
-
-
-
- Meeting Reminders
-
-
-
-
- Remind me before
-
-
-
-
-
- 15 minutes
- 30 minutes
- 1 hour
- 1 day
-
-
-
-
-
- Friend Requests
-
-
-
-
-
- Group Invitations/Updates
-
-
-
-
-
-
-
- Exit
- Save Changes
-
-
-
-
-
-
-
-
- Calendar & Availability
-
- Manage your calendar display, default availability, and iCal
- integrations.
-
-
-
-
-
- Display
-
-
-
- Default Calendar View
-
-
-
-
-
-
- Day
- Week
- Month
-
-
-
-
- Week Starts On
-
-
-
-
-
- Sunday
- Monday
-
-
-
-
-
- Show Weekends
-
-
-
-
-
-
-
- Availability
-
-
-
Working Hours
-
- Define your typical available hours (e.g.,
- Monday-Friday, 9 AM - 5 PM).
-
-
- Set Working Hours
-
-
-
-
- Minimum Notice for Bookings
-
-
- Min time before a booking can be made.
-
-
-
-
-
-
-
- Booking Window (days in advance)
-
-
- Max time in advance a booking can be made.
-
-
-
-
-
-
-
- iCalendar Integration
-
-
- Import iCal Feed URL
-
-
- Add Feed
-
-
-
- Export Your Calendar
-
- Get iCal Export URL
-
-
- Download .ics File
-
-
-
-
-
-
- Exit
- Save Changes
-
-
-
-
-
-
-
-
- Sharing & Privacy
-
- Control who can see your calendar and book time with you.
-
-
-
-
-
- Default Calendar Visibility
-
-
-
-
-
-
-
- Private (Only You)
-
-
- Free/Busy for Friends
-
-
- Full Details for Friends
-
-
-
-
-
-
- Who Can See Your Full Calendar Details?
-
-
- (Override for Default Visibility)
-
-
- This setting will override the default visibility for
- your calendar. You can set specific friends or groups to
- see your full calendar details.
-
-
-
-
-
-
-
- Only Me
- My Friends
-
- Specific Friends/Groups (manage separately)
-
-
-
-
-
-
- Who Can Book Time With You?
-
-
-
-
-
-
- No One
- My Friends
-
- Specific Friends/Groups (manage separately)
-
-
-
-
-
-
Blocked Users
-
- Manage Blocked Users
-
-
- Prevent specific users from seeing your calendar or
- booking time.
-
-
-
-
-
- Exit
- Save Changes
-
-
-
-
-
-
-
-
- Appearance
-
- Customize the look and feel of the application.
-
-
-
-
- Theme
-
-
-
-
-
- Light
- Dark
- System Default
-
-
-
-
- Date Format
-
-
-
-
-
- DD/MM/YYYY
- MM/DD/YYYY
- YYYY-MM-DD
-
-
-
-
- Time Format
-
-
-
-
-
- 24-hour
- 12-hour
-
-
-
-
-
-
- Exit
- Save Changes
-
-
-
-
-
-
- );
+export default function Page() {
+ return ;
}
diff --git a/src/components/misc/settings-dropdown.tsx b/src/components/misc/settings-dropdown.tsx
new file mode 100644
index 0000000..63a7b20
--- /dev/null
+++ b/src/components/misc/settings-dropdown.tsx
@@ -0,0 +1,158 @@
+'use client';
+
+import type React from 'react';
+
+import { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from '@/components/ui/command';
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover';
+import { cn } from '@/lib/utils';
+import {
+ Check,
+ ChevronDown,
+ User,
+ Bell,
+ Calendar,
+ Shield,
+ Palette,
+} from 'lucide-react';
+
+interface SettingsSection {
+ label: string;
+ value: string;
+ description: string;
+ icon: React.ComponentType<{ className?: string }>;
+}
+
+interface SettingsDropdownProps {
+ currentSection: string;
+ onSectionChange: (section: string) => void;
+ className?: string;
+}
+
+const settingsSections: SettingsSection[] = [
+ {
+ label: 'Account',
+ value: 'general',
+ description: 'Manage your account details and preferences',
+ icon: User,
+ },
+ {
+ label: 'Notifications',
+ value: 'notifications',
+ description: 'Choose how you want to be notified',
+ icon: Bell,
+ },
+ {
+ label: 'Calendar',
+ value: 'calendarAvailability',
+ description: 'Manage calendar display and availability',
+ icon: Calendar,
+ },
+ {
+ label: 'Privacy',
+ value: 'sharingPrivacy',
+ description: 'Control who can see your calendar',
+ icon: Shield,
+ },
+ {
+ label: 'Appearance',
+ value: 'appearance',
+ description: 'Customize the look and feel',
+ icon: Palette,
+ },
+];
+
+export function SettingsDropdown({
+ currentSection,
+ onSectionChange,
+ className,
+}: SettingsDropdownProps) {
+ const [open, setOpen] = useState(false);
+
+ const currentSectionData = settingsSections.find(
+ (section) => section.value === currentSection,
+ );
+ const CurrentIcon = currentSectionData?.icon || User;
+
+ const handleSelect = (value: string) => {
+ onSectionChange(value);
+ setOpen(false);
+ };
+
+ return (
+
+
+
+
+
+
+
+ {currentSectionData?.label}
+
+ {currentSectionData?.description}
+
+
+
+
+
+
+
+
+
+
+ No settings found.
+
+ {settingsSections.map((section) => {
+ const Icon = section.icon;
+ return (
+ handleSelect(section.value)}
+ className='flex items-center justify-between p-3'
+ >
+
+
+
+ {section.label}
+
+ {section.description}
+
+
+
+
+
+ );
+ })}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/misc/settings-page.tsx b/src/components/misc/settings-page.tsx
new file mode 100644
index 0000000..fb90614
--- /dev/null
+++ b/src/components/misc/settings-page.tsx
@@ -0,0 +1,467 @@
+'use client';
+
+import { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import {
+ Card,
+ CardContent,
+ CardDescription,
+ CardFooter,
+ CardHeader,
+ CardTitle,
+} from '@/components/ui/card';
+import { Input } from '@/components/ui/input';
+import { Label } from '@/components/ui/label';
+import { ScrollableSettingsWrapper } from '@/components/wrappers/settings-scroll';
+import { Switch } from '@/components/ui/switch';
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '@/components/ui/select';
+import { SettingsDropdown } from '@/components/misc/settings-dropdown';
+import { useRouter } from 'next/navigation';
+
+export default function SettingsPage() {
+ const router = useRouter();
+ const [currentSection, setCurrentSection] = useState('general');
+
+ const renderSettingsContent = () => {
+ switch (currentSection) {
+ case 'general':
+ return (
+
+
+
+ Account Settings
+
+ Manage your account details and preferences.
+
+
+
+
+ Display Name
+
+
+
+
Email Address
+
+
+ Email is managed by your SSO provider.
+
+
+
+
Profile Picture
+
+
+ Upload a new profile picture.
+
+
+
+ Timezone
+
+
+
+ Language
+
+
+
+
+
+ English
+ German
+
+
+
+
+
Delete Account
+
+ Permanently delete your account and all associated data.
+
+
+
+
+
+ );
+
+ case 'notifications':
+ return (
+
+
+
+ Notification Preferences
+
+ Choose how you want to be notified.
+
+
+
+
+
+ Enable All Email Notifications
+
+
+
+
+
+
+ New Meeting Bookings
+
+
+
+
+
+ Meeting Confirmations/Cancellations
+
+
+
+
+
+ Meeting Reminders
+
+
+
+
+ Remind me before
+
+
+
+
+
+ 15 minutes
+ 30 minutes
+ 1 hour
+ 1 day
+
+
+
+
+
+ Friend Requests
+
+
+
+
+
+ Group Invitations/Updates
+
+
+
+
+
+
+
+ );
+
+ case 'calendarAvailability':
+ return (
+
+
+
+ Calendar & Availability
+
+ Manage your calendar display, default availability, and iCal
+ integrations.
+
+
+
+
+ Display
+
+
+ Default Calendar View
+
+
+
+
+
+
+ Day
+ Week
+ Month
+
+
+
+
+ Week Starts On
+
+
+
+
+
+ Sunday
+ Monday
+
+
+
+
+
+ Show Weekends
+
+
+
+
+
+
+
+ Availability
+
+
+
Working Hours
+
+ Define your typical available hours (e.g., Monday-Friday,
+ 9 AM - 5 PM).
+
+
+ Set Working Hours
+
+
+
+
+ Minimum Notice for Bookings
+
+
+ Min time before a booking can be made.
+
+
+
+
+
+
+
+ Booking Window (days in advance)
+
+
+ Max time in advance a booking can be made.
+
+
+
+
+
+
+
+ iCalendar Integration
+
+
+ Import iCal Feed URL
+
+
+ Add Feed
+
+
+
+ Export Your Calendar
+
+ Get iCal Export URL
+
+
+ Download .ics File
+
+
+
+
+
+
+ );
+
+ case 'sharingPrivacy':
+ return (
+
+
+
+ Sharing & Privacy
+
+ Control who can see your calendar and book time with you.
+
+
+
+
+
+ Default Calendar Visibility
+
+
+
+
+
+
+
+ Private (Only You)
+
+
+ Free/Busy for Friends
+
+
+ Full Details for Friends
+
+
+
+
+
+
+ Who Can See Your Full Calendar Details?
+
+
+ (Override for Default Visibility)
+
+
+ This setting will override the default visibility for your
+ calendar. You can set specific friends or groups to see
+ your full calendar details.
+
+
+
+
+
+
+
+ Only Me
+ My Friends
+
+ Specific Friends/Groups (manage separately)
+
+
+
+
+
+
+ Who Can Book Time With You?
+
+
+
+
+
+
+ No One
+ My Friends
+
+ Specific Friends/Groups (manage separately)
+
+
+
+
+
+
Blocked Users
+
Manage Blocked Users
+
+ Prevent specific users from seeing your calendar or booking
+ time.
+
+
+
+
+
+ );
+
+ case 'appearance':
+ return (
+
+
+
+ Appearance
+
+ Customize the look and feel of the application.
+
+
+
+
+ Theme
+
+
+
+
+
+ Light
+ Dark
+ System Default
+
+
+
+
+ Date Format
+
+
+
+
+
+ DD/MM/YYYY
+ MM/DD/YYYY
+ YYYY-MM-DD
+
+
+
+
+ Time Format
+
+
+
+
+
+ 24-hour
+ 12-hour
+
+
+
+
+
+
+ );
+
+ default:
+ return null;
+ }
+ };
+
+ return (
+
+
+
+
+
{renderSettingsContent()}
+
+
+ router.back()} variant='secondary'>
+ Exit
+
+ Save Changes
+
+
+
+
+ );
+}
diff --git a/src/components/misc/settings-switcher.tsx b/src/components/misc/settings-switcher.tsx
new file mode 100644
index 0000000..40e6a05
--- /dev/null
+++ b/src/components/misc/settings-switcher.tsx
@@ -0,0 +1,123 @@
+'use client';
+
+import { useState } from 'react';
+import { Button } from '@/components/ui/button';
+import {
+ Command,
+ CommandEmpty,
+ CommandGroup,
+ CommandInput,
+ CommandItem,
+ CommandList,
+} from '@/components/ui/command';
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from '@/components/ui/popover';
+import { cn } from '@/lib/utils';
+import { Check, ChevronDown } from 'lucide-react';
+
+interface SettingsOption {
+ label: string;
+ value: string;
+ description?: string;
+}
+
+interface SettingsSwitcherProps {
+ title: string;
+ options: SettingsOption[];
+ defaultValue?: string;
+ onValueChange?: (value: string) => void;
+ placeholder?: string;
+ searchPlaceholder?: string;
+ className?: string;
+}
+
+export function SettingsSwitcher({
+ title,
+ options,
+ defaultValue,
+ onValueChange,
+ placeholder = 'Select option...',
+ searchPlaceholder = 'Search options...',
+ className,
+}: SettingsSwitcherProps) {
+ const [open, setOpen] = useState(false);
+ const [selectedValue, setSelectedValue] = useState(
+ defaultValue || options[0]?.value || '',
+ );
+
+ const selectedOption = options.find(
+ (option) => option.value === selectedValue,
+ );
+
+ const handleSelect = (value: string) => {
+ setSelectedValue(value);
+ setOpen(false);
+ onValueChange?.(value);
+ };
+
+ return (
+
+
+ {title}
+
+
+
+
+
+ {selectedOption?.label || placeholder}
+ {selectedOption?.description && (
+
+ {selectedOption.description}
+
+ )}
+
+
+
+
+
+
+
+
+ No option found.
+
+ {options.map((option) => (
+ handleSelect(option.value)}
+ className='flex items-center justify-between'
+ >
+
+ {option.label}
+ {option.description && (
+
+ {option.description}
+
+ )}
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/misc/user-card.tsx b/src/components/misc/user-card.tsx
index faefc35..457d0fc 100644
--- a/src/components/misc/user-card.tsx
+++ b/src/components/misc/user-card.tsx
@@ -21,7 +21,7 @@ export default function UserCard() {
)}
{data?.data.user.name}
-
+
{data?.data.user.email}
diff --git a/src/components/misc/user-dropdown.tsx b/src/components/misc/user-dropdown.tsx
index 8f5aa05..c0cc68a 100644
--- a/src/components/misc/user-dropdown.tsx
+++ b/src/components/misc/user-dropdown.tsx
@@ -5,7 +5,7 @@ import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
- DropdownMenuGroup,
+ // DropdownMenuGroup,
DropdownMenuItem,
// DropdownMenuLabel,
// DropdownMenuPortal,
@@ -48,11 +48,13 @@ export default function UserDropdown() {
- Settings
+
+ Settings
+
-
- Logout
-
+
+ Logout
+
);
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index 2a25f66..0b8ce1a 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -5,7 +5,7 @@ import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
- "radius-lg inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-button transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ "radius-lg inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-button transition-all cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-nonef",
{
variants: {
variant: {
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx
index c0894f8..72c0a9c 100644
--- a/src/components/ui/card.tsx
+++ b/src/components/ui/card.tsx
@@ -126,7 +126,7 @@ function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
);
diff --git a/src/components/ui/command.tsx b/src/components/ui/command.tsx
new file mode 100644
index 0000000..8cb4ca7
--- /dev/null
+++ b/src/components/ui/command.tsx
@@ -0,0 +1,184 @@
+"use client"
+
+import * as React from "react"
+import { Command as CommandPrimitive } from "cmdk"
+import { SearchIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog"
+
+function Command({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandDialog({
+ title = "Command Palette",
+ description = "Search for a command to run...",
+ children,
+ className,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ title?: string
+ description?: string
+ className?: string
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+ {title}
+ {description}
+
+
+
+ {children}
+
+
+
+ )
+}
+
+function CommandInput({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ )
+}
+
+function CommandList({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandEmpty({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandGroup({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandItem({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function CommandShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+export {
+ Command,
+ CommandDialog,
+ CommandInput,
+ CommandList,
+ CommandEmpty,
+ CommandGroup,
+ CommandItem,
+ CommandShortcut,
+ CommandSeparator,
+}
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..d9ccec9
--- /dev/null
+++ b/src/components/ui/dialog.tsx
@@ -0,0 +1,143 @@
+"use client"
+
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { XIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Dialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+}
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
new file mode 100644
index 0000000..01e468b
--- /dev/null
+++ b/src/components/ui/popover.tsx
@@ -0,0 +1,48 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+function Popover({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function PopoverTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function PopoverContent({
+ className,
+ align = "center",
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function PopoverAnchor({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
diff --git a/src/components/wrappers/settings-scroll.tsx b/src/components/wrappers/settings-scroll.tsx
index e0f7251..a647493 100644
--- a/src/components/wrappers/settings-scroll.tsx
+++ b/src/components/wrappers/settings-scroll.tsx
@@ -1,16 +1,16 @@
-import React from 'react';
+import { cn } from '@/lib/utils';
+import type * as React from 'react';
-interface ScrollableContentWrapperProps {
- children: React.ReactNode;
+interface ScrollableSettingsWrapperProps {
className?: string;
+ children: React.ReactNode;
}
-export const ScrollableSettingsWrapper: React.FC<
- ScrollableContentWrapperProps
-> = ({ children, className = '' }) => {
+export function ScrollableSettingsWrapper({
+ className,
+ children,
+}: ScrollableSettingsWrapperProps) {
return (
-
- {children}
-
+ {children}
);
-};
+}
diff --git a/yarn.lock b/yarn.lock
index 90dc258..7d3866e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1370,7 +1370,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-compose-refs@npm:1.1.2":
+"@radix-ui/react-compose-refs@npm:1.1.2, @radix-ui/react-compose-refs@npm:^1.1.1":
version: 1.1.2
resolution: "@radix-ui/react-compose-refs@npm:1.1.2"
peerDependencies:
@@ -1396,7 +1396,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-dialog@npm:^1.1.14":
+"@radix-ui/react-dialog@npm:^1.1.14, @radix-ui/react-dialog@npm:^1.1.6":
version: 1.1.14
resolution: "@radix-ui/react-dialog@npm:1.1.14"
dependencies:
@@ -1550,7 +1550,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-id@npm:1.1.1":
+"@radix-ui/react-id@npm:1.1.1, @radix-ui/react-id@npm:^1.1.0":
version: 1.1.1
resolution: "@radix-ui/react-id@npm:1.1.1"
dependencies:
@@ -1620,6 +1620,39 @@ __metadata:
languageName: node
linkType: hard
+"@radix-ui/react-popover@npm:^1.1.14":
+ version: 1.1.14
+ resolution: "@radix-ui/react-popover@npm:1.1.14"
+ dependencies:
+ "@radix-ui/primitive": "npm:1.1.2"
+ "@radix-ui/react-compose-refs": "npm:1.1.2"
+ "@radix-ui/react-context": "npm:1.1.2"
+ "@radix-ui/react-dismissable-layer": "npm:1.1.10"
+ "@radix-ui/react-focus-guards": "npm:1.1.2"
+ "@radix-ui/react-focus-scope": "npm:1.1.7"
+ "@radix-ui/react-id": "npm:1.1.1"
+ "@radix-ui/react-popper": "npm:1.2.7"
+ "@radix-ui/react-portal": "npm:1.1.9"
+ "@radix-ui/react-presence": "npm:1.1.4"
+ "@radix-ui/react-primitive": "npm:2.1.3"
+ "@radix-ui/react-slot": "npm:1.2.3"
+ "@radix-ui/react-use-controllable-state": "npm:1.2.2"
+ aria-hidden: "npm:^1.2.4"
+ react-remove-scroll: "npm:^2.6.3"
+ peerDependencies:
+ "@types/react": "*"
+ "@types/react-dom": "*"
+ react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ "@types/react":
+ optional: true
+ "@types/react-dom":
+ optional: true
+ checksum: 10c0/04e557bfcaab4887694d119555b101e16b8a4e99595541ff2cbe805c551be853cb02882a2ada04e6507ffc45bc092bc2b89704b7b79f5025251767d0b4f3230a
+ languageName: node
+ linkType: hard
+
"@radix-ui/react-popper@npm:1.2.7":
version: 1.2.7
resolution: "@radix-ui/react-popper@npm:1.2.7"
@@ -1688,7 +1721,7 @@ __metadata:
languageName: node
linkType: hard
-"@radix-ui/react-primitive@npm:2.1.3":
+"@radix-ui/react-primitive@npm:2.1.3, @radix-ui/react-primitive@npm:^2.0.2":
version: 2.1.3
resolution: "@radix-ui/react-primitive@npm:2.1.3"
dependencies:
@@ -4184,6 +4217,21 @@ __metadata:
languageName: node
linkType: hard
+"cmdk@npm:^1.1.1":
+ version: 1.1.1
+ resolution: "cmdk@npm:1.1.1"
+ dependencies:
+ "@radix-ui/react-compose-refs": "npm:^1.1.1"
+ "@radix-ui/react-dialog": "npm:^1.1.6"
+ "@radix-ui/react-id": "npm:^1.1.0"
+ "@radix-ui/react-primitive": "npm:^2.0.2"
+ peerDependencies:
+ react: ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^18 || ^19 || ^19.0.0-rc
+ checksum: 10c0/5605ac4396ec9bc65c82f954da19dd89a0636a54026df72780e2470da1381f9d57434a80a53f2d57eaa4e759660a3ebba9232b74258dc09970576591eae03116
+ languageName: node
+ linkType: hard
+
"color-convert@npm:^2.0.1":
version: 2.0.1
resolution: "color-convert@npm:2.0.1"
@@ -6681,6 +6729,7 @@ __metadata:
"@radix-ui/react-dropdown-menu": "npm:^2.1.15"
"@radix-ui/react-hover-card": "npm:^1.1.13"
"@radix-ui/react-label": "npm:^2.1.6"
+ "@radix-ui/react-popover": "npm:^1.1.14"
"@radix-ui/react-scroll-area": "npm:^1.2.8"
"@radix-ui/react-select": "npm:^2.2.4"
"@radix-ui/react-separator": "npm:^1.1.7"
@@ -6698,6 +6747,7 @@ __metadata:
bcryptjs: "npm:^3.0.2"
class-variance-authority: "npm:^0.7.1"
clsx: "npm:^2.1.1"
+ cmdk: "npm:^1.1.1"
dotenv-cli: "npm:8.0.0"
eslint: "npm:9.29.0"
eslint-config-next: "npm:15.3.4"