From c36b74fda67705f806565dfe75c5e12231c94cd6 Mon Sep 17 00:00:00 2001 From: dongmucat <1127093059@qq.com> Date: Fri, 20 Mar 2026 11:34:09 +0800 Subject: [PATCH] feat(web): migrate shared select to radix --- .../namespace/add-namespace-member-dialog.tsx | 29 +-- .../features/token/create-token-dialog.tsx | 28 ++- web/src/features/token/token-list.tsx | 28 ++- web/src/pages/admin/audit-log.tsx | 39 +++- web/src/pages/admin/users.tsx | 45 +++-- web/src/pages/dashboard/namespace-members.tsx | 22 ++- web/src/pages/dashboard/publish.tsx | 47 +++-- web/src/shared/ui/select.tsx | 172 ++++++++++++++++-- 8 files changed, 317 insertions(+), 93 deletions(-) diff --git a/web/src/features/namespace/add-namespace-member-dialog.tsx b/web/src/features/namespace/add-namespace-member-dialog.tsx index 581ce169..95b1a01b 100644 --- a/web/src/features/namespace/add-namespace-member-dialog.tsx +++ b/web/src/features/namespace/add-namespace-member-dialog.tsx @@ -15,7 +15,13 @@ import { } from '@/shared/ui/dialog' import { Input } from '@/shared/ui/input' import { Label } from '@/shared/ui/label' -import { Select } from '@/shared/ui/select' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/shared/ui/select' interface AddNamespaceMemberDialogProps { slug: string @@ -212,16 +218,17 @@ export function AddNamespaceMemberDialog({ slug, children }: AddNamespaceMemberD
- setRole(value as NamespaceRole)}> + + + + + {ROLE_OPTIONS.map((option) => ( + + {t(option === 'ADMIN' ? 'members.roleAdmin' : 'members.roleMember')} + + ))} +
diff --git a/web/src/features/token/create-token-dialog.tsx b/web/src/features/token/create-token-dialog.tsx index 180b2165..432420de 100644 --- a/web/src/features/token/create-token-dialog.tsx +++ b/web/src/features/token/create-token-dialog.tsx @@ -14,7 +14,13 @@ import { import { Button } from '@/shared/ui/button' import { Input } from '@/shared/ui/input' import { Label } from '@/shared/ui/label' -import { Select } from '@/shared/ui/select' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/shared/ui/select' import { centeredToastOptions, toast } from '@/shared/lib/toast' import { formatLocalDateTime } from '@/shared/lib/date-time' import type { CreateTokenRequest, CreateTokenResponse } from '@/api/types' @@ -176,18 +182,22 @@ export function CreateTokenDialog({ children, existingNames = [] }: CreateTokenD
{expirationMode === 'custom' ? (
{expirationDialog.mode === 'custom' ? ( diff --git a/web/src/pages/admin/audit-log.tsx b/web/src/pages/admin/audit-log.tsx index a53e9e6a..a67e6771 100644 --- a/web/src/pages/admin/audit-log.tsx +++ b/web/src/pages/admin/audit-log.tsx @@ -4,7 +4,14 @@ import { formatLocalDateTime } from '@/shared/lib/date-time' import { Card } from '@/shared/ui/card' import { Input } from '@/shared/ui/input' import { Button } from '@/shared/ui/button' -import { Select } from '@/shared/ui/select' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + normalizeSelectValue, +} from '@/shared/ui/select' import { Table, TableBody, @@ -42,6 +49,7 @@ const ACTION_OPTIONS = [ */ export function AuditLogPage() { const { t, i18n } = useTranslation() + const allActionFilterValue = '__all_actions__' const [actionFilter, setActionFilter] = useState('') const [userIdFilter, setUserIdFilter] = useState('') const [requestIdFilter, setRequestIdFilter] = useState('') @@ -105,15 +113,26 @@ export function AuditLogPage() {
- { + setActionFilter(value === allActionFilterValue ? '' : value) + setPage(0) + }} + > + + + + + {ACTION_OPTIONS.map((option) => ( + + {t(option.labelKey)} + + ))} +
- setStatusFilter(value === allStatusFilterValue ? '' : value)} + > + + + + + {t('adminUsers.filterAll')} + {t('adminUsers.filterActive')} + {t('adminUsers.filterPending')} + {t('adminUsers.filterDisabled')} +
@@ -288,12 +304,17 @@ export function AdminUsersPage() {
- + + + + + {roleOptions.map((roleOption) => ( + + {roleOption.label} + + ))} +
diff --git a/web/src/pages/dashboard/namespace-members.tsx b/web/src/pages/dashboard/namespace-members.tsx index 66184af6..959c3177 100644 --- a/web/src/pages/dashboard/namespace-members.tsx +++ b/web/src/pages/dashboard/namespace-members.tsx @@ -8,7 +8,13 @@ import { ConfirmDialog } from '@/shared/components/confirm-dialog' import { DashboardPageHeader } from '@/shared/components/dashboard-page-header' import { Button } from '@/shared/ui/button' import { Card } from '@/shared/ui/card' -import { Select } from '@/shared/ui/select' +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/shared/ui/select' import { useMyNamespaces, useNamespaceDetail, @@ -192,17 +198,21 @@ export function NamespaceMembersPage() { {canManageMembers && !isOwner ? (
- + + + + + {t('publish.visibilityOptions.public')} + {namespaceOnlyLabel} + {t('publish.visibilityOptions.private')} +
diff --git a/web/src/shared/ui/select.tsx b/web/src/shared/ui/select.tsx index f2726e01..80e5888c 100644 --- a/web/src/shared/ui/select.tsx +++ b/web/src/shared/ui/select.tsx @@ -1,19 +1,27 @@ import * as React from 'react' +import * as SelectPrimitive from '@radix-ui/react-select' +import { Check, ChevronDown, ChevronUp } from 'lucide-react' import { cn } from '@/shared/lib/utils' export const SELECT_TRIGGER_CLASS_NAME = cn( - 'flex h-11 w-full rounded-lg border border-border/60 bg-secondary/50 px-4 py-2 text-sm text-foreground', + 'flex h-11 w-full items-center justify-between gap-2 rounded-lg border border-border/60 bg-secondary/50 px-4 py-2 text-sm text-foreground', 'ring-offset-background transition-all duration-200', 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 focus-visible:border-primary/50', - 'disabled:cursor-not-allowed disabled:opacity-50' + 'disabled:cursor-not-allowed disabled:opacity-50', + 'data-[placeholder]:text-muted-foreground [&>span]:line-clamp-1' ) export const SELECT_CONTENT_CLASS_NAME = cn( - 'z-50 overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-md' + 'z-50 overflow-hidden rounded-lg border border-border bg-popover text-popover-foreground shadow-md', + 'data-[state=open]:animate-in data-[state=closed]:animate-out', + 'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0', + 'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95', + 'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2', + 'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2' ) export const SELECT_ITEM_CLASS_NAME = cn( - 'relative flex w-full cursor-default select-none items-center rounded-md py-2 pl-8 pr-3 text-sm outline-none', + 'relative flex w-full cursor-default select-none items-center rounded-md py-2 pl-8 pr-8 text-sm outline-none', 'focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50' ) @@ -21,22 +29,150 @@ export function normalizeSelectValue(value?: string | null) { return typeof value === 'string' && value.length > 0 ? value : undefined } -interface SelectProps extends React.SelectHTMLAttributes {} +const Select = SelectPrimitive.Root +const SelectGroup = SelectPrimitive.Group +const SelectValue = SelectPrimitive.Value -const Select = React.forwardRef( - ({ className, children, ...props }, ref) => { - return ( - - ) - } -) + + + + +)) -Select.displayName = 'Select' +SelectContent.displayName = SelectPrimitive.Content.displayName -export { Select } +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) + +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) + +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectSeparator, + SelectTrigger, + SelectValue, +}