diff --git a/packages/console/app/src/component/dropdown.css b/packages/console/app/src/component/dropdown.css new file mode 100644 index 00000000..982367c6 --- /dev/null +++ b/packages/console/app/src/component/dropdown.css @@ -0,0 +1,80 @@ +[data-component="dropdown"] { + position: relative; + + [data-slot="trigger"] { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-2); + padding: var(--space-2) var(--space-3); + border: none; + border-radius: var(--border-radius-sm); + background-color: transparent; + color: var(--color-text); + font-size: var(--font-size-sm); + font-family: var(--font-sans); + cursor: pointer; + transition: all 0.15s ease; + + &:hover { + background-color: var(--color-surface-hover); + } + + span { + flex: 1; + text-align: left; + font-weight: 500; + } + } + + [data-slot="chevron"] { + flex-shrink: 0; + color: var(--color-text-secondary); + } + + [data-slot="dropdown"] { + position: absolute; + top: 100%; + z-index: 1000; + margin-top: var(--space-1); + border: 1px solid var(--color-border); + border-radius: var(--border-radius-sm); + background-color: var(--color-bg); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); + min-width: 160px; + + &[data-align="left"] { + left: 0; + } + + &[data-align="right"] { + right: 0; + } + + @media (prefers-color-scheme: dark) { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + } + } + + [data-slot="item"] { + display: block; + width: 100%; + padding: var(--space-2-5) var(--space-3); + border: none; + background: none; + color: var(--color-text); + font-size: var(--font-size-sm); + font-family: var(--font-sans); + text-align: left; + cursor: pointer; + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--color-bg-surface); + } + + &[data-selected="true"] { + background-color: var(--color-accent-alpha); + } + } +} \ No newline at end of file diff --git a/packages/console/app/src/component/dropdown.tsx b/packages/console/app/src/component/dropdown.tsx new file mode 100644 index 00000000..de99d448 --- /dev/null +++ b/packages/console/app/src/component/dropdown.tsx @@ -0,0 +1,79 @@ +import { JSX, Show, createEffect, onCleanup } from "solid-js" +import { createStore } from "solid-js/store" +import { IconChevron } from "./icon" +import "./dropdown.css" + +interface DropdownProps { + trigger: JSX.Element | string + children: JSX.Element + open?: boolean + onOpenChange?: (open: boolean) => void + align?: "left" | "right" + class?: string +} + +export function Dropdown(props: DropdownProps) { + const [store, setStore] = createStore({ + isOpen: props.open ?? false, + }) + let dropdownRef: HTMLDivElement | undefined + + createEffect(() => { + if (props.open !== undefined) { + setStore("isOpen", props.open) + } + }) + + createEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (dropdownRef && !dropdownRef.contains(event.target as Node)) { + setStore("isOpen", false) + props.onOpenChange?.(false) + } + } + + document.addEventListener("click", handleClickOutside) + onCleanup(() => document.removeEventListener("click", handleClickOutside)) + }) + + const toggle = () => { + const newValue = !store.isOpen + setStore("isOpen", newValue) + props.onOpenChange?.(newValue) + } + + return ( +