From: Muhammad Rizki <[email protected]>
To: Ammar Faizi <[email protected]>
Cc: Muhammad Rizki <[email protected]>,
Alviro Iskandar Setiawan <[email protected]>,
GNU/Weeb Mailing List <[email protected]>
Subject: [PATCH v2 09/12] feat(ui): add dropdown-menu and update bits-ui version
Date: Sun, 9 Mar 2025 02:26:51 +0700 [thread overview]
Message-ID: <[email protected]> (raw)
In-Reply-To: <[email protected]>
Added dropdown-menu UI component and updated bits-ui version.
Signed-off-by: Muhammad Rizki <[email protected]>
---
package-lock.json | 8 +--
package.json | 2 +-
| 40 +++++++++++++++
| 27 ++++++++++
| 19 +++++++
| 23 +++++++++
| 23 +++++++++
| 30 +++++++++++
| 16 ++++++
| 20 ++++++++
| 19 +++++++
| 28 +++++++++++
| 50 +++++++++++++++++++
13 files changed, 300 insertions(+), 5 deletions(-)
create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte
create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte
create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte
create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte
create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte
create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte
create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
create mode 100644 src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte
create mode 100644 src/lib/components/ui/dropdown-menu/index.ts
diff --git a/package-lock.json b/package-lock.json
index 525092e..36dc7aa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -20,7 +20,7 @@
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"autoprefixer": "^10.4.20",
- "bits-ui": "^1.3.5",
+ "bits-ui": "^1.3.6",
"clsx": "^2.1.1",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
@@ -1898,9 +1898,9 @@
}
},
"node_modules/bits-ui": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-1.3.5.tgz",
- "integrity": "sha512-pfd8MK5Hp7bOvsW25LJrWVABmBIeAOH3g0pFPJLBIKlcqsaalEdBYejQmlSwrynEDX589aW3hTAWbhmWqRjjrQ==",
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-1.3.6.tgz",
+ "integrity": "sha512-0Ee7Ox5KqIBdio/+TG387Xlj6QJ0S7tHVS2K4DYiBHxBRbm6ni13i/pOoNDjeME6NOGUZxbSe4dNZIW3pGlxZA==",
"dev": true,
"license": "MIT",
"dependencies": {
diff --git a/package.json b/package.json
index 52b0e4c..4d084e5 100644
--- a/package.json
+++ b/package.json
@@ -18,7 +18,7 @@
"@sveltejs/kit": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"autoprefixer": "^10.4.20",
- "bits-ui": "^1.3.5",
+ "bits-ui": "^1.3.6",
"clsx": "^2.1.1",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
--git a/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte
new file mode 100644
index 0000000..660e574
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte
@@ -0,0 +1,40 @@
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive, type WithoutChildrenOrChild } from "bits-ui";
+ import Check from "lucide-svelte/icons/check";
+ import Minus from "lucide-svelte/icons/minus";
+ import { cn } from "$utils";
+ import type { Snippet } from "svelte";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children: childrenProp,
+ checked = $bindable(false),
+ indeterminate = $bindable(false),
+ ...restProps
+ }: WithoutChildrenOrChild<DropdownMenuPrimitive.CheckboxItemProps> & {
+ children?: Snippet;
+ } = $props();
+</script>
+
+<DropdownMenuPrimitive.CheckboxItem
+ bind:ref
+ bind:checked
+ bind:indeterminate
+ class={cn(
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
+ className
+ )}
+ {...restProps}
+>
+ {#snippet children({ checked, indeterminate })}
+ <span class="absolute left-2 flex size-3.5 items-center justify-center">
+ {#if indeterminate}
+ <Minus class="size-4" />
+ {:else}
+ <Check class={cn("size-4", !checked && "text-transparent")} />
+ {/if}
+ </span>
+ {@render childrenProp?.()}
+ {/snippet}
+</DropdownMenuPrimitive.CheckboxItem>
--git a/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
new file mode 100644
index 0000000..aada59c
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-content.svelte
@@ -0,0 +1,27 @@
+<script lang="ts">
+ import { cn } from "$utils";
+ import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ sideOffset = 4,
+ portalProps,
+ ...restProps
+ }: DropdownMenuPrimitive.ContentProps & {
+ portalProps?: DropdownMenuPrimitive.PortalProps;
+ } = $props();
+</script>
+
+<DropdownMenuPrimitive.Portal {...portalProps}>
+ <DropdownMenuPrimitive.Content
+ bind:ref
+ {sideOffset}
+ class={cn(
+ "z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md",
+ "outline-none 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",
+ className
+ )}
+ {...restProps}
+ />
+</DropdownMenuPrimitive.Portal>
--git a/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte
new file mode 100644
index 0000000..123869c
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
+ import { cn } from "$utils";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ inset,
+ ...restProps
+ }: DropdownMenuPrimitive.GroupHeadingProps & {
+ inset?: boolean;
+ } = $props();
+</script>
+
+<DropdownMenuPrimitive.GroupHeading
+ bind:ref
+ class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
+ {...restProps}
+/>
--git a/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
new file mode 100644
index 0000000..cb92909
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-item.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import { cn } from "$utils";
+ import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ inset,
+ ...restProps
+ }: DropdownMenuPrimitive.ItemProps & {
+ inset?: boolean;
+ } = $props();
+</script>
+
+<DropdownMenuPrimitive.Item
+ bind:ref
+ class={cn(
+ "relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0",
+ inset && "pl-8",
+ className
+ )}
+ {...restProps}
+/>
--git a/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte
new file mode 100644
index 0000000..4a0b460
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-label.svelte
@@ -0,0 +1,23 @@
+<script lang="ts">
+ import { cn } from "$utils";
+ import { type WithElementRef } from "bits-ui";
+ 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}
+ class={cn("px-2 py-1.5 text-sm font-semibold", inset && "pl-8", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</div>
--git a/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte
new file mode 100644
index 0000000..676f467
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte
@@ -0,0 +1,30 @@
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from "bits-ui";
+ import Circle from "lucide-svelte/icons/circle";
+ import { cn } from "$utils";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children: childrenProp,
+ ...restProps
+ }: WithoutChild<DropdownMenuPrimitive.RadioItemProps> = $props();
+</script>
+
+<DropdownMenuPrimitive.RadioItem
+ bind:ref
+ class={cn(
+ "relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none data-[disabled]:pointer-events-none data-[highlighted]:bg-accent data-[highlighted]:text-accent-foreground data-[disabled]:opacity-50",
+ className
+ )}
+ {...restProps}
+>
+ {#snippet children({ checked })}
+ <span class="absolute left-2 flex size-3.5 items-center justify-center">
+ {#if checked}
+ <Circle class="size-2 fill-current" />
+ {/if}
+ </span>
+ {@render childrenProp?.({ checked })}
+ {/snippet}
+</DropdownMenuPrimitive.RadioItem>
--git a/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte
new file mode 100644
index 0000000..da3d7b0
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-separator.svelte
@@ -0,0 +1,16 @@
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
+ import { cn } from "$utils";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: DropdownMenuPrimitive.SeparatorProps = $props();
+</script>
+
+<DropdownMenuPrimitive.Separator
+ bind:ref
+ class={cn("-mx-1 my-1 h-px bg-muted", className)}
+ {...restProps}
+/>
--git a/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte
new file mode 100644
index 0000000..8de03e7
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte
@@ -0,0 +1,20 @@
+<script lang="ts">
+ import type { HTMLAttributes } from "svelte/elements";
+ import { type WithElementRef } from "bits-ui";
+ import { cn } from "$utils";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ children,
+ ...restProps
+ }: WithElementRef<HTMLAttributes<HTMLSpanElement>> = $props();
+</script>
+
+<span
+ bind:this={ref}
+ class={cn("ml-auto text-xs tracking-widest opacity-60", className)}
+ {...restProps}
+>
+ {@render children?.()}
+</span>
--git a/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
new file mode 100644
index 0000000..d999904
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte
@@ -0,0 +1,19 @@
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
+ import { cn } from "$utils";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ ...restProps
+ }: DropdownMenuPrimitive.SubContentProps = $props();
+</script>
+
+<DropdownMenuPrimitive.SubContent
+ bind:ref
+ class={cn(
+ "z-50 min-w-[8rem] rounded-md border bg-popover p-1 text-popover-foreground shadow-lg focus:outline-none",
+ className
+ )}
+ {...restProps}
+/>
--git a/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte
new file mode 100644
index 0000000..cd0a621
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte
@@ -0,0 +1,28 @@
+<script lang="ts">
+ import { DropdownMenu as DropdownMenuPrimitive, type WithoutChild } from "bits-ui";
+ import ChevronRight from "lucide-svelte/icons/chevron-right";
+ import { cn } from "$utils";
+
+ let {
+ ref = $bindable(null),
+ class: className,
+ inset,
+ children,
+ ...restProps
+ }: WithoutChild<DropdownMenuPrimitive.SubTriggerProps> & {
+ inset?: boolean;
+ } = $props();
+</script>
+
+<DropdownMenuPrimitive.SubTrigger
+ bind:ref
+ class={cn(
+ "flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none data-[highlighted]:bg-accent data-[state=open]:bg-accent [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
+ inset && "pl-8",
+ className
+ )}
+ {...restProps}
+>
+ {@render children?.()}
+ <ChevronRight class="ml-auto" />
+</DropdownMenuPrimitive.SubTrigger>
--git a/src/lib/components/ui/dropdown-menu/index.ts b/src/lib/components/ui/dropdown-menu/index.ts
new file mode 100644
index 0000000..2ea0e8c
--- /dev/null
+++ b/src/lib/components/ui/dropdown-menu/index.ts
@@ -0,0 +1,50 @@
+import { DropdownMenu as DropdownMenuPrimitive } from "bits-ui";
+import CheckboxItem from "./dropdown-menu-checkbox-item.svelte";
+import Content from "./dropdown-menu-content.svelte";
+import GroupHeading from "./dropdown-menu-group-heading.svelte";
+import Item from "./dropdown-menu-item.svelte";
+import Label from "./dropdown-menu-label.svelte";
+import RadioItem from "./dropdown-menu-radio-item.svelte";
+import Separator from "./dropdown-menu-separator.svelte";
+import Shortcut from "./dropdown-menu-shortcut.svelte";
+import SubContent from "./dropdown-menu-sub-content.svelte";
+import SubTrigger from "./dropdown-menu-sub-trigger.svelte";
+
+const Sub = DropdownMenuPrimitive.Sub;
+const Root = DropdownMenuPrimitive.Root;
+const Trigger = DropdownMenuPrimitive.Trigger;
+const Group = DropdownMenuPrimitive.Group;
+const RadioGroup = DropdownMenuPrimitive.RadioGroup;
+
+export {
+ CheckboxItem,
+ Content,
+ Root as DropdownMenu,
+ CheckboxItem as DropdownMenuCheckboxItem,
+ Content as DropdownMenuContent,
+ Group as DropdownMenuGroup,
+ GroupHeading as DropdownMenuGroupHeading,
+ Item as DropdownMenuItem,
+ Label as DropdownMenuLabel,
+ RadioGroup as DropdownMenuRadioGroup,
+ RadioItem as DropdownMenuRadioItem,
+ Separator as DropdownMenuSeparator,
+ Shortcut as DropdownMenuShortcut,
+ Sub as DropdownMenuSub,
+ SubContent as DropdownMenuSubContent,
+ SubTrigger as DropdownMenuSubTrigger,
+ Trigger as DropdownMenuTrigger,
+ Group,
+ GroupHeading,
+ Item,
+ Label,
+ RadioGroup,
+ RadioItem,
+ Root,
+ Separator,
+ Shortcut,
+ Sub,
+ SubContent,
+ SubTrigger,
+ Trigger
+};
--
Muhammad Rizki
next prev parent reply other threads:[~2025-03-08 19:27 UTC|newest]
Thread overview: 15+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-03-08 19:26 [PATCH v2 00/12] Fix Auth Guard, Move SEO Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 01/12] fix(svelte): use relative false Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 02/12] fix(avatar): change avatarImg state to use from auth.user.photo state Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 03/12] chore(profile): add toUpperCase() on getShortName() Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 04/12] fix(profile): make social fields default to empty string Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 05/12] chore(toaster): change toast message position and use richColors Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 06/12] chore(profile): reset password value on success Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 07/12] fix(profile-avatar): add delete avatar method Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 08/12] chore(profile): add space for password confirmation form Muhammad Rizki
2025-03-08 19:26 ` Muhammad Rizki [this message]
2025-03-08 19:26 ` [PATCH v2 10/12] chore(sidebar-menu): change sidebar menu look Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 11/12] chore(seo): move seo from layout to /home page Muhammad Rizki
2025-03-08 19:26 ` [PATCH v2 12/12] fix(auth): fix auth guard when credentials is invalid Muhammad Rizki
2025-03-08 19:38 ` [PATCH v2 00/12] Fix Auth Guard, Move SEO Ammar Faizi
2025-03-08 19:51 ` Alviro Iskandar Setiawan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
[email protected] \
[email protected] \
[email protected] \
[email protected] \
[email protected] \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox