refactor(permission): one level menu and one dialog shell across the surfaces

The member list, the create-page draft and the two "新增授权" dialogs had
each grown their own version of the same two things — so the level
control looked different depending on where you opened it, and the
draft picker was a look-alike of the share dialog rather than the same
dialog.

- PermissionLevelMenu extracts the list's dropdown (levels, separator,
  destructive 移除) and the draft editor adopts it, dropping its
  RelationSelect + separate delete icon button. Removal now always
  lives inside the menu, never as a button beside it; a row with
  neither permission renders the plain label at the same width, so the
  column never jitters.
- permissionDialogStyles holds the share dialog's chrome verbatim —
  shell sizing (80vh card / full-screen under 768px), subject tabs,
  包含子部门 toggle, footer captions — and the draft picker consumes it
  instead of re-approximating it.
- Empty authorization lists get their own copy (new key, three
  languages) instead of borrowing the search-result empty text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kinyoo
2026-08-12 17:40:10 +08:00
parent 611f142a84
commit 748fc43455
13 changed files with 266 additions and 130 deletions
@@ -1,8 +1,5 @@
import { Button } from "@bisheng/ui";
import { Outlined } from "bisheng-icons";
import { useLocalize } from "~/hooks";
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/Tooltip2";
import { RelationSelect } from "./RelationSelect";
import { PermissionLevelMenu } from "./PermissionLevelMenu";
import type { RelationModelOption } from "./RelationSelect";
import {
getPermissionDraftRowKey,
@@ -58,6 +55,10 @@ export function PermissionDraftEditor({
&& capabilities.canChangeRelation
&& relationModels.length > 0;
const canRemove = !row.immutableCreator && capabilities.canRemove;
const activeModelId = row.modelId ?? row.relation;
const relationLabel =
capabilities.relationModels.find((model) => model.id === activeModelId)?.name
?? localize(`com_permission.level_${row.relation}`);
return (
<div key={rowKey} className="flex min-h-11 items-center gap-3 py-2">
@@ -68,34 +69,19 @@ export function PermissionDraftEditor({
<span className="min-w-0 truncate text-body text-text-1">{row.subjectName}</span>
</div>
{row.immutableCreator ? (
<span className="px-2 text-body text-[#999999]">{localize("creator")}</span>
<span className="inline-flex h-8 w-[96px] shrink-0 items-center justify-end whitespace-nowrap px-2 text-[14px] leading-[22px] text-[#999999]">
{localize("creator")}
</span>
) : (
<RelationSelect
value={row.modelId ?? row.relation}
onChange={(modelId) => handleRelationChange(row, modelId)}
<PermissionLevelMenu
label={relationLabel}
options={relationModels}
disabled={!canChangeRelation}
className="w-32"
activeId={row.modelId ?? row.relation}
canChangeLevel={canChangeRelation}
onChange={(modelId) => handleRelationChange(row, modelId)}
onRemove={canRemove ? () => handleRemove(row) : undefined}
/>
)}
{canRemove && (
<Tooltip>
<TooltipTrigger asChild>
<Button
type="button"
color="danger"
variant="text"
size="small"
iconOnly
aria-label={localize("com_permission.remove")}
onClick={() => handleRemove(row)}
>
<Outlined.Delete className="size-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{localize("com_permission.remove")}</TooltipContent>
</Tooltip>
)}
</div>
);
})}
@@ -3,6 +3,12 @@ import type { SubjectType } from "~/api/permission";
import { EmptyStateIllustration } from "~/components/illustrations";
import { useLocalize } from "~/hooks";
import { PermissionDraftEditor, type PermissionDraftEditorCapabilities } from "./PermissionDraftEditor";
import {
SUBJECT_TAB_BUTTON_ACTIVE_CLASS,
SUBJECT_TAB_BUTTON_CLASS,
SUBJECT_TAB_BUTTON_INACTIVE_CLASS,
SUBJECT_TAB_LIST_CLASS,
} from "./permissionDialogStyles";
import { getPermissionDraftRowKey, type PermissionDraftRow } from "./usePermissionDraft";
const SUBJECT_TYPES: SubjectType[] = ["user", "department", "user_group"];
@@ -43,15 +49,15 @@ export function PermissionDraftPanel({
{localize("com_unified_permission.authorization")}
</div>
<div className="flex items-center justify-between gap-3">
<div className="inline-flex rounded-md bg-fill-2 p-[3px]">
<div className={`inline-flex items-center justify-center ${SUBJECT_TAB_LIST_CLASS}`}>
{SUBJECT_TYPES.map((type) => (
<button
key={type}
type="button"
className={`rounded px-3 py-0.5 text-body ${
className={`${SUBJECT_TAB_BUTTON_CLASS} ${
activeSubjectType === type
? "bg-blue-500/[0.15] font-medium text-blue-500"
: "text-text-3"
? SUBJECT_TAB_BUTTON_ACTIVE_CLASS
: SUBJECT_TAB_BUTTON_INACTIVE_CLASS
}`}
onClick={() => onActiveSubjectTypeChange(type)}
>
@@ -65,7 +71,7 @@ export function PermissionDraftPanel({
color="primary"
variant="filled"
size="small"
className="h-7"
className="h-7 px-3"
onClick={onAddAuthorization}
>
{localize("com_unified_permission.add_authorization")}
@@ -73,16 +79,19 @@ export function PermissionDraftPanel({
)}
</div>
<div
className="h-[400px] overflow-y-auto rounded-xl border border-border-base bg-white px-3"
className="h-[400px] overflow-y-auto rounded-xl bg-white pl-2"
data-testid="authorization-list-body"
>
{visibleRows.length === 0 ? (
<div className="flex h-full items-center justify-center">
<div className="flex h-full flex-col items-center justify-center gap-2">
<EmptyStateIllustration
role="img"
aria-label={localize("com_subscription.no_data")}
className="size-[120px]"
/>
<p className="text-body text-text-3">
{localize("com_unified_permission.authorization_empty")}
</p>
</div>
) : (
<PermissionDraftEditor
@@ -12,6 +12,14 @@ import {
} from "~/components/ui/Dialog";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/Tabs";
import { useLocalize } from "~/hooks";
import {
INCLUDE_CHILDREN_CHECKBOX_CLASS,
INCLUDE_CHILDREN_LABEL_CLASS,
PERMISSION_DIALOG_CONTENT_CLASS,
PERMISSION_FOOTER_LABEL_CLASS,
SUBJECT_TAB_LIST_CLASS,
SUBJECT_TAB_TRIGGER_CLASS,
} from "./permissionDialogStyles";
import { RelationSelect, type RelationModelOption } from "./RelationSelect";
import { SubjectSearchDepartment } from "./SubjectSearchDepartment";
import { SubjectSearchUser } from "./SubjectSearchUser";
@@ -105,28 +113,41 @@ export function PermissionDraftPickerDialog({
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="flex h-[min(680px,80vh)] max-w-[720px] flex-col overflow-hidden bg-surface-primary">
<DialogHeader>
<DialogTitle>{localize("com_unified_permission.add_authorization")}</DialogTitle>
<DialogContent className={PERMISSION_DIALOG_CONTENT_CLASS}>
<DialogHeader className="shrink-0 text-left">
<DialogTitle className="text-left">
{localize("com_unified_permission.add_authorization")}
</DialogTitle>
</DialogHeader>
<Tabs
value={subjectType}
onValueChange={handleSubjectTypeChange}
className="flex min-h-0 flex-1 flex-col"
className="mt-4 flex min-h-0 flex-1 flex-col overflow-hidden"
>
<div className="flex items-center justify-between gap-3">
<TabsList>
<TabsTrigger value="user">{localize("com_permission.subject_user")}</TabsTrigger>
<TabsTrigger value="department" disabled={!canAddNonUserSubjects}>
<div className="flex items-center gap-3">
<TabsList className={SUBJECT_TAB_LIST_CLASS}>
<TabsTrigger value="user" className={SUBJECT_TAB_TRIGGER_CLASS}>
{localize("com_permission.subject_user")}
</TabsTrigger>
<TabsTrigger
value="department"
className={SUBJECT_TAB_TRIGGER_CLASS}
disabled={!canAddNonUserSubjects}
>
{localize("com_permission.subject_department")}
</TabsTrigger>
<TabsTrigger value="user_group" disabled={!canAddNonUserSubjects}>
<TabsTrigger
value="user_group"
className={SUBJECT_TAB_TRIGGER_CLASS}
disabled={!canAddNonUserSubjects}
>
{localize("com_permission.subject_user_group")}
</TabsTrigger>
</TabsList>
{subjectType === "department" && (
<label className="flex items-center gap-2 text-body text-text-2">
<label className={INCLUDE_CHILDREN_LABEL_CLASS}>
<Checkbox
className={INCLUDE_CHILDREN_CHECKBOX_CLASS}
checked={includeChildren}
onCheckedChange={(checked) => setIncludeChildren(checked === true)}
/>
@@ -134,10 +155,10 @@ export function PermissionDraftPickerDialog({
</label>
)}
</div>
<TabsContent value="user" className="mt-4 min-h-0 flex-1">
<TabsContent value="user" className="mt-3 min-h-0 flex-1 overflow-hidden p-0">
<SubjectSearchUser {...searchProps} grantUsersApi={searchApi?.grantUsersApi} />
</TabsContent>
<TabsContent value="department" className="mt-4 min-h-0 flex-1">
<TabsContent value="department" className="mt-3 min-h-0 flex-1 overflow-hidden p-0">
<SubjectSearchDepartment
{...searchProps}
includeChildren={includeChildren}
@@ -145,13 +166,15 @@ export function PermissionDraftPickerDialog({
grantDepartmentSearchApi={searchApi?.grantDepartmentSearchApi}
/>
</TabsContent>
<TabsContent value="user_group" className="mt-4 min-h-0 flex-1">
<TabsContent value="user_group" className="mt-3 min-h-0 flex-1 overflow-hidden p-0">
<SubjectSearchUserGroup {...searchProps} grantUserGroupsApi={searchApi?.grantUserGroupsApi} />
</TabsContent>
</Tabs>
<DialogFooter className="items-center sm:justify-between">
<div className="flex items-center gap-2 text-body-sm text-text-3">
<span>{localize("com_permission.uniform_grant")}</span>
<DialogFooter className="mt-3 shrink-0 items-center border-t pt-3 sm:justify-between">
<div className="flex items-center gap-2">
<span className={PERMISSION_FOOTER_LABEL_CLASS}>
{localize("com_permission.uniform_grant")}
</span>
<RelationSelect
value={activeModel?.id ?? ""}
onChange={setSelectedModelId}
@@ -0,0 +1,106 @@
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/DropdownMenu";
import { ChevronDown } from "lucide-react";
import { useLocalize } from "~/hooks";
import { cn } from "~/utils";
import type { RelationModelOption } from "./RelationSelect";
interface PermissionLevelMenuProps {
/** Current level shown on the trigger. */
label: string;
options: RelationModelOption[];
activeId?: string;
/** When false the level items are hidden — the menu then only offers removal. */
canChangeLevel: boolean;
onChange: (modelId: string) => void;
/** Renders the destructive "移除" item. Omit to hide it. */
onRemove?: () => void;
className?: string;
}
/**
* Level dropdown shared by the member-management list and the create-page
* authorization draft. Removal lives inside the menu — never as a separate
* icon button next to it.
*/
export function PermissionLevelMenu({
label,
options,
activeId,
canChangeLevel,
onChange,
onRemove,
className,
}: PermissionLevelMenuProps) {
const localize = useLocalize();
const showLevels = canChangeLevel && options.length > 0;
if (!showLevels && !onRemove) {
return (
<span
className={cn(
"inline-flex h-8 w-[96px] shrink-0 items-center justify-end whitespace-nowrap px-2 text-[14px] leading-[22px] text-[#999999]",
className,
)}
>
{label}
</span>
);
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
className={cn(
"inline-flex h-8 w-[96px] items-center justify-end gap-1 rounded-md px-2 text-[14px] leading-[22px] text-[#999999] transition-colors hover:bg-[#F7F7F7]",
className,
)}
>
<span className="truncate">{label}</span>
<ChevronDown className="size-3.5 shrink-0 text-[#999999]" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="z-[120] max-h-[240px] w-[100px] overflow-x-hidden overflow-y-auto overscroll-none rounded-lg border-0 bg-white p-1 shadow-[0px_6px_20px_1px_rgba(117,145,212,0.12)] scrollbar-hide [&::-webkit-scrollbar]:!w-0 [&::-webkit-scrollbar]:!h-0"
>
{showLevels && options.map((model) => {
const active = model.id === activeId;
return (
<DropdownMenuItem
key={model.id}
className={cn(
"rounded-md px-2 py-[5px] text-[14px] leading-[22px]",
active
? "bg-blue-500/[0.07] text-blue-500 data-[highlighted]:bg-blue-500/[0.07] data-[highlighted]:text-blue-500"
: "text-[#212121] data-[highlighted]:bg-[#F7F7F7] data-[highlighted]:text-[#212121]",
)}
onSelect={() => onChange(model.id)}
>
{model.name}
</DropdownMenuItem>
);
})}
{showLevels && onRemove && (
<DropdownMenuSeparator className="my-1 bg-[#EBECF0]" />
)}
{onRemove && (
<DropdownMenuItem
aria-label={localize("com_permission.remove")}
className="rounded-md px-2 py-[5px] text-[14px] leading-[22px] text-[#F53F3F] data-[highlighted]:bg-[#FFF2F0] data-[highlighted]:text-[#F53F3F]"
onSelect={() => onRemove()}
>
{localize("com_permission.remove")}
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
);
}
@@ -12,19 +12,13 @@ import type {
RevokeItem,
} from "~/api/permission";
import { Avatar, AvatarName } from "~/components/ui/Avatar";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "~/components/ui/DropdownMenu";
import { Tooltip, TooltipContent, TooltipTrigger } from "~/components/ui/Tooltip2";
import { Building2, ChevronDown, RotateCcw, Search, User, Users } from "lucide-react";
import { Building2, RotateCcw, Search, User, Users } from "lucide-react";
import { LoadingIcon } from "~/components/ui/icon/Loading";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useLocalize } from "~/hooks";
import { cn } from "~/utils";
import { PermissionLevelMenu } from "./PermissionLevelMenu";
import { RelationModelOption } from "./RelationSelect";
// Tooltip that only shows when the wrapped element's text is truncated.
@@ -596,61 +590,20 @@ export function PermissionListTab({
</TruncatedTooltip>
<div className="flex w-[136px] shrink-0 items-center justify-end gap-1">
{(canModifyEntry && (!isOwner || canManageOwnerEntry)) || canDeleteEntrySubject ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
type="button"
className="inline-flex h-8 w-[96px] items-center justify-end gap-1 rounded-md px-2 text-[14px] leading-[22px] text-[#999999] transition-colors hover:bg-[#F7F7F7]"
>
<span className="truncate">{getPermissionLabel(entry)}</span>
<ChevronDown className="size-3.5 shrink-0 text-[#999999]" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
className="z-[120] max-h-[240px] w-[100px] overflow-x-hidden overflow-y-auto overscroll-none rounded-lg border-0 bg-white p-1 shadow-[0px_6px_20px_1px_rgba(117,145,212,0.12)] scrollbar-hide [&::-webkit-scrollbar]:!w-0 [&::-webkit-scrollbar]:!h-0"
>
{canModifyEntry && (!isOwner || canManageOwnerEntry) && entryModelOptions.map((model) => {
const active = model.id === currentModelId;
return (
<DropdownMenuItem
key={model.id}
className={cn(
"rounded-md px-2 py-[5px] text-[14px] leading-[22px]",
active
? "bg-blue-500/[0.07] text-blue-500 data-[highlighted]:bg-blue-500/[0.07] data-[highlighted]:text-blue-500"
: "text-[#212121] data-[highlighted]:bg-[#F7F7F7] data-[highlighted]:text-[#212121]",
)}
onSelect={() => {
void handleModify(entry, model.id);
}}
>
{model.name}
</DropdownMenuItem>
);
})}
{canModifyEntry && (!isOwner || canManageOwnerEntry) && canDeleteEntrySubject && (
<DropdownMenuSeparator className="my-1 bg-[#EBECF0]" />
)}
{canDeleteEntrySubject && (
<DropdownMenuItem
aria-label={localize("com_permission.remove")}
className="rounded-md px-2 py-[5px] text-[14px] leading-[22px] text-[#F53F3F] data-[highlighted]:bg-[#FFF2F0] data-[highlighted]:text-[#F53F3F]"
onSelect={() => {
void handleDeleteSubject(entry);
}}
>
{localize("com_permission.remove")}
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
) : (
<span className="inline-flex h-8 w-[96px] shrink-0 items-center justify-end whitespace-nowrap px-2 text-[14px] leading-[22px] text-[#999999]">
{getPermissionLabel(entry)}
</span>
)}
<PermissionLevelMenu
label={getPermissionLabel(entry)}
options={entryModelOptions}
activeId={currentModelId}
canChangeLevel={canModifyEntry && (!isOwner || canManageOwnerEntry)}
onChange={(modelId) => {
void handleModify(entry, modelId);
}}
onRemove={canDeleteEntrySubject
? () => {
void handleDeleteSubject(entry);
}
: undefined}
/>
</div>
</div>
);
@@ -26,7 +26,7 @@ export function SettingsSectionHeader({
}: SettingsSectionHeaderProps) {
const Icon = SECTION_ICONS[kind];
return (
<div className="flex h-8 items-center gap-2 rounded-md bg-fill-2 px-3 text-body-sm font-medium text-text-1">
<div className="flex h-8 items-center gap-2 rounded-md bg-fill-1 px-3 text-body-sm font-medium text-text-1">
<Icon className="size-4 text-blue-500" />
<span>{title}</span>
</div>
@@ -0,0 +1,42 @@
/**
* Shared chrome for the permission dialogs.
*
* Extracted verbatim from KnowledgeSpaceShareDialog, which shipped the original
* "新增授权" dialog. The unified-permission draft picker reuses these so both
* dialogs stay pixel-identical instead of drifting into two look-alikes.
*/
/** Dialog shell: fixed 80vh card on desktop, full-screen sheet under 768px. */
export const PERMISSION_DIALOG_CONTENT_CLASS =
"!flex h-[80vh] max-h-[800px] w-[calc(100vw-80px)] max-w-[800px] min-w-0 flex-col gap-0 overflow-hidden p-5 max-[768px]:fixed max-[768px]:inset-0 max-[768px]:h-[100dvh] max-[768px]:max-h-[100dvh] max-[768px]:w-full max-[768px]:max-w-none max-[768px]:translate-x-0 max-[768px]:translate-y-0 max-[768px]:rounded-none max-[768px]:p-4";
/** Subject-type switcher: bordered pill group, brand-tinted active segment. */
export const SUBJECT_TAB_LIST_CLASS =
"w-fit shrink-0 rounded-md border border-[#ECECEC] bg-white p-[3px] shadow-none";
export const SUBJECT_TAB_TRIGGER_CLASS =
"min-w-0 rounded-[4px] px-3 py-0.5 text-[14px] font-normal leading-[22px] text-[#818181] shadow-none data-[state=active]:bg-[rgb(var(--brand-500)/0.15)] data-[state=active]:font-medium data-[state=active]:text-blue-500 data-[state=active]:shadow-none";
/**
* Same switcher rendered with plain buttons instead of Radix Tabs — used where
* the active segment is driven by external state. Wrap them in
* `inline-flex items-center justify-center ${SUBJECT_TAB_LIST_CLASS}`.
*/
export const SUBJECT_TAB_BUTTON_CLASS =
"min-w-0 rounded-[4px] px-3 py-0.5 text-[14px] leading-[22px] transition-colors";
export const SUBJECT_TAB_BUTTON_ACTIVE_CLASS =
"bg-[rgb(var(--brand-500)/0.15)] font-medium text-blue-500";
export const SUBJECT_TAB_BUTTON_INACTIVE_CLASS = "font-normal text-[#818181]";
/** "包含子部门" toggle sitting next to the tab group. */
export const INCLUDE_CHILDREN_LABEL_CLASS =
"flex shrink-0 cursor-pointer items-center gap-2 text-[14px] leading-[22px] text-[#212121]";
export const INCLUDE_CHILDREN_CHECKBOX_CLASS =
"border-[#D9D9D9] data-[state=checked]:border-primary data-[state=indeterminate]:border-primary";
/** Muted caption used by the footer labels ("已选用户:", "统一授权:"). */
export const PERMISSION_FOOTER_LABEL_CLASS =
"shrink-0 text-[14px] font-normal leading-[22px] text-[#999999]";
@@ -1867,6 +1867,7 @@
"add_authorization": "Add permission",
"advanced_settings": "Advanced settings",
"authorization": "Permissions",
"authorization_empty": "No authorized subjects for this type yet",
"basic_settings": "Basic settings",
"cancel": "Cancel",
"confirm_create": "Create",
@@ -1790,6 +1790,7 @@
"add_authorization": "権限を追加",
"advanced_settings": "詳細設定",
"authorization": "権限",
"authorization_empty": "現在のタイプに権限対象はまだありません",
"basic_settings": "基本設定",
"cancel": "キャンセル",
"confirm_create": "作成を確定",
@@ -1796,6 +1796,7 @@
"add_authorization": "新增授权",
"advanced_settings": "高级设置",
"authorization": "授权",
"authorization_empty": "当前类型下暂无授权对象",
"basic_settings": "基础设置",
"cancel": "取消",
"confirm_create": "确认创建",
@@ -1,4 +1,5 @@
import { Outlined } from "bisheng-icons";
// bisheng-icons has no boxed plus; SquarePlus matches the sibling "add condition" button.
import { SquarePlus } from "lucide-react";
import type { ComponentProps } from "react";
import { NotificationSeverity } from "~/common";
import {
@@ -183,7 +184,7 @@ export function ChannelBusinessSettings({
/>
{form.createSubChannel && (
<div
className="overflow-hidden rounded-lg border border-border-base bg-white"
className="divide-y divide-border-base overflow-hidden rounded-lg border border-border-base bg-white"
data-testid="sub-channel-list"
>
{form.subChannels.map((subChannel) => (
@@ -219,9 +220,12 @@ export function ChannelBusinessSettings({
<button
type="button"
onClick={form.handleAddSubChannel}
className="flex h-12 w-full items-center gap-2 bg-fill-2 px-4 text-body text-text-2 hover:bg-fill-3"
className="flex h-12 w-full items-center gap-3 bg-[#F8F8F8] px-4 text-left text-body text-text-1 transition-colors hover:bg-fill-2"
>
<Outlined.Plus className="size-4" />
<SquarePlus
className="size-4 shrink-0 text-text-2"
strokeWidth={1.5}
/>
{settings.localize("com_subscription.add_sub_channel")}
</button>
)}
@@ -1,4 +1,14 @@
import { useCallback, useEffect, useMemo, useState } from "react";
import {
INCLUDE_CHILDREN_CHECKBOX_CLASS,
INCLUDE_CHILDREN_LABEL_CLASS,
PERMISSION_DIALOG_CONTENT_CLASS,
SUBJECT_TAB_BUTTON_ACTIVE_CLASS,
SUBJECT_TAB_BUTTON_CLASS,
SUBJECT_TAB_BUTTON_INACTIVE_CLASS,
SUBJECT_TAB_LIST_CLASS,
SUBJECT_TAB_TRIGGER_CLASS,
} from "~/components/permission/permissionDialogStyles";
import { PermissionGrantTab } from "~/components/permission/PermissionGrantTab";
import { PermissionListTab } from "~/components/permission/PermissionListTab";
import {
@@ -113,12 +123,12 @@ export function KnowledgeSpaceShareDialog({
className="flex min-h-0 flex-1 flex-col"
>
<div className="flex items-center justify-between gap-3">
<TabsList className="w-fit shrink-0 rounded-md border border-[#ECECEC] bg-white p-[3px] shadow-none">
<TabsList className={SUBJECT_TAB_LIST_CLASS}>
{SUBJECT_TABS.map((tab) => (
<TabsTrigger
key={tab.value}
value={tab.value}
className="min-w-0 rounded-[4px] px-3 py-0.5 text-[14px] font-normal leading-[22px] text-[#818181] shadow-none data-[state=active]:bg-[rgb(var(--brand-500)/0.15)] data-[state=active]:font-medium data-[state=active]:text-blue-500 data-[state=active]:shadow-none"
className={SUBJECT_TAB_TRIGGER_CLASS}
>
{localize(tab.labelKey)}
</TabsTrigger>
@@ -160,7 +170,7 @@ export function KnowledgeSpaceShareDialog({
return (
<>
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="!flex h-[80vh] max-h-[800px] w-[calc(100vw-80px)] max-w-[800px] min-w-0 flex-col gap-0 overflow-hidden p-5 max-[768px]:fixed max-[768px]:inset-0 max-[768px]:h-[100dvh] max-[768px]:max-h-[100dvh] max-[768px]:w-full max-[768px]:max-w-none max-[768px]:translate-x-0 max-[768px]:translate-y-0 max-[768px]:rounded-none max-[768px]:p-4">
<DialogContent className={PERMISSION_DIALOG_CONTENT_CLASS}>
<DialogHeader className="shrink-0 text-left">
<DialogTitle className="text-left">{dialogTitle}</DialogTitle>
</DialogHeader>
@@ -172,7 +182,7 @@ export function KnowledgeSpaceShareDialog({
</Dialog>
<Dialog open={grantDialogOpen} onOpenChange={setGrantDialogOpen}>
<DialogContent className="!flex h-[80vh] max-h-[800px] w-[calc(100vw-80px)] max-w-[800px] min-w-0 flex-col gap-0 overflow-hidden p-5 max-[768px]:fixed max-[768px]:inset-0 max-[768px]:h-[100dvh] max-[768px]:max-h-[100dvh] max-[768px]:w-full max-[768px]:max-w-none max-[768px]:translate-x-0 max-[768px]:translate-y-0 max-[768px]:rounded-none max-[768px]:p-4">
<DialogContent className={PERMISSION_DIALOG_CONTENT_CLASS}>
<DialogHeader className="shrink-0 text-left">
<DialogTitle className="text-left">
{localize("com_permission.tab_grant")} - {resourceName}
@@ -181,16 +191,16 @@ export function KnowledgeSpaceShareDialog({
<div className="user-manger mt-4 flex min-h-0 flex-1 flex-col overflow-hidden">
<div className="flex items-center gap-3">
<div className="inline-flex w-fit shrink-0 items-center justify-center rounded-md border border-[#ECECEC] bg-white p-[3px]">
<div className={`inline-flex items-center justify-center ${SUBJECT_TAB_LIST_CLASS}`}>
{SUBJECT_TABS.map((tab) => (
<button
key={tab.value}
type="button"
className={[
"min-w-0 rounded-[4px] px-3 py-0.5 text-[14px] leading-[22px] transition-colors",
SUBJECT_TAB_BUTTON_CLASS,
grantSubjectType === tab.value
? "bg-[rgb(var(--brand-500)/0.15)] font-medium text-blue-500"
: "font-normal text-[#818181]",
? SUBJECT_TAB_BUTTON_ACTIVE_CLASS
: SUBJECT_TAB_BUTTON_INACTIVE_CLASS,
].join(" ")}
onClick={() => setGrantSubjectType(tab.value)}
>
@@ -200,9 +210,9 @@ export function KnowledgeSpaceShareDialog({
</div>
{grantSubjectType === "department" && (
<label className="flex shrink-0 cursor-pointer items-center gap-2 text-[14px] leading-[22px] text-[#212121]">
<label className={INCLUDE_CHILDREN_LABEL_CLASS}>
<Checkbox
className="border-[#D9D9D9] data-[state=checked]:border-primary data-[state=indeterminate]:border-primary"
className={INCLUDE_CHILDREN_CHECKBOX_CLASS}
checked={grantIncludeChildren}
onCheckedChange={(value) => setGrantIncludeChildren(value === true)}
/>
@@ -230,7 +230,7 @@ export function KnowledgeSpaceSettingsPage() {
return (
<main
className="flex h-full min-h-0 bg-white p-2"
className="flex h-full min-h-0 bg-white"
data-testid="knowledge-space-settings-page"
>
<div className="flex min-h-0 w-full flex-1 flex-col rounded-xl bg-white px-4 pt-4">