mirror of
https://github.com/different-ai/openwork
synced 2026-04-25 17:15:34 +02:00
feat(den): add shared select field (#1299)
* feat(den): add shared select field * remove screenshots from repo --------- Co-authored-by: Source Open <gh2@mcadam.io>
This commit is contained in:
@@ -27,7 +27,7 @@ export type DenInputProps = Omit<InputHTMLAttributes<HTMLInputElement>, "disable
|
||||
* Consistent text input for all dashboard pages, based on the
|
||||
* Shared Workspaces compact search field.
|
||||
*
|
||||
* Defaults: rounded-lg · py-2.5 · px-4 · text-[14px]
|
||||
* Defaults: rounded-lg · h-[42px] · px-4 · text-[14px]/leading-5
|
||||
* Icon: auto-positions and adjusts left padding.
|
||||
* No className needed at the call site — override only when necessary.
|
||||
*/
|
||||
@@ -56,7 +56,7 @@ export function DenInput({
|
||||
className={[
|
||||
// base visual style
|
||||
"w-full rounded-lg border border-gray-200 bg-white",
|
||||
"py-2.5 px-4 text-[14px] text-gray-900",
|
||||
"h-[42px] px-4 text-[14px] leading-5 text-gray-900",
|
||||
"outline-none transition-all placeholder:text-gray-400",
|
||||
"focus:border-gray-300 focus:ring-2 focus:ring-gray-900/5",
|
||||
// disabled state
|
||||
|
||||
57
ee/apps/den-web/app/(den)/_components/ui/select.tsx
Normal file
57
ee/apps/den-web/app/(den)/_components/ui/select.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import type { SelectHTMLAttributes } from "react";
|
||||
|
||||
export type DenSelectProps = Omit<SelectHTMLAttributes<HTMLSelectElement>, "disabled"> & {
|
||||
/**
|
||||
* Disables the select and dims it to 60 % opacity.
|
||||
* Forwarded as the native `disabled` attribute.
|
||||
*/
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* DenSelect
|
||||
*
|
||||
* Consistent native select for all dashboard pages, matched to the
|
||||
* Shared Workspaces compact field sizing used by DenInput.
|
||||
*
|
||||
* Defaults: rounded-lg · h-[42px] · px-4/pr-10 · text-[14px]/leading-5
|
||||
* Chevron: custom Lucide chevron replaces browser-native control chrome.
|
||||
* No className needed at the call site - override only when necessary.
|
||||
*/
|
||||
export function DenSelect({
|
||||
disabled = false,
|
||||
className,
|
||||
children,
|
||||
...rest
|
||||
}: DenSelectProps) {
|
||||
return (
|
||||
<div className="relative">
|
||||
<select
|
||||
{...rest}
|
||||
disabled={disabled}
|
||||
className={[
|
||||
"w-full appearance-none rounded-lg border border-gray-200 bg-white",
|
||||
"h-[42px] px-4 pr-10 text-[14px] leading-5 text-gray-900",
|
||||
"outline-none transition-all",
|
||||
"focus:border-gray-300 focus:ring-2 focus:ring-gray-900/5",
|
||||
disabled ? "cursor-not-allowed opacity-60" : "",
|
||||
className ?? "",
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" ")}
|
||||
>
|
||||
{children}
|
||||
</select>
|
||||
<div className="pointer-events-none absolute inset-y-0 right-3 flex items-center">
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className={disabled ? "text-gray-300" : "text-gray-400"}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -29,6 +29,7 @@ import { UnderlineTabs } from "../../../../_components/ui/tabs";
|
||||
import { DashboardPageTemplate } from "../../../../_components/ui/dashboard-page-template";
|
||||
import { DenButton } from "../../../../_components/ui/button";
|
||||
import { DenInput } from "../../../../_components/ui/input";
|
||||
import { DenSelect } from "../../../../_components/ui/select";
|
||||
|
||||
type MembersTab = "members" | "teams" | "roles" | "invitations";
|
||||
|
||||
@@ -304,17 +305,13 @@ export function ManageMembersScreen() {
|
||||
</label>
|
||||
<label className="grid gap-3">
|
||||
<span className="text-[14px] font-medium text-gray-700">Role</span>
|
||||
<select
|
||||
value={inviteRole}
|
||||
onChange={(event) => setInviteRole(event.target.value)}
|
||||
className="h-14 rounded-[20px] border border-gray-200 bg-[#f8fafc] px-4 text-[15px] text-gray-900 outline-none transition focus:border-gray-300 focus:ring-4 focus:ring-gray-900/5"
|
||||
>
|
||||
<DenSelect value={inviteRole} onChange={(event) => setInviteRole(event.target.value)}>
|
||||
{assignableRoles.map((role) => (
|
||||
<option key={role.id} value={role.role}>
|
||||
{formatRoleLabel(role.role)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</DenSelect>
|
||||
</label>
|
||||
<div className="flex gap-2 lg:justify-end">
|
||||
<ActionButton onClick={resetInviteForm}>Cancel</ActionButton>
|
||||
@@ -348,17 +345,13 @@ export function ManageMembersScreen() {
|
||||
>
|
||||
<label className="grid gap-3">
|
||||
<span className="text-[14px] font-medium text-gray-700">Role</span>
|
||||
<select
|
||||
value={memberRoleDraft}
|
||||
onChange={(event) => setMemberRoleDraft(event.target.value)}
|
||||
className="h-14 rounded-[20px] border border-gray-200 bg-[#f8fafc] px-4 text-[15px] text-gray-900 outline-none transition focus:border-gray-300 focus:ring-4 focus:ring-gray-900/5"
|
||||
>
|
||||
<DenSelect value={memberRoleDraft} onChange={(event) => setMemberRoleDraft(event.target.value)}>
|
||||
{assignableRoles.map((role) => (
|
||||
<option key={role.id} value={role.role}>
|
||||
{formatRoleLabel(role.role)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</DenSelect>
|
||||
</label>
|
||||
<div className="flex gap-2 lg:justify-end">
|
||||
<ActionButton onClick={resetMemberEditor}>Cancel</ActionButton>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { ArrowLeft, Upload } from "lucide-react";
|
||||
import { DenButton } from "../../../../_components/ui/button";
|
||||
import { DenInput } from "../../../../_components/ui/input";
|
||||
import { DenSelect } from "../../../../_components/ui/select";
|
||||
import { DenTextarea } from "../../../../_components/ui/textarea";
|
||||
import { getErrorMessage, requestJson } from "../../../../_lib/den-flow";
|
||||
import {
|
||||
@@ -238,15 +239,14 @@ export function SkillEditorScreen({ skillId }: { skillId?: string }) {
|
||||
|
||||
<label className="grid gap-2">
|
||||
<span className="text-[13px] font-medium text-gray-600">Visibility</span>
|
||||
<select
|
||||
<DenSelect
|
||||
value={visibility}
|
||||
onChange={(event) => setVisibility(event.target.value as SkillVisibility)}
|
||||
className="w-full rounded-lg border border-gray-200 bg-white px-4 py-2.5 text-[14px] text-gray-900 outline-none transition-all focus:border-gray-300 focus:ring-2 focus:ring-gray-900/5"
|
||||
>
|
||||
<option value="private">Private</option>
|
||||
<option value="org">Org</option>
|
||||
<option value="public">Public</option>
|
||||
</select>
|
||||
</DenSelect>
|
||||
</label>
|
||||
|
||||
<label className="grid gap-2">
|
||||
|
||||
Reference in New Issue
Block a user