feat: add settings page #49
10 changed files with 1005 additions and 0 deletions
|
@ -18,7 +18,12 @@
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
"@radix-ui/react-dropdown-menu": "^2.1.14",
|
||||||
"@radix-ui/react-hover-card": "^1.1.13",
|
"@radix-ui/react-hover-card": "^1.1.13",
|
||||||
"@radix-ui/react-label": "^2.1.6",
|
"@radix-ui/react-label": "^2.1.6",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.2.8",
|
||||||
|
"@radix-ui/react-select": "^2.2.4",
|
||||||
|
"@radix-ui/react-separator": "^1.1.6",
|
||||||
"@radix-ui/react-slot": "^1.2.2",
|
"@radix-ui/react-slot": "^1.2.2",
|
||||||
|
"@radix-ui/react-switch": "^1.2.4",
|
||||||
|
"@radix-ui/react-tabs": "^1.1.11",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-react": "^0.509.0",
|
"lucide-react": "^0.509.0",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { Logout } from '@/components/user/sso-logout-button';
|
import { Logout } from '@/components/user/sso-logout-button';
|
||||||
|
import { RedirectButton } from '@/components/user/redirect-button';
|
||||||
import { ThemePicker } from '@/components/user/theme-picker';
|
import { ThemePicker } from '@/components/user/theme-picker';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
|
@ -8,6 +9,7 @@ export default function Home() {
|
||||||
<div>
|
<div>
|
||||||
<h1>Home</h1>
|
<h1>Home</h1>
|
||||||
<Logout />
|
<Logout />
|
||||||
|
<RedirectButton redirectUrl='/settings' buttonText='Settings' />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
476
src/app/settings/page.tsx
Normal file
476
src/app/settings/page.tsx
Normal file
|
@ -0,0 +1,476 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
export default function SettingsPage() {
|
||||||
|
return (
|
||||||
|
<div className='fixed inset-0 flex items-center justify-center p-4 bg-background/50 backdrop-blur-sm'>
|
||||||
|
<div className='rounded-lg border bg-card text-card-foreground shadow-xl max-w-[700px] w-full h-auto max-h-[calc(100vh-2rem)] flex flex-col'>
|
||||||
|
<Tabs
|
||||||
|
defaultValue='general'
|
||||||
|
className='w-full flex flex-col flex-grow min-h-0'
|
||||||
|
>
|
||||||
|
<TabsList className='grid w-full grid-cols-3 sm:grid-cols-5'>
|
||||||
|
<TabsTrigger value='general'>Account</TabsTrigger>
|
||||||
|
<TabsTrigger value='notifications'>Notifications</TabsTrigger>
|
||||||
|
<TabsTrigger value='calendarAvailability'>Calendar</TabsTrigger>
|
||||||
|
<TabsTrigger value='sharingPrivacy'>Privacy</TabsTrigger>
|
||||||
|
<TabsTrigger value='appearance'>Appearance</TabsTrigger>
|
||||||
|
</TabsList>
|
||||||
|
|
||||||
|
<TabsContent value='general' className='flex-grow overflow-hidden'>
|
||||||
|
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
||||||
|
<ScrollableSettingsWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Account Settings</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Manage your account details and preferences.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-6'>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='displayName'>Display Name</Label>
|
||||||
|
<Input id='displayName' placeholder='Your Name' />
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='email'>Email Address</Label>
|
||||||
|
<Input
|
||||||
|
id='email'
|
||||||
|
type='email'
|
||||||
|
placeholder='your.email@example.com'
|
||||||
|
readOnly
|
||||||
|
value='user-email@example.com'
|
||||||
|
/>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Email is managed by your SSO provider.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='profilePicture'>Profile Picture</Label>
|
||||||
|
<Input id='profilePicture' type='file' />
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Upload a new profile picture.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='timezone'>Timezone</Label>
|
||||||
|
<Input id='displayName' placeholder='Europe/Berlin' />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='language'>Language</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='language'>
|
||||||
|
<SelectValue placeholder='Select language' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='en'>English</SelectItem>
|
||||||
|
<SelectItem value='de'>German</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='pt-4'>
|
||||||
|
<Button variant='destructive'>Delete Account</Button>
|
||||||
|
<p className='text-sm text-muted-foreground pt-1'>
|
||||||
|
Permanently delete your account and all associated data.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</ScrollableSettingsWrapper>
|
||||||
|
<CardFooter className='mt-auto border-t pt-4 flex justify-between'>
|
||||||
|
<Button variant='secondary'>Exit</Button>
|
||||||
|
<Button>Save Changes</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent
|
||||||
|
value='notifications'
|
||||||
|
className='flex-grow overflow-hidden'
|
||||||
|
>
|
||||||
|
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
||||||
|
<ScrollableSettingsWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Notification Preferences</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Choose how you want to be notified.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-6'>
|
||||||
|
<div className='flex items-center justify-between space-x-2 p-3 rounded-md border'>
|
||||||
|
<Label
|
||||||
|
htmlFor='masterEmailNotifications'
|
||||||
|
className='font-normal'
|
||||||
|
>
|
||||||
|
Enable All Email Notifications
|
||||||
|
</Label>
|
||||||
|
<Switch id='masterEmailNotifications' />
|
||||||
|
</div>
|
||||||
|
<div className='space-y-4 pl-2 border-l-2 ml-2'>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label
|
||||||
|
htmlFor='newMeetingBookings'
|
||||||
|
className='font-normal'
|
||||||
|
>
|
||||||
|
New Meeting Bookings
|
||||||
|
</Label>
|
||||||
|
<Switch id='newMeetingBookings' />
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label
|
||||||
|
htmlFor='meetingConfirmations'
|
||||||
|
className='font-normal'
|
||||||
|
>
|
||||||
|
Meeting Confirmations/Cancellations
|
||||||
|
</Label>
|
||||||
|
<Switch id='meetingConfirmations' />
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label
|
||||||
|
htmlFor='enableMeetingReminders'
|
||||||
|
className='font-normal'
|
||||||
|
>
|
||||||
|
Meeting Reminders
|
||||||
|
</Label>
|
||||||
|
<Switch id='enableMeetingReminders' />
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2 pl-6'>
|
||||||
|
<Label htmlFor='remindBefore'>Remind me before</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='remindBefore'>
|
||||||
|
<SelectValue placeholder='Select reminder time' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='15m'>15 minutes</SelectItem>
|
||||||
|
<SelectItem value='30m'>30 minutes</SelectItem>
|
||||||
|
<SelectItem value='1h'>1 hour</SelectItem>
|
||||||
|
<SelectItem value='1d'>1 day</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label htmlFor='friendRequests' className='font-normal'>
|
||||||
|
Friend Requests
|
||||||
|
</Label>
|
||||||
|
<Switch id='friendRequests' />
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label htmlFor='groupUpdates' className='font-normal'>
|
||||||
|
Group Invitations/Updates
|
||||||
|
</Label>
|
||||||
|
<Switch id='groupUpdates' />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</ScrollableSettingsWrapper>
|
||||||
|
<CardFooter className='mt-auto border-t pt-4 flex justify-between'>
|
||||||
|
<Button variant='secondary'>Exit</Button>
|
||||||
|
<Button>Save Changes</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent
|
||||||
|
value='calendarAvailability'
|
||||||
|
className='flex-grow overflow-hidden'
|
||||||
|
>
|
||||||
|
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
||||||
|
<ScrollableSettingsWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Calendar & Availability</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Manage your calendar display, default availability, and iCal
|
||||||
|
integrations.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-6'>
|
||||||
|
<fieldset className='space-y-4 p-4 border rounded-md'>
|
||||||
|
<legend className='text-sm font-medium px-1'>
|
||||||
|
Display
|
||||||
|
</legend>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='defaultCalendarView'>
|
||||||
|
Default Calendar View
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='defaultCalendarView'>
|
||||||
|
<SelectValue placeholder='Select view' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='day'>Day</SelectItem>
|
||||||
|
<SelectItem value='week'>Week</SelectItem>
|
||||||
|
<SelectItem value='month'>Month</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='weekStartsOn'>Week Starts On</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='weekStartsOn'>
|
||||||
|
<SelectValue placeholder='Select day' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='sunday'>Sunday</SelectItem>
|
||||||
|
<SelectItem value='monday'>Monday</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='flex items-center justify-between space-x-2'>
|
||||||
|
<Label htmlFor='showWeekends' className='font-normal'>
|
||||||
|
Show Weekends
|
||||||
|
</Label>
|
||||||
|
<Switch id='showWeekends' defaultChecked />
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset className='space-y-4 p-4 border rounded-md'>
|
||||||
|
<legend className='text-sm font-medium px-1'>
|
||||||
|
Availability
|
||||||
|
</legend>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label>Working Hours</Label>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Define your typical available hours (e.g.,
|
||||||
|
Monday-Friday, 9 AM - 5 PM).
|
||||||
|
</p>
|
||||||
|
<Button variant='outline' size='sm'>
|
||||||
|
Set Working Hours
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='minNoticeBooking'>
|
||||||
|
Minimum Notice for Bookings
|
||||||
|
</Label>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Min time before a booking can be made.
|
||||||
|
</p>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Input
|
||||||
|
id='bookingWindow'
|
||||||
|
type='text'
|
||||||
|
placeholder='e.g., 1h'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='bookingWindow'>
|
||||||
|
Booking Window (days in advance)
|
||||||
|
</Label>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Max time in advance a booking can be made.
|
||||||
|
</p>
|
||||||
|
<Input
|
||||||
|
id='bookingWindow'
|
||||||
|
type='number'
|
||||||
|
placeholder='e.g., 30d'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
|
<fieldset className='space-y-4 p-4 border rounded-md'>
|
||||||
|
<legend className='text-sm font-medium px-1'>
|
||||||
|
iCalendar Integration
|
||||||
|
</legend>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='icalImport'>Import iCal Feed URL</Label>
|
||||||
|
<Input
|
||||||
|
id='icalImport'
|
||||||
|
type='url'
|
||||||
|
placeholder='https://calendar.example.com/feed.ics'
|
||||||
|
/>
|
||||||
|
<Button size='sm' className='mt-1'>
|
||||||
|
Add Feed
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label>Export Your Calendar</Label>
|
||||||
|
<Button variant='outline' size='sm'>
|
||||||
|
Get iCal Export URL
|
||||||
|
</Button>
|
||||||
|
<Button variant='outline' size='sm' className='ml-2'>
|
||||||
|
Download .ics File
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</CardContent>
|
||||||
|
</ScrollableSettingsWrapper>
|
||||||
|
<CardFooter className='mt-auto border-t pt-4 flex justify-between'>
|
||||||
|
<Button variant='secondary'>Exit</Button>
|
||||||
|
<Button>Save Changes</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent
|
||||||
|
value='sharingPrivacy'
|
||||||
|
className='flex-grow overflow-hidden'
|
||||||
|
>
|
||||||
|
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
||||||
|
<ScrollableSettingsWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Sharing & Privacy</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Control who can see your calendar and book time with you.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-6'>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='defaultVisibility'>
|
||||||
|
Default Calendar Visibility
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='defaultVisibility'>
|
||||||
|
<SelectValue placeholder='Select visibility' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='private'>
|
||||||
|
Private (Only You)
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value='freebusy'>
|
||||||
|
Free/Busy for Friends
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value='fulldetails'>
|
||||||
|
Full Details for Friends
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='whoCanSeeFull'>
|
||||||
|
Who Can See Your Full Calendar Details?
|
||||||
|
</Label>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
(Override for Default Visibility)
|
||||||
|
<br />
|
||||||
|
<span className='text-sm text-muted-foreground'>
|
||||||
|
This setting will override the default visibility for
|
||||||
|
your calendar. You can set specific friends or groups to
|
||||||
|
see your full calendar details.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='whoCanSeeFull'>
|
||||||
|
<SelectValue placeholder='Select audience' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='me'>Only Me</SelectItem>
|
||||||
|
<SelectItem value='friends'>My Friends</SelectItem>
|
||||||
|
<SelectItem value='specific'>
|
||||||
|
Specific Friends/Groups (manage separately)
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='whoCanBook'>
|
||||||
|
Who Can Book Time With You?
|
||||||
|
</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='whoCanBook'>
|
||||||
|
<SelectValue placeholder='Select audience' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='none'>No One</SelectItem>
|
||||||
|
<SelectItem value='friends'>My Friends</SelectItem>
|
||||||
|
<SelectItem value='specific'>
|
||||||
|
Specific Friends/Groups (manage separately)
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label>Blocked Users</Label>
|
||||||
|
<Button variant='outline'>Manage Blocked Users</Button>
|
||||||
|
<p className='text-sm text-muted-foreground'>
|
||||||
|
Prevent specific users from seeing your calendar or
|
||||||
|
booking time.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</ScrollableSettingsWrapper>
|
||||||
|
<CardFooter className='mt-auto border-t pt-4 flex justify-between'>
|
||||||
|
<Button variant='secondary'>Exit</Button>
|
||||||
|
<Button>Save Changes</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
|
||||||
|
<TabsContent value='appearance' className='flex-grow overflow-hidden'>
|
||||||
|
<Card className='h-full flex flex-col border-0 shadow-none rounded-none'>
|
||||||
|
<ScrollableSettingsWrapper>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Appearance</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Customize the look and feel of the application.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className='space-y-6'>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='theme'>Theme</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='theme'>
|
||||||
|
<SelectValue placeholder='Select theme' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='light'>Light</SelectItem>
|
||||||
|
<SelectItem value='dark'>Dark</SelectItem>
|
||||||
|
<SelectItem value='system'>System Default</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='dateFormat'>Date Format</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='dateFormat'>
|
||||||
|
<SelectValue placeholder='Select date format' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='ddmmyyyy'>DD/MM/YYYY</SelectItem>
|
||||||
|
<SelectItem value='mmddyyyy'>MM/DD/YYYY</SelectItem>
|
||||||
|
<SelectItem value='yyyymmdd'>YYYY-MM-DD</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className='space-y-2'>
|
||||||
|
<Label htmlFor='timeFormat'>Time Format</Label>
|
||||||
|
<Select>
|
||||||
|
<SelectTrigger id='timeFormat'>
|
||||||
|
<SelectValue placeholder='Select time format' />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value='24h'>24-hour</SelectItem>
|
||||||
|
<SelectItem value='12h'>12-hour</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</ScrollableSettingsWrapper>
|
||||||
|
<CardFooter className='mt-auto border-t pt-4 flex justify-between'>
|
||||||
|
<Button variant='secondary'>Exit</Button>
|
||||||
|
<Button>Save Changes</Button>
|
||||||
|
</CardFooter>
|
||||||
|
</Card>
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
185
src/components/ui/select.tsx
Normal file
185
src/components/ui/select.tsx
Normal file
|
@ -0,0 +1,185 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||||
|
import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
function Select({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
||||||
|
return <SelectPrimitive.Root data-slot='select' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectGroup({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
||||||
|
return <SelectPrimitive.Group data-slot='select-group' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectValue({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
||||||
|
return <SelectPrimitive.Value data-slot='select-value' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectTrigger({
|
||||||
|
className,
|
||||||
|
size = 'default',
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
||||||
|
size?: 'sm' | 'default';
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
data-slot='select-trigger'
|
||||||
|
data-size={size}
|
||||||
|
className={cn(
|
||||||
|
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDownIcon className='size-4 opacity-50' />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectContent({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
position = 'popper',
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
data-slot='select-content'
|
||||||
|
className={cn(
|
||||||
|
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
|
||||||
|
position === 'popper' &&
|
||||||
|
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
'p-1',
|
||||||
|
position === 'popper' &&
|
||||||
|
'h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)] scroll-my-1',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectLabel({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
data-slot='select-label'
|
||||||
|
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectItem({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
data-slot='select-item'
|
||||||
|
className={cn(
|
||||||
|
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className='absolute right-2 flex size-3.5 items-center justify-center'>
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className='size-4' />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
data-slot='select-separator'
|
||||||
|
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectScrollUpButton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
data-slot='select-scroll-up-button'
|
||||||
|
className={cn(
|
||||||
|
'flex cursor-default items-center justify-center py-1',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronUpIcon className='size-4' />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function SelectScrollDownButton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
data-slot='select-scroll-down-button'
|
||||||
|
className={cn(
|
||||||
|
'flex cursor-default items-center justify-center py-1',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDownIcon className='size-4' />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectGroup,
|
||||||
|
SelectItem,
|
||||||
|
SelectLabel,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
};
|
28
src/components/ui/separator.tsx
Normal file
28
src/components/ui/separator.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
function Separator({
|
||||||
|
className,
|
||||||
|
orientation = 'horizontal',
|
||||||
|
decorative = true,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
data-slot='separator-root'
|
||||||
|
decorative={decorative}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Separator };
|
31
src/components/ui/switch.tsx
Normal file
31
src/components/ui/switch.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as SwitchPrimitive from '@radix-ui/react-switch';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
function Switch({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<SwitchPrimitive.Root
|
||||||
|
data-slot='switch'
|
||||||
|
className={cn(
|
||||||
|
'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SwitchPrimitive.Thumb
|
||||||
|
data-slot='switch-thumb'
|
||||||
|
className={cn(
|
||||||
|
'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitive.Root>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Switch };
|
66
src/components/ui/tabs.tsx
Normal file
66
src/components/ui/tabs.tsx
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
function Tabs({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Root
|
||||||
|
data-slot='tabs'
|
||||||
|
className={cn('flex flex-col gap-2', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsList({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.List>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.List
|
||||||
|
data-slot='tabs-list'
|
||||||
|
className={cn(
|
||||||
|
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsTrigger({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
data-slot='tabs-trigger'
|
||||||
|
className={cn(
|
||||||
|
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function TabsContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Content
|
||||||
|
data-slot='tabs-content'
|
||||||
|
className={cn('flex-1 outline-none', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Tabs, TabsList, TabsTrigger, TabsContent };
|
16
src/components/user/redirect-button.tsx
Normal file
16
src/components/user/redirect-button.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { Button } from '../ui/button';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
export function RedirectButton({
|
||||||
|
redirectUrl,
|
||||||
|
buttonText,
|
||||||
|
}: {
|
||||||
|
redirectUrl: string;
|
||||||
|
buttonText: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<Link href={redirectUrl}>
|
||||||
|
<Button>{buttonText}</Button>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
16
src/components/wrappers/settings-scroll.tsx
Normal file
16
src/components/wrappers/settings-scroll.tsx
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface ScrollableContentWrapperProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ScrollableSettingsWrapper: React.FC<
|
||||||
|
ScrollableContentWrapperProps
|
||||||
|
> = ({ children, className = '' }) => {
|
||||||
|
return (
|
||||||
|
<div className={`h-[500px] overflow-y-auto space-y-2 ${className}`}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
180
yarn.lock
180
yarn.lock
|
@ -940,6 +940,13 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/number@npm:1.1.1":
|
||||||
|
version: 1.1.1
|
||||||
|
resolution: "@radix-ui/number@npm:1.1.1"
|
||||||
|
checksum: 10c0/0570ad92287398e8a7910786d7cee0a998174cdd6637ba61571992897c13204adf70b9ed02d0da2af554119411128e701d9c6b893420612897b438dc91db712b
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/primitive@npm:1.1.2":
|
"@radix-ui/primitive@npm:1.1.2":
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
resolution: "@radix-ui/primitive@npm:1.1.2"
|
resolution: "@radix-ui/primitive@npm:1.1.2"
|
||||||
|
@ -1320,6 +1327,91 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-scroll-area@npm:^1.2.8":
|
||||||
|
version: 1.2.8
|
||||||
|
resolution: "@radix-ui/react-scroll-area@npm:1.2.8"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/number": "npm:1.1.1"
|
||||||
|
"@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-direction": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-presence": "npm:1.1.4"
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.2"
|
||||||
|
"@radix-ui/react-use-callback-ref": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-use-layout-effect": "npm:1.1.1"
|
||||||
|
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/4f7f7ab06fbb138b50d4aeecf70d482b990833a4e4a5dab394b0bea72a298a83b25aaf1609d754932018a71e1fdb34a7f3443bc28d40c720814a1bf37835e6cd
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-select@npm:^2.2.4":
|
||||||
|
version: 2.2.4
|
||||||
|
resolution: "@radix-ui/react-select@npm:2.2.4"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/number": "npm:1.1.1"
|
||||||
|
"@radix-ui/primitive": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-collection": "npm:1.1.6"
|
||||||
|
"@radix-ui/react-compose-refs": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-context": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-direction": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-dismissable-layer": "npm:1.1.9"
|
||||||
|
"@radix-ui/react-focus-guards": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-focus-scope": "npm:1.1.6"
|
||||||
|
"@radix-ui/react-id": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-popper": "npm:1.2.6"
|
||||||
|
"@radix-ui/react-portal": "npm:1.1.8"
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.2"
|
||||||
|
"@radix-ui/react-slot": "npm:1.2.2"
|
||||||
|
"@radix-ui/react-use-callback-ref": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
|
||||||
|
"@radix-ui/react-use-layout-effect": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-use-previous": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-visually-hidden": "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/96b64414abd6dab5f12cdc27bec21b08af2089a89226e84f7fef657e40a46e13465dac2338bc818ff7883b2ef2027efb644759f5be48d955655b059bd0363ff2
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-separator@npm:^1.1.6":
|
||||||
|
version: 1.1.6
|
||||||
|
resolution: "@radix-ui/react-separator@npm:1.1.6"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.2"
|
||||||
|
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/498c581d6f712a1a2a5f956fd415c41e85769b0891cf8253fcc84bacb3e344dc4c0b8fa416283b46d04d5d7511044bc41bc448591b2bb39338864f277d915d16
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-slot@npm:1.2.2, @radix-ui/react-slot@npm:^1.2.2":
|
"@radix-ui/react-slot@npm:1.2.2, @radix-ui/react-slot@npm:^1.2.2":
|
||||||
version: 1.2.2
|
version: 1.2.2
|
||||||
resolution: "@radix-ui/react-slot@npm:1.2.2"
|
resolution: "@radix-ui/react-slot@npm:1.2.2"
|
||||||
|
@ -1335,6 +1427,57 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-switch@npm:^1.2.4":
|
||||||
|
version: 1.2.4
|
||||||
|
resolution: "@radix-ui/react-switch@npm:1.2.4"
|
||||||
|
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-primitive": "npm:2.1.2"
|
||||||
|
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
|
||||||
|
"@radix-ui/react-use-previous": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-use-size": "npm:1.1.1"
|
||||||
|
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/53f1f985dd0ed7b28b108b8075078912fed1496313f23270f0be3234fedebb5df4b4b33f2cb1a9c5670d48a38200fc4e1f09db2608c3e94d1de61339ac2d2c53
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-tabs@npm:^1.1.11":
|
||||||
|
version: 1.1.11
|
||||||
|
resolution: "@radix-ui/react-tabs@npm:1.1.11"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/primitive": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-context": "npm:1.1.2"
|
||||||
|
"@radix-ui/react-direction": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-id": "npm:1.1.1"
|
||||||
|
"@radix-ui/react-presence": "npm:1.1.4"
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.2"
|
||||||
|
"@radix-ui/react-roving-focus": "npm:1.1.9"
|
||||||
|
"@radix-ui/react-use-controllable-state": "npm:1.2.2"
|
||||||
|
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/eebecb25f4e245c4abf0968b86bb4ee468965e4d3524d48147298715cd54a58dab8b813cd65ed12ceb2a3e1410f80097e2b0532da01de79e78fb398e002578a3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-use-callback-ref@npm:1.1.1":
|
"@radix-ui/react-use-callback-ref@npm:1.1.1":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1"
|
resolution: "@radix-ui/react-use-callback-ref@npm:1.1.1"
|
||||||
|
@ -1407,6 +1550,19 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-use-previous@npm:1.1.1":
|
||||||
|
version: 1.1.1
|
||||||
|
resolution: "@radix-ui/react-use-previous@npm:1.1.1"
|
||||||
|
peerDependencies:
|
||||||
|
"@types/react": "*"
|
||||||
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@types/react":
|
||||||
|
optional: true
|
||||||
|
checksum: 10c0/52f1089d941491cd59b7f52a5679a14e9381711419a0557ce0f3bc9a4c117078224efec54dcced41a3653a13a386a7b6ec75435d61a273e8b9f5d00235f2b182
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/react-use-rect@npm:1.1.1":
|
"@radix-ui/react-use-rect@npm:1.1.1":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "@radix-ui/react-use-rect@npm:1.1.1"
|
resolution: "@radix-ui/react-use-rect@npm:1.1.1"
|
||||||
|
@ -1437,6 +1593,25 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@radix-ui/react-visually-hidden@npm:1.2.2":
|
||||||
|
version: 1.2.2
|
||||||
|
resolution: "@radix-ui/react-visually-hidden@npm:1.2.2"
|
||||||
|
dependencies:
|
||||||
|
"@radix-ui/react-primitive": "npm:2.1.2"
|
||||||
|
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/464b955cbe66ae5de819af5b7c2796eba77f84ab677eade0aa57e98ea588ef5d189c822e7bee0e18608864d5d262a1c70b5dda487269219f147a2a7cea625292
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"@radix-ui/rect@npm:1.1.1":
|
"@radix-ui/rect@npm:1.1.1":
|
||||||
version: 1.1.1
|
version: 1.1.1
|
||||||
resolution: "@radix-ui/rect@npm:1.1.1"
|
resolution: "@radix-ui/rect@npm:1.1.1"
|
||||||
|
@ -4407,7 +4582,12 @@ __metadata:
|
||||||
"@radix-ui/react-dropdown-menu": "npm:^2.1.14"
|
"@radix-ui/react-dropdown-menu": "npm:^2.1.14"
|
||||||
"@radix-ui/react-hover-card": "npm:^1.1.13"
|
"@radix-ui/react-hover-card": "npm:^1.1.13"
|
||||||
"@radix-ui/react-label": "npm:^2.1.6"
|
"@radix-ui/react-label": "npm:^2.1.6"
|
||||||
|
"@radix-ui/react-scroll-area": "npm:^1.2.8"
|
||||||
|
"@radix-ui/react-select": "npm:^2.2.4"
|
||||||
|
"@radix-ui/react-separator": "npm:^1.1.6"
|
||||||
"@radix-ui/react-slot": "npm:^1.2.2"
|
"@radix-ui/react-slot": "npm:^1.2.2"
|
||||||
|
"@radix-ui/react-switch": "npm:^1.2.4"
|
||||||
|
"@radix-ui/react-tabs": "npm:^1.1.11"
|
||||||
"@tailwindcss/postcss": "npm:4.1.6"
|
"@tailwindcss/postcss": "npm:4.1.6"
|
||||||
"@types/node": "npm:22.15.17"
|
"@types/node": "npm:22.15.17"
|
||||||
"@types/react": "npm:19.1.3"
|
"@types/react": "npm:19.1.3"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue