fix(admin): adjust admin panel settings style (#11291)

Adjusted the style of some runtime configurations to match the new parameters.
Adjusted the style of dialog and right sidebar.

close AF-2411 AF-2412 AF-2413 AF-2422
This commit is contained in:
JimmFly
2025-03-31 10:26:07 +00:00
parent 51dddc10be
commit eda680ccdc
19 changed files with 202 additions and 88 deletions
@@ -1,6 +1,7 @@
import { cn } from '@affine/admin/utils'; import { cn } from '@affine/admin/utils';
import { CloseIcon } from '@blocksuite/icons/rc';
import * as DialogPrimitive from '@radix-ui/react-dialog'; import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react'; import { cssVarV2 } from '@toeverything/theme/v2';
import * as React from 'react'; import * as React from 'react';
const Dialog = DialogPrimitive.Root; const Dialog = DialogPrimitive.Root;
@@ -35,14 +36,14 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content <DialogPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg', 'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] border bg-background p-6 shadow-lg duration-200 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-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className className
)} )}
{...props} {...props}
> >
{children} {children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"> <DialogPrimitive.Close className="cursor-pointer absolute right-7 top-7 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" /> <CloseIcon fontSize={20} color={cssVarV2('selfhost/icon/secondary')} />
<span className="sr-only">Close</span> <span className="sr-only">Close</span>
</DialogPrimitive.Close> </DialogPrimitive.Close>
</DialogPrimitive.Content> </DialogPrimitive.Content>
@@ -56,7 +57,7 @@ const DialogHeader = ({
}: React.HTMLAttributes<HTMLDivElement>) => ( }: React.HTMLAttributes<HTMLDivElement>) => (
<div <div
className={cn( className={cn(
'flex flex-col space-y-1.5 text-center sm:text-left', 'flex flex-col space-y-3 text-center sm:text-left',
className className
)} )}
{...props} {...props}
@@ -85,7 +86,7 @@ const DialogTitle = React.forwardRef<
<DialogPrimitive.Title <DialogPrimitive.Title
ref={ref} ref={ref}
className={cn( className={cn(
'text-lg font-semibold leading-none tracking-tight', 'text-lg font-semibold tracking-tight leading-6.5',
className className
)} )}
{...props} {...props}
@@ -99,7 +100,7 @@ const DialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DialogPrimitive.Description <DialogPrimitive.Description
ref={ref} ref={ref}
className={cn('text-sm text-muted-foreground', className)} className={cn('text-[15px] text-muted-foreground leading-6', className)}
{...props} {...props}
/> />
)); ));
@@ -11,7 +11,7 @@ const ScrollArea = React.forwardRef<
className={cn('relative overflow-hidden', className)} className={cn('relative overflow-hidden', className)}
{...props} {...props}
> >
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] [&>*:first-child]:!block">
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<ScrollBar /> <ScrollBar />
@@ -4,10 +4,14 @@ import {
AvatarImage, AvatarImage,
} from '@affine/admin/components/ui/avatar'; } from '@affine/admin/components/ui/avatar';
import { FeatureType } from '@affine/graphql'; import { FeatureType } from '@affine/graphql';
import { AccountIcon, LockIcon, UnlockIcon } from '@blocksuite/icons/rc'; import {
AccountIcon,
EmailIcon,
LockIcon,
UnlockIcon,
} from '@blocksuite/icons/rc';
import type { ColumnDef } from '@tanstack/react-table'; import type { ColumnDef } from '@tanstack/react-table';
import { cssVarV2 } from '@toeverything/theme/v2'; import { cssVarV2 } from '@toeverything/theme/v2';
import { MailIcon } from 'lucide-react';
import { import {
type Dispatch, type Dispatch,
type ReactNode, type ReactNode,
@@ -201,19 +205,29 @@ export const useColumns = ({
color={cssVarV2('selfhost/icon/tertiary')} color={cssVarV2('selfhost/icon/tertiary')}
/> />
} }
IconFalse={<UnlockIcon fontSize={16} />} IconFalse={
<UnlockIcon
fontSize={16}
color={cssVarV2('toast/iconState/error')}
/>
}
textTrue="Password Set" textTrue="Password Set"
textFalse="No Password" textFalse="No Password"
/> />
<StatusItem <StatusItem
condition={user.emailVerified} condition={user.emailVerified}
IconTrue={ IconTrue={
<MailIcon <EmailIcon
size={16} fontSize={16}
color={cssVarV2('selfhost/icon/tertiary')} color={cssVarV2('selfhost/icon/tertiary')}
/> />
} }
IconFalse={<MailIcon size={16} />} IconFalse={
<EmailIcon
fontSize={16}
color={cssVarV2('toast/iconState/error')}
/>
}
textTrue="Email Verified" textTrue="Email Verified"
textFalse="Email Not Verified" textFalse="Email Not Verified"
/> />
@@ -53,9 +53,9 @@ export const DeleteAccountDialog = ({
value={input} value={input}
onChange={handleInput} onChange={handleInput}
placeholder="Please type email to confirm" placeholder="Please type email to confirm"
className="placeholder:opacity-50" className="placeholder:opacity-50 mt-4 h-9"
/> />
<DialogFooter> <DialogFooter className="mt-6">
<div className="flex justify-end gap-2 items-center w-full"> <div className="flex justify-end gap-2 items-center w-full">
<Button type="button" variant="outline" size="sm" onClick={onClose}> <Button type="button" variant="outline" size="sm" onClick={onClose}>
Cancel Cancel
@@ -53,9 +53,9 @@ export const DisableAccountDialog = ({
value={input} value={input}
onChange={handleInput} onChange={handleInput}
placeholder="Please type email to confirm" placeholder="Please type email to confirm"
className="placeholder:opacity-50" className="placeholder:opacity-50 mt-4 h-9"
/> />
<DialogFooter> <DialogFooter className="mt-6">
<div className="flex justify-end gap-2 items-center w-full"> <div className="flex justify-end gap-2 items-center w-full">
<Button type="button" variant="outline" size="sm" onClick={onClose}> <Button type="button" variant="outline" size="sm" onClick={onClose}>
Cancel Cancel
@@ -23,12 +23,12 @@ export const DiscardChanges = ({
<Dialog open={open} onOpenChange={onOpenChange}> <Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:w-[460px]"> <DialogContent className="sm:w-[460px]">
<DialogHeader> <DialogHeader>
<DialogTitle className="leading-6.5">Discard Changes</DialogTitle> <DialogTitle>Discard Changes</DialogTitle>
<DialogDescription className="leading-6 text-[15px]"> <DialogDescription className="leading-6 text-[15px]">
Changes to this user will not be saved. Changes to this user will not be saved.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<DialogFooter> <DialogFooter className="mt-6">
<div className="flex justify-end gap-2 items-center w-full"> <div className="flex justify-end gap-2 items-center w-full">
<Button type="button" onClick={onClose} variant="outline"> <Button type="button" onClick={onClose} variant="outline">
<span>Cancel</span> <span>Cancel</span>
@@ -32,7 +32,7 @@ export const EnableAccountDialog = ({
used to log in. used to log in.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<DialogFooter> <DialogFooter className="mt-6">
<div className="flex justify-end gap-2 items-center w-full"> <div className="flex justify-end gap-2 items-center w-full">
<Button type="button" onClick={onClose} variant="outline"> <Button type="button" onClick={onClose} variant="outline">
<span>Cancel</span> <span>Cancel</span>
@@ -105,7 +105,7 @@ export function ExportUsersDialog({
))} ))}
</div> </div>
<DialogFooter> <DialogFooter className="mt-6">
<Button <Button
type="button" type="button"
onClick={handleExport} onClick={handleExport}
@@ -31,7 +31,7 @@ export const ResetPasswordDialog = ({
complete it. complete it.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<DialogFooter> <DialogFooter className="mt-4">
<div className="flex justify-end gap-2 items-center w-full"> <div className="flex justify-end gap-2 items-center w-full">
<Input <Input
type="text" type="text"
@@ -99,13 +99,14 @@ function UserForm({
handleConfirm={handleConfirm} handleConfirm={handleConfirm}
canSave={canSave} canSave={canSave}
/> />
<div className="p-4 flex-grow overflow-y-auto space-y-[10px]"> <div className="p-4 flex-grow overflow-y-auto space-y-[8px]">
<div className="flex flex-col rounded-md border py-4 gap-4"> <div className="flex flex-col rounded-md border">
<InputItem <InputItem
label="Name" label="User name"
field="name" field="name"
value={changes.name} value={changes.name}
onChange={setField} onChange={setField}
placeholder="Enter user name"
/> />
<Separator /> <Separator />
<InputItem <InputItem
@@ -113,6 +114,7 @@ function UserForm({
field="email" field="email"
value={changes.email} value={changes.email}
onChange={setField} onChange={setField}
placeholder="Enter email address"
/> />
</div> </div>
@@ -153,8 +155,10 @@ function ToggleItem({
); );
return ( return (
<Label className="flex items-center justify-between px-4 py-3"> <Label className="flex items-center justify-between p-3 text-[15px] gap-2 font-medium leading-6 overflow-hidden">
<span>{name}</span> <span className="overflow-hidden text-ellipsis" title={name}>
{name}
</span>
<Switch checked={checked} onCheckedChange={onToggle} /> <Switch checked={checked} onCheckedChange={onToggle} />
</Label> </Label>
); );
@@ -165,11 +169,13 @@ function InputItem({
field, field,
value, value,
onChange, onChange,
placeholder,
}: { }: {
label: string; label: string;
field: keyof UserInput; field: keyof UserInput;
value?: string; value?: string;
onChange: (field: keyof UserInput, value: string) => void; onChange: (field: keyof UserInput, value: string) => void;
placeholder?: string;
}) { }) {
const onValueChange = useCallback( const onValueChange = useCallback(
(e: ChangeEvent<HTMLInputElement>) => { (e: ChangeEvent<HTMLInputElement>) => {
@@ -179,13 +185,16 @@ function InputItem({
); );
return ( return (
<div className="px-5 space-y-3"> <div className="p-3">
<Label className="text-sm font-medium">{label}</Label> <Label className="text-[15px] leading-6 font-medium mb-1.5">
{label}
</Label>
<Input <Input
type="text" type="text"
className="py-2 px-3 text-base font-normal" className="py-2 px-3 text-[15px] font-normal h-9"
value={value} value={value}
onChange={onValueChange} onChange={onValueChange}
placeholder={placeholder}
/> />
</div> </div>
); );
@@ -28,7 +28,7 @@ export const DiscardChanges = ({
Changes to this prompt will not be saved. Changes to this prompt will not be saved.
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<DialogFooter> <DialogFooter className="mt-6">
<div className="flex justify-end gap-2 items-center w-full"> <div className="flex justify-end gap-2 items-center w-full">
<Button type="button" onClick={onClose} variant="outline"> <Button type="button" onClick={onClose} variant="outline">
<span>Cancel</span> <span>Cancel</span>
@@ -7,6 +7,9 @@ import {
import { useCallback } from 'react'; import { useCallback } from 'react';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { buttonVariants } from '../../components/ui/button';
import { cn } from '../../utils';
export const CollapsibleItem = ({ export const CollapsibleItem = ({
title, title,
changeModule, changeModule,
@@ -14,22 +17,12 @@ export const CollapsibleItem = ({
title: string; title: string;
changeModule?: (module: string) => void; changeModule?: (module: string) => void;
}) => { }) => {
const handleClick = useCallback( const handleClick = useCallback(() => {
(id: string) => { changeModule?.(title);
const targetElement = document.getElementById(id); }, [changeModule, title]);
if (targetElement) {
targetElement.scrollIntoView({
behavior: 'smooth',
block: 'center',
});
}
changeModule?.(title);
},
[changeModule, title]
);
return ( return (
<Accordion type="multiple" className="w-full "> <Accordion type="multiple" className="w-full">
<AccordionItem value="item-1" className="border-b-0 ml-7 "> <AccordionItem value="item-1" className="border-b-0 ml-7">
<NavLink <NavLink
to={`/admin/settings/${title}`} to={`/admin/settings/${title}`}
className={({ isActive }) => { className={({ isActive }) => {
@@ -39,7 +32,7 @@ export const CollapsibleItem = ({
}} }}
> >
<AccordionTrigger <AccordionTrigger
onClick={() => handleClick(title)} onClick={handleClick}
className="py-2 px-2 rounded [&[data-state=closed]>svg]:rotate-270 [&[data-state=open]>svg]:rotate-360" className="py-2 px-2 rounded [&[data-state=closed]>svg]:rotate-270 [&[data-state=open]>svg]:rotate-360"
> >
{title} {title}
@@ -50,6 +43,36 @@ export const CollapsibleItem = ({
); );
}; };
export const NormalSubItem = ({
title,
changeModule,
}: {
title: string;
changeModule?: (module: string) => void;
}) => {
const handleClick = useCallback(() => {
changeModule?.(title);
}, [changeModule, title]);
return (
<div className="w-full flex">
<NavLink
to={`/admin/settings/${title}`}
onClick={handleClick}
className={({ isActive }) => {
return cn(
buttonVariants({
variant: 'ghost',
className: `ml-8 px-2 w-full justify-start ${isActive ? 'bg-zinc-100' : ''}`,
})
);
}}
>
{title}
</NavLink>
</div>
);
};
export const OtherModules = ({ export const OtherModules = ({
moduleList, moduleList,
changeModule, changeModule,
@@ -58,14 +81,14 @@ export const OtherModules = ({
changeModule?: (module: string) => void; changeModule?: (module: string) => void;
}) => { }) => {
return ( return (
<Accordion type="multiple" className="w-full "> <Accordion type="multiple" className="w-full">
<AccordionItem value="item-1" className="border-b-0"> <AccordionItem value="item-1" className="border-b-0">
<AccordionTrigger className="ml-7 py-2 px-2 rounded [&[data-state=closed]>svg]:rotate-270 [&[data-state=open]>svg]:rotate-360"> <AccordionTrigger className="ml-8 py-2 px-2 rounded [&[data-state=closed]>svg]:rotate-270 [&[data-state=open]>svg]:rotate-360">
Other Other
</AccordionTrigger> </AccordionTrigger>
<AccordionContent className="flex flex-col gap-2 py-1"> <AccordionContent className="flex flex-col gap-1 py-1">
{moduleList.map(module => ( {moduleList.map(module => (
<CollapsibleItem <NormalSubItem
key={module} key={module}
title={module} title={module}
changeModule={changeModule} changeModule={changeModule}
@@ -33,12 +33,15 @@ export const ServerVersion = () => {
} }
return ( return (
<div <div
className="flex items-center justify-between pt-2 border-t px-2 text-xs" className="inline-flex items-center justify-between pt-2 border-t px-2 text-xs flex-nowrap gap-1"
style={{ style={{
color: cssVarV2('text/tertiary'), color: cssVarV2('text/tertiary'),
}} }}
> >
ServerVersion<span>v{version}</span> <span>ServerVersion</span>
<span className="overflow-hidden text-ellipsis" title={version}>
v{version}
</span>
</div> </div>
); );
}; };
@@ -13,7 +13,7 @@ import { cssVarV2 } from '@toeverything/theme/v2';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
import { ALL_CONFIGURABLE_MODULES } from '../settings/config'; import { ALL_CONFIGURABLE_MODULES } from '../settings/config';
import { CollapsibleItem, OtherModules } from './collapsible-item'; import { NormalSubItem, OtherModules } from './collapsible-item';
import { useNav } from './context'; import { useNav } from './context';
const authModule = ALL_CONFIGURABLE_MODULES.find(module => module === 'auth'); const authModule = ALL_CONFIGURABLE_MODULES.find(module => module === 'auth');
@@ -53,13 +53,35 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
</NavigationMenuPrimitive.Trigger> </NavigationMenuPrimitive.Trigger>
<NavigationMenuPrimitive.Content> <NavigationMenuPrimitive.Content>
<ul <ul
className="border rounded-lg w-full flex flex-col p-1" className="border rounded-lg w-full flex flex-col p-1 min-w-[160px] max-h-[200px] overflow-y-auto"
style={{ style={{
backgroundColor: cssVarV2('layer/background/overlayPanel'), backgroundColor: cssVarV2('layer/background/overlayPanel'),
borderColor: cssVarV2('layer/insideBorder/blackBorder'), borderColor: cssVarV2('layer/insideBorder/blackBorder'),
}} }}
> >
{ALL_CONFIGURABLE_MODULES.map(module => ( {authModule ? (
<li key={authModule} className="flex">
<NavLink
to={`/admin/settings/${authModule}`}
className={cn(
buttonVariants({
variant: 'ghost',
className:
'p-2 rounded-[6px] text-[14px] w-full justify-start font-normal',
})
)}
style={({ isActive }) => ({
backgroundColor: isActive
? cssVarV2('selfhost/button/sidebarButton/bg/select')
: undefined,
})}
onClick={() => setCurrentModule?.(authModule)}
>
{authModule}
</NavLink>
</li>
) : null}
{otherModules.map(module => (
<li key={module} className="flex"> <li key={module} className="flex">
<NavLink <NavLink
to={`/admin/settings/${module}`} to={`/admin/settings/${module}`}
@@ -67,7 +89,7 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
buttonVariants({ buttonVariants({
variant: 'ghost', variant: 'ghost',
className: className:
'p-1.5 rounded-[6px] text-[14px] w-full justify-start', 'p-2 rounded-[6px] text-[14px] w-full justify-start font-normal',
}) })
)} )}
style={({ isActive }) => ({ style={({ isActive }) => ({
@@ -91,7 +113,7 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
} }
return ( return (
<Accordion type="multiple" className="w-full h-full overflow-hidden"> <Accordion type="multiple" className="w-full overflow-hidden">
<AccordionItem <AccordionItem
value="item-1" value="item-1"
className="border-b-0 h-full flex flex-col gap-1 w-full" className="border-b-0 h-full flex flex-col gap-1 w-full"
@@ -130,7 +152,7 @@ export const SettingsItem = ({ isCollapsed }: { isCollapsed: boolean }) => {
> >
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] [&>div]:!block"> <ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit] [&>div]:!block">
{authModule && ( {authModule && (
<CollapsibleItem <NormalSubItem
title={authModule} title={authModule}
changeModule={setCurrentModule} changeModule={setCurrentModule}
/> />
@@ -24,6 +24,40 @@ interface UserDropdownProps {
isCollapsed: boolean; isCollapsed: boolean;
} }
const UserInfo = ({
name,
email,
avatarUrl,
}: {
email: string;
avatarUrl: string | null;
name?: string;
}) => {
return (
<>
<Avatar className="w-8 h-8">
<AvatarImage src={avatarUrl ?? undefined} />
<AvatarFallback>
<CircleUser size={32} />
</AvatarFallback>
</Avatar>
<div className="flex flex-col font-medium gap-1">
{name ?? email.split('@')[0]}
<span
className="w-fit rounded px-2 py-0.5 text-xs h-5 border text-center inline-flex items-center font-normal"
style={{
borderRadius: '4px',
backgroundColor: cssVarV2('chip/label/blue'),
borderColor: cssVarV2('layer/insideBorder/border'),
}}
>
Admin
</span>
</div>
</>
);
};
export function UserDropdown({ isCollapsed }: UserDropdownProps) { export function UserDropdown({ isCollapsed }: UserDropdownProps) {
const currentUser = useCurrentUser(); const currentUser = useCurrentUser();
const relative = useRevalidateCurrentUser(); const relative = useRevalidateCurrentUser();
@@ -53,7 +87,15 @@ export function UserDropdown({ isCollapsed }: UserDropdownProps) {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" side="right"> <DropdownMenuContent align="end" side="right">
<DropdownMenuLabel>{currentUser?.name}</DropdownMenuLabel> <DropdownMenuLabel className="flex items-center gap-2">
{currentUser ? (
<UserInfo
email={currentUser.email}
name={currentUser.name}
avatarUrl={currentUser.avatarUrl}
/>
) : null}
</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem onSelect={handleLogout}>Logout</DropdownMenuItem> <DropdownMenuItem onSelect={handleLogout}>Logout</DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
@@ -104,25 +146,13 @@ export function UserDropdown({ isCollapsed }: UserDropdownProps) {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" side="right"> <DropdownMenuContent align="end" side="right">
<DropdownMenuLabel className="flex items-center gap-2"> <DropdownMenuLabel className="flex items-center gap-2">
<Avatar className="w-8 h-8"> {currentUser ? (
<AvatarImage src={currentUser?.avatarUrl ?? undefined} /> <UserInfo
<AvatarFallback> email={currentUser.email}
<CircleUser size={32} /> name={currentUser.name}
</AvatarFallback> avatarUrl={currentUser.avatarUrl}
</Avatar> />
<div className="flex flex-col font-medium gap-1"> ) : null}
{currentUser?.name ?? currentUser?.email.split('@')[0]}
<span
className="w-fit rounded px-2 py-0.5 text-xs h-5 border text-center inline-flex items-center font-normal"
style={{
borderRadius: '4px',
backgroundColor: cssVarV2('chip/label/blue'),
borderColor: cssVarV2('layer/insideBorder/border'),
}}
>
Admin
</span>
</div>
</DropdownMenuLabel> </DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem onSelect={handleLogout}>Logout</DropdownMenuItem> <DropdownMenuItem onSelect={handleLogout}>Logout</DropdownMenuItem>
@@ -2,6 +2,7 @@ import { Input } from '@affine/admin/components/ui/input';
import { Switch } from '@affine/admin/components/ui/switch'; import { Switch } from '@affine/admin/components/ui/switch';
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import { Textarea } from '../../components/ui/textarea';
import { isEqual } from './utils'; import { isEqual } from './utils';
interface ConfigInputProps { interface ConfigInputProps {
@@ -59,7 +60,7 @@ const Inputs: Record<
); );
}, },
JSON: function ObjectInput({ defaultValue, onChange }) { JSON: function ObjectInput({ defaultValue, onChange }) {
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
try { try {
const value = JSON.parse(e.target.value); const value = JSON.parse(e.target.value);
onChange(value); onChange(value);
@@ -67,10 +68,10 @@ const Inputs: Record<
}; };
return ( return (
<Input <Textarea
type="text"
defaultValue={JSON.stringify(defaultValue)} defaultValue={JSON.stringify(defaultValue)}
onChange={handleInputChange} onChange={handleInputChange}
className="w-full"
/> />
); );
}, },
@@ -98,9 +99,15 @@ export const ConfigInput = ({
const isValueEqual = isEqual(value, defaultValue); const isValueEqual = isEqual(value, defaultValue);
return ( return (
<div> <div className="flex flex-col items-end gap-2 w-full">
<Input defaultValue={defaultValue} onChange={onValueChange} /> <Input defaultValue={defaultValue} onChange={onValueChange} />
<div style={{ opacity: isValueEqual ? 0 : 1 }}> <div
className="w-full break-words"
style={{
opacity: isValueEqual ? 0 : 1,
height: isValueEqual ? 0 : 'auto',
}}
>
<span <span
className="line-through" className="line-through"
style={{ style={{
@@ -40,7 +40,7 @@ export const ConfirmChanges = ({
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
{modifiedKeys.length > 0 ? ( {modifiedKeys.length > 0 ? (
<pre className="flex flex-col text-sm bg-zinc-100 gap-1 min-h-[64px] rounded-md p-[12px_16px_16px_12px] mt-2 overflow-hidden"> <pre className="flex flex-col text-sm bg-zinc-100 gap-1 min-h-[64px] rounded-md p-[12px_16px_16px_12px] mt-2 overflow-auto">
<p>{'{'}</p> <p>{'{'}</p>
{modifiedKeys.map(key => ( {modifiedKeys.map(key => (
<p key={key}> <p key={key}>
@@ -70,7 +70,7 @@ export const ConfirmChanges = ({
) : ( ) : (
'There is no change.' 'There is no change.'
)} )}
<DialogFooter> <DialogFooter className="mt-6">
<div className="flex justify-end items-center w-full gap-2"> <div className="flex justify-end items-center w-full gap-2">
<Button type="button" onClick={onClose} variant="outline"> <Button type="button" onClick={onClose} variant="outline">
<span>Cancel</span> <span>Cancel</span>
@@ -106,6 +106,9 @@ export const AdminPanel = ({
key={field} key={field}
id={field} id={field}
description={desc} description={desc}
orientation={
type === 'Boolean' ? 'horizontal' : 'vertical'
}
> >
<ConfigInput <ConfigInput
module={module} module={module}
@@ -3,15 +3,17 @@ import { type ReactNode } from 'react';
export const RuntimeSettingRow = ({ export const RuntimeSettingRow = ({
id, id,
description, description,
orientation = 'horizontal',
children, children,
}: { }: {
id: string; id: string;
description: string; description: string;
orientation?: 'horizontal' | 'vertical';
children: ReactNode; children: ReactNode;
}) => { }) => {
return ( return (
<div <div
className="flex justify-between flex-grow overflow-y-auto space-y-[10px] gap-5 " className={`flex justify-between flex-grow space-y-[10px] gap-5 ${orientation === 'vertical' ? 'flex-col' : 'flex-row'}`}
id={id} id={id}
> >
<div className="flex flex-col gap-1"> <div className="flex flex-col gap-1">