feat: 优化drawer, 完善一下静态样式
This commit is contained in:
parent
ef0bdff317
commit
6177e64684
|
|
@ -6,7 +6,9 @@ import { cn } from '@/lib';
|
|||
import { Select, Switch, Number, FontSize } from '@/components/ui/inputs';
|
||||
import Background from './Background';
|
||||
import { AddIcon } from '@/assets/common';
|
||||
import { ModelSelectDialog } from '@/components';
|
||||
import ModelSelectDialog from '@/components/feature/ModelSelectDialog';
|
||||
import VoiceActorSelectDialog from '@/components/feature/VoiceActorSelectDialog';
|
||||
import BubbleSelectDialog from '@/components/feature/BubbleSelectDialog';
|
||||
import IconFont from '@/components/ui/iconFont';
|
||||
|
||||
const Title: React.FC<
|
||||
|
|
@ -27,7 +29,6 @@ const Title: React.FC<
|
|||
};
|
||||
|
||||
const SettingForm = React.memo(() => {
|
||||
console.log('SettingForm');
|
||||
const options = [
|
||||
{
|
||||
label: 'Model 1',
|
||||
|
|
@ -80,12 +81,7 @@ const SettingForm = React.memo(() => {
|
|||
<Title text="Sound">
|
||||
<FormItem
|
||||
name="name12"
|
||||
render={({ value, onChange }) => (
|
||||
<Select.View
|
||||
icon={'/character/voice_actor.svg'}
|
||||
text="Voice Actor"
|
||||
/>
|
||||
)}
|
||||
render={({ value, onChange }) => <VoiceActorSelectDialog />}
|
||||
/>
|
||||
<FormItem
|
||||
name="name12"
|
||||
|
|
@ -131,12 +127,7 @@ const SettingForm = React.memo(() => {
|
|||
/>
|
||||
<FormItem
|
||||
name="name12"
|
||||
render={({ value, onChange }) => (
|
||||
<Select.View
|
||||
icon={'/character/chat_bubble.svg'}
|
||||
text="Chat Bubble"
|
||||
/>
|
||||
)}
|
||||
render={({ value, onChange }) => <BubbleSelectDialog />}
|
||||
/>
|
||||
</Title>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { atom } from 'jotai';
|
||||
|
||||
// 是否打开两侧的设置
|
||||
export const settingOpenAtom = atom(false);
|
||||
export const settingOpenAtom = atom(true);
|
||||
|
||||
// 是否是立绘模式
|
||||
export const isPortraitModeAtom = atom(false);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import { ExitFullScreenIcon, FullScreenIcon } from '@/assets/common';
|
|||
|
||||
export default function CharacterChat() {
|
||||
const [settingOpen, setSettingOpen] = useAtom(settingOpenAtom);
|
||||
console.log('settingOpen', settingOpen);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
'use client';
|
||||
|
||||
import { Select } from '../ui/inputs';
|
||||
import Modal from '../ui/modal';
|
||||
|
||||
type BubbleSelectDialogProps = {};
|
||||
export default function BubbleSelectDialog(props: BubbleSelectDialogProps) {
|
||||
return (
|
||||
<Modal
|
||||
title="Chat Bubble"
|
||||
trigger={
|
||||
<Select.View icon={'/character/chat_bubble.svg'} text="Chat Bubble" />
|
||||
}
|
||||
>
|
||||
BubbleSelectDialog
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ type ModelSelectDialogProps = {
|
|||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
};
|
||||
|
||||
export default function ModelSelectDialog(props: ModelSelectDialogProps) {
|
||||
const [value, onChange] = useControllableValue(props, {
|
||||
valuePropName: 'value',
|
||||
|
|
@ -26,7 +27,11 @@ export default function ModelSelectDialog(props: ModelSelectDialogProps) {
|
|||
<Select.View icon={'/character/model_switch.svg'} text="Model 1" />
|
||||
}
|
||||
>
|
||||
Content
|
||||
<div className="flex flex-col pr-2.5">
|
||||
{options?.map((i) => {
|
||||
return <div key={i.value}>{i.label}</div>;
|
||||
})}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
'use client';
|
||||
|
||||
import { Select } from '../ui/inputs';
|
||||
import Modal from '../ui/modal';
|
||||
import Image from 'next/image';
|
||||
|
||||
type VoiceActorSelectDialogProps = {};
|
||||
export default function VoiceActorSelectDialog(
|
||||
props: VoiceActorSelectDialogProps
|
||||
) {
|
||||
const options = [
|
||||
{ label: 'Voice Actor 1', value: 'voiceActor1', gender: 'male' },
|
||||
{ label: 'Voice Actor 2', value: 'voiceActor2', gender: 'female' },
|
||||
{ label: 'Voice Actor 3', value: 'voiceActor3', gender: 'male' },
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
classNames={{
|
||||
content: 'w-200',
|
||||
}}
|
||||
title="Voice Actor"
|
||||
trigger={
|
||||
<Select.View icon={'/character/voice_actor.svg'} text="Voice Actor" />
|
||||
}
|
||||
>
|
||||
<div className="grid max-h-80 grid-cols-2 gap-5 overflow-auto pr-2.5">
|
||||
{options?.map((i) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
background:
|
||||
i.gender === 'male'
|
||||
? 'linear-gradient(90deg, rgba(0, 157, 255, 0.3) 0%, rgba(0, 157, 255, 0.1) 100%)'
|
||||
: 'rgba(112, 27, 140, 1)',
|
||||
}}
|
||||
key={i.value}
|
||||
className="flex h-20 cursor-pointer items-center justify-between rounded-[10px] p-2 pr-5"
|
||||
>
|
||||
<div className="flex flex-1 gap-2.5">
|
||||
<div>
|
||||
<Image
|
||||
src={'/avator.png'}
|
||||
className="rounded-full"
|
||||
width={50}
|
||||
height={50}
|
||||
alt="avator"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-bold">{'Name 1'}</div>
|
||||
<div className="text-sm text-white/60">
|
||||
{'Anime-Style Girl'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-center h-4.5 w-4.5 rounded-full bg-black">
|
||||
<div className="h-3 w-3 rounded-full bg-green-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
@ -4,6 +4,5 @@ export { default as Rate } from './ui/rate';
|
|||
export { default as Form } from './ui/form';
|
||||
export { default as Icon } from './ui/icon';
|
||||
export { default as Drawer } from './ui/drawer';
|
||||
export { default as ModelSelectDialog } from './feature/ModelSelectDialog';
|
||||
export { default as VirtualGrid } from './ui/VirtualGrid';
|
||||
export { default as TagSelect } from './ui/TagSelect';
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@ export default function Drawer({
|
|||
zIndex,
|
||||
}: DrawerProps) {
|
||||
// shouldRender 控制是否渲染 DOM(用于 destroyOnClose)
|
||||
const [shouldRender, setShouldRender] = useState(false);
|
||||
// 初始值应该根据 open 的初始值来设置,避免初始 open=true 时组件不渲染
|
||||
const [shouldRender, setShouldRender] = useState(open);
|
||||
const mounted = useMounted();
|
||||
const drawerRef = useRef<HTMLDivElement>(null);
|
||||
const previousState = useRef<{
|
||||
|
|
@ -37,21 +38,7 @@ export default function Drawer({
|
|||
open: false,
|
||||
});
|
||||
|
||||
// 当 open 变为 true 时,立即设置 shouldRender 为 true
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setShouldRender(true);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
// 处理动画结束后的销毁
|
||||
const handleTransitionEnd = useCallback(() => {
|
||||
// 只有在关闭状态且设置了 destroyOnClose 时,才销毁 DOM
|
||||
if (!open && destroyOnClose) {
|
||||
setShouldRender(false);
|
||||
}
|
||||
}, [open, destroyOnClose, setShouldRender]);
|
||||
|
||||
// 计算隐藏时的 transform 值,需要在 useEffect 之前定义
|
||||
const hiddenTransform = useMemo(() => {
|
||||
switch (position) {
|
||||
case 'left':
|
||||
|
|
@ -67,6 +54,51 @@ export default function Drawer({
|
|||
}
|
||||
}, [position]);
|
||||
|
||||
// 当 open 变为 true 时,立即设置 shouldRender 为 true
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setShouldRender(true);
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
// 处理打开/关闭动画,确保 DOM 已经挂载
|
||||
useEffect(() => {
|
||||
if (!shouldRender || !mounted || !drawerRef.current) return;
|
||||
|
||||
// 判断是否是初始状态
|
||||
const isInitialState =
|
||||
previousState.current.open === false &&
|
||||
Object.keys(previousState.current.styles).length === 0;
|
||||
|
||||
// 无论是初始状态还是状态变化,都需要触发动画
|
||||
if (open) {
|
||||
// 从隐藏状态变为显示状态,触发显示动画
|
||||
// 使用双重 requestAnimationFrame 确保浏览器已经渲染了初始的隐藏状态
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
if (drawerRef.current) {
|
||||
drawerRef.current.style.transform = 'translateX(0) translateY(0)';
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (!isInitialState) {
|
||||
// 从打开状态变为关闭状态,触发隐藏动画(初始状态不需要)
|
||||
requestAnimationFrame(() => {
|
||||
if (drawerRef.current) {
|
||||
drawerRef.current.style.transform = hiddenTransform;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [open, shouldRender, mounted, hiddenTransform]);
|
||||
|
||||
// 处理动画结束后的销毁
|
||||
const handleTransitionEnd = useCallback(() => {
|
||||
// 只有在关闭状态且设置了 destroyOnClose 时,才销毁 DOM
|
||||
if (!open && destroyOnClose) {
|
||||
setShouldRender(false);
|
||||
}
|
||||
}, [open, destroyOnClose, setShouldRender]);
|
||||
|
||||
const positionStyles = useMemo(() => {
|
||||
switch (position) {
|
||||
case 'left':
|
||||
|
|
@ -110,28 +142,20 @@ export default function Drawer({
|
|||
}
|
||||
|
||||
const positionType = inBody ? 'fixed' : 'absolute';
|
||||
|
||||
const baseStyles: React.CSSProperties = {
|
||||
...positionStyles,
|
||||
position: positionType,
|
||||
zIndex,
|
||||
boxShadow: '0 0 10px rgba(0, 0, 0, 0.1)',
|
||||
transition: 'transform 0.3s ease-in-out',
|
||||
// 初始状态总是先设置为隐藏,然后通过 useEffect 中的动画来显示
|
||||
// 这样即使初始 open=true,也能看到从隐藏到显示的动画效果
|
||||
transform: hiddenTransform,
|
||||
};
|
||||
if (open) {
|
||||
requestAnimationFrame(() => {
|
||||
if (drawerRef.current) {
|
||||
drawerRef.current.style.transform = 'translateX(0) translateY(0)';
|
||||
}
|
||||
});
|
||||
} else {
|
||||
requestAnimationFrame(() => {
|
||||
if (drawerRef.current) {
|
||||
drawerRef.current.style.transform = hiddenTransform;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 缓存当前状态
|
||||
// 注意:动画逻辑已经在 useEffect 中处理,这里只需要设置初始样式
|
||||
previousState.current = {
|
||||
styles: baseStyles,
|
||||
open,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ type ModalProps = {
|
|||
children?: React.ReactNode;
|
||||
trigger?: React.ReactNode;
|
||||
title?: string;
|
||||
classNames?: Record<'content' | 'overlay', string>;
|
||||
classNames?: Partial<Record<'content' | 'overlay', string>>;
|
||||
};
|
||||
|
||||
export default function Modal(props: ModalProps) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue