[功能] 添加新的输入、文本区域、标签和开关组件,优化样式和功能

This commit is contained in:
ryan
2026-03-20 23:15:35 +08:00
parent 2c87254bb3
commit a6787ac30d
12 changed files with 5948 additions and 156 deletions
@@ -0,0 +1,22 @@
import * as React from 'react';
import { cn } from '@/lib/utils/cn';
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-3 py-2 text-sm text-[var(--foreground-primary)] shadow-none outline-none transition placeholder:text-[var(--foreground-muted)] focus-visible:border-[var(--border-strong)] focus-visible:ring-2 focus-visible:ring-[var(--accent-soft)] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = 'Input';
export { Input };
@@ -0,0 +1,21 @@
import * as React from 'react';
import { cn } from '@/lib/utils/cn';
const Label = React.forwardRef<
HTMLLabelElement,
React.ComponentProps<'label'>
>(({ className, ...props }, ref) => (
<label
ref={ref}
className={cn(
'text-sm font-medium leading-none text-[var(--foreground-primary)] peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
className,
)}
{...props}
/>
));
Label.displayName = 'Label';
export { Label };
@@ -0,0 +1,28 @@
"use client"
import * as React from "react"
import { Separator as SeparatorPrimitive } from "radix-ui"
import { cn } from "@/lib/utils"
function Separator({
className,
orientation = "horizontal",
decorative = true,
...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return (
<SeparatorPrimitive.Root
data-slot="separator"
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
className
)}
{...props}
/>
)
}
export { Separator }
@@ -0,0 +1,70 @@
import * as React from 'react';
import { cn } from '@/lib/utils/cn';
type SwitchProps = Omit<React.ComponentProps<'button'>, 'onChange'> & {
checked?: boolean;
defaultChecked?: boolean;
disabled?: boolean;
onCheckedChange?: (checked: boolean) => void;
};
const Switch = React.forwardRef<HTMLButtonElement, SwitchProps>(
(
{
className,
checked,
defaultChecked = false,
disabled,
onCheckedChange,
...props
},
ref,
) => {
const [internalChecked, setInternalChecked] = React.useState(defaultChecked);
const isControlled = checked !== undefined;
const isChecked = isControlled ? checked : internalChecked;
const updateChecked = React.useCallback(
(nextChecked: boolean) => {
if (!isControlled) {
setInternalChecked(nextChecked);
}
onCheckedChange?.(nextChecked);
},
[isControlled, onCheckedChange],
);
return (
<button
ref={ref}
type="button"
role="switch"
aria-checked={isChecked}
data-state={isChecked ? 'checked' : 'unchecked'}
disabled={disabled}
className={cn(
'peer inline-flex h-6 w-11 shrink-0 items-center rounded-full border border-transparent bg-[var(--control-background-hover)] transition-colors outline-none focus-visible:ring-2 focus-visible:ring-[var(--accent-soft)] focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-[var(--brand-primary)]',
className,
)}
onClick={() => updateChecked(!isChecked)}
onKeyDown={(event) => {
if (event.key === 'Enter' || event.key === ' ') {
event.preventDefault();
updateChecked(!isChecked);
}
}}
{...props}
>
<span
data-state={isChecked ? 'checked' : 'unchecked'}
className="pointer-events-none block h-5 w-5 translate-x-0.5 rounded-full bg-white shadow-sm transition-transform data-[state=checked]:translate-x-5"
/>
</button>
);
},
);
Switch.displayName = 'Switch';
export { Switch };
@@ -0,0 +1,22 @@
import * as React from 'react';
import { cn } from '@/lib/utils/cn';
const Textarea = React.forwardRef<
HTMLTextAreaElement,
React.ComponentProps<'textarea'>
>(({ className, ...props }, ref) => {
return (
<textarea
className={cn(
'flex min-h-28 w-full rounded-xl border border-[var(--border-default)] bg-[var(--surface-elevated)] px-3 py-2 text-sm text-[var(--foreground-primary)] shadow-none outline-none transition placeholder:text-[var(--foreground-muted)] focus-visible:border-[var(--border-strong)] focus-visible:ring-2 focus-visible:ring-[var(--accent-soft)] disabled:cursor-not-allowed disabled:opacity-50',
className,
)}
ref={ref}
{...props}
/>
);
});
Textarea.displayName = 'Textarea';
export { Textarea };