mirror of
https://github.com/pheralb/svgl.git
synced 2025-12-29 08:01:36 +08:00
Merge pull request #777 from SameerJS6/add-lina
feat: add Lina (Improved ScrollArea)
This commit is contained in:
+1
-1
@@ -69,7 +69,7 @@
|
||||
"@sveltejs/vite-plugin-svelte": "6.1.3",
|
||||
"@tailwindcss/vite": "4.1.12",
|
||||
"@types/node": "24.3.0",
|
||||
"bits-ui": "2.9.4",
|
||||
"bits-ui": "2.11.3",
|
||||
"clsx": "2.1.1",
|
||||
"eslint": "9.33.0",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
|
||||
Generated
+30
-20
@@ -79,8 +79,8 @@ importers:
|
||||
specifier: 24.3.0
|
||||
version: 24.3.0
|
||||
bits-ui:
|
||||
specifier: 2.9.4
|
||||
version: 2.9.4(@internationalized/date@3.8.2)(svelte@5.38.2)
|
||||
specifier: 2.11.3
|
||||
version: 2.11.3(@internationalized/date@3.8.2)(svelte@5.38.2)
|
||||
clsx:
|
||||
specifier: 2.1.1
|
||||
version: 2.1.1
|
||||
@@ -1127,8 +1127,8 @@ packages:
|
||||
balanced-match@1.0.2:
|
||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||
|
||||
bits-ui@2.9.4:
|
||||
resolution: {integrity: sha512-Cqn685P6DDuEyBZT/CWMyS5+8JAnYbctvoEVPcmiut+HUpG3SozVgjoDaUib5VG4ZYUKEi1FPwHxiXo9c6J0PA==}
|
||||
bits-ui@2.11.3:
|
||||
resolution: {integrity: sha512-XhTuOgcgFatw2qxd+hIlu3EqfYu9R2FFMeJg8srZo3UVKBdOVWHbT0L3GBH46XsaESkTz3aEjU+BiGVoI/2rnQ==}
|
||||
engines: {node: '>=20'}
|
||||
peerDependencies:
|
||||
'@internationalized/date': ^3.8.1
|
||||
@@ -2661,6 +2661,11 @@ packages:
|
||||
peerDependencies:
|
||||
svelte: ^5.7.0
|
||||
|
||||
runed@0.31.1:
|
||||
resolution: {integrity: sha512-v3czcTnO+EJjiPvD4dwIqfTdHLZ8oH0zJheKqAHh9QMViY7Qb29UlAMRpX7ZtHh7AFqV60KmfxaJ9QMy+L1igQ==}
|
||||
peerDependencies:
|
||||
svelte: ^5.7.0
|
||||
|
||||
sade@1.8.1:
|
||||
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -2863,18 +2868,18 @@ packages:
|
||||
peerDependencies:
|
||||
svelte: ^5.0.0
|
||||
|
||||
svelte-toolbelt@0.10.5:
|
||||
resolution: {integrity: sha512-8e+eWTgxw1aiLxhDE8Rb1X6AoLitqpJz+WhAul2W7W58C8KoLoJQf1TgQdFPBiCPJ0Jg5y0Zi1uyua9em4VS0w==}
|
||||
engines: {node: '>=18', pnpm: '>=8.7.0'}
|
||||
peerDependencies:
|
||||
svelte: ^5.30.2
|
||||
|
||||
svelte-toolbelt@0.7.1:
|
||||
resolution: {integrity: sha512-HcBOcR17Vx9bjaOceUvxkY3nGmbBmCBBbuWLLEWO6jtmWH8f/QoWmbyUfQZrpDINH39en1b8mptfPQT9VKQ1xQ==}
|
||||
engines: {node: '>=18', pnpm: '>=8.7.0'}
|
||||
peerDependencies:
|
||||
svelte: ^5.0.0
|
||||
|
||||
svelte-toolbelt@0.9.3:
|
||||
resolution: {integrity: sha512-HCSWxCtVmv+c6g1ACb8LTwHVbDqLKJvHpo6J8TaqwUme2hj9ATJCpjCPNISR1OCq2Q4U1KT41if9ON0isINQZw==}
|
||||
engines: {node: '>=18', pnpm: '>=8.7.0'}
|
||||
peerDependencies:
|
||||
svelte: ^5.30.2
|
||||
|
||||
svelte@5.38.2:
|
||||
resolution: {integrity: sha512-iAcp/oFAWauVSGILdD67n7DiwgLHXZzWZIdzl7araRxu72jUr7PFAo2Iie7gXt0IbnlYvhxCb9GT3ZJUquO3PA==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -4173,15 +4178,15 @@ snapshots:
|
||||
|
||||
balanced-match@1.0.2: {}
|
||||
|
||||
bits-ui@2.9.4(@internationalized/date@3.8.2)(svelte@5.38.2):
|
||||
bits-ui@2.11.3(@internationalized/date@3.8.2)(svelte@5.38.2):
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.3
|
||||
'@floating-ui/dom': 1.7.4
|
||||
'@internationalized/date': 3.8.2
|
||||
esm-env: 1.2.2
|
||||
runed: 0.29.2(svelte@5.38.2)
|
||||
runed: 0.31.1(svelte@5.38.2)
|
||||
svelte: 5.38.2
|
||||
svelte-toolbelt: 0.9.3(svelte@5.38.2)
|
||||
svelte-toolbelt: 0.10.5(svelte@5.38.2)
|
||||
tabbable: 6.2.0
|
||||
|
||||
body-parser@2.2.0:
|
||||
@@ -5775,6 +5780,11 @@ snapshots:
|
||||
esm-env: 1.2.2
|
||||
svelte: 5.38.2
|
||||
|
||||
runed@0.31.1(svelte@5.38.2):
|
||||
dependencies:
|
||||
esm-env: 1.2.2
|
||||
svelte: 5.38.2
|
||||
|
||||
sade@1.8.1:
|
||||
dependencies:
|
||||
mri: 1.2.0
|
||||
@@ -6023,6 +6033,13 @@ snapshots:
|
||||
runed: 0.28.0(svelte@5.38.2)
|
||||
svelte: 5.38.2
|
||||
|
||||
svelte-toolbelt@0.10.5(svelte@5.38.2):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
runed: 0.29.2(svelte@5.38.2)
|
||||
style-to-object: 1.0.9
|
||||
svelte: 5.38.2
|
||||
|
||||
svelte-toolbelt@0.7.1(svelte@5.38.2):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
@@ -6030,13 +6047,6 @@ snapshots:
|
||||
style-to-object: 1.0.9
|
||||
svelte: 5.38.2
|
||||
|
||||
svelte-toolbelt@0.9.3(svelte@5.38.2):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
runed: 0.29.2(svelte@5.38.2)
|
||||
style-to-object: 1.0.9
|
||||
svelte: 5.38.2
|
||||
|
||||
svelte@5.38.2:
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import ShowCategories from "@/components/layout/showCategories.svelte";
|
||||
import ShowSidebarLinks from "@/components/layout/showSidebarLinks.svelte";
|
||||
import ScrollArea from "@/components/ui/scroll-area/scroll-area.svelte";
|
||||
import ScrollAreaScrollbar from "@/components/ui/scroll-area/scroll-area-scrollbar.svelte";
|
||||
</script>
|
||||
|
||||
<section>
|
||||
@@ -11,17 +13,25 @@
|
||||
class={cn(
|
||||
"md:fixed md:left-1 md:h-[calc(100vh-4.5rem)]",
|
||||
"overflow-x-hidden",
|
||||
"w-54 pr-2 pl-2",
|
||||
"w-54",
|
||||
"hidden flex-col space-y-3 md:flex",
|
||||
"bg-neutral-100 dark:bg-neutral-950",
|
||||
)}
|
||||
>
|
||||
<nav class="flex flex-col space-y-0.5">
|
||||
<nav class="flex flex-col space-y-0.5 px-2">
|
||||
<ShowSidebarLinks />
|
||||
</nav>
|
||||
<Separator orientation="horizontal" />
|
||||
<nav class="relative flex h-auto flex-col space-y-0.5 overflow-y-auto">
|
||||
<ShowCategories />
|
||||
<div class="px-2">
|
||||
<Separator orientation="horizontal" />
|
||||
</div>
|
||||
<nav class="relative flex h-auto flex-col space-y-0.5 overflow-hidden">
|
||||
<ScrollArea
|
||||
maskClassName="before:from-neutral-100 after:from-neutral-100 dark:before:from-neutral-950 dark:after:from-neutral-950"
|
||||
class="flex size-full flex-col gap-y-0.5 overflow-hidden px-2"
|
||||
>
|
||||
<ShowCategories />
|
||||
<ScrollAreaScrollbar orientation="vertical" />
|
||||
</ScrollArea>
|
||||
</nav>
|
||||
</aside>
|
||||
<main class={cn("px-2 md:mr-4 md:ml-56 md:px-0", "overflow-hidden")}>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
import ShowCategories from "@/components/layout/showCategories.svelte";
|
||||
import ShowSidebarLinks from "@/components/layout/showSidebarLinks.svelte";
|
||||
import ScrollArea from "@/components/ui/scroll-area/scroll-area.svelte";
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
@@ -33,10 +34,14 @@
|
||||
<h2 class="text-xl font-medium tracking-tight">svgl</h2>
|
||||
</Sheet.Title>
|
||||
</Sheet.Header>
|
||||
<nav class="flex flex-col space-y-0.5 overflow-y-auto px-3 pb-3">
|
||||
<ScrollArea
|
||||
class="flex size-full flex-col"
|
||||
viewportClassName="pb-3 px-3 space-y-0.5"
|
||||
maskClassName="before:from-white after:from-white dark:before:from-neutral-900 dark:after:from-neutral-900"
|
||||
>
|
||||
<ShowSidebarLinks />
|
||||
<Separator orientation="horizontal" class="my-3" />
|
||||
<ShowCategories />
|
||||
</nav>
|
||||
</ScrollArea>
|
||||
</Sheet.Content>
|
||||
</Sheet.Root>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
import { cn } from "@/utils/cn";
|
||||
import ScrollArea from "./ui/scroll-area/scroll-area.svelte";
|
||||
import { ScrollAreaScrollbar } from "./ui/scroll-area";
|
||||
|
||||
interface PageCardProps {
|
||||
children: Snippet;
|
||||
@@ -21,14 +23,16 @@
|
||||
containerClass,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
<ScrollArea
|
||||
maskHeight={50}
|
||||
maskClassName="before:from-transparent after:from-white dark:before:from-[#0f0f0f] dark:after:from-[#0f0f0f]"
|
||||
class={cn(
|
||||
"max-h-[calc(100vh-4.5rem)] min-h-[calc(100vh-4.5rem)]",
|
||||
"overflow-hidden overflow-y-auto",
|
||||
"flex size-full max-h-[calc(100vh-4.5rem)] min-h-[calc(100vh-4.5rem)] flex-col",
|
||||
contentCardClass,
|
||||
)}
|
||||
>
|
||||
<ScrollAreaScrollbar orientation="vertical" class="relative z-99999" />
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import Scrollbar from "./scroll-area-scrollbar.svelte";
|
||||
import Root from "./scroll-area.svelte";
|
||||
import Mask from "./scroll-area-mask.svelte";
|
||||
|
||||
export {
|
||||
Root,
|
||||
Scrollbar,
|
||||
Mask,
|
||||
//
|
||||
Root as ScrollArea,
|
||||
Scrollbar as ScrollAreaScrollbar,
|
||||
Mask as ScrollAreaMask,
|
||||
};
|
||||
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import { cn } from "@/utils/cn";
|
||||
type Mask = {
|
||||
top: boolean;
|
||||
bottom: boolean;
|
||||
left: boolean;
|
||||
right: boolean;
|
||||
};
|
||||
|
||||
let {
|
||||
showMask,
|
||||
maskHeight,
|
||||
class: className = "",
|
||||
}: {
|
||||
showMask: Mask;
|
||||
maskHeight: number;
|
||||
class?: string;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style={`--top-fade-height: ${showMask.top ? `${maskHeight}px` : "0px"}; --bottom-fade-height: ${showMask.bottom ? `${maskHeight}px` : "0px"};`}
|
||||
class={cn(
|
||||
"pointer-events-none absolute inset-0 z-10",
|
||||
"before:absolute before:inset-x-0 before:top-0 before:transition-[height,opacity] before:duration-300 before:content-['']",
|
||||
"after:absolute after:inset-x-0 after:bottom-0 after:transition-[height,opacity] after:duration-300 after:content-['']",
|
||||
"before:h-(--top-fade-height) after:h-(--bottom-fade-height)",
|
||||
showMask.top ? "before:opacity-100" : "before:opacity-0",
|
||||
showMask.bottom ? "after:opacity-100" : "after:opacity-0",
|
||||
"before:from-background before:bg-gradient-to-b before:to-transparent",
|
||||
"after:from-background after:bg-gradient-to-t after:to-transparent",
|
||||
className,
|
||||
)}
|
||||
></div>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
style={`--left-fade-width: ${showMask.left ? `${maskHeight}px` : "0px"}; --right-fade-width: ${showMask.right ? `${maskHeight}px` : "0px"};`}
|
||||
class={cn(
|
||||
"pointer-events-none absolute inset-0 z-10",
|
||||
"before:absolute before:inset-y-0 before:left-0 before:transition-[width,opacity] before:duration-300 before:content-['']",
|
||||
"after:absolute after:inset-y-0 after:right-0 after:transition-[width,opacity] after:duration-300 after:content-['']",
|
||||
"before:w-(--left-fade-width) after:w-(--right-fade-width)",
|
||||
showMask.left ? "before:opacity-100" : "before:opacity-0",
|
||||
showMask.right ? "after:opacity-100" : "after:opacity-0",
|
||||
"before:from-background before:bg-gradient-to-r before:to-transparent",
|
||||
"after:from-background after:bg-gradient-to-l after:to-transparent",
|
||||
className,
|
||||
)}
|
||||
></div>
|
||||
@@ -0,0 +1,44 @@
|
||||
<script lang="ts">
|
||||
import { ScrollArea as ScrollAreaPrimitive } from "bits-ui";
|
||||
import { cn, type WithoutChild } from "@/utils/cn";
|
||||
import { useHasPrimaryTouch } from "@/hooks/use-has-primary-touch";
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
orientation = "vertical",
|
||||
children,
|
||||
thumbClassName,
|
||||
...restProps
|
||||
}: WithoutChild<ScrollAreaPrimitive.ScrollbarProps> & {
|
||||
thumbClassName?: string;
|
||||
} = $props();
|
||||
|
||||
const hasPrimaryTouch = useHasPrimaryTouch();
|
||||
</script>
|
||||
|
||||
{#if !$hasPrimaryTouch}
|
||||
<ScrollAreaPrimitive.Scrollbar
|
||||
bind:ref
|
||||
data-slot="scroll-area-scrollbar"
|
||||
{orientation}
|
||||
class={cn(
|
||||
"flex touch-none p-px transition-[colors] duration-150 select-none hover:bg-neutral-200 data-[state=hidden]:animate-out data-[state=hidden]:fade-out-0 data-[state=visible]:animate-in data-[state=visible]:fade-in-0 dark:hover:bg-neutral-900",
|
||||
orientation === "vertical" &&
|
||||
"h-full w-2.5 border-l border-l-transparent",
|
||||
orientation === "horizontal" &&
|
||||
"h-2.5 flex-col border-t border-t-transparent px-1 pr-1.25",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
{@render children?.()}
|
||||
<ScrollAreaPrimitive.Thumb
|
||||
data-slot="scroll-area-thumb"
|
||||
class={cn(
|
||||
"relative my-0.5 flex-1 rounded-full bg-neutral-300 transition-colors ease-out hover:bg-neutral-500/50 active:bg-neutral-500/75 dark:bg-neutral-800 dark:hover:bg-neutral-700 dark:active:bg-neutral-600",
|
||||
thumbClassName,
|
||||
)}
|
||||
/>
|
||||
</ScrollAreaPrimitive.Scrollbar>
|
||||
{/if}
|
||||
@@ -0,0 +1,141 @@
|
||||
<script lang="ts">
|
||||
import { cn, type WithoutChild } from "@/utils/cn";
|
||||
import { ScrollArea as ScrollAreaPrimitive } from "bits-ui";
|
||||
import { Scrollbar } from "./index.js";
|
||||
import ScrollAreaMask from "./scroll-area-mask.svelte";
|
||||
import { useHasPrimaryTouch } from "@/hooks/use-has-primary-touch";
|
||||
|
||||
type Mask = {
|
||||
top: boolean;
|
||||
bottom: boolean;
|
||||
left: boolean;
|
||||
right: boolean;
|
||||
};
|
||||
|
||||
let {
|
||||
ref = $bindable(null),
|
||||
class: className,
|
||||
orientation = "vertical",
|
||||
scrollbarXClasses = "",
|
||||
scrollbarYClasses = "",
|
||||
children,
|
||||
scrollHideDelay = 0,
|
||||
maskHeight = 30,
|
||||
maskClassName = "",
|
||||
viewportClassName = "",
|
||||
...restProps
|
||||
}: WithoutChild<ScrollAreaPrimitive.RootProps> & {
|
||||
orientation?: "vertical" | "horizontal" | "both" | undefined;
|
||||
scrollbarXClasses?: string | undefined;
|
||||
scrollbarYClasses?: string | undefined;
|
||||
maskHeight?: number;
|
||||
maskClassName?: string;
|
||||
viewportClassName?: string;
|
||||
} = $props();
|
||||
|
||||
let viewportRef: HTMLDivElement | null = $state(null);
|
||||
const showMask = $state<Mask>({
|
||||
top: true,
|
||||
bottom: true,
|
||||
left: true,
|
||||
right: true,
|
||||
});
|
||||
|
||||
const hasPrimaryTouchStore = useHasPrimaryTouch();
|
||||
|
||||
const checkScrollability = () => {
|
||||
const element = viewportRef;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {
|
||||
scrollTop,
|
||||
scrollLeft,
|
||||
scrollWidth,
|
||||
clientWidth,
|
||||
scrollHeight,
|
||||
clientHeight,
|
||||
} = element;
|
||||
|
||||
showMask.top = scrollTop > 0;
|
||||
showMask.bottom = scrollTop + clientHeight < scrollHeight - 1;
|
||||
showMask.left = scrollLeft > 0;
|
||||
showMask.right = scrollLeft + clientWidth < scrollWidth - 1;
|
||||
};
|
||||
|
||||
$effect(() => {
|
||||
const element = viewportRef;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
|
||||
const resizeObserver = new ResizeObserver(checkScrollability);
|
||||
resizeObserver.observe(element);
|
||||
|
||||
element.addEventListener("scroll", checkScrollability, { signal });
|
||||
window.addEventListener("resize", checkScrollability, { signal });
|
||||
|
||||
checkScrollability();
|
||||
|
||||
return () => {
|
||||
controller.abort();
|
||||
resizeObserver.disconnect();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if $hasPrimaryTouchStore}
|
||||
<div
|
||||
bind:this={ref}
|
||||
role="group"
|
||||
data-slot="scroll-area"
|
||||
aria-roledescription="scroll area"
|
||||
class={cn("relative overflow-hidden", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<div
|
||||
bind:this={viewportRef}
|
||||
data-slot="scroll-area-viewport"
|
||||
class={cn("size-full overflow-auto rounded-[inherit]", viewportClassName)}
|
||||
tabIndex={0}
|
||||
>
|
||||
{@render children?.()}
|
||||
</div>
|
||||
|
||||
{#if maskHeight > 0}
|
||||
<ScrollAreaMask {showMask} class={maskClassName} {maskHeight} />{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<ScrollAreaPrimitive.Root
|
||||
bind:ref
|
||||
{scrollHideDelay}
|
||||
data-slot="scroll-area"
|
||||
class={cn("relative", className)}
|
||||
{...restProps}
|
||||
>
|
||||
<ScrollAreaPrimitive.Viewport
|
||||
bind:ref={viewportRef}
|
||||
data-slot="scroll-area-viewport"
|
||||
class={cn("focus-ring size-full rounded-[inherit]", viewportClassName)}
|
||||
>
|
||||
{@render children?.()}
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
|
||||
{#if maskHeight > 0}
|
||||
<ScrollAreaMask {maskHeight} class={maskClassName} {showMask} />
|
||||
{/if}
|
||||
|
||||
{#if orientation === "vertical" || orientation === "both"}
|
||||
<Scrollbar orientation="vertical" class={scrollbarYClasses} />
|
||||
{/if}
|
||||
|
||||
{#if orientation === "horizontal" || orientation === "both"}
|
||||
<Scrollbar orientation="horizontal" class={scrollbarXClasses} />
|
||||
{/if}
|
||||
<ScrollAreaPrimitive.Corner />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
{/if}
|
||||
@@ -0,0 +1,31 @@
|
||||
import { browser } from "$app/environment";
|
||||
import { writable } from "svelte/store";
|
||||
|
||||
export function useHasPrimaryTouch() {
|
||||
const { subscribe, set } = writable(false);
|
||||
if (!browser) {
|
||||
return {
|
||||
subscribe,
|
||||
destroy: () => {},
|
||||
};
|
||||
}
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
|
||||
const handleTouch = () => {
|
||||
const hasTouch = "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
||||
const prefersTouch = window.matchMedia("(pointer: coarse)").matches;
|
||||
set(hasTouch && prefersTouch);
|
||||
};
|
||||
|
||||
const mq = window.matchMedia("(pointer: coarse)");
|
||||
mq.addEventListener("change", handleTouch, { signal });
|
||||
window.addEventListener("pointerdown", handleTouch, { signal });
|
||||
|
||||
handleTouch();
|
||||
|
||||
return {
|
||||
subscribe,
|
||||
destroy: () => controller.abort(),
|
||||
};
|
||||
}
|
||||
+14
-3
@@ -1,6 +1,17 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export type WithoutChildren<T> = T extends { children?: any }
|
||||
? Omit<T, "children">
|
||||
: T;
|
||||
export type WithoutChildrenOrChild<T> = WithoutChildren<WithoutChild<T>>;
|
||||
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & {
|
||||
ref?: U | null;
|
||||
};
|
||||
Reference in New Issue
Block a user