feat: add comment wrapping to ASCII art tool
Add comment style selector (shadcn Select) to wrap generated ASCII art with language-appropriate comment syntax (// # -- ; /* */ <!-- --> """). Refactor preview controls to use shadcn ToggleGroup, Tooltip, and Badge. Alignment is disabled when a comment style is active. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ export function ASCIIConverter() {
|
|||||||
const [asciiArt, setAsciiArt] = React.useState('');
|
const [asciiArt, setAsciiArt] = React.useState('');
|
||||||
const [fonts, setFonts] = React.useState<ASCIIFont[]>([]);
|
const [fonts, setFonts] = React.useState<ASCIIFont[]>([]);
|
||||||
const [isLoading, setIsLoading] = React.useState(false);
|
const [isLoading, setIsLoading] = React.useState(false);
|
||||||
|
const commentedTextRef = React.useRef('');
|
||||||
|
|
||||||
// Load fonts and check URL params on mount
|
// Load fonts and check URL params on mount
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
@@ -71,7 +72,7 @@ export function ASCIIConverter() {
|
|||||||
if (!asciiArt) return;
|
if (!asciiArt) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(asciiArt);
|
await navigator.clipboard.writeText(commentedTextRef.current || asciiArt);
|
||||||
toast.success('Copied to clipboard!');
|
toast.success('Copied to clipboard!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to copy:', error);
|
console.error('Failed to copy:', error);
|
||||||
@@ -83,7 +84,7 @@ export function ASCIIConverter() {
|
|||||||
const handleDownload = () => {
|
const handleDownload = () => {
|
||||||
if (!asciiArt) return;
|
if (!asciiArt) return;
|
||||||
|
|
||||||
const blob = new Blob([asciiArt], { type: 'text/plain' });
|
const blob = new Blob([commentedTextRef.current || asciiArt], { type: 'text/plain' });
|
||||||
const url = URL.createObjectURL(blob);
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a');
|
const a = document.createElement('a');
|
||||||
a.href = url;
|
a.href = url;
|
||||||
@@ -137,6 +138,7 @@ export function ASCIIConverter() {
|
|||||||
onCopy={handleCopy}
|
onCopy={handleCopy}
|
||||||
onDownload={handleDownload}
|
onDownload={handleDownload}
|
||||||
onShare={handleShare}
|
onShare={handleShare}
|
||||||
|
onCommentedTextChange={React.useCallback((t: string) => { commentedTextRef.current = t; }, [])}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,20 @@ import { toPng } from 'html-to-image';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Skeleton } from '@/components/ui/skeleton';
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
import { Badge } from '@/components/ui/badge';
|
||||||
|
import { ToggleGroup, ToggleGroupItem } from '@/components/ui/toggle-group';
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from '@/components/ui/select';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/ui/tooltip';
|
||||||
import {
|
import {
|
||||||
Empty,
|
Empty,
|
||||||
EmptyDescription,
|
EmptyDescription,
|
||||||
@@ -12,10 +26,41 @@ import {
|
|||||||
EmptyMedia,
|
EmptyMedia,
|
||||||
EmptyTitle,
|
EmptyTitle,
|
||||||
} from "@/components/ui/empty"
|
} from "@/components/ui/empty"
|
||||||
import { Copy, Download, Share2, Image as ImageIcon, AlignLeft, AlignCenter, AlignRight, Type } from 'lucide-react';
|
import { Copy, Download, Share2, Image as ImageIcon, AlignLeft, AlignCenter, AlignRight, Type, MessageSquareCode } from 'lucide-react';
|
||||||
import { cn } from '@/lib/utils/cn';
|
import { cn } from '@/lib/utils/cn';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
export type CommentStyle = 'none' | '//' | '#' | '--' | ';' | '/* */' | '<!-- -->' | '"""';
|
||||||
|
|
||||||
|
const COMMENT_STYLES: { value: CommentStyle; label: string }[] = [
|
||||||
|
{ value: 'none', label: 'None' },
|
||||||
|
{ value: '//', label: '// C, JS, Go' },
|
||||||
|
{ value: '#', label: '# Python, Shell' },
|
||||||
|
{ value: '--', label: '-- SQL, Lua' },
|
||||||
|
{ value: ';', label: '; Lisp, ASM' },
|
||||||
|
{ value: '/* */', label: '/* */ Block' },
|
||||||
|
{ value: '<!-- -->', label: '<!-- --> HTML' },
|
||||||
|
{ value: '"""', label: '""" Docstring' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function applyCommentStyle(text: string, style: CommentStyle): string {
|
||||||
|
if (style === 'none' || !text) return text;
|
||||||
|
const lines = text.split('\n');
|
||||||
|
switch (style) {
|
||||||
|
case '//':
|
||||||
|
case '#':
|
||||||
|
case '--':
|
||||||
|
case ';':
|
||||||
|
return lines.map(line => `${style} ${line}`).join('\n');
|
||||||
|
case '/* */':
|
||||||
|
return ['/*', ...lines.map(line => ` * ${line}`), ' */'].join('\n');
|
||||||
|
case '<!-- -->':
|
||||||
|
return ['<!--', ...lines, '-->'].join('\n');
|
||||||
|
case '"""':
|
||||||
|
return ['"""', ...lines, '"""'].join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface FontPreviewProps {
|
export interface FontPreviewProps {
|
||||||
text: string;
|
text: string;
|
||||||
font?: string;
|
font?: string;
|
||||||
@@ -23,17 +68,25 @@ export interface FontPreviewProps {
|
|||||||
onCopy?: () => void;
|
onCopy?: () => void;
|
||||||
onDownload?: () => void;
|
onDownload?: () => void;
|
||||||
onShare?: () => void;
|
onShare?: () => void;
|
||||||
|
onCommentedTextChange?: (commentedText: string) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TextAlign = 'left' | 'center' | 'right';
|
type TextAlign = 'left' | 'center' | 'right';
|
||||||
|
|
||||||
export function FontPreview({ text, font, isLoading, onCopy, onDownload, onShare, className }: FontPreviewProps) {
|
export function FontPreview({ text, font, isLoading, onCopy, onDownload, onShare, onCommentedTextChange, className }: FontPreviewProps) {
|
||||||
const lineCount = text ? text.split('\n').length : 0;
|
|
||||||
const charCount = text ? text.length : 0;
|
|
||||||
const previewRef = React.useRef<HTMLDivElement>(null);
|
const previewRef = React.useRef<HTMLDivElement>(null);
|
||||||
const [textAlign, setTextAlign] = React.useState<TextAlign>('left');
|
const [textAlign, setTextAlign] = React.useState<TextAlign>('left');
|
||||||
const [fontSize, setFontSize] = React.useState<'xs' | 'sm' | 'base'>('sm');
|
const [fontSize, setFontSize] = React.useState<'xs' | 'sm' | 'base'>('sm');
|
||||||
|
const [commentStyle, setCommentStyle] = React.useState<CommentStyle>('none');
|
||||||
|
|
||||||
|
const commentedText = React.useMemo(() => applyCommentStyle(text, commentStyle), [text, commentStyle]);
|
||||||
|
const lineCount = commentedText ? commentedText.split('\n').length : 0;
|
||||||
|
const charCount = commentedText ? commentedText.length : 0;
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
onCommentedTextChange?.(commentedText);
|
||||||
|
}, [commentedText, onCommentedTextChange]);
|
||||||
|
|
||||||
const handleExportPNG = async () => {
|
const handleExportPNG = async () => {
|
||||||
if (!previewRef.current || !text) return;
|
if (!previewRef.current || !text) return;
|
||||||
@@ -55,92 +108,116 @@ export function FontPreview({ text, font, isLoading, onCopy, onDownload, onShare
|
|||||||
toast.error('Failed to export PNG');
|
toast.error('Failed to export PNG');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={cn('relative', className)}>
|
<Card className={cn('relative', className)}>
|
||||||
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
|
<CardHeader className="flex flex-row items-center justify-between flex-wrap gap-2">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<CardTitle>Preview</CardTitle>
|
<CardTitle>Preview</CardTitle>
|
||||||
{font && (
|
{font && (
|
||||||
<span className="text-[10px] px-1.5 py-0.5 bg-primary/10 text-primary rounded font-mono">
|
<Badge className="text-[10px] font-mono">
|
||||||
{font}
|
{font}
|
||||||
</span>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-1.5 flex-wrap">
|
<div className="flex gap-1.5 flex-wrap">
|
||||||
{onCopy && (
|
{onCopy && (
|
||||||
<Button variant="outline" size="xs" onClick={onCopy}>
|
<Tooltip>
|
||||||
<Copy className="h-3 w-3 mr-1" />
|
<TooltipTrigger asChild>
|
||||||
Copy
|
<Button variant="outline" size="xs" onClick={onCopy}>
|
||||||
</Button>
|
<Copy className="h-3 w-3 mr-1" />
|
||||||
|
Copy
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Copy to clipboard</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{onShare && (
|
{onShare && (
|
||||||
<Button variant="outline" size="xs" onClick={onShare} title="Copy shareable URL">
|
<Tooltip>
|
||||||
<Share2 className="h-3 w-3 mr-1" />
|
<TooltipTrigger asChild>
|
||||||
Share
|
<Button variant="outline" size="xs" onClick={onShare}>
|
||||||
</Button>
|
<Share2 className="h-3 w-3 mr-1" />
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Copy shareable URL</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Button variant="outline" size="xs" onClick={handleExportPNG} title="Export as PNG">
|
<Tooltip>
|
||||||
<ImageIcon className="h-3 w-3 mr-1" />
|
<TooltipTrigger asChild>
|
||||||
PNG
|
<Button variant="outline" size="xs" onClick={handleExportPNG}>
|
||||||
</Button>
|
<ImageIcon className="h-3 w-3 mr-1" />
|
||||||
|
PNG
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Export as PNG</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
{onDownload && (
|
{onDownload && (
|
||||||
<Button variant="outline" size="xs" onClick={onDownload}>
|
<Tooltip>
|
||||||
<Download className="h-3 w-3 mr-1" />
|
<TooltipTrigger asChild>
|
||||||
TXT
|
<Button variant="outline" size="xs" onClick={onDownload}>
|
||||||
</Button>
|
<Download className="h-3 w-3 mr-1" />
|
||||||
|
TXT
|
||||||
|
</Button>
|
||||||
|
</TooltipTrigger>
|
||||||
|
<TooltipContent>Download as text file</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-3">
|
<CardContent className="space-y-3">
|
||||||
{/* Controls */}
|
{/* Controls */}
|
||||||
<div className="flex items-center gap-2 flex-wrap">
|
<div className="flex items-center gap-2 flex-wrap">
|
||||||
<div className="flex items-center border rounded-md p-0.5">
|
<ToggleGroup
|
||||||
<button
|
type="single"
|
||||||
onClick={() => setTextAlign('left')}
|
value={textAlign}
|
||||||
className={cn(
|
onValueChange={(v) => v && setTextAlign(v as TextAlign)}
|
||||||
'p-1 rounded transition-colors',
|
variant="outline"
|
||||||
textAlign === 'left' ? 'bg-accent' : 'hover:bg-accent/50'
|
size="sm"
|
||||||
)}
|
disabled={commentStyle !== 'none'}
|
||||||
title="Align left"
|
>
|
||||||
>
|
<ToggleGroupItem value="left" aria-label="Align left" className="px-1.5">
|
||||||
<AlignLeft className="h-3 w-3" />
|
<AlignLeft className="h-3 w-3" />
|
||||||
</button>
|
</ToggleGroupItem>
|
||||||
<button
|
<ToggleGroupItem value="center" aria-label="Align center" className="px-1.5">
|
||||||
onClick={() => setTextAlign('center')}
|
|
||||||
className={cn(
|
|
||||||
'p-1 rounded transition-colors',
|
|
||||||
textAlign === 'center' ? 'bg-accent' : 'hover:bg-accent/50'
|
|
||||||
)}
|
|
||||||
title="Align center"
|
|
||||||
>
|
|
||||||
<AlignCenter className="h-3 w-3" />
|
<AlignCenter className="h-3 w-3" />
|
||||||
</button>
|
</ToggleGroupItem>
|
||||||
<button
|
<ToggleGroupItem value="right" aria-label="Align right" className="px-1.5">
|
||||||
onClick={() => setTextAlign('right')}
|
|
||||||
className={cn(
|
|
||||||
'p-1 rounded transition-colors',
|
|
||||||
textAlign === 'right' ? 'bg-accent' : 'hover:bg-accent/50'
|
|
||||||
)}
|
|
||||||
title="Align right"
|
|
||||||
>
|
|
||||||
<AlignRight className="h-3 w-3" />
|
<AlignRight className="h-3 w-3" />
|
||||||
</button>
|
</ToggleGroupItem>
|
||||||
</div>
|
</ToggleGroup>
|
||||||
|
|
||||||
<div className="flex items-center border rounded-md p-0.5">
|
<ToggleGroup
|
||||||
{(['xs', 'sm', 'base'] as const).map((s) => (
|
type="single"
|
||||||
<button
|
value={fontSize}
|
||||||
key={s}
|
onValueChange={(v) => v && setFontSize(v as 'xs' | 'sm' | 'base')}
|
||||||
onClick={() => setFontSize(s)}
|
variant="outline"
|
||||||
className={cn(
|
size="sm"
|
||||||
'px-1.5 py-0.5 text-[10px] rounded transition-colors uppercase',
|
>
|
||||||
fontSize === s ? 'bg-accent' : 'hover:bg-accent/50'
|
<ToggleGroupItem value="xs" aria-label="Extra small font" className="px-1.5 text-[10px] uppercase">
|
||||||
)}
|
xs
|
||||||
>
|
</ToggleGroupItem>
|
||||||
{s === 'base' ? 'md' : s}
|
<ToggleGroupItem value="sm" aria-label="Small font" className="px-1.5 text-[10px] uppercase">
|
||||||
</button>
|
sm
|
||||||
))}
|
</ToggleGroupItem>
|
||||||
</div>
|
<ToggleGroupItem value="base" aria-label="Medium font" className="px-1.5 text-[10px] uppercase">
|
||||||
|
md
|
||||||
|
</ToggleGroupItem>
|
||||||
|
</ToggleGroup>
|
||||||
|
|
||||||
|
<Select value={commentStyle} onValueChange={(v) => setCommentStyle(v as CommentStyle)}>
|
||||||
|
<SelectTrigger size="sm" className="h-8 w-auto gap-1 text-xs">
|
||||||
|
<MessageSquareCode className="h-3 w-3 text-foreground shrink-0" />
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{COMMENT_STYLES.map((s) => (
|
||||||
|
<SelectItem key={s.value} value={s.value}>
|
||||||
|
{s.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
|
||||||
{!isLoading && text && (
|
{!isLoading && text && (
|
||||||
<div className="flex gap-2 text-[10px] text-muted-foreground ml-auto">
|
<div className="flex gap-2 text-[10px] text-muted-foreground ml-auto">
|
||||||
@@ -154,8 +231,8 @@ export function FontPreview({ text, font, isLoading, onCopy, onDownload, onShare
|
|||||||
ref={previewRef}
|
ref={previewRef}
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative min-h-[200px] bg-muted/50 rounded-lg p-4 overflow-x-auto',
|
'relative min-h-[200px] bg-muted/50 rounded-lg p-4 overflow-x-auto',
|
||||||
textAlign === 'center' && 'text-center',
|
commentStyle === 'none' && textAlign === 'center' && 'text-center',
|
||||||
textAlign === 'right' && 'text-right'
|
commentStyle === 'none' && textAlign === 'right' && 'text-right'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
@@ -174,7 +251,7 @@ export function FontPreview({ text, font, isLoading, onCopy, onDownload, onShare
|
|||||||
fontSize === 'sm' && 'text-xs sm:text-sm',
|
fontSize === 'sm' && 'text-xs sm:text-sm',
|
||||||
fontSize === 'base' && 'text-sm sm:text-base'
|
fontSize === 'base' && 'text-sm sm:text-base'
|
||||||
)}>
|
)}>
|
||||||
{text}
|
{commentedText}
|
||||||
</pre>
|
</pre>
|
||||||
) : (
|
) : (
|
||||||
<Empty>
|
<Empty>
|
||||||
|
|||||||
83
components/ui/toggle-group.tsx
Normal file
83
components/ui/toggle-group.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { type VariantProps } from "class-variance-authority"
|
||||||
|
import { ToggleGroup as ToggleGroupPrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils/index"
|
||||||
|
import { toggleVariants } from "@/components/ui/toggle"
|
||||||
|
|
||||||
|
const ToggleGroupContext = React.createContext<
|
||||||
|
VariantProps<typeof toggleVariants> & {
|
||||||
|
spacing?: number
|
||||||
|
}
|
||||||
|
>({
|
||||||
|
size: "default",
|
||||||
|
variant: "default",
|
||||||
|
spacing: 0,
|
||||||
|
})
|
||||||
|
|
||||||
|
function ToggleGroup({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
spacing = 0,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ToggleGroupPrimitive.Root> &
|
||||||
|
VariantProps<typeof toggleVariants> & {
|
||||||
|
spacing?: number
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<ToggleGroupPrimitive.Root
|
||||||
|
data-slot="toggle-group"
|
||||||
|
data-variant={variant}
|
||||||
|
data-size={size}
|
||||||
|
data-spacing={spacing}
|
||||||
|
style={{ "--gap": spacing } as React.CSSProperties}
|
||||||
|
className={cn(
|
||||||
|
"group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ToggleGroupContext.Provider value={{ variant, size, spacing }}>
|
||||||
|
{children}
|
||||||
|
</ToggleGroupContext.Provider>
|
||||||
|
</ToggleGroupPrimitive.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function ToggleGroupItem({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof ToggleGroupPrimitive.Item> &
|
||||||
|
VariantProps<typeof toggleVariants>) {
|
||||||
|
const context = React.useContext(ToggleGroupContext)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToggleGroupPrimitive.Item
|
||||||
|
data-slot="toggle-group-item"
|
||||||
|
data-variant={context.variant || variant}
|
||||||
|
data-size={context.size || size}
|
||||||
|
data-spacing={context.spacing}
|
||||||
|
className={cn(
|
||||||
|
toggleVariants({
|
||||||
|
variant: context.variant || variant,
|
||||||
|
size: context.size || size,
|
||||||
|
}),
|
||||||
|
"w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
|
||||||
|
"data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ToggleGroupPrimitive.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { ToggleGroup, ToggleGroupItem }
|
||||||
47
components/ui/toggle.tsx
Normal file
47
components/ui/toggle.tsx
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
import { Toggle as TogglePrimitive } from "radix-ui"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils/index"
|
||||||
|
|
||||||
|
const toggleVariants = cva(
|
||||||
|
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-transparent",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-2 min-w-9",
|
||||||
|
sm: "h-8 px-1.5 min-w-8",
|
||||||
|
lg: "h-10 px-2.5 min-w-10",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function Toggle({
|
||||||
|
className,
|
||||||
|
variant,
|
||||||
|
size,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof TogglePrimitive.Root> &
|
||||||
|
VariantProps<typeof toggleVariants>) {
|
||||||
|
return (
|
||||||
|
<TogglePrimitive.Root
|
||||||
|
data-slot="toggle"
|
||||||
|
className={cn(toggleVariants({ variant, size, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Toggle, toggleVariants }
|
||||||
Reference in New Issue
Block a user