summaryrefslogtreecommitdiffhomepage
path: root/packages/console/app/src/component
diff options
context:
space:
mode:
Diffstat (limited to 'packages/console/app/src/component')
-rw-r--r--packages/console/app/src/component/dropdown.css80
-rw-r--r--packages/console/app/src/component/dropdown.tsx79
2 files changed, 159 insertions, 0 deletions
diff --git a/packages/console/app/src/component/dropdown.css b/packages/console/app/src/component/dropdown.css
new file mode 100644
index 000000000..982367c6b
--- /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 000000000..de99d4481
--- /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 (
+ <div data-component="dropdown" class={props.class} ref={dropdownRef}>
+ <button data-slot="trigger" type="button" onClick={toggle}>
+ {typeof props.trigger === "string" ? <span>{props.trigger}</span> : props.trigger}
+ <IconChevron data-slot="chevron" />
+ </button>
+
+ <Show when={store.isOpen}>
+ <div data-slot="dropdown" data-align={props.align ?? "left"}>
+ {props.children}
+ </div>
+ </Show>
+ </div>
+ )
+}
+
+interface DropdownItemProps {
+ children: JSX.Element
+ selected?: boolean
+ onClick?: () => void
+ type?: "button" | "submit" | "reset"
+}
+
+export function DropdownItem(props: DropdownItemProps) {
+ return (
+ <button
+ data-slot="item"
+ data-selected={props.selected ?? false}
+ type={props.type ?? "button"}
+ onClick={props.onClick}
+ >
+ {props.children}
+ </button>
+ )
+}