mirror of
https://github.com/perfect-panel/ppanel-web.git
synced 2026-02-16 21:31:10 -05:00
♻️ refactor: Update component imports and improve code consistency
- Refactored sidebar component to include additional sheet elements and improved accessibility with SheetHeader, SheetTitle, and SheetDescription. - Updated skeleton component for better readability and consistency in class names. - Refined slider component by standardizing import statements and enhancing class name formatting. - Enhanced sonner component with consistent import statements and improved class name formatting. - Standardized switch component imports and class names for better readability. - Improved table component structure and class name consistency across various elements. - Refactored tabs component for better readability and consistent class name formatting. - Updated textarea component for improved readability and consistency in class names. - Fixed timeline component to safely access getBoundingClientRect. - Refactored toggle group and toggle components for improved readability and consistency in class names. - Updated tooltip component for better readability and consistent class name formatting. - Enhanced enhanced-input component to support generic types for better type safety. - Refactored use-mobile hook for improved readability and consistency. - Updated countries utility to make certain properties optional for better flexibility. - Refined tailwind configuration for improved readability and consistency in theme settings.
This commit is contained in:
parent
6ccf9b8bdc
commit
59faeab34a
@ -34,8 +34,8 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
const deviceSchema = z.object({
|
const deviceSchema = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
method: z.string().default('device'),
|
method: z.string(),
|
||||||
enabled: z.boolean().default(false),
|
enabled: z.boolean(),
|
||||||
config: z
|
config: z
|
||||||
.object({
|
.object({
|
||||||
show_ads: z.boolean().optional(),
|
show_ads: z.boolean().optional(),
|
||||||
|
|||||||
@ -40,23 +40,23 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
const emailSettingsSchema = z.object({
|
const emailSettingsSchema = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
method: z.string().default('email'),
|
method: z.string(),
|
||||||
enabled: z.boolean().default(false),
|
enabled: z.boolean(),
|
||||||
config: z
|
config: z
|
||||||
.object({
|
.object({
|
||||||
enable_verify: z.boolean().default(false),
|
enable_verify: z.boolean(),
|
||||||
enable_domain_suffix: z.boolean().default(false),
|
enable_domain_suffix: z.boolean(),
|
||||||
domain_suffix_list: z.string().optional(),
|
domain_suffix_list: z.string().optional(),
|
||||||
verify_email_template: z.string().optional(),
|
verify_email_template: z.string().optional(),
|
||||||
expiration_email_template: z.string().optional(),
|
expiration_email_template: z.string().optional(),
|
||||||
maintenance_email_template: z.string().optional(),
|
maintenance_email_template: z.string().optional(),
|
||||||
traffic_exceed_email_template: z.string().optional(),
|
traffic_exceed_email_template: z.string().optional(),
|
||||||
platform: z.string().default('smtp'),
|
platform: z.string(),
|
||||||
platform_config: z
|
platform_config: z
|
||||||
.object({
|
.object({
|
||||||
host: z.string().optional(),
|
host: z.string().optional(),
|
||||||
port: z.coerce.number().optional(),
|
port: z.number().optional(),
|
||||||
ssl: z.boolean().default(false),
|
ssl: z.boolean(),
|
||||||
user: z.string().optional(),
|
user: z.string().optional(),
|
||||||
pass: z.string().optional(),
|
pass: z.string().optional(),
|
||||||
from: z.string().optional(),
|
from: z.string().optional(),
|
||||||
|
|||||||
@ -33,8 +33,8 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
const googleSchema = z.object({
|
const googleSchema = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
method: z.string().default('google'),
|
method: z.string().default('google').optional(),
|
||||||
enabled: z.boolean().default(false),
|
enabled: z.boolean().default(false).optional(),
|
||||||
config: z
|
config: z
|
||||||
.object({
|
.object({
|
||||||
client_id: z.string().optional(),
|
client_id: z.string().optional(),
|
||||||
|
|||||||
@ -49,8 +49,8 @@ import { z } from 'zod';
|
|||||||
|
|
||||||
const phoneSettingsSchema = z.object({
|
const phoneSettingsSchema = z.object({
|
||||||
id: z.number(),
|
id: z.number(),
|
||||||
method: z.string().default('mobile'),
|
method: z.string(),
|
||||||
enabled: z.boolean().default(false),
|
enabled: z.boolean(),
|
||||||
config: z
|
config: z
|
||||||
.object({
|
.object({
|
||||||
enable_whitelist: z.boolean().optional(),
|
enable_whitelist: z.boolean().optional(),
|
||||||
|
|||||||
@ -267,7 +267,7 @@ export default function CouponForm<T extends Record<string, any>>({
|
|||||||
<DatePicker
|
<DatePicker
|
||||||
placeholder={t('form.enterValue')}
|
placeholder={t('form.enterValue')}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
disabled={(date) => date < new Date(Date.now() - 24 * 60 * 60 * 1000)}
|
disabled={(date: Date) => date < new Date(Date.now() - 24 * 60 * 60 * 1000)}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
form.setValue(field.name, value);
|
form.setValue(field.name, value);
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
|
import { UserDetail } from '@/app/dashboard/user/user-detail';
|
||||||
import { ProTable } from '@/components/pro-table';
|
import { ProTable } from '@/components/pro-table';
|
||||||
import { filterTrafficLogDetails } from '@/services/admin/log';
|
import { filterTrafficLogDetails } from '@/services/admin/log';
|
||||||
import { formatBytes, formatDate } from '@workspace/ui/utils';
|
import { formatBytes, formatDate } from '@workspace/ui/utils';
|
||||||
@ -22,7 +23,11 @@ export default function TrafficDetailsPage() {
|
|||||||
initialFilters={initialFilters}
|
initialFilters={initialFilters}
|
||||||
columns={[
|
columns={[
|
||||||
{ accessorKey: 'server_id', header: t('column.serverId') },
|
{ accessorKey: 'server_id', header: t('column.serverId') },
|
||||||
{ accessorKey: 'user_id', header: t('column.userId') },
|
{
|
||||||
|
accessorKey: 'user_id',
|
||||||
|
header: t('column.userId'),
|
||||||
|
cell: ({ row }) => <UserDetail id={Number(row.original.user_id)} />,
|
||||||
|
},
|
||||||
{ accessorKey: 'subscribe_id', header: t('column.subscribeId') },
|
{ accessorKey: 'subscribe_id', header: t('column.subscribeId') },
|
||||||
{
|
{
|
||||||
accessorKey: 'upload',
|
accessorKey: 'upload',
|
||||||
|
|||||||
@ -47,7 +47,7 @@ export default function EmailBroadcastForm() {
|
|||||||
const emailBroadcastSchema = z.object({
|
const emailBroadcastSchema = z.object({
|
||||||
subject: z.string().min(1, t('subject') + ' ' + t('cannotBeEmpty')),
|
subject: z.string().min(1, t('subject') + ' ' + t('cannotBeEmpty')),
|
||||||
content: z.string().min(1, t('content') + ' ' + t('cannotBeEmpty')),
|
content: z.string().min(1, t('content') + ' ' + t('cannotBeEmpty')),
|
||||||
scope: z.string().default('all'),
|
scope: z.string(),
|
||||||
register_start_time: z.string().optional(),
|
register_start_time: z.string().optional(),
|
||||||
register_end_time: z.string().optional(),
|
register_end_time: z.string().optional(),
|
||||||
additional: z
|
additional: z
|
||||||
|
|||||||
@ -42,39 +42,31 @@ export type ProtocolName =
|
|||||||
|
|
||||||
type ServerRow = API.Server;
|
type ServerRow = API.Server;
|
||||||
|
|
||||||
export type NodeFormValues = {
|
const buildSchema = (t: ReturnType<typeof useTranslations>) =>
|
||||||
name: string;
|
z.object({
|
||||||
server_id?: number;
|
name: z.string().trim().min(1, t('errors.nameRequired')),
|
||||||
protocol: ProtocolName | '';
|
server_id: z
|
||||||
address: string;
|
.number({ message: t('errors.serverRequired') })
|
||||||
port: number;
|
.int()
|
||||||
tags: string[];
|
.gt(0, t('errors.serverRequired'))
|
||||||
};
|
.optional(),
|
||||||
|
protocol: z.string().min(1, t('errors.protocolRequired')),
|
||||||
|
address: z.string().trim().min(1, t('errors.serverAddrRequired')),
|
||||||
|
port: z
|
||||||
|
.number({ message: t('errors.portRange') })
|
||||||
|
.int()
|
||||||
|
.min(1, t('errors.portRange'))
|
||||||
|
.max(65535, t('errors.portRange')),
|
||||||
|
tags: z.array(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export type NodeFormValues = z.infer<ReturnType<typeof buildSchema>>;
|
||||||
|
|
||||||
async function getServers(): Promise<ServerRow[]> {
|
async function getServers(): Promise<ServerRow[]> {
|
||||||
const { data } = await filterServerList({ page: 1, size: 1000 });
|
const { data } = await filterServerList({ page: 1, size: 1000 });
|
||||||
return (data?.data?.list || []) as ServerRow[];
|
return (data?.data?.list || []) as ServerRow[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const buildSchema = (t: ReturnType<typeof useTranslations>) =>
|
|
||||||
z.object({
|
|
||||||
name: z.string().trim().min(1, t('errors.nameRequired')),
|
|
||||||
server_id: z.coerce
|
|
||||||
.number({ invalid_type_error: t('errors.serverRequired') })
|
|
||||||
.int()
|
|
||||||
.gt(0, t('errors.serverRequired')),
|
|
||||||
protocol: z.custom<ProtocolName>((v) => typeof v === 'string' && v.length > 0, {
|
|
||||||
message: t('errors.protocolRequired'),
|
|
||||||
}),
|
|
||||||
address: z.string().trim().min(1, t('errors.serverAddrRequired')),
|
|
||||||
port: z.coerce
|
|
||||||
.number({ invalid_type_error: t('errors.portRange') })
|
|
||||||
.int()
|
|
||||||
.min(1, t('errors.portRange'))
|
|
||||||
.max(65535, t('errors.portRange')),
|
|
||||||
tags: z.array(z.string()).default([]),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function NodeForm(props: {
|
export default function NodeForm(props: {
|
||||||
trigger: string;
|
trigger: string;
|
||||||
title: string;
|
title: string;
|
||||||
|
|||||||
@ -75,9 +75,9 @@ export default function PaymentForm<T>({
|
|||||||
icon: z.string().optional(),
|
icon: z.string().optional(),
|
||||||
domain: z.string().optional(),
|
domain: z.string().optional(),
|
||||||
config: z.any(),
|
config: z.any(),
|
||||||
fee_mode: z.coerce.number().min(0).max(2),
|
fee_mode: z.number().min(0).max(2),
|
||||||
fee_percent: z.coerce.number().optional(),
|
fee_percent: z.number().optional(),
|
||||||
fee_amount: z.coerce.number().optional(),
|
fee_amount: z.number().optional(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -319,7 +319,7 @@ export default function PaymentForm<T>({
|
|||||||
}}
|
}}
|
||||||
defaultValue={field.value}
|
defaultValue={field.value}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
// @ts-expect-error
|
// @ts-expect-error - disabled prop type mismatch with SelectTrigger component
|
||||||
disabled={isEdit && Boolean(initialValues?.platform)}
|
disabled={isEdit && Boolean(initialValues?.platform)}
|
||||||
>
|
>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
|
|||||||
@ -87,7 +87,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
name: z.string(),
|
name: z.string(),
|
||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
unit_price: z.number(),
|
unit_price: z.number(),
|
||||||
unit_time: z.string().default('Month'),
|
unit_time: z.string(),
|
||||||
replacement: z.number().optional(),
|
replacement: z.number().optional(),
|
||||||
discount: z
|
discount: z
|
||||||
.array(
|
.array(
|
||||||
@ -97,22 +97,22 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.optional(),
|
.optional(),
|
||||||
inventory: z.number().optional().default(-1),
|
inventory: z.number().optional(),
|
||||||
speed_limit: z.number().optional().default(0),
|
speed_limit: z.number().optional(),
|
||||||
device_limit: z.number().optional().default(0),
|
device_limit: z.number().optional(),
|
||||||
traffic: z.number().optional().default(0),
|
traffic: z.number().optional(),
|
||||||
quota: z.number().optional().default(0),
|
quota: z.number().optional(),
|
||||||
group_id: z.number().optional().nullish(),
|
group_id: z.number().optional().nullish(),
|
||||||
// Use tags as group identifiers; accept string (tag) or number (legacy id)
|
// Use tags as group identifiers; accept string (tag) or number (legacy id)
|
||||||
node_tags: z.array(z.string()).optional().default([]),
|
node_tags: z.array(z.string()).optional(),
|
||||||
nodes: z.array(z.number()).optional().default([]),
|
nodes: z.array(z.number()).optional(),
|
||||||
deduction_ratio: z.number().optional().default(0),
|
deduction_ratio: z.number().optional(),
|
||||||
allow_deduction: z.boolean().optional().default(false),
|
allow_deduction: z.boolean().optional(),
|
||||||
reset_cycle: z.number().optional().default(0),
|
reset_cycle: z.number().optional(),
|
||||||
renewal_reset: z.boolean().optional().default(false),
|
renewal_reset: z.boolean().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm<z.infer<typeof formSchema>>({
|
||||||
resolver: zodResolver(formSchema),
|
resolver: zodResolver(formSchema),
|
||||||
defaultValues: assign(
|
defaultValues: assign(
|
||||||
defaultValues,
|
defaultValues,
|
||||||
@ -204,7 +204,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (hasChanges) {
|
if (hasChanges) {
|
||||||
form.setValue(fieldName, calculatedValues, { shouldDirty: true });
|
form.setValue(fieldName as any, calculatedValues, { shouldDirty: true });
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
},
|
},
|
||||||
@ -321,6 +321,7 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
<Combobox<number, false>
|
<Combobox<number, false>
|
||||||
placeholder={t('form.selectSubscribeGroup')}
|
placeholder={t('form.selectSubscribeGroup')}
|
||||||
{...field}
|
{...field}
|
||||||
|
value={field.value ?? undefined}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
form.setValue(field.name, value || 0);
|
form.setValue(field.name, value || 0);
|
||||||
}}
|
}}
|
||||||
@ -665,7 +666,8 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
min: 0,
|
min: 0,
|
||||||
step: 0.01,
|
step: 0.01,
|
||||||
formatInput: (value) => unitConversion('centsToDollars', value),
|
formatInput: (value) => unitConversion('centsToDollars', value),
|
||||||
formatOutput: (value) => unitConversion('dollarsToCents', value),
|
formatOutput: (value) =>
|
||||||
|
unitConversion('dollarsToCents', value).toString(),
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
@ -911,7 +913,8 @@ export default function SubscribeForm<T extends Record<string, any>>({
|
|||||||
const keys = Object.keys(errors);
|
const keys = Object.keys(errors);
|
||||||
for (const key of keys) {
|
for (const key of keys) {
|
||||||
const formattedKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
const formattedKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
|
||||||
toast.error(`${t(`form.${formattedKey}`)} is ${errors[key]?.message}`);
|
const error = (errors as any)[key];
|
||||||
|
toast.error(`${t(`form.${formattedKey}`)} is ${error?.message}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
|
|||||||
@ -96,8 +96,8 @@ function getTimeRangeData(slots: API.TimePeriod[]) {
|
|||||||
|
|
||||||
const nodeConfigSchema = z.object({
|
const nodeConfigSchema = z.object({
|
||||||
node_secret: z.string().optional(),
|
node_secret: z.string().optional(),
|
||||||
node_pull_interval: z.number().or(z.string().pipe(z.coerce.number())).optional(),
|
node_pull_interval: z.number().optional(),
|
||||||
node_push_interval: z.number().or(z.string().pipe(z.coerce.number())).optional(),
|
node_push_interval: z.number().optional(),
|
||||||
});
|
});
|
||||||
type NodeConfigFormData = z.infer<typeof nodeConfigSchema>;
|
type NodeConfigFormData = z.infer<typeof nodeConfigSchema>;
|
||||||
|
|
||||||
|
|||||||
@ -64,9 +64,9 @@ const createClientFormSchema = (t: any) =>
|
|||||||
description: z.string().optional(),
|
description: z.string().optional(),
|
||||||
icon: z.string().optional(),
|
icon: z.string().optional(),
|
||||||
user_agent: z.string().min(1, `User-Agent ${t('form.validation.userAgentRequiredSuffix')}`),
|
user_agent: z.string().min(1, `User-Agent ${t('form.validation.userAgentRequiredSuffix')}`),
|
||||||
scheme: z.string().default(''),
|
scheme: z.string(),
|
||||||
template: z.string().default(''),
|
template: z.string(),
|
||||||
output_format: z.string().default(''),
|
output_format: z.string(),
|
||||||
download_link: z.object({
|
download_link: z.object({
|
||||||
windows: z.string().optional(),
|
windows: z.string().optional(),
|
||||||
mac: z.string().optional(),
|
mac: z.string().optional(),
|
||||||
|
|||||||
@ -142,7 +142,7 @@ export default function UserForm<T extends Record<string, any>>({
|
|||||||
placeholder={t('areaCodePlaceholder')}
|
placeholder={t('areaCodePlaceholder')}
|
||||||
value={field.value}
|
value={field.value}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
form.setValue(field.name, value.phone);
|
form.setValue(field.name, value.phone as string);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@ -205,7 +205,7 @@ export function SubscriptionForm({ trigger, title, loading, initialData, onSubmi
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
placeholder={t('permanent')}
|
placeholder={t('permanent')}
|
||||||
value={field.value}
|
value={field.value ?? undefined}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
if (value === field.value) {
|
if (value === field.value) {
|
||||||
form.setValue(field.name, 0);
|
form.setValue(field.name, 0);
|
||||||
|
|||||||
@ -13,10 +13,10 @@ import { toast } from 'sonner';
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
const FormSchema = z.object({
|
const FormSchema = z.object({
|
||||||
enable_balance_notify: z.boolean().default(false),
|
enable_balance_notify: z.boolean(),
|
||||||
enable_login_notify: z.boolean().default(false),
|
enable_login_notify: z.boolean(),
|
||||||
enable_subscribe_notify: z.boolean().default(false),
|
enable_subscribe_notify: z.boolean(),
|
||||||
enable_trade_notify: z.boolean().default(false),
|
enable_trade_notify: z.boolean(),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default function NotifySettings() {
|
export default function NotifySettings() {
|
||||||
|
|||||||
@ -6,12 +6,6 @@ import useGlobalStore from '@/config/use-global';
|
|||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@workspace/ui/components/card';
|
||||||
import { Sidebar, SidebarContent } from '@workspace/ui/components/sidebar';
|
import { Sidebar, SidebarContent } from '@workspace/ui/components/sidebar';
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from '@workspace/ui/components/tooltip';
|
|
||||||
import { Icon } from '@workspace/ui/custom-components/icon';
|
import { Icon } from '@workspace/ui/custom-components/icon';
|
||||||
import { isBrowser } from '@workspace/ui/utils';
|
import { isBrowser } from '@workspace/ui/utils';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
@ -54,25 +48,18 @@ export function SidebarRight({ ...props }: React.ComponentProps<typeof Sidebar>)
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader className='flex flex-row items-center justify-between space-y-0 p-3 pb-2'>
|
<CardHeader className='flex flex-row items-center justify-between space-y-0 p-3 pb-2'>
|
||||||
<CardTitle className='text-sm font-medium'>{t('inviteCode')}</CardTitle>
|
<CardTitle className='text-sm font-medium'>{t('inviteCode')}</CardTitle>
|
||||||
<TooltipProvider>
|
<CopyToClipboard
|
||||||
<Tooltip>
|
text={`${isBrowser() && location?.origin}/auth?invite=${user?.refer_code}`}
|
||||||
<TooltipTrigger asChild>
|
onCopy={(text, result) => {
|
||||||
<CopyToClipboard
|
if (result) {
|
||||||
text={`${isBrowser() && location?.origin}/auth?invite=${user?.refer_code}`}
|
toast.success(t('copySuccess'));
|
||||||
onCopy={(text, result) => {
|
}
|
||||||
if (result) {
|
}}
|
||||||
toast.success(t('copySuccess'));
|
>
|
||||||
}
|
<Button variant='ghost' className='size-5 p-0'>
|
||||||
}}
|
<Icon icon='mdi:content-copy' className='text-primary text-2xl' />
|
||||||
>
|
</Button>
|
||||||
<Button variant='ghost' className='size-5 p-0'>
|
</CopyToClipboard>
|
||||||
<Icon icon='mdi:content-copy' className='text-primary text-2xl' />
|
|
||||||
</Button>
|
|
||||||
</CopyToClipboard>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>{t('copyInviteLink')}</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='truncate p-3 font-bold'>{user?.refer_code}</CardContent>
|
<CardContent className='truncate p-3 font-bold'>{user?.refer_code}</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -15,12 +15,12 @@
|
|||||||
"./utils/*": "./src/utils/*.ts"
|
"./utils/*": "./src/utils/*.ts"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint . --max-warnings 0"
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@dnd-kit/core": "^6.3.1",
|
"@dnd-kit/core": "^6.3.1",
|
||||||
"@dnd-kit/sortable": "^10.0.0",
|
"@dnd-kit/sortable": "^10.0.0",
|
||||||
"@hookform/resolvers": "^3.10.0",
|
"@hookform/resolvers": "^5.2.1",
|
||||||
"@iconify-json/flagpack": "^1.2.2",
|
"@iconify-json/flagpack": "^1.2.2",
|
||||||
"@iconify-json/logos": "^1.2.4",
|
"@iconify-json/logos": "^1.2.4",
|
||||||
"@iconify-json/mdi": "^1.2.2",
|
"@iconify-json/mdi": "^1.2.2",
|
||||||
@ -28,78 +28,78 @@
|
|||||||
"@iconify-json/uil": "^1.2.3",
|
"@iconify-json/uil": "^1.2.3",
|
||||||
"@iconify/react": "^5.2.0",
|
"@iconify/react": "^5.2.0",
|
||||||
"@monaco-editor/react": "^4.6.0",
|
"@monaco-editor/react": "^4.6.0",
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.12",
|
||||||
"@radix-ui/react-alert-dialog": "^1.1.4",
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-aspect-ratio": "^1.1.1",
|
"@radix-ui/react-aspect-ratio": "^1.1.7",
|
||||||
"@radix-ui/react-avatar": "^1.1.2",
|
"@radix-ui/react-avatar": "^1.1.10",
|
||||||
"@radix-ui/react-checkbox": "^1.1.3",
|
"@radix-ui/react-checkbox": "^1.3.3",
|
||||||
"@radix-ui/react-collapsible": "^1.1.2",
|
"@radix-ui/react-collapsible": "^1.1.12",
|
||||||
"@radix-ui/react-context-menu": "^2.2.4",
|
"@radix-ui/react-context-menu": "^2.2.16",
|
||||||
"@radix-ui/react-dialog": "^1.1.4",
|
"@radix-ui/react-dialog": "^1.1.15",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-hover-card": "^1.1.4",
|
"@radix-ui/react-hover-card": "^1.1.15",
|
||||||
"@radix-ui/react-label": "^2.1.1",
|
"@radix-ui/react-label": "^2.1.7",
|
||||||
"@radix-ui/react-menubar": "^1.1.4",
|
"@radix-ui/react-menubar": "^1.1.16",
|
||||||
"@radix-ui/react-navigation-menu": "^1.2.3",
|
"@radix-ui/react-navigation-menu": "^1.2.14",
|
||||||
"@radix-ui/react-popover": "^1.1.4",
|
"@radix-ui/react-popover": "^1.1.15",
|
||||||
"@radix-ui/react-progress": "^1.1.1",
|
"@radix-ui/react-progress": "^1.1.7",
|
||||||
"@radix-ui/react-radio-group": "^1.2.2",
|
"@radix-ui/react-radio-group": "^1.3.8",
|
||||||
"@radix-ui/react-scroll-area": "^1.2.2",
|
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||||
"@radix-ui/react-select": "^2.1.4",
|
"@radix-ui/react-select": "^2.2.6",
|
||||||
"@radix-ui/react-separator": "^1.1.1",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slider": "^1.2.2",
|
"@radix-ui/react-slider": "^1.3.6",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
"@radix-ui/react-switch": "^1.1.2",
|
"@radix-ui/react-switch": "^1.2.6",
|
||||||
"@radix-ui/react-tabs": "^1.1.2",
|
"@radix-ui/react-tabs": "^1.1.13",
|
||||||
"@radix-ui/react-toast": "^1.2.4",
|
"@radix-ui/react-toast": "^1.2.4",
|
||||||
"@radix-ui/react-toggle": "^1.1.1",
|
"@radix-ui/react-toggle": "^1.1.10",
|
||||||
"@radix-ui/react-toggle-group": "^1.1.1",
|
"@radix-ui/react-toggle-group": "^1.1.11",
|
||||||
"@radix-ui/react-tooltip": "^1.1.6",
|
"@radix-ui/react-tooltip": "^1.2.8",
|
||||||
"@tanstack/react-table": "^8.20.6",
|
"@tanstack/react-table": "^8.20.6",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"cmdk": "1.0.4",
|
"cmdk": "^1.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel-react": "^8.5.2",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"framer-motion": "^11.18.1",
|
"framer-motion": "^11.18.1",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.473.0",
|
"lucide-react": "^0.542.0",
|
||||||
"mathjs": "^14.0.1",
|
"mathjs": "^14.0.1",
|
||||||
"monaco-themes": "^0.4.6",
|
"monaco-themes": "^0.4.6",
|
||||||
"motion": "^11.18.1",
|
"motion": "^11.18.1",
|
||||||
"next-themes": "^0.4.4",
|
"next-themes": "^0.4.6",
|
||||||
"react-day-picker": "8.10.1",
|
"react-day-picker": "^9.9.0",
|
||||||
"react-hook-form": "^7.54.2",
|
"react-hook-form": "^7.62.0",
|
||||||
"react-markdown": "^9.0.3",
|
"react-markdown": "^9.0.3",
|
||||||
"react-resizable-panels": "^2.1.7",
|
"react-resizable-panels": "^3.0.5",
|
||||||
"react-syntax-highlighter": "^15.6.1",
|
"react-syntax-highlighter": "^15.6.1",
|
||||||
"recharts": "^2.15.0",
|
"recharts": "2.15.4",
|
||||||
"rehype-katex": "^7.0.1",
|
"rehype-katex": "^7.0.1",
|
||||||
"rehype-raw": "^7.0.0",
|
"rehype-raw": "^7.0.0",
|
||||||
"remark-gfm": "^4.0.0",
|
"remark-gfm": "^4.0.0",
|
||||||
"remark-math": "^6.0.0",
|
"remark-math": "^6.0.0",
|
||||||
"remark-toc": "^9.0.0",
|
"remark-toc": "^9.0.0",
|
||||||
"rtl-detect": "^1.1.2",
|
"rtl-detect": "^1.1.2",
|
||||||
"sonner": "^1.7.2",
|
"sonner": "^2.0.7",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2",
|
||||||
"zod": "^3.24.1"
|
"zod": "^4.1.5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@turbo/gen": "^2.3.3",
|
"@turbo/gen": "^2.5.6",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^24.3.0",
|
||||||
"@types/react": "^19.0.4",
|
"@types/react": "^19.1.12",
|
||||||
"@types/react-dom": "^19.0.2",
|
"@types/react-dom": "^19.1.9",
|
||||||
"@types/react-syntax-highlighter": "^15.5.13",
|
"@types/react-syntax-highlighter": "^15.5.13",
|
||||||
"@types/rtl-detect": "^1.0.3",
|
"@types/rtl-detect": "^1.0.3",
|
||||||
"@workspace/eslint-config": "workspace:*",
|
"@workspace/eslint-config": "workspace:*",
|
||||||
"@workspace/typescript-config": "workspace:*",
|
"@workspace/typescript-config": "workspace:*",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.21",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.5.6",
|
||||||
"react": "^19.0.0",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.1.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5.7.3"
|
"typescript": "^5.9.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,69 +1,178 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
import { ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon } from 'lucide-react';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { DayPicker } from 'react-day-picker';
|
import { DayButton, DayPicker, getDefaultClassNames } from 'react-day-picker';
|
||||||
|
|
||||||
import { buttonVariants } from '@workspace/ui/components/button';
|
import { Button, buttonVariants } from '@workspace/ui/components/button';
|
||||||
import { cn } from '@workspace/ui/lib/utils';
|
import { cn } from '@workspace/ui/lib/utils';
|
||||||
|
|
||||||
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
export type CalendarProps = React.ComponentProps<typeof DayPicker>;
|
||||||
|
|
||||||
function Calendar({ className, classNames, showOutsideDays = true, ...props }: CalendarProps) {
|
function Calendar({
|
||||||
|
className,
|
||||||
|
classNames,
|
||||||
|
showOutsideDays = true,
|
||||||
|
captionLayout = 'label',
|
||||||
|
buttonVariant = 'ghost',
|
||||||
|
formatters,
|
||||||
|
components,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DayPicker> & {
|
||||||
|
buttonVariant?: React.ComponentProps<typeof Button>['variant'];
|
||||||
|
}) {
|
||||||
|
const defaultClassNames = getDefaultClassNames();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DayPicker
|
<DayPicker
|
||||||
showOutsideDays={showOutsideDays}
|
showOutsideDays={showOutsideDays}
|
||||||
className={cn('p-3', className)}
|
className={cn(
|
||||||
|
'bg-background group/calendar p-3 [--cell-size:2rem] [[data-slot=card-content]_&]:bg-transparent [[data-slot=popover-content]_&]:bg-transparent',
|
||||||
|
String.raw`rtl:**:[.rdp-button\_next>svg]:rotate-180`,
|
||||||
|
String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
captionLayout={captionLayout}
|
||||||
|
formatters={{
|
||||||
|
formatMonthDropdown: (date) => date.toLocaleString('default', { month: 'short' }),
|
||||||
|
...formatters,
|
||||||
|
}}
|
||||||
classNames={{
|
classNames={{
|
||||||
months: 'flex flex-col sm:flex-row space-y-4 sm:space-x-4 sm:space-y-0',
|
root: cn('w-fit', defaultClassNames.root),
|
||||||
month: 'space-y-4',
|
months: cn('relative flex flex-col gap-4 md:flex-row', defaultClassNames.months),
|
||||||
caption: 'flex justify-center pt-1 relative items-center',
|
month: cn('flex w-full flex-col gap-4', defaultClassNames.month),
|
||||||
caption_label: 'text-sm font-medium',
|
nav: cn(
|
||||||
nav: 'space-x-1 flex items-center',
|
'absolute inset-x-0 top-0 flex w-full items-center justify-between gap-1',
|
||||||
nav_button: cn(
|
defaultClassNames.nav,
|
||||||
buttonVariants({ variant: 'outline' }),
|
|
||||||
'h-7 w-7 bg-transparent p-0 opacity-50 hover:opacity-100',
|
|
||||||
),
|
),
|
||||||
nav_button_previous: 'absolute left-1',
|
button_previous: cn(
|
||||||
nav_button_next: 'absolute right-1',
|
buttonVariants({ variant: buttonVariant }),
|
||||||
table: 'w-full border-collapse space-y-1',
|
'h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50',
|
||||||
head_row: 'flex',
|
defaultClassNames.button_previous,
|
||||||
head_cell: 'text-muted-foreground rounded-md w-8 font-normal text-[0.8rem]',
|
),
|
||||||
row: 'flex w-full mt-2',
|
button_next: cn(
|
||||||
cell: cn(
|
buttonVariants({ variant: buttonVariant }),
|
||||||
'relative p-0 text-center text-sm focus-within:relative focus-within:z-20 [&:has([aria-selected])]:bg-accent [&:has([aria-selected].day-outside)]:bg-accent/50 [&:has([aria-selected].day-range-end)]:rounded-r-md',
|
'h-[--cell-size] w-[--cell-size] select-none p-0 aria-disabled:opacity-50',
|
||||||
props.mode === 'range'
|
defaultClassNames.button_next,
|
||||||
? '[&:has(>.day-range-end)]:rounded-r-md [&:has(>.day-range-start)]:rounded-l-md first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md'
|
),
|
||||||
: '[&:has([aria-selected])]:rounded-md',
|
month_caption: cn(
|
||||||
|
'flex h-[--cell-size] w-full items-center justify-center px-[--cell-size]',
|
||||||
|
defaultClassNames.month_caption,
|
||||||
|
),
|
||||||
|
dropdowns: cn(
|
||||||
|
'flex h-[--cell-size] w-full items-center justify-center gap-1.5 text-sm font-medium',
|
||||||
|
defaultClassNames.dropdowns,
|
||||||
|
),
|
||||||
|
dropdown_root: cn(
|
||||||
|
'has-focus:border-ring border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] relative rounded-md border',
|
||||||
|
defaultClassNames.dropdown_root,
|
||||||
|
),
|
||||||
|
dropdown: cn('bg-popover absolute inset-0 opacity-0', defaultClassNames.dropdown),
|
||||||
|
caption_label: cn(
|
||||||
|
'select-none font-medium',
|
||||||
|
captionLayout === 'label'
|
||||||
|
? 'text-sm'
|
||||||
|
: '[&>svg]:text-muted-foreground flex h-8 items-center gap-1 rounded-md pl-2 pr-1 text-sm [&>svg]:size-3.5',
|
||||||
|
defaultClassNames.caption_label,
|
||||||
|
),
|
||||||
|
table: 'w-full border-collapse',
|
||||||
|
weekdays: cn('flex', defaultClassNames.weekdays),
|
||||||
|
weekday: cn(
|
||||||
|
'text-muted-foreground flex-1 select-none rounded-md text-[0.8rem] font-normal',
|
||||||
|
defaultClassNames.weekday,
|
||||||
|
),
|
||||||
|
week: cn('mt-2 flex w-full', defaultClassNames.week),
|
||||||
|
week_number_header: cn('w-[--cell-size] select-none', defaultClassNames.week_number_header),
|
||||||
|
week_number: cn(
|
||||||
|
'text-muted-foreground select-none text-[0.8rem]',
|
||||||
|
defaultClassNames.week_number,
|
||||||
),
|
),
|
||||||
day: cn(
|
day: cn(
|
||||||
buttonVariants({ variant: 'ghost' }),
|
'group/day relative aspect-square h-full w-full select-none p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md',
|
||||||
'h-8 w-8 p-0 font-normal aria-selected:opacity-100',
|
defaultClassNames.day,
|
||||||
),
|
),
|
||||||
day_range_start: 'day-range-start',
|
range_start: cn('bg-accent rounded-l-md', defaultClassNames.range_start),
|
||||||
day_range_end: 'day-range-end',
|
range_middle: cn('rounded-none', defaultClassNames.range_middle),
|
||||||
day_selected:
|
range_end: cn('bg-accent rounded-r-md', defaultClassNames.range_end),
|
||||||
'bg-primary text-primary-foreground hover:bg-primary hover:text-primary-foreground focus:bg-primary focus:text-primary-foreground',
|
today: cn(
|
||||||
day_today: 'bg-accent text-accent-foreground',
|
'bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none',
|
||||||
day_outside:
|
defaultClassNames.today,
|
||||||
'day-outside text-muted-foreground aria-selected:bg-accent/50 aria-selected:text-muted-foreground',
|
),
|
||||||
day_disabled: 'text-muted-foreground opacity-50',
|
outside: cn(
|
||||||
day_range_middle: 'aria-selected:bg-accent aria-selected:text-accent-foreground',
|
'text-muted-foreground aria-selected:text-muted-foreground',
|
||||||
day_hidden: 'invisible',
|
defaultClassNames.outside,
|
||||||
|
),
|
||||||
|
disabled: cn('text-muted-foreground opacity-50', defaultClassNames.disabled),
|
||||||
|
hidden: cn('invisible', defaultClassNames.hidden),
|
||||||
...classNames,
|
...classNames,
|
||||||
}}
|
}}
|
||||||
components={{
|
components={{
|
||||||
IconLeft: ({ className, ...props }) => (
|
Root: ({ className, rootRef, ...props }) => {
|
||||||
<ChevronLeft className={cn('h-4 w-4', className)} {...props} />
|
return <div data-slot='calendar' ref={rootRef} className={cn(className)} {...props} />;
|
||||||
),
|
},
|
||||||
IconRight: ({ className, ...props }) => (
|
Chevron: ({ className, orientation, ...props }) => {
|
||||||
<ChevronRight className={cn('h-4 w-4', className)} {...props} />
|
if (orientation === 'left') {
|
||||||
),
|
return <ChevronLeftIcon className={cn('size-4', className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orientation === 'right') {
|
||||||
|
return <ChevronRightIcon className={cn('size-4', className)} {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ChevronDownIcon className={cn('size-4', className)} {...props} />;
|
||||||
|
},
|
||||||
|
DayButton: CalendarDayButton,
|
||||||
|
WeekNumber: ({ children, ...props }) => {
|
||||||
|
return (
|
||||||
|
<td {...props}>
|
||||||
|
<div className='flex size-[--cell-size] items-center justify-center text-center'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
...components,
|
||||||
}}
|
}}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Calendar.displayName = 'Calendar';
|
|
||||||
|
|
||||||
export { Calendar };
|
function CalendarDayButton({
|
||||||
|
className,
|
||||||
|
day,
|
||||||
|
modifiers,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DayButton>) {
|
||||||
|
const defaultClassNames = getDefaultClassNames();
|
||||||
|
|
||||||
|
const ref = React.useRef<HTMLButtonElement>(null);
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (modifiers.focused) ref.current?.focus();
|
||||||
|
}, [modifiers.focused]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
ref={ref}
|
||||||
|
variant='ghost'
|
||||||
|
size='icon'
|
||||||
|
data-day={day.date.toLocaleDateString()}
|
||||||
|
data-selected-single={
|
||||||
|
modifiers.selected &&
|
||||||
|
!modifiers.range_start &&
|
||||||
|
!modifiers.range_end &&
|
||||||
|
!modifiers.range_middle
|
||||||
|
}
|
||||||
|
data-range-start={modifiers.range_start}
|
||||||
|
data-range-end={modifiers.range_end}
|
||||||
|
data-range-middle={modifiers.range_middle}
|
||||||
|
className={cn(
|
||||||
|
'data-[selected-single=true]:bg-primary data-[selected-single=true]:text-primary-foreground data-[range-middle=true]:bg-accent data-[range-middle=true]:text-accent-foreground data-[range-start=true]:bg-primary data-[range-start=true]:text-primary-foreground data-[range-end=true]:bg-primary data-[range-end=true]:text-primary-foreground group-data-[focused=true]/day:border-ring group-data-[focused=true]/day:ring-ring/50 flex aspect-square h-auto w-full min-w-[--cell-size] flex-col gap-1 font-normal leading-none data-[range-end=true]:rounded-md data-[range-middle=true]:rounded-none data-[range-start=true]:rounded-md group-data-[focused=true]/day:relative group-data-[focused=true]/day:z-10 group-data-[focused=true]/day:ring-[3px] [&>span]:text-xs [&>span]:opacity-70',
|
||||||
|
defaultClassNames.day,
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Calendar, CalendarDayButton };
|
||||||
|
|||||||
@ -39,7 +39,6 @@ const CommandInput = React.forwardRef<
|
|||||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
React.ElementRef<typeof CommandPrimitive.Input>,
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
// eslint-disable-next-line react/no-unknown-property
|
|
||||||
<div className='flex items-center border-b px-3' cmdk-input-wrapper=''>
|
<div className='flex items-center border-b px-3' cmdk-input-wrapper=''>
|
||||||
<Search className='mr-2 h-4 w-4 shrink-0 opacity-50' />
|
<Search className='mr-2 h-4 w-4 shrink-0 opacity-50' />
|
||||||
<CommandPrimitive.Input
|
<CommandPrimitive.Input
|
||||||
|
|||||||
@ -46,7 +46,7 @@ const ContextMenuSubContent = React.forwardRef<
|
|||||||
<ContextMenuPrimitive.SubContent
|
<ContextMenuPrimitive.SubContent
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg',
|
'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 z-50 min-w-[8rem] origin-[--radix-context-menu-content-transform-origin] overflow-hidden rounded-md border p-1 shadow-lg',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -62,7 +62,7 @@ const ContextMenuContent = React.forwardRef<
|
|||||||
<ContextMenuPrimitive.Content
|
<ContextMenuPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
|
'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 z-50 max-h-[--radix-context-menu-content-available-height] min-w-[8rem] origin-[--radix-context-menu-content-transform-origin] overflow-y-auto overflow-x-hidden rounded-md border p-1 shadow-md',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -13,13 +13,13 @@ const Drawer = ({
|
|||||||
);
|
);
|
||||||
Drawer.displayName = 'Drawer';
|
Drawer.displayName = 'Drawer';
|
||||||
|
|
||||||
const DrawerTrigger = DrawerPrimitive.Trigger;
|
const DrawerTrigger: typeof DrawerPrimitive.Trigger = DrawerPrimitive.Trigger;
|
||||||
|
|
||||||
const DrawerPortal = DrawerPrimitive.Portal;
|
const DrawerPortal = DrawerPrimitive.Portal;
|
||||||
|
|
||||||
const DrawerClose = DrawerPrimitive.Close;
|
const DrawerClose: typeof DrawerPrimitive.Close = DrawerPrimitive.Close;
|
||||||
|
|
||||||
const DrawerOverlay = React.forwardRef<
|
const DrawerOverlay: typeof DrawerPrimitive.Overlay = React.forwardRef<
|
||||||
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
React.ElementRef<typeof DrawerPrimitive.Overlay>,
|
||||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Overlay>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
@ -31,7 +31,7 @@ const DrawerOverlay = React.forwardRef<
|
|||||||
));
|
));
|
||||||
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName;
|
||||||
|
|
||||||
const DrawerContent = React.forwardRef<
|
const DrawerContent: typeof DrawerPrimitive.Content = React.forwardRef<
|
||||||
React.ElementRef<typeof DrawerPrimitive.Content>,
|
React.ElementRef<typeof DrawerPrimitive.Content>,
|
||||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content>
|
||||||
>(({ className, children, ...props }, ref) => (
|
>(({ className, children, ...props }, ref) => (
|
||||||
@ -62,7 +62,7 @@ const DrawerFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivEleme
|
|||||||
);
|
);
|
||||||
DrawerFooter.displayName = 'DrawerFooter';
|
DrawerFooter.displayName = 'DrawerFooter';
|
||||||
|
|
||||||
const DrawerTitle = React.forwardRef<
|
const DrawerTitle: typeof DrawerPrimitive.Title = React.forwardRef<
|
||||||
React.ElementRef<typeof DrawerPrimitive.Title>,
|
React.ElementRef<typeof DrawerPrimitive.Title>,
|
||||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Title>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
@ -74,7 +74,7 @@ const DrawerTitle = React.forwardRef<
|
|||||||
));
|
));
|
||||||
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
|
DrawerTitle.displayName = DrawerPrimitive.Title.displayName;
|
||||||
|
|
||||||
const DrawerDescription = React.forwardRef<
|
const DrawerDescription: typeof DrawerPrimitive.Description = React.forwardRef<
|
||||||
React.ElementRef<typeof DrawerPrimitive.Description>,
|
React.ElementRef<typeof DrawerPrimitive.Description>,
|
||||||
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Description>
|
||||||
>(({ className, ...props }, ref) => (
|
>(({ className, ...props }, ref) => (
|
||||||
|
|||||||
@ -46,7 +46,7 @@ const DropdownMenuSubContent = React.forwardRef<
|
|||||||
<DropdownMenuPrimitive.SubContent
|
<DropdownMenuPrimitive.SubContent
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg',
|
'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 z-50 min-w-[8rem] origin-[--radix-dropdown-menu-content-transform-origin] overflow-hidden rounded-md border p-1 shadow-lg',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -63,8 +63,8 @@ const DropdownMenuContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-popover text-popover-foreground z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-md',
|
'bg-popover text-popover-foreground z-50 max-h-[var(--radix-dropdown-menu-content-available-height)] min-w-[8rem] overflow-y-auto overflow-x-hidden rounded-md border p-1 shadow-md',
|
||||||
'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',
|
'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 origin-[--radix-dropdown-menu-content-transform-origin]',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -5,11 +5,11 @@ import { Slot } from '@radix-ui/react-slot';
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import {
|
import {
|
||||||
Controller,
|
Controller,
|
||||||
ControllerProps,
|
|
||||||
FieldPath,
|
|
||||||
FieldValues,
|
|
||||||
FormProvider,
|
FormProvider,
|
||||||
useFormContext,
|
useFormContext,
|
||||||
|
type ControllerProps,
|
||||||
|
type FieldPath,
|
||||||
|
type FieldValues,
|
||||||
} from 'react-hook-form';
|
} from 'react-hook-form';
|
||||||
|
|
||||||
import { Label } from '@workspace/ui/components/label';
|
import { Label } from '@workspace/ui/components/label';
|
||||||
@ -138,7 +138,7 @@ const FormMessage = React.forwardRef<
|
|||||||
React.HTMLAttributes<HTMLParagraphElement>
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
>(({ className, children, ...props }, ref) => {
|
>(({ className, children, ...props }, ref) => {
|
||||||
const { error, formMessageId } = useFormField();
|
const { error, formMessageId } = useFormField();
|
||||||
const body = error ? String(error?.message) : children;
|
const body = error ? String(error?.message ?? '') : children;
|
||||||
|
|
||||||
if (!body) {
|
if (!body) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const HoverCardContent = React.forwardRef<
|
|||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
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 z-50 w-64 rounded-md border p-4 shadow-md outline-none',
|
'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 z-50 w-64 origin-[--radix-hover-card-content-transform-origin] rounded-md border p-4 shadow-md outline-none',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -6,15 +6,25 @@ import * as React from 'react';
|
|||||||
|
|
||||||
import { cn } from '@workspace/ui/lib/utils';
|
import { cn } from '@workspace/ui/lib/utils';
|
||||||
|
|
||||||
const MenubarMenu = MenubarPrimitive.Menu;
|
function MenubarMenu({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Menu>) {
|
||||||
|
return <MenubarPrimitive.Menu {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
const MenubarGroup = MenubarPrimitive.Group;
|
function MenubarGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Group>) {
|
||||||
|
return <MenubarPrimitive.Group {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
const MenubarPortal = MenubarPrimitive.Portal;
|
function MenubarPortal({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Portal>) {
|
||||||
|
return <MenubarPrimitive.Portal {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
const MenubarSub = MenubarPrimitive.Sub;
|
function MenubarRadioGroup({ ...props }: React.ComponentProps<typeof MenubarPrimitive.RadioGroup>) {
|
||||||
|
return <MenubarPrimitive.RadioGroup {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
const MenubarRadioGroup = MenubarPrimitive.RadioGroup;
|
function MenubarSub({ ...props }: React.ComponentProps<typeof MenubarPrimitive.Sub>) {
|
||||||
|
return <MenubarPrimitive.Sub data-slot='menubar-sub' {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
const Menubar = React.forwardRef<
|
const Menubar = React.forwardRef<
|
||||||
React.ElementRef<typeof MenubarPrimitive.Root>,
|
React.ElementRef<typeof MenubarPrimitive.Root>,
|
||||||
@ -74,7 +84,7 @@ const MenubarSubContent = React.forwardRef<
|
|||||||
<MenubarPrimitive.SubContent
|
<MenubarPrimitive.SubContent
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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 z-50 min-w-[8rem] overflow-hidden rounded-md border p-1 shadow-lg',
|
'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 z-50 min-w-[8rem] origin-[--radix-menubar-content-transform-origin] overflow-hidden rounded-md border p-1 shadow-lg',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -93,7 +103,7 @@ const MenubarContent = React.forwardRef<
|
|||||||
alignOffset={alignOffset}
|
alignOffset={alignOffset}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-popover text-popover-foreground data-[state=open]:animate-in 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 z-50 min-w-[12rem] overflow-hidden rounded-md border p-1 shadow-md',
|
'bg-popover text-popover-foreground data-[state=open]:animate-in 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 z-50 min-w-[12rem] origin-[--radix-menubar-content-transform-origin] overflow-hidden rounded-md border p-1 shadow-md',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -35,7 +35,7 @@ NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName;
|
|||||||
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
const NavigationMenuItem = NavigationMenuPrimitive.Item;
|
||||||
|
|
||||||
const navigationMenuTriggerStyle = cva(
|
const navigationMenuTriggerStyle = cva(
|
||||||
'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50',
|
'group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[state=open]:text-accent-foreground data-[state=open]:bg-accent/50 data-[state=open]:hover:bg-accent data-[state=open]:focus:bg-accent',
|
||||||
);
|
);
|
||||||
|
|
||||||
const NavigationMenuTrigger = React.forwardRef<
|
const NavigationMenuTrigger = React.forwardRef<
|
||||||
|
|||||||
@ -21,7 +21,7 @@ const PopoverContent = React.forwardRef<
|
|||||||
align={align}
|
align={align}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
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 z-50 w-72 rounded-md border p-4 shadow-md outline-none',
|
'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 z-50 w-72 origin-[--radix-popover-content-transform-origin] rounded-md border p-4 shadow-md outline-none',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const SelectTrigger = React.forwardRef<
|
|||||||
<SelectPrimitive.Trigger
|
<SelectPrimitive.Trigger
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'border-input ring-offset-background placeholder:text-muted-foreground focus:ring-ring flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-1 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
'border-input ring-offset-background data-[placeholder]:text-muted-foreground focus:ring-ring flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border bg-transparent px-3 py-2 text-sm shadow-sm focus:outline-none focus:ring-1 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -68,7 +68,7 @@ const SelectContent = React.forwardRef<
|
|||||||
<SelectPrimitive.Content
|
<SelectPrimitive.Content
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
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-96 min-w-[8rem] overflow-hidden rounded-md border shadow-md',
|
'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-y-auto overflow-x-hidden rounded-md border shadow-md',
|
||||||
position === 'popper' &&
|
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',
|
'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,
|
className,
|
||||||
|
|||||||
@ -8,7 +8,13 @@ import * as React from 'react';
|
|||||||
import { Button } from '@workspace/ui/components/button';
|
import { Button } from '@workspace/ui/components/button';
|
||||||
import { Input } from '@workspace/ui/components/input';
|
import { Input } from '@workspace/ui/components/input';
|
||||||
import { Separator } from '@workspace/ui/components/separator';
|
import { Separator } from '@workspace/ui/components/separator';
|
||||||
import { Sheet, SheetContent } from '@workspace/ui/components/sheet';
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetHeader,
|
||||||
|
SheetTitle,
|
||||||
|
} from '@workspace/ui/components/sheet';
|
||||||
import { Skeleton } from '@workspace/ui/components/skeleton';
|
import { Skeleton } from '@workspace/ui/components/skeleton';
|
||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
@ -19,14 +25,14 @@ import {
|
|||||||
import { useIsMobile } from '@workspace/ui/hooks/use-mobile';
|
import { useIsMobile } from '@workspace/ui/hooks/use-mobile';
|
||||||
import { cn } from '@workspace/ui/lib/utils';
|
import { cn } from '@workspace/ui/lib/utils';
|
||||||
|
|
||||||
const SIDEBAR_COOKIE_NAME = 'sidebar:state';
|
const SIDEBAR_COOKIE_NAME = 'sidebar_state';
|
||||||
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
|
||||||
const SIDEBAR_WIDTH = '16rem';
|
const SIDEBAR_WIDTH = '16rem';
|
||||||
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
const SIDEBAR_WIDTH_MOBILE = '18rem';
|
||||||
const SIDEBAR_WIDTH_ICON = '3rem';
|
const SIDEBAR_WIDTH_ICON = '3rem';
|
||||||
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
|
||||||
|
|
||||||
type SidebarContext = {
|
type SidebarContextProps = {
|
||||||
state: 'expanded' | 'collapsed';
|
state: 'expanded' | 'collapsed';
|
||||||
open: boolean;
|
open: boolean;
|
||||||
setOpen: (open: boolean) => void;
|
setOpen: (open: boolean) => void;
|
||||||
@ -36,7 +42,7 @@ type SidebarContext = {
|
|||||||
toggleSidebar: () => void;
|
toggleSidebar: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const SidebarContext = React.createContext<SidebarContext | null>(null);
|
const SidebarContext = React.createContext<SidebarContextProps | null>(null);
|
||||||
|
|
||||||
function useSidebar() {
|
function useSidebar() {
|
||||||
const context = React.useContext(SidebarContext);
|
const context = React.useContext(SidebarContext);
|
||||||
@ -111,7 +117,7 @@ const SidebarProvider = React.forwardRef<
|
|||||||
// This makes it easier to style the sidebar with Tailwind classes.
|
// This makes it easier to style the sidebar with Tailwind classes.
|
||||||
const state = open ? 'expanded' : 'collapsed';
|
const state = open ? 'expanded' : 'collapsed';
|
||||||
|
|
||||||
const contextValue = React.useMemo<SidebarContext>(
|
const contextValue = React.useMemo<SidebarContextProps>(
|
||||||
() => ({
|
() => ({
|
||||||
state,
|
state,
|
||||||
open,
|
open,
|
||||||
@ -201,6 +207,10 @@ const Sidebar = React.forwardRef<
|
|||||||
}
|
}
|
||||||
side={side}
|
side={side}
|
||||||
>
|
>
|
||||||
|
<SheetHeader className='sr-only'>
|
||||||
|
<SheetTitle>Sidebar</SheetTitle>
|
||||||
|
<SheetDescription>Displays the mobile sidebar.</SheetDescription>
|
||||||
|
</SheetHeader>
|
||||||
<div className='flex h-full w-full flex-col'>{children}</div>
|
<div className='flex h-full w-full flex-col'>{children}</div>
|
||||||
</SheetContent>
|
</SheetContent>
|
||||||
</Sheet>
|
</Sheet>
|
||||||
@ -219,7 +229,7 @@ const Sidebar = React.forwardRef<
|
|||||||
{/* This is what handles the sidebar gap on desktop */}
|
{/* This is what handles the sidebar gap on desktop */}
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative h-svh w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear',
|
'relative w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear',
|
||||||
'group-data-[collapsible=offcanvas]:w-0',
|
'group-data-[collapsible=offcanvas]:w-0',
|
||||||
'group-data-[side=right]:rotate-180',
|
'group-data-[side=right]:rotate-180',
|
||||||
variant === 'floating' || variant === 'inset'
|
variant === 'floating' || variant === 'inset'
|
||||||
@ -314,8 +324,8 @@ const SidebarInset = React.forwardRef<HTMLDivElement, React.ComponentProps<'main
|
|||||||
<main
|
<main
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-background relative flex min-h-svh flex-1 flex-col',
|
'bg-background relative flex w-full flex-1 flex-col',
|
||||||
'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
|
'md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@ -428,7 +438,7 @@ const SidebarGroupLabel = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
data-sidebar='group-label'
|
data-sidebar='group-label'
|
||||||
className={cn(
|
className={cn(
|
||||||
'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-none transition-[margin,opa] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
'text-sidebar-foreground/70 ring-sidebar-ring flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium outline-none transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
|
||||||
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -14,7 +14,7 @@ export const Timeline = ({ data }: { data: TimelineEntry[] }) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ref.current) {
|
if (ref.current) {
|
||||||
const rect = ref.current.getBoundingClientRect();
|
const rect = ref.current?.getBoundingClientRect?.();
|
||||||
setHeight(rect.height);
|
setHeight(rect.height);
|
||||||
}
|
}
|
||||||
}, [ref]);
|
}, [ref]);
|
||||||
|
|||||||
@ -20,7 +20,7 @@ const TooltipContent = React.forwardRef<
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
sideOffset={sideOffset}
|
sideOffset={sideOffset}
|
||||||
className={cn(
|
className={cn(
|
||||||
'bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 z-50 overflow-hidden rounded-md px-3 py-1.5 text-xs',
|
'bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-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 z-50 origin-[--radix-tooltip-content-transform-origin] overflow-hidden rounded-md px-3 py-1.5 text-xs',
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
|
|||||||
@ -2,19 +2,20 @@ import { Input } from '@workspace/ui/components/input';
|
|||||||
import { cn } from '@workspace/ui/lib/utils';
|
import { cn } from '@workspace/ui/lib/utils';
|
||||||
import { ChangeEvent, ReactNode, useEffect, useState } from 'react';
|
import { ChangeEvent, ReactNode, useEffect, useState } from 'react';
|
||||||
|
|
||||||
export interface EnhancedInputProps
|
export interface EnhancedInputProps<T = string>
|
||||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'prefix'> {
|
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'prefix' | 'value' | 'onChange'> {
|
||||||
prefix?: string | ReactNode;
|
prefix?: string | ReactNode;
|
||||||
suffix?: string | ReactNode;
|
suffix?: string | ReactNode;
|
||||||
formatInput?: (value: string | number) => string | number;
|
value?: T;
|
||||||
formatOutput?: (value: string | number) => string | number;
|
formatInput?: (value: T) => string | number;
|
||||||
onValueChange?: (value: string | number) => void;
|
formatOutput?: (value: string | number) => T;
|
||||||
onValueBlur?: (value: string | number) => void;
|
onValueChange?: (value: T) => void;
|
||||||
|
onValueBlur?: (value: T) => void;
|
||||||
min?: number;
|
min?: number;
|
||||||
max?: number;
|
max?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function EnhancedInput({
|
export function EnhancedInput<T = string>({
|
||||||
suffix,
|
suffix,
|
||||||
prefix,
|
prefix,
|
||||||
formatInput,
|
formatInput,
|
||||||
@ -24,38 +25,36 @@ export function EnhancedInput({
|
|||||||
onValueChange,
|
onValueChange,
|
||||||
onValueBlur,
|
onValueBlur,
|
||||||
...props
|
...props
|
||||||
}: EnhancedInputProps) {
|
}: EnhancedInputProps<T>) {
|
||||||
const getProcessedValue = (inputValue: unknown) => {
|
const getProcessedValue = (inputValue: unknown) => {
|
||||||
if (inputValue === '' || inputValue === 0 || inputValue === '0') return '';
|
if (inputValue === '' || inputValue === 0 || inputValue === '0') return '';
|
||||||
const newValue = String(inputValue ?? '');
|
const newValue = String(inputValue ?? '');
|
||||||
return formatInput ? formatInput(newValue) : newValue;
|
return formatInput ? formatInput(inputValue as T) : newValue;
|
||||||
};
|
};
|
||||||
|
|
||||||
const [value, setValue] = useState<string | number>(() => getProcessedValue(initialValue));
|
const [value, setValue] = useState<string | number>(() => getProcessedValue(initialValue));
|
||||||
// @ts-expect-error - This is a controlled component
|
const [internalValue, setInternalValue] = useState<T | string | number>(initialValue ?? '');
|
||||||
const [internalValue, setInternalValue] = useState<string | number>(initialValue ?? '');
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initialValue !== internalValue) {
|
if (initialValue !== internalValue) {
|
||||||
const newValue = getProcessedValue(initialValue);
|
const newValue = getProcessedValue(initialValue);
|
||||||
if (value !== newValue) {
|
if (value !== newValue) {
|
||||||
setValue(newValue);
|
setValue(newValue);
|
||||||
// @ts-expect-error - This is a controlled component
|
|
||||||
setInternalValue(initialValue ?? '');
|
setInternalValue(initialValue ?? '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [initialValue, formatInput]);
|
}, [initialValue, formatInput]);
|
||||||
|
|
||||||
const processValue = (inputValue: string | number) => {
|
const processValue = (inputValue: string | number): T => {
|
||||||
let processedValue: number | string = inputValue?.toString().trim();
|
let processedValue: number | string = inputValue?.toString().trim();
|
||||||
|
|
||||||
if (processedValue === '0' && props.type === 'number') {
|
if (processedValue === '0' && props.type === 'number') {
|
||||||
return 0;
|
return (formatOutput ? formatOutput(0) : 0) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (processedValue && props.type === 'number') processedValue = Number(processedValue);
|
if (processedValue && props.type === 'number') processedValue = Number(processedValue);
|
||||||
return formatOutput ? formatOutput(processedValue) : processedValue;
|
return formatOutput ? formatOutput(processedValue) : (processedValue as T);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -65,7 +64,7 @@ export function EnhancedInput({
|
|||||||
if (inputValue === '0') {
|
if (inputValue === '0') {
|
||||||
setValue('');
|
setValue('');
|
||||||
setInternalValue(0);
|
setInternalValue(0);
|
||||||
onValueChange?.(0);
|
onValueChange?.(processValue(0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,14 +95,14 @@ export function EnhancedInput({
|
|||||||
if (value === '-' || value === '.') {
|
if (value === '-' || value === '.') {
|
||||||
setValue('');
|
setValue('');
|
||||||
setInternalValue('');
|
setInternalValue('');
|
||||||
onValueBlur?.('');
|
onValueBlur?.('' as T);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保0值显示为空
|
// 确保0值显示为空
|
||||||
if (value === '0') {
|
if (value === '0') {
|
||||||
setValue('');
|
setValue('');
|
||||||
onValueBlur?.(0);
|
onValueBlur?.(processValue(0));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
export interface ICountry {
|
export interface ICountry {
|
||||||
name: string;
|
name: string;
|
||||||
alpha2: string;
|
alpha2: string;
|
||||||
alpha3: string | null;
|
alpha3?: string | null;
|
||||||
numeric: string | null;
|
numeric?: string | null;
|
||||||
phone: string | null;
|
phone?: string | null;
|
||||||
lang: string | null;
|
lang?: string | null;
|
||||||
langs: string[];
|
langs: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user