From 133da0f44844105bdf069a00214f9a62905c98c7 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 11 Oct 2025 07:54:57 -0400 Subject: [PATCH] wip: zen refactor selector --- .../console/app/src/component/dropdown.css | 80 +++++++ .../console/app/src/component/dropdown.tsx | 79 +++++++ packages/console/app/src/routes/user-menu.css | 67 +----- packages/console/app/src/routes/user-menu.tsx | 44 +--- .../app/src/routes/workspace-picker.css | 77 ++----- .../app/src/routes/workspace-picker.tsx | 62 ++---- .../[id]/members/member-section.module.css | 207 +----------------- .../workspace/[id]/members/member-section.tsx | 142 ++---------- .../workspace/[id]/members/role-dropdown.css | 66 ++++++ .../workspace/[id]/members/role-dropdown.tsx | 43 ++++ 10 files changed, 337 insertions(+), 530 deletions(-) create mode 100644 packages/console/app/src/component/dropdown.css create mode 100644 packages/console/app/src/component/dropdown.tsx create mode 100644 packages/console/app/src/routes/workspace/[id]/members/role-dropdown.css create mode 100644 packages/console/app/src/routes/workspace/[id]/members/role-dropdown.tsx 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 ( +
+ + + +
+ {props.children} +
+
+
+ ) +} + +interface DropdownItemProps { + children: JSX.Element + selected?: boolean + onClick?: () => void + type?: "button" | "submit" | "reset" +} + +export function DropdownItem(props: DropdownItemProps) { + return ( + + ) +} diff --git a/packages/console/app/src/routes/user-menu.css b/packages/console/app/src/routes/user-menu.css index 28c7937f..15700579 100644 --- a/packages/console/app/src/routes/user-menu.css +++ b/packages/console/app/src/routes/user-menu.css @@ -1,68 +1,17 @@ [data-component="user-menu"] { - 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-component="dropdown"] { + [data-slot="trigger"] span { color: var(--color-text-muted); } - } - [data-slot="chevron"] { - flex-shrink: 0; - color: var(--color-text-secondary); - } - - [data-slot="dropdown"] { - position: absolute; - top: 100%; - right: 0; - 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; - - @media (prefers-color-scheme: dark) { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + [data-slot="dropdown"] { + form { + width: 100%; + } } - form { - width: 100%; + [data-slot="item"] { + color: var(--color-danger); } } - - [data-slot="item"], - [data-slot="create-item"] { - width: 100%; - padding: var(--space-2-5) var(--space-3); - border: none; - background: none; - color: var(--color-danger); - font-size: var(--font-size-sm); - font-family: var(--font-sans); - text-align: left; - } } \ No newline at end of file diff --git a/packages/console/app/src/routes/user-menu.tsx b/packages/console/app/src/routes/user-menu.tsx index 8c011fc0..0af89f81 100644 --- a/packages/console/app/src/routes/user-menu.tsx +++ b/packages/console/app/src/routes/user-menu.tsx @@ -1,9 +1,7 @@ -import { Show, onCleanup, createEffect } from "solid-js" -import { createStore } from "solid-js/store" import { action, redirect } from "@solidjs/router" import { getRequestEvent } from "solid-js/web" import { useAuthSession } from "~/context/auth.session" -import { IconChevron } from "~/component/icon" +import { Dropdown } from "~/component/dropdown" import "./user-menu.css" const logout = action(async () => { @@ -23,41 +21,15 @@ const logout = action(async () => { }) export function UserMenu(props: { email: string | null | undefined }) { - const [store, setStore] = createStore({ - showDropdown: false, - }) - let dropdownRef: HTMLDivElement | undefined - - createEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef && !dropdownRef.contains(event.target as Node)) { - setStore("showDropdown", false) - } - } - - document.addEventListener("click", handleClickOutside) - - onCleanup(() => document.removeEventListener("click", handleClickOutside)) - }) - return (
-
- - - -
-
- -
-
-
-
+ +
+ +
+
) } diff --git a/packages/console/app/src/routes/workspace-picker.css b/packages/console/app/src/routes/workspace-picker.css index dec48228..ab7f5be6 100644 --- a/packages/console/app/src/routes/workspace-picker.css +++ b/packages/console/app/src/routes/workspace-picker.css @@ -1,66 +1,23 @@ [data-component="workspace-picker"] { - position: relative; - - [data-slot="trigger"] { - /* Override blue accent colors with neutral colors for dropdown trigger */ - --color-accent: var(--color-border); - --color-accent-hover: var(--color-border); - --color-accent-active: var(--color-border); - --color-primary: var(--color-border); - --color-primary-hover: var(--color-border); - --color-primary-active: var(--color-border); - --color-primary-alpha-20: transparent; - 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); + [data-component="dropdown"] { + [data-slot="trigger"] { + /* Override blue accent colors with neutral colors for dropdown trigger */ + --color-accent: var(--color-border); + --color-accent-hover: var(--color-border); + --color-accent-active: var(--color-border); + --color-primary: var(--color-border); + --color-primary-hover: var(--color-border); + --color-primary-active: var(--color-border); + --color-primary-alpha-20: transparent; } - span { - flex: 1; - text-align: left; - font-weight: 500; - color: var(--color-text); + [data-slot="dropdown"] { + max-height: 240px; + overflow-y: auto; + min-width: 200px; } } - [data-slot="chevron"] { - flex-shrink: 0; - color: var(--color-text-secondary); - } - - [data-slot="dropdown"] { - position: absolute; - top: 100%; - left: 0; - 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); - max-height: 240px; - overflow-y: auto; - min-width: 200px; - - @media (prefers-color-scheme: dark) { - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); - } - } - - [data-slot="item"], [data-slot="create-item"] { width: 100%; padding: var(--space-2-5) var(--space-3); @@ -70,6 +27,12 @@ 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-slot="create-form"] { diff --git a/packages/console/app/src/routes/workspace-picker.tsx b/packages/console/app/src/routes/workspace-picker.tsx index 34a54497..934ec168 100644 --- a/packages/console/app/src/routes/workspace-picker.tsx +++ b/packages/console/app/src/routes/workspace-picker.tsx @@ -1,5 +1,5 @@ import { query, useParams, action, createAsync, redirect, useSubmission } from "@solidjs/router" -import { For, Show, createEffect, onCleanup } from "solid-js" +import { For, Show, createEffect } from "solid-js" import { createStore } from "solid-js/store" import { withActor } from "~/context/auth.withActor" import { Actor } from "@opencode-ai/console-core/actor.js" @@ -7,7 +7,7 @@ import { and, Database, eq, isNull } from "@opencode-ai/console-core/drizzle/ind import { WorkspaceTable } from "@opencode-ai/console-core/schema/workspace.sql.js" import { UserTable } from "@opencode-ai/console-core/schema/user.sql.js" import { Workspace } from "@opencode-ai/console-core/workspace.js" -import { IconChevron } from "~/component/icon" +import { Dropdown, DropdownItem } from "~/component/dropdown" import { Modal } from "~/component/modal" import "./workspace-picker.css" @@ -45,9 +45,7 @@ export function WorkspacePicker() { const submission = useSubmission(createWorkspace) const [store, setStore] = createStore({ showForm: false, - showDropdown: false, }) - let dropdownRef: HTMLDivElement | undefined let inputRef: HTMLInputElement | undefined const currentWorkspace = () => { @@ -56,7 +54,7 @@ export function WorkspacePicker() { } const handleWorkspaceNew = () => { - setStore({ showForm: true, showDropdown: false }) + setStore("showForm", true) } createEffect(() => { @@ -66,11 +64,7 @@ export function WorkspacePicker() { }) const handleSelectWorkspace = (workspaceID: string) => { - if (workspaceID === params.id) { - setStore("showDropdown", false) - return - } - + if (workspaceID === params.id) return window.location.href = `/workspace/${workspaceID}` } @@ -78,48 +72,22 @@ export function WorkspacePicker() { createEffect(() => { params.id setStore("showForm", false) - setStore("showDropdown", false) - }) - - createEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (dropdownRef && !dropdownRef.contains(event.target as Node)) { - setStore("showDropdown", false) - } - } - - document.addEventListener("click", handleClickOutside) - onCleanup(() => document.removeEventListener("click", handleClickOutside)) }) return (
-
- - - -
- - {(workspace) => ( - - )} - - -
-
-
+ setStore("showForm", false)} title="Create New Workspace">
diff --git a/packages/console/app/src/routes/workspace/[id]/members/member-section.module.css b/packages/console/app/src/routes/workspace/[id]/members/member-section.module.css index 25b5eabf..9d6bbb39 100644 --- a/packages/console/app/src/routes/workspace/[id]/members/member-section.module.css +++ b/packages/console/app/src/routes/workspace/[id]/members/member-section.module.css @@ -92,93 +92,6 @@ line-height: 1.4; margin-top: calc(var(--space-1) * -1); } - - [data-slot="role-selector"] { - position: relative; - - [data-slot="trigger"] { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--space-2); - width: 100%; - padding: var(--space-2) var(--space-3); - border: 1px solid var(--color-border); - border-radius: var(--border-radius-sm); - background-color: var(--color-bg); - color: var(--color-text); - font-size: var(--font-size-sm); - line-height: 1.5; - cursor: pointer; - transition: all 0.15s ease; - - &:hover { - border-color: var(--color-accent); - } - - &:focus { - outline: none; - border-color: var(--color-accent); - box-shadow: 0 0 0 3px var(--color-accent-alpha); - } - - [data-slot="chevron"] { - opacity: 0.6; - transition: transform 0.15s ease; - } - } - - [data-slot="dropdown"] { - position: absolute; - top: 100%; - left: 0; - z-index: 10; - margin-top: var(--space-1); - padding: var(--space-1); - background-color: var(--color-bg); - border: 1px solid var(--color-border); - border-radius: var(--border-radius-sm); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - min-width: 280px; - width: max-content; - - [data-slot="item"] { - display: block; - width: 100%; - padding: var(--space-2) var(--space-3); - border: none; - background-color: transparent; - color: var(--color-text); - font-size: var(--font-size-sm); - text-align: left; - cursor: pointer; - border-radius: var(--border-radius-sm); - transition: background-color 0.15s ease; - - &:hover { - background-color: var(--color-bg-surface); - } - - &[data-selected="true"] { - background-color: var(--color-accent-alpha); - } - - div { - strong { - display: block; - color: var(--color-text); - margin-bottom: var(--space-1); - } - - p { - font-size: var(--font-size-xs); - color: var(--color-text-muted); - margin: 0; - } - } - } - } - } } [data-slot="members-table"] { @@ -226,125 +139,7 @@ &[data-slot="member-role"] { font-family: var(--font-mono); - - [data-slot="role-selector"] { - position: relative; - - [data-slot="trigger"] { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--space-2); - width: 100%; - padding: var(--space-2) var(--space-3); - border: 1px solid var(--color-border); - border-radius: var(--border-radius-sm); - background-color: var(--color-bg); - color: var(--color-text); - font-size: var(--font-size-sm); - line-height: 1.5; - cursor: pointer; - transition: all 0.15s ease; - font-family: var(--font-sans); - - &:hover { - border-color: var(--color-accent); - } - - &:focus { - outline: none; - border-color: var(--color-accent); - box-shadow: 0 0 0 3px var(--color-accent-alpha); - } - - [data-slot="chevron"] { - opacity: 0.6; - transition: transform 0.15s ease; - } - } - - [data-slot="dropdown"] { - position: absolute; - top: 100%; - left: 0; - z-index: 10; - margin-top: var(--space-1); - padding: var(--space-1); - background-color: var(--color-bg); - border: 1px solid var(--color-border); - border-radius: var(--border-radius-sm); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); - min-width: 280px; - width: max-content; - - [data-slot="item"] { - display: block; - width: 100%; - padding: var(--space-2) var(--space-3); - border: none; - background-color: transparent; - color: var(--color-text); - font-size: var(--font-size-sm); - text-align: left; - cursor: pointer; - border-radius: var(--border-radius-sm); - transition: background-color 0.15s ease; - - &:hover { - background-color: var(--color-bg-surface); - } - - &[data-selected="true"] { - background-color: var(--color-accent-alpha); - } - - div { - strong { - display: block; - color: var(--color-text); - margin-bottom: var(--space-1); - } - - p { - font-size: var(--font-size-xs); - color: var(--color-text-muted); - margin: 0; - } - } - } - } - } - - button { - display: flex; - align-items: center; - gap: var(--space-2); - padding: var(--space-2) var(--space-3); - font-size: var(--font-size-sm); - font-weight: 400; - border: none; - background-color: transparent; - color: var(--color-text-muted); - font-family: var(--font-mono); - border-radius: var(--border-radius-sm); - cursor: pointer; - transition: all 0.15s ease; - text-transform: none; - - &:hover:not(:disabled) { - background-color: var(--color-bg-surface); - color: var(--color-text); - } - - &:disabled { - cursor: default; - color: var(--color-text); - } - - span { - font-family: inherit; - } - } + text-transform: capitalize; } &[data-slot="member-usage"] { diff --git a/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx b/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx index e60049a7..557cd316 100644 --- a/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx +++ b/packages/console/app/src/routes/workspace/[id]/members/member-section.tsx @@ -1,12 +1,12 @@ import { json, query, action, useParams, createAsync, useSubmission } from "@solidjs/router" -import { createEffect, createSignal, For, Show, onCleanup } from "solid-js" +import { createEffect, For, Show } from "solid-js" import { withActor } from "~/context/auth.withActor" import { createStore } from "solid-js/store" import styles from "./member-section.module.css" import { UserRole } from "@opencode-ai/console-core/schema/user.sql.js" import { Actor } from "@opencode-ai/console-core/actor.js" import { User } from "@opencode-ai/console-core/user.js" -import { IconChevron } from "~/component/icon" +import { RoleDropdown } from "./role-dropdown" const listMembers = query(async (workspaceID: string) => { "use server" @@ -92,29 +92,15 @@ function MemberRow(props: { member: any; workspaceID: string; actorID: string; a const [store, setStore] = createStore({ editing: false, selectedRole: props.member.role as (typeof UserRole)[number], - showRoleDropdown: false, limit: "", }) - let roleDropdownRef: HTMLDivElement | undefined - createEffect(() => { if (!submission.pending && submission.result && !submission.result.error) { setStore("editing", false) } }) - createEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (roleDropdownRef && !roleDropdownRef.contains(event.target as Node)) { - setStore("showRoleDropdown", false) - } - } - - document.addEventListener("click", handleClickOutside) - onCleanup(() => document.removeEventListener("click", handleClickOutside)) - }) - function show() { while (true) { submission.clear() @@ -127,7 +113,6 @@ function MemberRow(props: { member: any; workspaceID: string; actorID: string; a function hide() { setStore("editing", false) - setStore("showRoleDropdown", false) } function getUsageDisplay() { @@ -153,58 +138,16 @@ function MemberRow(props: { member: any; workspaceID: string; actorID: string; a return `$${currentUsage} / ${limit}` } - const roleLabels = { - admin: { title: "Admin", description: "Can manage models, members, and billing" }, - member: { title: "Member", description: "Can only generate API keys for themselves" }, - } - return ( {props.member.accountEmail ?? props.member.email} {props.member.role}}> -
- - -
- - -
-
-
+ setStore("selectedRole", value as (typeof UserRole)[number])} + />
@@ -260,6 +203,11 @@ function MemberRow(props: { member: any; workspaceID: string; actorID: string; a ) } +const roleOptions = [ + { value: "admin", description: "Can manage models, members, and billing" }, + { value: "member", description: "Can only generate API keys for themselves" }, +] + export function MemberSection() { const params = useParams() const data = createAsync(() => listMembers(params.id)) @@ -267,12 +215,10 @@ export function MemberSection() { const [store, setStore] = createStore({ show: false, selectedRole: "member" as (typeof UserRole)[number], - showRoleDropdown: false, limit: "", }) let input: HTMLInputElement - let roleDropdownRef: HTMLDivElement | undefined createEffect(() => { if (!submission.pending && submission.result && !submission.result.error) { @@ -280,17 +226,6 @@ export function MemberSection() { } }) - createEffect(() => { - const handleClickOutside = (event: MouseEvent) => { - if (roleDropdownRef && !roleDropdownRef.contains(event.target as Node)) { - setStore("showRoleDropdown", false) - } - } - - document.addEventListener("click", handleClickOutside) - onCleanup(() => document.removeEventListener("click", handleClickOutside)) - }) - function show() { while (true) { submission.clear() @@ -304,12 +239,6 @@ export function MemberSection() { function hide() { setStore("show", false) - setStore("showRoleDropdown", false) - } - - const roleLabels = { - admin: { title: "Admin", description: "Can manage models, members, and billing" }, - member: { title: "Member", description: "Can only generate API keys for themselves" }, } return ( @@ -340,48 +269,11 @@ export function MemberSection() {

Role

-
- - -
- - -
-
-
+ setStore("selectedRole", value as (typeof UserRole)[number])} + />

Monthly spending limit

diff --git a/packages/console/app/src/routes/workspace/[id]/members/role-dropdown.css b/packages/console/app/src/routes/workspace/[id]/members/role-dropdown.css new file mode 100644 index 00000000..f54f92c4 --- /dev/null +++ b/packages/console/app/src/routes/workspace/[id]/members/role-dropdown.css @@ -0,0 +1,66 @@ +.role-dropdown { + [data-slot="trigger"] { + border: 1px solid var(--color-border); + background-color: var(--color-bg); + width: 100%; + text-transform: capitalize; + + &:hover { + border-color: var(--color-accent); + background-color: var(--color-bg); + } + + &:focus { + outline: none; + border-color: var(--color-accent); + box-shadow: 0 0 0 3px var(--color-accent-alpha); + } + } + + [data-slot="chevron"] { + opacity: 0.6; + } + + [data-slot="dropdown"] { + padding: var(--space-1); + min-width: 280px; + width: max-content; + } + + [data-slot="role-item"] { + display: block; + width: 100%; + padding: var(--space-2) var(--space-3); + border: none; + background-color: transparent; + color: var(--color-text); + font-size: var(--font-size-sm); + text-align: left; + cursor: pointer; + border-radius: var(--border-radius-sm); + transition: background-color 0.15s ease; + + &:hover { + background-color: var(--color-bg-surface); + } + + &[data-selected="true"] { + background-color: var(--color-accent-alpha); + } + + div { + strong { + display: block; + color: var(--color-text); + margin-bottom: var(--space-1); + text-transform: capitalize; + } + + p { + font-size: var(--font-size-xs); + color: var(--color-text-muted); + margin: 0; + } + } + } +} \ No newline at end of file diff --git a/packages/console/app/src/routes/workspace/[id]/members/role-dropdown.tsx b/packages/console/app/src/routes/workspace/[id]/members/role-dropdown.tsx new file mode 100644 index 00000000..93dfb4e4 --- /dev/null +++ b/packages/console/app/src/routes/workspace/[id]/members/role-dropdown.tsx @@ -0,0 +1,43 @@ +import { createSignal } from "solid-js" +import { Dropdown } from "~/component/dropdown" +import "./role-dropdown.css" + +interface RoleOption { + value: string + description: string +} + +interface RoleDropdownProps { + value: string + options: RoleOption[] + onChange: (value: string) => void +} + +export function RoleDropdown(props: RoleDropdownProps) { + const [open, setOpen] = createSignal(false) + + const handleSelect = (value: string) => { + props.onChange(value) + setOpen(false) + } + + return ( + + <> + {props.options.map((option) => ( + + ))} + + + ) +}