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