fix: linter issues

This commit is contained in:
K1ngfish3r
2026-04-23 01:04:42 +05:00
parent 5e195b7f53
commit 2396df38bb
111 changed files with 2738 additions and 2160 deletions
+3 -7
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState, ReactNode } from 'react';
import React from 'react';
import { Button } from '@/components/ui/button';
import {
Sheet,
@@ -158,8 +158,6 @@ export function FiltersSheet({
filters,
refetch,
}: FiltersSheetProps) {
const [filterElements, setFilterElements] = useState<ReactNode>(null);
const setFilterWithKey = (key: string, newValue: AnyFilterValue) =>
setValues(fValues =>
!fValues
@@ -186,15 +184,13 @@ export function FiltersSheet({
}
};
useEffect(() => {
setFilterElements(renderFilters(filters, values, setFilterWithKey));
}, [values, filters]);
const handleApply = () => {
refetch();
onOpenChange(false);
};
const filterElements = renderFilters(filters, values, setFilterWithKey);
return (
<Sheet open={open} onOpenChange={onOpenChange}>
<SheetContent side="right" className="w-full sm:max-w-md overflow-y-auto">
+15 -4
View File
@@ -15,7 +15,7 @@ import {
import { useAppStore } from '@/store';
import { usePluginCustomAssets } from '@/hooks/usePluginCustomAssets';
export default function ParseChapterSection() {
const ParseChapterSection = React.memo(function ParseChapterSection() {
const plugin = useAppStore(state => state.plugin);
const parseChapterPath = useAppStore(state => state.parseChapterPath);
const shouldAutoSubmitChapter = useAppStore(
@@ -30,6 +30,15 @@ export default function ParseChapterSection() {
const [fetchError, setFetchError] = useState('');
const [showRawHtml, setShowRawHtml] = useState(false);
const lastProcessedPath = useRef<string>();
const [prevPluginId, setPrevPluginId] = useState<string | undefined>();
if (plugin?.id !== prevPluginId) {
setPrevPluginId(plugin?.id);
setChapterPath('');
setChapterText('');
setFetchError('');
setShowRawHtml(false);
}
const { customCSSLoaded, customJSLoaded, customCSSError, customJSError } =
usePluginCustomAssets(plugin, chapterText);
@@ -56,7 +65,7 @@ export default function ParseChapterSection() {
await fetchChapterByPath(chapterPath);
};
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && chapterPath.trim()) {
fetchChapter();
}
@@ -128,7 +137,7 @@ export default function ParseChapterSection() {
placeholder="Enter chapter path..."
value={chapterPath}
onChange={e => setChapterPath(e.target.value)}
onKeyPress={handleKeyPress}
onKeyDown={handleKeyDown}
className="flex-1"
disabled={!plugin}
/>
@@ -327,4 +336,6 @@ export default function ParseChapterSection() {
</Card>
</div>
);
}
});
export default ParseChapterSection;
+16 -4
View File
@@ -27,7 +27,7 @@ type ParseNovelSectionProps = {
onNavigateToParseChapter?: () => void;
};
export default function ParseNovelSection({
const ParseNovelSection = React.memo(function ParseNovelSection({
onNavigateToParseChapter,
}: ParseNovelSectionProps) {
const plugin = useAppStore(state => state.plugin);
@@ -46,6 +46,16 @@ export default function ParseNovelSection({
const [currentPage, setCurrentPage] = useState(1);
const [fetchError, setFetchError] = useState('');
const lastProcessedPath = useRef<string>();
const [prevPluginId, setPrevPluginId] = useState<string | undefined>();
if (plugin?.id !== prevPluginId) {
setPrevPluginId(plugin?.id);
setNovelPath('');
setSourceNovel(undefined);
setChapters([]);
setCurrentPage(1);
setFetchError('');
}
const { exportEpub, isExporting } = useEpubExport({
plugin: plugin || null,
@@ -100,7 +110,7 @@ export default function ParseNovelSection({
}
};
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && novelPath.trim()) {
fetchNovel();
}
@@ -168,7 +178,7 @@ export default function ParseNovelSection({
placeholder="Enter novel path..."
value={novelPath}
onChange={e => setNovelPath(e.target.value)}
onKeyPress={handleKeyPress}
onKeyDown={handleKeyDown}
className="flex-1"
disabled={!plugin}
/>
@@ -551,4 +561,6 @@ export default function ParseNovelSection({
</Card>
</div>
);
}
});
export default ParseNovelSection;
+37 -29
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import { Filter, BookOpen } from 'lucide-react';
import { FiltersSheet } from '@/components/filters/filters-sheet';
@@ -16,7 +16,7 @@ type PopularNovelsSectionProps = {
onNavigateToParseNovel?: () => void;
};
export default function PopularNovelsSection({
const PopularNovelsSection = React.memo(function PopularNovelsSection({
onNavigateToParseNovel,
}: PopularNovelsSectionProps) {
const plugin = useAppStore(state => state.plugin);
@@ -30,14 +30,38 @@ export default function PopularNovelsSection({
const [filterValues, setFilterValues] = useState<
FilterToValues<Filters> | undefined
>();
const [prevPluginId, setPrevPluginId] = useState<string | undefined>();
const fetchNovelsByIndex = async (index: number) => {
if (plugin?.id !== prevPluginId) {
setPrevPluginId(plugin?.id);
setCurrentIndex(0);
setMaxIndex(0);
setNovels([]);
if (plugin?.filters) {
const filters: FilterToValues<typeof plugin.filters> = {};
for (const fKey in plugin.filters) {
filters[fKey as keyof typeof filters] = {
type: plugin.filters[fKey].type,
value: plugin.filters[fKey].value,
};
}
setFilterValues(filters);
} else {
setFilterValues(undefined);
}
}
const fetchNovelsByIndex = async (
index: number,
latestOverride?: boolean,
) => {
if (plugin && index) {
setLoading(true);
try {
const fetchedNovels = await plugin.popularNovels(index, {
filters: filterValues || {},
showLatestNovels: isLatest,
showLatestNovels: latestOverride ?? isLatest,
});
if (fetchedNovels.length !== 0) {
setCurrentIndex(index);
@@ -54,31 +78,13 @@ export default function PopularNovelsSection({
}
};
useEffect(() => {
if (plugin) {
setCurrentIndex(1);
setMaxIndex(1);
fetchNovelsByIndex(1);
}
}, [isLatest]);
useEffect(() => {
// Reset when changing plugins
const handleIsLatestChange = (latest: boolean) => {
if (isLatest === latest) return;
setIsLatest(latest);
setCurrentIndex(0);
setMaxIndex(0);
setNovels([]);
if (plugin?.filters) {
const filters: FilterToValues<typeof plugin.filters> = {};
for (const fKey in plugin.filters) {
filters[fKey as keyof typeof filters] = {
type: plugin.filters[fKey].type,
value: plugin.filters[fKey].value,
};
}
setFilterValues(filters);
}
}, [plugin]);
};
const handleParseNovel = (path: string) => {
setParseNovelPath(path, true);
@@ -136,7 +142,7 @@ export default function PopularNovelsSection({
isLatest === (option === 'Latest') ? 'default' : 'outline'
}
className="cursor-pointer"
onClick={() => setIsLatest(option === 'Latest')}
onClick={() => handleIsLatestChange(option === 'Latest')}
>
{option}
</Badge>
@@ -149,7 +155,7 @@ export default function PopularNovelsSection({
min="1"
max={maxIndex}
value={currentIndex}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
onChange={e => {
const page = parseInt(e.target.value);
if (page > 0 && page <= maxIndex) {
fetchNovelsByIndex(page);
@@ -216,4 +222,6 @@ export default function PopularNovelsSection({
/>
</div>
);
}
});
export default PopularNovelsSection;
+16 -12
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import { Search as SearchIcon } from 'lucide-react';
import { NovelCard } from '@/components/novel-card';
@@ -14,7 +14,7 @@ type SearchNovelsSectionProps = {
onNavigateToParseNovel?: () => void;
};
export default function SearchNovelsSection({
const SearchNovelsSection = React.memo(function SearchNovelsSection({
onNavigateToParseNovel,
}: SearchNovelsSectionProps) {
const plugin = useAppStore(state => state.plugin);
@@ -24,6 +24,15 @@ export default function SearchNovelsSection({
const [novels, setNovels] = useState<Plugin.NovelItem[]>([]);
const [loading, setLoading] = useState(false);
const [fetchError, setFetchError] = useState('');
const [prevPluginId, setPrevPluginId] = useState<string | undefined>();
if (plugin?.id !== prevPluginId) {
setPrevPluginId(plugin?.id);
setCurrentPage(1);
setNovels([]);
setSearchTerm('');
setFetchError('');
}
const fetchNovels = async (page: number) => {
if (plugin && searchTerm.trim()) {
@@ -44,14 +53,7 @@ export default function SearchNovelsSection({
}
};
useEffect(() => {
setCurrentPage(1);
setNovels([]);
setSearchTerm('');
setFetchError('');
}, [plugin]);
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter' && searchTerm.trim()) {
fetchNovels(1);
}
@@ -86,7 +88,7 @@ export default function SearchNovelsSection({
placeholder="Enter search term..."
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
onKeyPress={handleKeyPress}
onKeyDown={handleKeyDown}
className="flex-1"
disabled={!plugin}
/>
@@ -159,4 +161,6 @@ export default function SearchNovelsSection({
</Card>
</div>
);
}
});
export default SearchNovelsSection;
+138 -124
View File
@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useRef } from 'react';
import { CheckedState } from '@radix-ui/react-checkbox';
import { Check } from 'lucide-react';
@@ -16,59 +16,75 @@ import {
import useDebounce from '@/hooks/useDebounce';
import { FetchMode } from '@/types/types';
export default function SettingsSection() {
const [cookies, setCookies] = useState('');
const debouncedCookies = useDebounce(cookies, 500);
const [fetchMode, setFetchMode] = useState<FetchMode>(FetchMode.PROXY);
const [useUserAgent, setUseUserAgent] = useState<CheckedState>(true);
const [loading, setLoading] = useState(false);
const [alertVisible, setAlertVisible] = useState(false);
const FETCH_MODES = {
[FetchMode.PROXY]: 'Proxy',
[FetchMode.NODE_FETCH]: 'Node Fetch',
[FetchMode.CURL]: 'Curl',
};
const SettingsSection = React.memo(function SettingsSection() {
const [settings, setSettings] = useState({
cookies: '',
fetchMode: FetchMode.PROXY,
useUserAgent: true as CheckedState,
});
const [status, setStatus] = useState<'idle' | 'loading' | 'saved'>('idle');
const init = useRef(false);
const lastSaved = useRef<typeof settings | null>(null);
const debouncedCookies = useDebounce(settings.cookies, 500);
useEffect(() => {
if (alertVisible) {
const id = setTimeout(() => setAlertVisible(false), 2000);
return () => clearTimeout(id);
}
}, [alertVisible]);
fetch('settings')
.then(res => res.json())
.then(data => {
const loaded = {
cookies: data.cookies || '',
fetchMode: data.fetchMode ?? FetchMode.PROXY,
useUserAgent: data.useUserAgent ?? true,
};
setSettings(loaded);
lastSaved.current = loaded;
init.current = true;
})
.catch(console.error);
}, []);
useEffect(() => {
setLoading(true);
if (!init.current || debouncedCookies !== settings.cookies) return;
const current = { ...settings, cookies: debouncedCookies };
if (JSON.stringify(lastSaved.current) === JSON.stringify(current)) return;
setStatus('loading');
fetch('settings', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
cookies: debouncedCookies,
fetchMode: fetchMode,
useUserAgent: useUserAgent === true,
}),
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(current),
})
.then(() => setAlertVisible(true))
.catch(error => console.error('Failed to save settings:', error))
.finally(() => setLoading(false));
}, [debouncedCookies, fetchMode, useUserAgent]);
.then(() => {
lastSaved.current = current;
setStatus('saved');
setTimeout(() => setStatus('idle'), 2000);
})
.catch(console.error);
}, [
debouncedCookies,
settings.fetchMode,
settings.useUserAgent,
settings.cookies,
]);
const getFetchModeLabel = (mode: FetchMode) => {
switch (mode) {
case FetchMode.PROXY:
return 'Proxy';
case FetchMode.NODE_FETCH:
return 'Node Fetch';
case FetchMode.CURL:
return 'Curl';
default:
return 'Proxy';
}
};
const update = <K extends keyof typeof settings>(
k: K,
v: (typeof settings)[K],
) => setSettings(settings => ({ ...settings, [k]: v }));
return (
<div className="space-y-6">
<Card className="p-6 relative">
{alertVisible && (
{status === 'saved' && (
<div className="absolute top-4 right-4 z-10 bg-green-500/90 text-white px-4 py-2 rounded-md flex items-center gap-2 shadow-lg animate-in fade-in slide-in-from-top-2">
<Check className="w-4 h-4" />
Settings updated
<Check className="w-4 h-4" /> Settings updated
</div>
)}
@@ -79,86 +95,61 @@ export default function SettingsSection() {
Settings are automatically saved
</p>
</div>
{loading && (
{status === 'loading' && (
<div className="text-sm text-muted-foreground">Saving...</div>
)}
</div>
<div className="space-y-6">
{/* Request Configuration Section */}
<div>
<div className="flex items-center gap-2 mb-4">
<div className="h-px flex-1 bg-border"></div>
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
Request Configuration
</h3>
<div className="h-px flex-1 bg-border"></div>
</div>
<div className="space-y-6">
{/* User Agent */}
<div className="space-y-2">
<Label className="font-semibold text-foreground">
Browser User Agent
</Label>
<div className="flex items-center gap-3">
<Input
value={navigator.userAgent}
disabled
className="font-mono text-xs flex-1 opacity-60"
title={navigator.userAgent}
/>
<div className="flex items-center gap-2 whitespace-nowrap">
<Checkbox
id="use-ua"
checked={useUserAgent}
onCheckedChange={setUseUserAgent}
/>
<Label
htmlFor="use-ua"
className="text-sm text-foreground cursor-pointer"
>
Use
</Label>
</div>
</div>
<p className="text-xs text-muted-foreground">
Enable to send your browser's user agent with requests
</p>
</div>
{/* Cookies */}
<div className="space-y-2">
<Label
htmlFor="cookies"
className="font-semibold text-foreground"
>
Cookies
</Label>
<Section title="Request Configuration">
<div className="space-y-2">
<Label className="font-semibold text-foreground">
Browser User Agent
</Label>
<div className="flex items-center gap-3">
<Input
id="cookies"
value={cookies}
onChange={e => setCookies(e.target.value.trim())}
placeholder="Enter cookies (optional)..."
className="font-mono text-xs"
value={navigator.userAgent}
disabled
className="font-mono text-xs flex-1 opacity-60"
title={navigator.userAgent}
/>
<p className="text-xs text-muted-foreground">
Additional cookies to send with requests (optional)
</p>
<div className="flex items-center gap-2 whitespace-nowrap">
<Checkbox
id="use-ua"
checked={settings.useUserAgent}
onCheckedChange={v => update('useUserAgent', v)}
/>
<Label
htmlFor="use-ua"
className="text-sm text-foreground cursor-pointer"
>
Use
</Label>
</div>
</div>
</div>
</div>
{/* Fetch Settings Section */}
<div>
<div className="flex items-center gap-2 mb-4">
<div className="h-px flex-1 bg-border"></div>
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
Fetch Settings
</h3>
<div className="h-px flex-1 bg-border"></div>
<div className="space-y-2">
<Label
htmlFor="cookies"
className="font-semibold text-foreground"
>
Cookies
</Label>
<Input
id="cookies"
value={settings.cookies}
onChange={e => update('cookies', e.target.value.trim())}
placeholder="Enter cookies (optional)..."
className="font-mono text-xs"
/>
<p className="text-xs text-muted-foreground">
Additional cookies to send with requests (optional)
</p>
</div>
</Section>
<Section title="Fetch Settings">
<div className="space-y-2">
<Label
htmlFor="fetch-mode"
@@ -167,33 +158,56 @@ export default function SettingsSection() {
Fetch Mode
</Label>
<Select
value={fetchMode.toString()}
onValueChange={value =>
setFetchMode(parseInt(value) as FetchMode)
}
value={settings.fetchMode.toString()}
onValueChange={v => update('fetchMode', parseInt(v))}
>
<SelectTrigger id="fetch-mode">
<SelectValue>{getFetchModeLabel(fetchMode)}</SelectValue>
<SelectValue>
{
FETCH_MODES[
settings.fetchMode as keyof typeof FETCH_MODES
]
}
</SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value={FetchMode.PROXY.toString()}>
Proxy
</SelectItem>
<SelectItem value={FetchMode.NODE_FETCH.toString()}>
Node Fetch
</SelectItem>
<SelectItem value={FetchMode.CURL.toString()}>
Curl
</SelectItem>
{Object.entries(FETCH_MODES).map(([v, l]) => (
<SelectItem key={v} value={v}>
{l}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
Select the method used to fetch data from sources
</p>
</div>
</div>
</Section>
</div>
</Card>
</div>
);
});
function Section({
title,
children,
}: {
title: string;
children: React.ReactNode;
}) {
return (
<div>
<div className="flex items-center gap-2 mb-4">
<div className="h-px flex-1 bg-border" />
<h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
{title}
</h3>
<div className="h-px flex-1 bg-border" />
</div>
<div className="space-y-6">{children}</div>
</div>
);
}
export default SettingsSection;
+19 -16
View File
@@ -25,22 +25,25 @@ const badgeVariants = cva(
},
);
function Badge({
className,
variant,
asChild = false,
...props
}: React.ComponentProps<'span'> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : 'span';
type BadgeProps = React.ComponentPropsWithoutRef<'span'> &
VariantProps<typeof badgeVariants> & {
asChild?: boolean;
};
return (
<Comp
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
}
const Badge = React.forwardRef<HTMLSpanElement, BadgeProps>(
({ className, variant, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'span';
return (
<Comp
ref={ref}
data-slot="badge"
className={cn(badgeVariants({ variant }), className)}
{...props}
/>
);
},
);
Badge.displayName = 'Badge';
export { Badge, badgeVariants };
+17 -17
View File
@@ -36,25 +36,25 @@ const buttonVariants = cva(
},
);
function Button({
className,
variant,
size,
asChild = false,
...props
}: React.ComponentProps<'button'> &
type ButtonProps = React.ComponentPropsWithoutRef<'button'> &
VariantProps<typeof buttonVariants> & {
asChild?: boolean;
}) {
const Comp = asChild ? Slot : 'button';
};
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
{...props}
/>
);
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
data-slot="button"
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = 'Button';
export { Button, buttonVariants };
+78 -59
View File
@@ -2,9 +2,10 @@ import * as React from 'react';
import { cn } from '@/lib/utils';
function Card({ className, ...props }: React.ComponentProps<'div'>) {
return (
const Card = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
({ className, ...props }, ref) => (
<div
ref={ref}
data-slot="card"
className={cn(
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
@@ -12,74 +13,92 @@ function Card({ className, ...props }: React.ComponentProps<'div'>) {
)}
{...props}
/>
);
}
),
);
Card.displayName = 'Card';
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-header"
className={cn(
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
className,
)}
{...props}
/>
);
}
const CardHeader = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-slot="card-header"
className={cn(
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-2 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6',
className,
)}
{...props}
/>
));
CardHeader.displayName = 'CardHeader';
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
return (
const CardTitle = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
({ className, ...props }, ref) => (
<div
ref={ref}
data-slot="card-title"
className={cn('leading-none font-semibold', className)}
{...props}
/>
);
}
),
);
CardTitle.displayName = 'CardTitle';
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
);
}
const CardDescription = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-slot="card-description"
className={cn('text-muted-foreground text-sm', className)}
{...props}
/>
));
CardDescription.displayName = 'CardDescription';
function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-action"
className={cn(
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
className,
)}
{...props}
/>
);
}
const CardAction = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-slot="card-action"
className={cn(
'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
className,
)}
{...props}
/>
));
CardAction.displayName = 'CardAction';
function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-content"
className={cn('px-6', className)}
{...props}
/>
);
}
const CardContent = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-slot="card-content"
className={cn('px-6', className)}
{...props}
/>
));
CardContent.displayName = 'CardContent';
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
return (
<div
data-slot="card-footer"
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
{...props}
/>
);
}
const CardFooter = React.forwardRef<
HTMLDivElement,
React.ComponentProps<'div'>
>(({ className, ...props }, ref) => (
<div
ref={ref}
data-slot="card-footer"
className={cn('flex items-center px-6 [.border-t]:pt-6', className)}
{...props}
/>
));
CardFooter.displayName = 'CardFooter';
export {
Card,
+21 -21
View File
@@ -6,27 +6,27 @@ import { CheckIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
function Checkbox({
className,
...props
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
return (
<CheckboxPrimitive.Root
data-slot="checkbox"
className={cn(
'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',
className,
)}
{...props}
const Checkbox = React.forwardRef<
React.ElementRef<typeof CheckboxPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
>(({ className, ...props }, ref) => (
<CheckboxPrimitive.Root
ref={ref}
data-slot="checkbox"
className={cn(
'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',
className,
)}
{...props}
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none"
>
<CheckboxPrimitive.Indicator
data-slot="checkbox-indicator"
className="grid place-content-center text-current transition-none"
>
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
);
}
<CheckIcon className="size-3.5" />
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
));
Checkbox.displayName = CheckboxPrimitive.Root.displayName;
export { Checkbox };
+19 -15
View File
@@ -2,20 +2,24 @@ import * as React from 'react';
import { cn } from '@/lib/utils';
function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
return (
<input
type={type}
data-slot="input"
className={cn(
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className,
)}
{...props}
/>
);
}
const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
data-slot="input"
className={cn(
'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = 'Input';
export { Input };
+15 -15
View File
@@ -3,20 +3,20 @@ import * as LabelPrimitive from '@radix-ui/react-label';
import { cn } from '@/lib/utils';
function Label({
className,
...props
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
return (
<LabelPrimitive.Root
data-slot="label"
className={cn(
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
className,
)}
{...props}
/>
);
}
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
data-slot="label"
className={cn(
'flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50',
className,
)}
{...props}
/>
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label };
+119 -131
View File
@@ -4,60 +4,47 @@ import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
function Select({
...props
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
return <SelectPrimitive.Root data-slot="select" {...props} />;
}
const Select = SelectPrimitive.Root;
function SelectGroup({
...props
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
return <SelectPrimitive.Group data-slot="select-group" {...props} />;
}
const SelectGroup = SelectPrimitive.Group;
function SelectValue({
...props
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
return <SelectPrimitive.Value data-slot="select-value" {...props} />;
}
const SelectValue = SelectPrimitive.Value;
function SelectTrigger({
className,
size = 'default',
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
size?: 'sm' | 'default';
}) {
return (
<SelectPrimitive.Trigger
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
}
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & {
size?: 'sm' | 'default';
}
>(({ className, size = 'default', children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
data-slot="select-trigger"
data-size={size}
className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDownIcon className="size-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
function SelectContent({
className,
children,
position = 'popper',
align = 'center',
...props
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
return (
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(
(
{ className, children, position = 'popper', align = 'center', ...props },
ref,
) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
data-slot="select-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 relative z-50 max-h-(--radix-select-content-available-height) min-w-[8rem] origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border shadow-md',
@@ -82,94 +69,95 @@ function SelectContent({
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
);
}
),
);
SelectContent.displayName = SelectPrimitive.Content.displayName;
function SelectLabel({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
return (
<SelectPrimitive.Label
data-slot="select-label"
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
{...props}
/>
);
}
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
data-slot="select-label"
className={cn('text-muted-foreground px-2 py-1.5 text-xs', className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
function SelectItem({
className,
children,
...props
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
return (
<SelectPrimitive.Item
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
);
}
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
data-slot="select-item"
className={cn(
"focus:bg-accent focus:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground relative flex w-full cursor-default items-center gap-2 rounded-sm py-1.5 pr-8 pl-2 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
className,
)}
{...props}
>
<span className="absolute right-2 flex size-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<CheckIcon className="size-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
function SelectSeparator({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
return (
<SelectPrimitive.Separator
data-slot="select-separator"
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
{...props}
/>
);
}
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
data-slot="select-separator"
className={cn('bg-border pointer-events-none -mx-1 my-1 h-px', className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
function SelectScrollUpButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
return (
<SelectPrimitive.ScrollUpButton
data-slot="select-scroll-up-button"
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
);
}
const SelectScrollUpButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
data-slot="select-scroll-up-button"
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronUpIcon className="size-4" />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
function SelectScrollDownButton({
className,
...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
return (
<SelectPrimitive.ScrollDownButton
data-slot="select-scroll-down-button"
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
);
}
const SelectScrollDownButton = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
data-slot="select-scroll-down-button"
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronDownIcon className="size-4" />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName;
export {
Select,
+7 -5
View File
@@ -1,16 +1,18 @@
import * as React from 'react';
import { cn } from '@/lib/utils';
function Skeleton({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) {
const Skeleton = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
return (
<div
ref={ref}
className={cn('animate-pulse rounded-md bg-muted/50', className)}
{...props}
/>
);
}
});
Skeleton.displayName = 'Skeleton';
export { Skeleton };
+58 -54
View File
@@ -3,62 +3,66 @@ import * as TabsPrimitive from '@radix-ui/react-tabs';
import { cn } from '@/lib/utils';
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot="tabs"
className={cn('flex flex-col gap-2', className)}
{...props}
/>
);
}
const Tabs = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Root>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Root
ref={ref}
data-slot="tabs"
className={cn('flex flex-col gap-2', className)}
{...props}
/>
));
Tabs.displayName = TabsPrimitive.Root.displayName;
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot="tabs-list"
className={cn(
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
className,
)}
{...props}
/>
);
}
const TabsList = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.List>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
data-slot="tabs-list"
className={cn(
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
className,
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow,border-color] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm data-[state=active]:border-b-2 data-[state=active]:border-accent [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
);
}
const TabsTrigger = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
data-slot="tabs-trigger"
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow,border-color] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm data-[state=active]:border-b-2 data-[state=active]:border-accent [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className,
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot="tabs-content"
className={cn('flex-1 outline-none', className)}
{...props}
/>
);
}
const TabsContent = React.forwardRef<
React.ElementRef<typeof TabsPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
data-slot="tabs-content"
forceMount
className={cn(
'flex-1 outline-none data-[state=inactive]:hidden',
className,
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsList, TabsTrigger, TabsContent };
+19 -28
View File
@@ -5,35 +5,26 @@ import { cn } from '@/lib/utils';
const TooltipProvider = TooltipPrimitive.Provider;
function Tooltip({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
return <TooltipPrimitive.Root {...props} />;
}
const Tooltip = TooltipPrimitive.Root;
function TooltipTrigger({
...props
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
return <TooltipPrimitive.Trigger {...props} />;
}
const TooltipTrigger = TooltipPrimitive.Trigger;
function TooltipContent({
className,
sideOffset = 4,
...props
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
return (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs 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',
className,
)}
{...props}
/>
</TooltipPrimitive.Portal>
);
}
const TooltipContent = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Portal>
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs 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',
className,
)}
{...props}
/>
</TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };