完善 ip 提取功能,优化更新主题样式

This commit is contained in:
2025-04-12 11:10:51 +08:00
parent e0c75f9506
commit e928b5a270
29 changed files with 615 additions and 383 deletions

View File

@@ -0,0 +1,392 @@
'use client'
import {z} from 'zod'
import {zodResolver} from '@hookform/resolvers/zod'
import {Form, FormField, FormLabel} from '@/components/ui/form'
import {RadioGroup, RadioGroupItem} from '@/components/ui/radio-group'
import {Input} from '@/components/ui/input'
import {Select, SelectContent, SelectItem, SelectSeparator, SelectTrigger, SelectValue} from '@/components/ui/select'
import {Button} from '@/components/ui/button'
import {useForm} from 'react-hook-form'
import {Alert, AlertTitle} from '@/components/ui/alert'
import {Box, CircleAlert, Loader, Timer} from 'lucide-react'
import {useEffect, useState} from 'react'
import {useStatus} from '@/lib/states'
import {allResource} from '@/actions/resource'
import {Resource, name} from '@/lib/models'
import {format, intlFormatDistance} from 'date-fns'
type ExtractProps = {}
export default function Extract(props: ExtractProps) {
const [resources, setResources] = useState<Resource[]>([])
const [status, setStatus] = useStatus()
const schema = z.object({
resource: z.number().optional(),
prov: z.string().optional(),
city: z.string().optional(),
regionType: z.enum(['unlimited', 'specific']).default('unlimited'),
isp: z.enum(['all', '1', '2', '3']),
proto: z.enum(['all', '1', '2', '3']),
distinct: z.enum(['1', '0']),
format: z.enum(['text', 'json']),
separator: z.string(),
breaker: z.string(),
count: z.number().min(1),
})
type Schema = z.infer<typeof schema>
const form = useForm<Schema>({
resolver: zodResolver(schema),
defaultValues: {
regionType: 'unlimited',
isp: 'all',
proto: 'all',
count: 1,
distinct: '1',
format: 'text',
breaker: '\\n',
separator: '|',
},
})
const regionType = form.watch('regionType')
const onSubmit = (values: z.infer<typeof schema>) => {
console.log(values)
}
const getResources = async () => {
setStatus('load')
try {
const resp = await allResource()
if (!resp.success) {
throw new Error('Unable to fetch packages.')
}
setResources(resp.data)
setStatus('done')
}
catch (error) {
console.error('Error fetching packages:', error)
setStatus('fail')
}
}
useEffect(() => {
getResources().then()
}, [])
return (
<Form
form={form}
onSubmit={onSubmit}
className={`p-4 bg-white flex flex-col gap-4 rounded-md`}
>
<Alert variant={`warn`}>
<CircleAlert/>
<AlertTitle>IP前需要将本机IP添加到白名单后才可使用</AlertTitle>
</Alert>
<div className={`flex flex-col gap-y-6`}>
{/* 选择套餐 */}
<div className="flex items-center">
<FormField name="resource" label={`选择套餐`}>
{({field}) => (
<Select
value={field.value ? String(field.value) : undefined}
onValueChange={value => field.onChange(Number(value))}
>
<SelectTrigger className={`min-h-10 h-auto w-84`}>
<SelectValue placeholder={`选择套餐`}/>
</SelectTrigger>
<SelectContent>
{status === 'load' ? (
<div className={`p-4 flex gap-1 items-center`}>
<Loader className={`animate-spin`} size={20}/>
<span>...</span>
</div>
) : resources.length === 0 ? (
<SelectItem value="0">
</SelectItem>
) : resources.map((resource, i) => (<>
<SelectItem
key={`${resource.id}`} value={String(resource.id)} className={`p-3`}>
<div className={`flex flex-col gap-2 w-72`}>
{resource.type === 1 && resource.pss.type === 1 && (<>
<div className={`flex gap-2 items-center bg-green-50 w-fit px-2 py-1 rounded-md text-sm`}>
<Timer size={20}/>
<span>{name(resource)}</span>
</div>
<div className={`flex justify-between gap-2 text-xs text-weak`}>
<span>{format(resource.pss.expire, 'yyyy-MM-dd HH:mm')}</span>
<span>{intlFormatDistance(resource.pss.expire, new Date())}</span>
</div>
</>)}
{resource.type === 1 && resource.pss.type === 2 && (<>
<div className={`flex gap-2 items-center bg-blue-50 w-fit px-2 py-1 rounded-md text-sm`}>
<Box size={20}/>
<span>{name(resource)}</span>
</div>
<div className={`flex justify-between gap-2 text-xs text-weak`}>
<span>{resource.pss.used} / {resource.pss.quota}</span>
<span> {resource.pss.quota - resource.pss.used}</span>
</div>
</>)}
</div>
</SelectItem>
{i < resources.length - 1 && <SelectSeparator className={`m-2`}/>}
</>))
}
</SelectContent>
</Select>
)}
</FormField>
</div>
{/* 地区筛选 */}
<div className="flex flex-col gap-4">
<FormField name="regionType" label={`地区筛选`}>
{({id, field}) => (
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex gap-4"
>
<FormLabel htmlFor={`${id}-v-unlimited`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="unlimited" id={`${id}-v-unlimited`} className="mr-2"/>
<span></span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-specific`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="specific" id={`${id}-v-specific`} className="mr-2"/>
<span></span>
</FormLabel>
</RadioGroup>
)}
</FormField>
{regionType === 'specific' && (
<div className="flex gap-4">
<FormField name="prov" label="">
{({field}) => (
<Select
value={field.value}
onValueChange={field.onChange}
>
<SelectTrigger className="w-40">
<SelectValue placeholder="选择省份"/>
</SelectTrigger>
<SelectContent>
<SelectItem value="bj"></SelectItem>
<SelectItem value="sh"></SelectItem>
<SelectItem value="gd">广</SelectItem>
{/* 更多省份... */}
</SelectContent>
</Select>
)}
</FormField>
<FormField name="city" label="">
{({field}) => (
<Select
value={field.value}
onValueChange={field.onChange}
disabled={!form.watch('prov')}
>
<SelectTrigger className="w-40">
<SelectValue placeholder="选择城市"/>
</SelectTrigger>
<SelectContent>
{form.watch('prov') === 'bj' && <SelectItem value="bj01"></SelectItem>}
{form.watch('prov') === 'sh' && <SelectItem value="sh01"></SelectItem>}
{form.watch('prov') === 'gd' && (
<>
<SelectItem value="gz">广</SelectItem>
<SelectItem value="sz"></SelectItem>
</>
)}
{/* 更多城市... */}
</SelectContent>
</Select>
)}
</FormField>
</div>
)}
</div>
{/* 运营商筛选 */}
<div className="flex items-center">
<FormField name="isp" label={`运营商筛选`}>
{({id, field}) => (
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex gap-4">
<FormLabel htmlFor={`${id}-v-all`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="all" id={`${id}-v-all`}/>
<span></span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-telecom`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="1" id={`${id}-v-telecom`}/>
<span></span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-mobile`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="2" id={`${id}-v-mobile`}/>
<span></span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-unicom`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="3" id={`${id}-v-unicom`}/>
<span></span>
</FormLabel>
</RadioGroup>
)}
</FormField>
</div>
{/* 协议类型 */}
<div className="flex items-center">
<FormField name="proto" label={`协议类型`}>
{({id, field}) => (
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex gap-4">
<FormLabel htmlFor={`${id}-v-all`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="all" id={`${id}-v-all`} className="mr-2"/>
<span></span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-http`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="1" id={`${id}-v-http`} className="mr-2"/>
<span>HTTP</span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-https`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="2" id={`${id}-v-https`} className="mr-2"/>
<span>HTTPS</span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-socks5`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="3" id={`${id}-v-socks5`} className="mr-2"/>
<span>SOCKS5</span>
</FormLabel>
</RadioGroup>
)}
</FormField>
</div>
{/* 去重选项 */}
<div className="flex items-center">
<FormField name="distinct" label={`去重选项`}>
{({id, field}) => (
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex gap-4">
<FormLabel htmlFor={`${id}-v-true`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="1" id={`${id}-v-true`} className="mr-2"/>
<span></span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-false`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="0" id={`${id}-v-false`} className="mr-2"/>
<span></span>
</FormLabel>
</RadioGroup>
)}
</FormField>
</div>
{/* 导出格式 */}
<div className="flex items-center">
<FormField name="format" label={`导出格式`}>
{({id, field}) => (
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex gap-4"
>
<FormLabel htmlFor={`${id}-v-txt`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="text" id={`${id}-v-txt`} className="mr-2"/>
<span>TXT </span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-json`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="json" id={`${id}-v-json`} className="mr-2"/>
<span>JSON </span>
</FormLabel>
</RadioGroup>
)}
</FormField>
</div>
{/* 分隔符 */}
<div className="flex items-center">
<FormField name="separator" label={`分隔符`}>
{({id, field}) => (
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex gap-4">
<FormLabel htmlFor={`${id}-v-comma`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="|" id={`${id}-v-comma`} className="mr-2"/>
<span>线 ( | )</span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-semicolon`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value=":" id={`${id}-v-semicolon`} className="mr-2"/>
<span> ( : )</span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-space`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="\t" id={`${id}-v-space`} className="mr-2"/>
<span> ( \t )</span>
</FormLabel>
</RadioGroup>
)}
</FormField>
</div>
{/* 换行符 */}
<div className="flex items-center">
<FormField name="breaker" label={`换行符`}>
{({id, field}) => (
<RadioGroup
onValueChange={field.onChange}
defaultValue={field.value}
className="flex gap-4">
<FormLabel htmlFor={`${id}-v-newline`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="\n" id={`${id}-v-newline`} className="mr-2"/>
<span> ( \n )</span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-newline3`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="\r" id={`${id}-v-newline3`} className="mr-2"/>
<span> ( \r )</span>
</FormLabel>
<FormLabel htmlFor={`${id}-v-newline2`} className={`px-3 h-10 border rounded-md flex items-center w-40 text-sm`}>
<RadioGroupItem value="\r\n" id={`${id}-v-newline2`} className="mr-2"/>
<span> ( \r\n )</span>
</FormLabel>
</RadioGroup>
)}
</FormField>
</div>
{/* 提取数量 */}
<div className="flex items-center">
<FormField name="count" label={`提取数量`}>
{({id, field}) => (
<Input
{...field}
id={id}
type="number"
onChange={e => field.onChange(Number(e.target.value))}
className="h-10 w-84"
placeholder="输入提取数量"
/>
)}
</FormField>
</div>
</div>
<div className="flex mt-6 justify-center">
<Button type="submit" className="w-40 h-10 bg-blue-500 text-white rounded-md"></Button>
</div>
</Form>
)
}

View File

@@ -83,7 +83,7 @@ export default function Center() {
{({id, field}) => (
<div className={`flex gap-2 items-center`}>
<Button
variant={`outline`}
theme={`outline`}
type="button"
className={`h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg`}
onClick={() => form.setValue('quota', Math.max(10_000, Number(field.value) - 5_000))}
@@ -99,7 +99,7 @@ export default function Center() {
step={5_000}
/>
<Button
variant={`outline`}
theme={`outline`}
type="button"
className={`h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg`}
onClick={() => form.setValue('quota', Number(field.value) + 5_000)}>
@@ -140,7 +140,7 @@ export default function Center() {
{({id, field}) => (
<div className={`flex gap-2 items-center`}>
<Button
variant={`outline`}
theme={`outline`}
type="button"
className={`h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg`}
onClick={() => form.setValue('daily_limit', Math.max(2_000, Number(field.value) - 1_000))}
@@ -156,7 +156,7 @@ export default function Center() {
step={1_000}
/>
<Button
variant={`outline`}
theme={`outline`}
type="button"
className={`h-10 w-10 border border-gray-200 rounded-sm flex items-center justify-center text-lg`}
onClick={() => form.setValue('daily_limit', Number(field.value) + 1_000)}>

View File

@@ -61,7 +61,7 @@ export default function RechargeModal(props: RechargeModelProps) {
return (
<Dialog>
<DialogTrigger asChild>
<Button variant={`accent`} type={`button`} className={`px-4 h-8`}></Button>
<Button theme={`accent`} type={`button`} className={`px-4 h-8`}></Button>
</DialogTrigger>
<DialogContent>

View File

@@ -54,7 +54,7 @@ export default function DataTable<T extends Record<string, unknown>>(props: Data
<TableBody>
{props.status === 'fail' ? (
<TableRow>
<TableCell colSpan={props.columns.length} className={`text-center text-destructive`}></TableCell>
<TableCell colSpan={props.columns.length} className={`text-center text-fail`}></TableCell>
</TableRow>
) : !props.data?.length ? (
<TableRow>

View File

@@ -26,7 +26,7 @@ export default function DatePicker(props: DatePickerProps) {
<Popover>
<PopoverTrigger asChild>
<Button
variant={'outline'}
theme={'outline'}
className={merge(
'w-40 justify-start text-left font-normal h-9',
!props.value && 'text-muted-foreground',

View File

@@ -0,0 +1,68 @@
import * as React from 'react'
import {cva, type VariantProps} from 'class-variance-authority'
import {merge} from '@/lib/utils'
const alertVariants = cva(
'relative w-full rounded-lg px-4 py-3 text-sm grid has-[>svg]:grid-cols-[calc(var(--spacing)*4)_1fr] grid-cols-[0_1fr] has-[>svg]:gap-x-3 gap-y-0.5 items-start [&>svg]:size-4 [&>svg]:translate-y-0.5 [&>svg]:text-current',
{
variants: {
variant: {
default: 'bg-card text-card-foreground',
primary: '',
done: '',
warn: 'text-warn bg-warn/10',
fail: 'text-fail bg-card [&>svg]:text-current *:data-[slot=alert-description]:text-fail/90',
},
},
defaultVariants: {
variant: 'default',
},
},
)
function Alert({
className,
variant,
...props
}: React.ComponentProps<'div'> & VariantProps<typeof alertVariants>) {
return (
<div
data-slot="alert"
role="alert"
className={merge(alertVariants({variant}), className)}
{...props}
/>
)
}
function AlertTitle({className, ...props}: React.ComponentProps<'div'>) {
return (
<div
data-slot="alert-title"
className={merge(
'col-start-2 line-clamp-1 min-h-4 font-medium tracking-tight',
className,
)}
{...props}
/>
)
}
function AlertDescription({
className,
...props
}: React.ComponentProps<'div'>) {
return (
<div
data-slot="alert-description"
className={merge(
'text-muted-foreground col-start-2 grid justify-items-start gap-1 text-sm [&_p]:leading-relaxed',
className,
)}
{...props}
/>
)
}
export {Alert, AlertTitle, AlertDescription}

View File

@@ -2,38 +2,6 @@ import * as React from 'react'
import {merge} from '@/lib/utils'
import {cva} from 'class-variance-authority'
type ButtonProps = React.ComponentProps<'button'> & {
variant?: 'default' | 'outline' | 'gradient' | 'danger' | 'accent'
}
function Button(rawProps: ButtonProps) {
const {className, variant, ...props} = rawProps
return (
<button
className={merge(
`transition-all duration-200 ease-in-out`,
`h-10 px-4 rounded-md cursor-pointer`,
'whitespace-nowrap',
'inline-flex items-center justify-center gap-2',
'disabled:pointer-events-none disabled:opacity-50 ',
'[&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4 shrink-0 [&_svg]:shrink-0 ',
'outline-none focus-visible:ring-4 ring-blue-200',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
{
gradient: 'bg-gradient-to-r from-blue-400 to-cyan-300 text-white ring-offset-2',
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
accent: 'bg-accent text-accent-foreground hover:bg-accent/90',
danger: 'bg-destructive text-white hover:bg-destructive/90',
outline: 'border bg-background hover:bg-secondary hover:text-secondary-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
}[variant ?? 'default'],
className,
)}
{...props}
/>
)
}
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
@@ -42,7 +10,7 @@ const buttonVariants = cva(
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
"bg-fail text-fail-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-secondary hover:text-secondary-foreground",
secondary:
@@ -64,4 +32,35 @@ const buttonVariants = cva(
}
)
type ButtonProps = React.ComponentProps<'button'> & {
theme?: 'default' | 'outline' | 'gradient' | 'error' | 'accent'
}
function Button(rawProps: ButtonProps) {
const {className, theme, ...props} = rawProps
return (
<button
className={merge(
`transition-all duration-200 ease-in-out`,
`h-10 px-4 rounded-md cursor-pointer`,
'whitespace-nowrap',
'inline-flex items-center justify-center gap-2',
'disabled:pointer-events-none disabled:opacity-50 ',
'[&_svg]:pointer-events-none [&_svg:not([class*=\'size-\'])]:size-4 shrink-0 [&_svg]:shrink-0 ',
'outline-none focus-visible:ring-4 ring-blue-200',
'aria-invalid:ring-fail/20 dark:aria-invalid:ring-fail/40 aria-invalid:border-fail',
{
gradient: 'bg-gradient-to-r from-blue-400 to-cyan-300 text-white ring-offset-2',
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
accent: 'bg-accent text-accent-foreground hover:bg-accent/90',
error: 'bg-fail text-white hover:bg-fail/90',
outline: 'border bg-background hover:bg-secondary hover:text-secondary-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50',
}[theme ?? 'default'],
className,
)}
{...props}
/>
)
}
export {Button, buttonVariants}

View File

@@ -14,7 +14,7 @@ function Checkbox({
<CheckboxPrimitive.Root
data-slot="checkbox"
className={merge(
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-fail/20 dark:aria-invalid:ring-fail/40 aria-invalid:border-fail size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}

View File

@@ -73,8 +73,8 @@ function FormField<
{!!props.label &&
<Label
data-slot="form-label"
data-error={!!fieldState.error}
className={merge('data-[error=true]:text-destructive')}
data-fail={!!fieldState.error}
className={merge('data-[error=true]:text-fail')}
htmlFor={id}>
{props.label}
</Label>
@@ -94,7 +94,7 @@ function FormField<
{!fieldState.error ? null : (
<p
data-slot="form-message"
className={merge('text-destructive text-sm')}>
className={merge('text-fail text-sm')}>
{fieldState.error?.message}
</p>
)}
@@ -120,8 +120,8 @@ function FormLabel({className, ...props}: ComponentProps<typeof LabelPrimitive.R
return (
<Label
data-slot="form-label"
data-error={!!error}
className={merge('data-[error=true]:text-destructive', className)}
data-fail={!!error}
className={merge('data-[error=true]:text-fail', className)}
htmlFor={id}
{...props}
/>
@@ -153,7 +153,7 @@ function FormMessage({className, ...props}: ComponentProps<'p'>) {
<p
data-slot="form-message"
id={`${id}-message`}
className={merge('text-destructive text-sm', className)}
className={merge('text-fail text-sm', className)}
{...props}
>
{body}

View File

@@ -15,7 +15,7 @@ function Input({className, type, ...props}: React.ComponentProps<'input'>) {
'flex rounded-md border bg-transparent px-3 py-1 text-base shadow-xs',
'outline-none focus-visible:ring-4 ring-ring/50',
'disabled:cursor-not-allowed disabled:opacity-50',
'aria-invalid:ring-destructive/20 aria-invalid:border-destructive dark:aria-invalid:ring-destructive/40 dark:bg-input/30',
'aria-invalid:ring-fail/20 aria-invalid:border-fail dark:aria-invalid:ring-fail/40 dark:bg-input/30',
'file:text-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:disabled:pointer-events-none',
className,
)}

View File

@@ -27,7 +27,7 @@ function RadioGroupItem({
<RadioGroupPrimitive.Item
data-slot="radio-group-item"
className={merge(
"border-input text-primary 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 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
"border-input text-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-fail/20 dark:aria-invalid:ring-fail/40 aria-invalid:border-fail dark:bg-input/30 aspect-square size-4 shrink-0 rounded-full border shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
className
)}
{...props}

View File

@@ -37,7 +37,17 @@ function SelectTrigger({
data-slot="select-trigger"
data-size={size}
className={merge(
'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',
'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-fail/20 dark:aria-invalid:ring-fail/40 ',
'aria-invalid:border-fail 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 transition-[color] ',
'outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 ',
{
sm: 'h-9',
default: 'h-10',
}[size],
'*: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}

View File

@@ -70,7 +70,7 @@ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
<th
data-slot="table-head"
className={merge(
"text-gray-600 h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
"text-weak h-10 px-2 text-left align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
className
)}
{...props}

View File

@@ -7,7 +7,7 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
<textarea
data-slot="textarea"
className={merge(
"border-input placeholder: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 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-fail/20 dark:aria-invalid:ring-fail/40 aria-invalid:border-fail dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
className
)}
{...props}