🎨 Add context-menu UI component

This commit is contained in:
pheralb
2025-09-30 15:59:33 +01:00
parent 5126eed189
commit c14555d21f
8 changed files with 181 additions and 0 deletions
@@ -0,0 +1,25 @@
<script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
import { cn } from "@/utils/cn";
let {
ref = $bindable(null),
portalProps,
class: className,
...restProps
}: ContextMenuPrimitive.ContentProps & {
portalProps?: ContextMenuPrimitive.PortalProps;
} = $props();
</script>
<ContextMenuPrimitive.Portal {...portalProps}>
<ContextMenuPrimitive.Content
bind:ref
data-slot="context-menu-content"
class={cn(
"z-50 max-h-(--bits-context-menu-content-available-height) min-w-[8rem] origin-(--bits-context-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border border-neutral-200 bg-white p-1 text-neutral-950 shadow-md 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 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 dark:border-neutral-800 dark:bg-neutral-900 dark:text-neutral-50",
className,
)}
{...restProps}
/>
</ContextMenuPrimitive.Portal>
@@ -0,0 +1,12 @@
<script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let { ref = $bindable(null), ...restProps }: ContextMenuPrimitive.GroupProps =
$props();
</script>
<ContextMenuPrimitive.Group
bind:ref
data-slot="context-menu-group"
{...restProps}
/>
@@ -0,0 +1,27 @@
<script lang="ts">
import { cn } from "@/utils/cn";
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let {
ref = $bindable(null),
class: className,
inset,
variant = "default",
...restProps
}: ContextMenuPrimitive.ItemProps & {
inset?: boolean;
variant?: "default" | "destructive";
} = $props();
</script>
<ContextMenuPrimitive.Item
bind:ref
data-slot="context-menu-item"
data-inset={inset}
data-variant={variant}
class={cn(
"relative flex cursor-default items-center gap-2.5 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-highlighted:bg-neutral-200 data-highlighted:text-neutral-900 data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-neutral-700 data-[variant=destructive]:data-highlighted:bg-neutral-100 data-[variant=destructive]:data-highlighted:text-neutral-700 dark:data-highlighted:bg-neutral-800 dark:data-highlighted:text-neutral-50 dark:data-[variant=destructive]:data-highlighted:bg-neutral-700 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-neutral-500 dark:[&_svg:not([class*='text-'])]:text-neutral-400 data-[variant=destructive]:*:[svg]:text-neutral-700",
className,
)}
{...restProps}
/>
@@ -0,0 +1,28 @@
<script lang="ts">
import { cn } from "@/utils/cn";
import type { WithElementRef } from "@/types/components";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
inset,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLDivElement>> & {
inset?: boolean;
} = $props();
</script>
<div
bind:this={ref}
data-slot="context-menu-label"
data-inset={inset}
class={cn(
"px-2 py-1.5 text-sm font-medium text-neutral-900 data-[inset]:pl-8 dark:text-neutral-100",
className,
)}
{...restProps}
>
{@render children?.()}
</div>
@@ -0,0 +1,17 @@
<script lang="ts">
import { cn } from "@/utils/cn";
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let {
ref = $bindable(null),
class: className,
...restProps
}: ContextMenuPrimitive.SeparatorProps = $props();
</script>
<ContextMenuPrimitive.Separator
bind:ref
data-slot="context-menu-separator"
class={cn("-mx-1 my-1 h-px bg-neutral-200 dark:bg-neutral-800", className)}
{...restProps}
/>
@@ -0,0 +1,24 @@
<script lang="ts">
import { cn } from "@/utils/cn";
import type { WithElementRef } from "bits-ui";
import type { HTMLAttributes } from "svelte/elements";
let {
ref = $bindable(null),
class: className,
children,
...restProps
}: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
</script>
<span
bind:this={ref}
data-slot="context-menu-shortcut"
class={cn(
"ml-auto text-xs tracking-widest text-neutral-400 dark:text-neutral-600",
className,
)}
{...restProps}
>
{@render children?.()}
</span>
@@ -0,0 +1,14 @@
<script lang="ts">
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
let {
ref = $bindable(null),
...restProps
}: ContextMenuPrimitive.TriggerProps = $props();
</script>
<ContextMenuPrimitive.Trigger
bind:ref
data-slot="context-menu-trigger"
{...restProps}
/>
+34
View File
@@ -0,0 +1,34 @@
import { ContextMenu as ContextMenuPrimitive } from "bits-ui";
import Trigger from "./context-menu-trigger.svelte";
import Group from "./context-menu-group.svelte";
import Item from "./context-menu-item.svelte";
import Content from "./context-menu-content.svelte";
import Shortcut from "./context-menu-shortcut.svelte";
import Separator from "./context-menu-separator.svelte";
import Label from "./context-menu-label.svelte";
const Sub = ContextMenuPrimitive.Sub;
const Root = ContextMenuPrimitive.Root;
export {
Sub,
Root,
Item,
Label,
Group,
Trigger,
Content,
Shortcut,
Separator,
//
Root as ContextMenu,
Sub as ContextMenuSub,
Item as ContextMenuItem,
Group as ContextMenuGroup,
Content as ContextMenuContent,
Trigger as ContextMenuTrigger,
Shortcut as ContextMenuShortcut,
Separator as ContextMenuSeparator,
Label as ContextMenuLabel,
};