From 0a357be160d672791bb99a36fc7d7c1299d5493d Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Tue, 9 Dec 2025 03:45:50 -0600
Subject: wip(desktop): progress
---
packages/ui/src/components/avatar.css | 35 +++
packages/ui/src/components/avatar.tsx | 28 +++
packages/ui/src/components/dropdown-menu.css | 119 +++++++++++
packages/ui/src/components/dropdown-menu.tsx | 308 +++++++++++++++++++++++++++
packages/ui/src/components/icon.tsx | 2 +
packages/ui/src/components/select.tsx | 12 +-
6 files changed, 501 insertions(+), 3 deletions(-)
create mode 100644 packages/ui/src/components/avatar.css
create mode 100644 packages/ui/src/components/avatar.tsx
create mode 100644 packages/ui/src/components/dropdown-menu.css
create mode 100644 packages/ui/src/components/dropdown-menu.tsx
(limited to 'packages/ui/src/components')
diff --git a/packages/ui/src/components/avatar.css b/packages/ui/src/components/avatar.css
new file mode 100644
index 000000000..bc87f3bd8
--- /dev/null
+++ b/packages/ui/src/components/avatar.css
@@ -0,0 +1,35 @@
+[data-component="avatar"] {
+ --avatar-bg: var(--color-surface-info-base);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ border-radius: var(--radius-sm);
+ border: 1px solid var(--color-border-weak-base);
+ font-family: var(--font-mono);
+ font-weight: 500;
+ text-transform: uppercase;
+ background-color: var(--avatar-bg);
+ color: oklch(from var(--avatar-bg) calc(l * 0.72) calc(c * 8) h);
+}
+
+[data-component="avatar"][data-size="small"] {
+ width: 1.25rem;
+ height: 1.25rem;
+ font-size: 0.75rem;
+ line-height: 1;
+}
+
+[data-component="avatar"][data-size="normal"] {
+ width: 1.5rem;
+ height: 1.5rem;
+ font-size: 1.125rem;
+ line-height: 1.5rem;
+}
+
+[data-component="avatar"][data-size="large"] {
+ width: 2rem;
+ height: 2rem;
+ font-size: 1.25rem;
+ line-height: 2rem;
+}
diff --git a/packages/ui/src/components/avatar.tsx b/packages/ui/src/components/avatar.tsx
new file mode 100644
index 000000000..183a15b9b
--- /dev/null
+++ b/packages/ui/src/components/avatar.tsx
@@ -0,0 +1,28 @@
+import { type ComponentProps, splitProps, Show } from "solid-js"
+
+export interface AvatarProps extends ComponentProps<"div"> {
+ fallback: string
+ background?: string
+ size?: "small" | "normal" | "large"
+}
+
+export function Avatar(props: AvatarProps) {
+ const [split, rest] = splitProps(props, ["fallback", "background", "size", "class", "classList", "style"])
+ return (
+
+ {split.fallback[0]}
+
+ )
+}
diff --git a/packages/ui/src/components/dropdown-menu.css b/packages/ui/src/components/dropdown-menu.css
new file mode 100644
index 000000000..710ff2ddc
--- /dev/null
+++ b/packages/ui/src/components/dropdown-menu.css
@@ -0,0 +1,119 @@
+[data-component="dropdown-menu-content"],
+[data-component="dropdown-menu-sub-content"] {
+ min-width: 8rem;
+ overflow: hidden;
+ border-radius: var(--radius-md);
+ border: 1px solid var(--border-weak-base);
+ background-color: var(--surface-raised-stronger-non-alpha);
+ padding: 4px;
+ box-shadow: var(--shadow-md);
+ z-index: 50;
+ transform-origin: var(--kb-menu-content-transform-origin);
+
+ &[data-closed] {
+ animation: dropdown-menu-close 0.15s ease-out;
+ }
+
+ &[data-expanded] {
+ animation: dropdown-menu-open 0.15s ease-out;
+ }
+}
+
+[data-component="dropdown-menu-content"],
+[data-component="dropdown-menu-sub-content"] {
+ [data-slot="dropdown-menu-item"],
+ [data-slot="dropdown-menu-checkbox-item"],
+ [data-slot="dropdown-menu-radio-item"],
+ [data-slot="dropdown-menu-sub-trigger"] {
+ position: relative;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 4px 8px;
+ border-radius: var(--radius-sm);
+ cursor: default;
+ user-select: none;
+ outline: none;
+
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-small);
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-strong);
+
+ &[data-highlighted] {
+ background: var(--surface-raised-base-hover);
+ }
+
+ &[data-disabled] {
+ color: var(--text-weak);
+ pointer-events: none;
+ }
+ }
+
+ [data-slot="dropdown-menu-sub-trigger"] {
+ &[data-expanded] {
+ background: var(--surface-raised-base-hover);
+ }
+ }
+
+ [data-slot="dropdown-menu-item-indicator"] {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 16px;
+ height: 16px;
+ }
+
+ [data-slot="dropdown-menu-item-label"] {
+ flex: 1;
+ }
+
+ [data-slot="dropdown-menu-item-description"] {
+ font-size: var(--font-size-x-small);
+ color: var(--text-weak);
+ }
+
+ [data-slot="dropdown-menu-separator"] {
+ height: 1px;
+ margin: 4px -4px;
+ border-top-color: var(--border-weak-base);
+ }
+
+ [data-slot="dropdown-menu-group-label"] {
+ padding: 4px 8px;
+ font-family: var(--font-family-sans);
+ font-size: var(--font-size-x-small);
+ font-weight: var(--font-weight-medium);
+ line-height: var(--line-height-large);
+ letter-spacing: var(--letter-spacing-normal);
+ color: var(--text-weak);
+ }
+
+ [data-slot="dropdown-menu-arrow"] {
+ fill: var(--surface-raised-stronger-non-alpha);
+ }
+}
+
+@keyframes dropdown-menu-open {
+ from {
+ opacity: 0;
+ transform: scale(0.96);
+ }
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes dropdown-menu-close {
+ from {
+ opacity: 1;
+ transform: scale(1);
+ }
+ to {
+ opacity: 0;
+ transform: scale(0.96);
+ }
+}
diff --git a/packages/ui/src/components/dropdown-menu.tsx b/packages/ui/src/components/dropdown-menu.tsx
new file mode 100644
index 000000000..efb2b45ca
--- /dev/null
+++ b/packages/ui/src/components/dropdown-menu.tsx
@@ -0,0 +1,308 @@
+import { DropdownMenu as Kobalte } from "@kobalte/core/dropdown-menu"
+import { splitProps } from "solid-js"
+import type { ComponentProps, ParentProps } from "solid-js"
+
+export interface DropdownMenuProps extends ComponentProps {}
+export interface DropdownMenuTriggerProps extends ComponentProps {}
+export interface DropdownMenuIconProps extends ComponentProps {}
+export interface DropdownMenuPortalProps extends ComponentProps {}
+export interface DropdownMenuContentProps extends ComponentProps {}
+export interface DropdownMenuArrowProps extends ComponentProps {}
+export interface DropdownMenuSeparatorProps extends ComponentProps {}
+export interface DropdownMenuGroupProps extends ComponentProps {}
+export interface DropdownMenuGroupLabelProps extends ComponentProps {}
+export interface DropdownMenuItemProps extends ComponentProps {}
+export interface DropdownMenuItemLabelProps extends ComponentProps {}
+export interface DropdownMenuItemDescriptionProps extends ComponentProps {}
+export interface DropdownMenuItemIndicatorProps extends ComponentProps {}
+export interface DropdownMenuRadioGroupProps extends ComponentProps {}
+export interface DropdownMenuRadioItemProps extends ComponentProps {}
+export interface DropdownMenuCheckboxItemProps extends ComponentProps {}
+export interface DropdownMenuSubProps extends ComponentProps {}
+export interface DropdownMenuSubTriggerProps extends ComponentProps {}
+export interface DropdownMenuSubContentProps extends ComponentProps {}
+
+function DropdownMenuRoot(props: DropdownMenuProps) {
+ return
+}
+
+function DropdownMenuTrigger(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuIcon(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuPortal(props: DropdownMenuPortalProps) {
+ return
+}
+
+function DropdownMenuContent(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuArrow(props: DropdownMenuArrowProps) {
+ const [local, rest] = splitProps(props, ["class", "classList"])
+ return (
+
+ )
+}
+
+function DropdownMenuSeparator(props: DropdownMenuSeparatorProps) {
+ const [local, rest] = splitProps(props, ["class", "classList"])
+ return (
+
+ )
+}
+
+function DropdownMenuGroup(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuGroupLabel(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuItem(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuItemLabel(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuItemDescription(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuItemIndicator(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuRadioGroup(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuRadioItem(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuCheckboxItem(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuSub(props: DropdownMenuSubProps) {
+ return
+}
+
+function DropdownMenuSubTrigger(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+function DropdownMenuSubContent(props: ParentProps) {
+ const [local, rest] = splitProps(props, ["class", "classList", "children"])
+ return (
+
+ {local.children}
+
+ )
+}
+
+export const DropdownMenu = Object.assign(DropdownMenuRoot, {
+ Trigger: DropdownMenuTrigger,
+ Icon: DropdownMenuIcon,
+ Portal: DropdownMenuPortal,
+ Content: DropdownMenuContent,
+ Arrow: DropdownMenuArrow,
+ Separator: DropdownMenuSeparator,
+ Group: DropdownMenuGroup,
+ GroupLabel: DropdownMenuGroupLabel,
+ Item: DropdownMenuItem,
+ ItemLabel: DropdownMenuItemLabel,
+ ItemDescription: DropdownMenuItemDescription,
+ ItemIndicator: DropdownMenuItemIndicator,
+ RadioGroup: DropdownMenuRadioGroup,
+ RadioItem: DropdownMenuRadioItem,
+ CheckboxItem: DropdownMenuCheckboxItem,
+ Sub: DropdownMenuSub,
+ SubTrigger: DropdownMenuSubTrigger,
+ SubContent: DropdownMenuSubContent,
+})
diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx
index 9e4f00a0d..8c83b41ce 100644
--- a/packages/ui/src/components/icon.tsx
+++ b/packages/ui/src/components/icon.tsx
@@ -133,6 +133,7 @@ const newIcons = {
"magnifying-glass": ``,
"plus-small": ``,
"chevron-down": ``,
+ "chevron-right": ``,
"arrow-up": ``,
"check-small": ``,
"edit-small-2": ``,
@@ -170,6 +171,7 @@ const newIcons = {
"layout-bottom": ``,
"layout-bottom-partial": ``,
"layout-bottom-full": ``,
+ "dot-grid": ``,
}
export interface IconProps extends ComponentProps<"svg"> {
diff --git a/packages/ui/src/components/select.tsx b/packages/ui/src/components/select.tsx
index 9ba1f177b..bde5325e9 100644
--- a/packages/ui/src/components/select.tsx
+++ b/packages/ui/src/components/select.tsx
@@ -1,10 +1,10 @@
import { Select as Kobalte } from "@kobalte/core/select"
-import { createMemo, splitProps, type ComponentProps } from "solid-js"
+import { createMemo, splitProps, type ComponentProps, type JSX } from "solid-js"
import { pipe, groupBy, entries, map } from "remeda"
import { Button, ButtonProps } from "./button"
import { Icon } from "./icon"
-export type SelectProps = Omit>, "value" | "onSelect"> & {
+export type SelectProps = Omit>, "value" | "onSelect" | "children"> & {
placeholder?: string
options: T[]
current?: T
@@ -14,6 +14,7 @@ export type SelectProps = Omit>, "value" | "
onSelect?: (value: T | undefined) => void
class?: ComponentProps<"div">["class"]
classList?: ComponentProps<"div">["classList"]
+ children?: (item: T | undefined) => JSX.Element
}
export function Select(props: SelectProps & ButtonProps) {
@@ -27,6 +28,7 @@ export function Select(props: SelectProps & ButtonProps) {
"label",
"groupBy",
"onSelect",
+ "children",
])
const grouped = createMemo(() => {
const result = pipe(
@@ -63,7 +65,11 @@ export function Select(props: SelectProps & ButtonProps) {
{...itemProps}
>
- {local.label ? local.label(itemProps.item.rawValue) : (itemProps.item.rawValue as string)}
+ {local.children
+ ? local.children(itemProps.item.rawValue)
+ : local.label
+ ? local.label(itemProps.item.rawValue)
+ : (itemProps.item.rawValue as string)}
--
cgit v1.2.3