mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-25 18:26:05 +08:00
feat: replace modal with new design (#4324)
Co-authored-by: Peng Xiao <pengxiao@outlook.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import * as styles from './index.css';
|
||||
|
||||
export interface MenuItemProps extends React.HTMLAttributes<HTMLButtonElement> {
|
||||
export interface MenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
icon?: React.ReactElement;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
@@ -25,7 +25,7 @@ const stopPropagation: React.MouseEventHandler = e => {
|
||||
e.stopPropagation();
|
||||
};
|
||||
|
||||
export const MenuItem = React.forwardRef<HTMLButtonElement, MenuItemProps>(
|
||||
export const MenuItem = React.forwardRef<HTMLDivElement, MenuItemProps>(
|
||||
(
|
||||
{
|
||||
onClick,
|
||||
@@ -42,7 +42,7 @@ export const MenuItem = React.forwardRef<HTMLButtonElement, MenuItemProps>(
|
||||
) => {
|
||||
const collapsible = onCollapsedChange !== undefined;
|
||||
return (
|
||||
<button
|
||||
<div
|
||||
ref={ref}
|
||||
{...props}
|
||||
onClick={onClick}
|
||||
@@ -82,22 +82,21 @@ export const MenuItem = React.forwardRef<HTMLButtonElement, MenuItemProps>(
|
||||
{postfix}
|
||||
</div>
|
||||
) : null}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
MenuItem.displayName = 'MenuItem';
|
||||
|
||||
export const MenuLinkItem = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
MenuLinkItemProps
|
||||
>(({ to, ...props }, ref) => {
|
||||
return (
|
||||
<Link to={to} className={styles.linkItemRoot}>
|
||||
{/* The <a> element rendered by Link does not generate display box due to `display: contents` style */}
|
||||
{/* Thus ref is passed to MenuItem instead of Link */}
|
||||
<MenuItem ref={ref} {...props}></MenuItem>
|
||||
</Link>
|
||||
);
|
||||
});
|
||||
export const MenuLinkItem = React.forwardRef<HTMLDivElement, MenuLinkItemProps>(
|
||||
({ to, ...props }, ref) => {
|
||||
return (
|
||||
<Link to={to} className={styles.linkItemRoot}>
|
||||
{/* The <a> element rendered by Link does not generate display box due to `display: contents` style */}
|
||||
{/* Thus ref is passed to MenuItem instead of Link */}
|
||||
<MenuItem ref={ref} {...props}></MenuItem>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
);
|
||||
MenuLinkItem.displayName = 'MenuLinkItem';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import clsx from 'clsx';
|
||||
import type { FC } from 'react';
|
||||
import type { FC, HTMLAttributes } from 'react';
|
||||
|
||||
import { Input, type InputProps } from '../../ui/input';
|
||||
import { authInputWrapper, formHint } from './share.css';
|
||||
@@ -9,6 +9,7 @@ export type AuthInputProps = InputProps & {
|
||||
errorHint?: string;
|
||||
withoutHint?: boolean;
|
||||
onEnter?: () => void;
|
||||
wrapperProps?: HTMLAttributes<HTMLDivElement>;
|
||||
};
|
||||
export const AuthInput: FC<AuthInputProps> = ({
|
||||
label,
|
||||
@@ -16,13 +17,15 @@ export const AuthInput: FC<AuthInputProps> = ({
|
||||
errorHint,
|
||||
withoutHint = false,
|
||||
onEnter,
|
||||
wrapperProps: { className, ...otherWrapperProps } = {},
|
||||
...inputProps
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className={clsx(authInputWrapper, {
|
||||
className={clsx(authInputWrapper, className, {
|
||||
'without-hint': withoutHint,
|
||||
})}
|
||||
{...otherWrapperProps}
|
||||
>
|
||||
{label ? <label>{label}</label> : null}
|
||||
<Input
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Modal, ModalCloseButton, ModalWrapper } from '@affine/component';
|
||||
import { Modal } from '@toeverything/components/modal';
|
||||
import type { FC, PropsWithChildren } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export type AuthModalProps = {
|
||||
open: boolean;
|
||||
@@ -12,32 +11,18 @@ export const AuthModal: FC<PropsWithChildren<AuthModalProps>> = ({
|
||||
open,
|
||||
setOpen,
|
||||
}) => {
|
||||
const handleClose = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
wrapperPosition={['center', 'center']}
|
||||
data-testid="auth-modal"
|
||||
onOpenChange={setOpen}
|
||||
width={400}
|
||||
height={468}
|
||||
contentOptions={{
|
||||
['data-testid' as string]: 'auth-modal',
|
||||
style: { padding: '44px 40px 0' },
|
||||
}}
|
||||
>
|
||||
<ModalWrapper
|
||||
width={1080}
|
||||
height={760}
|
||||
style={{
|
||||
height: '468px',
|
||||
width: '400px',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: 'var(--affine-white)',
|
||||
boxShadow: 'var(--affine-popover-shadow)',
|
||||
padding: '44px 40px 0',
|
||||
}}
|
||||
>
|
||||
<ModalCloseButton top={20} right={20} onClick={handleClose} />
|
||||
{children}
|
||||
</ModalWrapper>
|
||||
{children}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import {
|
||||
CloseIcon,
|
||||
ExportToHtmlIcon,
|
||||
ExportToMarkdownIcon,
|
||||
HelpIcon,
|
||||
NewIcon,
|
||||
NotionIcon,
|
||||
} from '@blocksuite/icons';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import { Tooltip } from '@toeverything/components/tooltip';
|
||||
|
||||
import { ModalCloseButton } from '../../ui/modal';
|
||||
import { BlockCard } from '../card/block-card';
|
||||
import {
|
||||
importPageBodyStyle,
|
||||
@@ -27,7 +28,17 @@ export const ImportPage = ({
|
||||
onClose: () => void;
|
||||
}) => (
|
||||
<div className={importPageContainerStyle}>
|
||||
<ModalCloseButton top={6} right={6} onClick={onClose} />
|
||||
<IconButton
|
||||
style={{
|
||||
position: 'absolute',
|
||||
right: 6,
|
||||
top: 6,
|
||||
}}
|
||||
onClick={() => {
|
||||
onClose();
|
||||
}}
|
||||
icon={<CloseIcon />}
|
||||
/>
|
||||
<div className={importPageBodyStyle}>
|
||||
<div className="title">Import</div>
|
||||
<span>
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import { Permission } from '@affine/graphql';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { ConfirmModal } from '@toeverything/components/modal';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
|
||||
import { Modal, ModalCloseButton, ModalWrapper } from '../../ui/modal';
|
||||
import { AuthInput } from '..//auth-components';
|
||||
import { emailRegex } from '..//auth-components/utils';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export interface InviteModalProps {
|
||||
open: boolean;
|
||||
@@ -26,10 +24,6 @@ export const InviteModal = ({
|
||||
const [permission] = useState(Permission.Write);
|
||||
const [isValidEmail, setIsValidEmail] = useState(true);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
|
||||
const handleConfirm = useCallback(() => {
|
||||
if (!emailRegex.test(inviteEmail)) {
|
||||
setIsValidEmail(false);
|
||||
@@ -51,47 +45,41 @@ export const InviteModal = ({
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
wrapperPosition={['center', 'center']}
|
||||
data-testid="invite-modal"
|
||||
<ConfirmModal
|
||||
open={open}
|
||||
>
|
||||
<ModalWrapper
|
||||
width={480}
|
||||
height={254}
|
||||
style={{
|
||||
onOpenChange={setOpen}
|
||||
width={480}
|
||||
title={t['Invite Members']()}
|
||||
description={t['Invite Members Message']()}
|
||||
cancelText={t['com.affine.inviteModal.button.cancel']()}
|
||||
contentOptions={{
|
||||
['data-testid' as string]: 'invite-modal',
|
||||
style: {
|
||||
padding: '20px 26px',
|
||||
},
|
||||
}}
|
||||
confirmButtonOptions={{
|
||||
loading: isMutating,
|
||||
type: 'primary',
|
||||
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
|
||||
children: t['Invite'](),
|
||||
}}
|
||||
onConfirm={handleConfirm}
|
||||
>
|
||||
{/*TODO: check email & add placeholder*/}
|
||||
<AuthInput
|
||||
disabled={isMutating}
|
||||
placeholder="email@example.com"
|
||||
value={inviteEmail}
|
||||
onChange={setInviteEmail}
|
||||
error={!isValidEmail}
|
||||
errorHint={isValidEmail ? '' : t['com.affine.auth.sign.email.error']()}
|
||||
onEnter={handleConfirm}
|
||||
wrapperProps={{
|
||||
style: { padding: 0 },
|
||||
}}
|
||||
>
|
||||
<ModalCloseButton top={20} right={20} onClick={handleCancel} />
|
||||
|
||||
<div className={styles.inviteModalTitle}>{t['Invite Members']()}</div>
|
||||
<div className={styles.inviteModalContent}>
|
||||
{t['Invite Members Message']()}
|
||||
{/*TODO: check email & add placeholder*/}
|
||||
<AuthInput
|
||||
disabled={isMutating}
|
||||
placeholder="email@example.com"
|
||||
value={inviteEmail}
|
||||
onChange={setInviteEmail}
|
||||
error={!isValidEmail}
|
||||
errorHint={
|
||||
isValidEmail ? '' : t['com.affine.auth.sign.email.error']()
|
||||
}
|
||||
onEnter={handleConfirm}
|
||||
style={{ marginTop: 20 }}
|
||||
size="large"
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.inviteModalButtonContainer}>
|
||||
<Button style={{ marginRight: 20 }} onClick={handleCancel}>
|
||||
{t['com.affine.inviteModal.button.cancel']()}
|
||||
</Button>
|
||||
<Button type="primary" onClick={handleConfirm} loading={isMutating}>
|
||||
{t['Invite']()}
|
||||
</Button>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
size="large"
|
||||
/>
|
||||
</ConfirmModal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,10 +10,11 @@ import {
|
||||
} from '@blocksuite/icons';
|
||||
import { IconButton } from '@toeverything/components/button';
|
||||
import { Menu, MenuIcon, MenuItem } from '@toeverything/components/menu';
|
||||
import { ConfirmModal } from '@toeverything/components/modal';
|
||||
import { Tooltip } from '@toeverything/components/tooltip';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Confirm, FlexWrapper } from '../../..';
|
||||
import { FlexWrapper } from '../../..';
|
||||
import { DisablePublicSharing, MoveToTrash } from './operation-menu-items';
|
||||
|
||||
export interface OperationCellProps {
|
||||
@@ -106,19 +107,12 @@ export const OperationCell = ({
|
||||
onRemoveToTrash();
|
||||
setOpen(false);
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
onOpenChange={setOpen}
|
||||
/>
|
||||
<DisablePublicSharing.DisablePublicSharingModal
|
||||
onConfirmDisable={onDisablePublicSharing}
|
||||
onConfirm={onDisablePublicSharing}
|
||||
open={openDisableShared}
|
||||
onClose={() => {
|
||||
setOpenDisableShared(false);
|
||||
}}
|
||||
onOpenChange={setOpenDisableShared}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
@@ -161,22 +155,20 @@ export const TrashOperationCell = ({
|
||||
<DeletePermanentlyIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Confirm
|
||||
<ConfirmModal
|
||||
title={`${t['com.affine.trashOperation.deletePermanently']()}?`}
|
||||
content={t['com.affine.trashOperation.deleteDescription']()}
|
||||
confirmText={t['com.affine.trashOperation.delete']()}
|
||||
confirmType="error"
|
||||
description={t['com.affine.trashOperation.deleteDescription']()}
|
||||
cancelText={t['com.affine.confirmModal.button.cancel']()}
|
||||
confirmButtonOptions={{
|
||||
type: 'error',
|
||||
children: t['com.affine.trashOperation.delete'](),
|
||||
}}
|
||||
open={open}
|
||||
onOpenChange={setOpen}
|
||||
onConfirm={() => {
|
||||
onPermanentlyDeletePage();
|
||||
setOpen(false);
|
||||
}}
|
||||
onClose={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
onCancel={() => {
|
||||
setOpen(false);
|
||||
}}
|
||||
/>
|
||||
</FlexWrapper>
|
||||
);
|
||||
|
||||
@@ -5,9 +5,10 @@ import {
|
||||
MenuItem,
|
||||
type MenuItemProps,
|
||||
} from '@toeverything/components/menu';
|
||||
|
||||
import type { ConfirmProps } from '../../..';
|
||||
import { Confirm } from '../../..';
|
||||
import {
|
||||
ConfirmModal,
|
||||
type ConfirmModalProps,
|
||||
} from '@toeverything/components/modal';
|
||||
|
||||
export const MoveToTrash = (props: MenuItemProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
@@ -27,26 +28,29 @@ export const MoveToTrash = (props: MenuItemProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ConfirmModal = ({
|
||||
const MoveToTrashConfirm = ({
|
||||
title,
|
||||
...confirmModalProps
|
||||
}: {
|
||||
title: string;
|
||||
} & ConfirmProps) => {
|
||||
} & ConfirmModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<Confirm
|
||||
<ConfirmModal
|
||||
title={t['com.affine.moveToTrash.confirmModal.title']()}
|
||||
content={t['com.affine.moveToTrash.confirmModal.description']({
|
||||
description={t['com.affine.moveToTrash.confirmModal.description']({
|
||||
title: title || 'Untitled',
|
||||
})}
|
||||
confirmButtonTestId="confirm-delete-page"
|
||||
confirmText={t.Delete()}
|
||||
confirmType="error"
|
||||
cancelText={t['com.affine.confirmModal.button.cancel']()}
|
||||
confirmButtonOptions={{
|
||||
['data-testid' as string]: 'confirm-delete-page',
|
||||
type: 'error',
|
||||
children: t.Delete(),
|
||||
}}
|
||||
{...confirmModalProps}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
MoveToTrash.ConfirmModal = ConfirmModal;
|
||||
MoveToTrash.ConfirmModal = MoveToTrashConfirm;
|
||||
|
||||
@@ -5,14 +5,14 @@ import { ViewLayersIcon } from '@blocksuite/icons';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { Tooltip } from '@toeverything/components/tooltip';
|
||||
import clsx from 'clsx';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import {
|
||||
type CollectionsAtom,
|
||||
useCollectionManager,
|
||||
} from '../use-collection-manager';
|
||||
import * as styles from './collection-bar.css';
|
||||
import { EditCollectionModel } from './create-collection';
|
||||
import { EditCollectionModal } from './create-collection';
|
||||
import { useActions } from './use-action';
|
||||
|
||||
interface CollectionBarProps {
|
||||
@@ -33,21 +33,20 @@ export const CollectionBar = (props: CollectionBarProps) => {
|
||||
setting,
|
||||
openEdit: () => setOpen(true),
|
||||
});
|
||||
const onClose = useCallback(() => setOpen(false), []);
|
||||
|
||||
return !setting.isDefault ? (
|
||||
<tr style={{ userSelect: 'none' }}>
|
||||
<td>
|
||||
<div className={styles.view}>
|
||||
<EditCollectionModel
|
||||
<EditCollectionModal
|
||||
propertiesMeta={propertiesMeta}
|
||||
getPageInfo={getPageInfo}
|
||||
init={collection}
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
onOpenChange={setOpen}
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
onConfirm={setting.updateCollection}
|
||||
></EditCollectionModel>
|
||||
/>
|
||||
<ViewLayersIcon
|
||||
style={{
|
||||
height: 20,
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useCallback, useRef, useState } from 'react';
|
||||
import { CreateFilterMenu } from '../filter/vars';
|
||||
import type { useCollectionManager } from '../use-collection-manager';
|
||||
import * as styles from './collection-list.css';
|
||||
import { EditCollectionModel } from './create-collection';
|
||||
import { EditCollectionModal } from './create-collection';
|
||||
import { useActions } from './use-action';
|
||||
|
||||
const CollectionOption = ({
|
||||
@@ -126,14 +126,16 @@ export const CollectionList = ({
|
||||
},
|
||||
[setting]
|
||||
);
|
||||
const closeUpdateCollectionModal = useCallback(
|
||||
() => setCollection(undefined),
|
||||
[]
|
||||
);
|
||||
const closeUpdateCollectionModal = useCallback((open: boolean) => {
|
||||
if (!open) {
|
||||
setCollection(undefined);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const onConfirm = useCallback(
|
||||
async (view: Collection) => {
|
||||
await setting.updateCollection(view);
|
||||
closeUpdateCollectionModal();
|
||||
closeUpdateCollectionModal(false);
|
||||
},
|
||||
[closeUpdateCollectionModal, setting]
|
||||
);
|
||||
@@ -214,14 +216,14 @@ export const CollectionList = ({
|
||||
{t['com.affine.filter']()}
|
||||
</Button>
|
||||
</Menu>
|
||||
<EditCollectionModel
|
||||
<EditCollectionModal
|
||||
propertiesMeta={propertiesMeta}
|
||||
getPageInfo={getPageInfo}
|
||||
init={collection}
|
||||
open={!!collection}
|
||||
onClose={closeUpdateCollectionModal}
|
||||
onOpenChange={closeUpdateCollectionModal}
|
||||
onConfirm={onConfirm}
|
||||
></EditCollectionModel>
|
||||
/>
|
||||
</FlexWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -10,72 +10,69 @@ import {
|
||||
} from '@blocksuite/icons';
|
||||
import { uuidv4 } from '@blocksuite/store';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { Modal } from '@toeverything/components/modal';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
Input,
|
||||
Modal,
|
||||
ModalCloseButton,
|
||||
ModalWrapper,
|
||||
ScrollableContainer,
|
||||
} from '../../..';
|
||||
import { Input, ScrollableContainer } from '../../..';
|
||||
import { FilterList } from '../filter';
|
||||
import * as styles from './collection-list.css';
|
||||
|
||||
interface EditCollectionModelProps {
|
||||
interface EditCollectionModalProps {
|
||||
init?: Collection;
|
||||
title?: string;
|
||||
open: boolean;
|
||||
getPageInfo: GetPageInfoById;
|
||||
propertiesMeta: PropertiesMeta;
|
||||
onClose: () => void;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
onConfirm: (view: Collection) => Promise<void>;
|
||||
}
|
||||
|
||||
export const EditCollectionModel = ({
|
||||
export const EditCollectionModal = ({
|
||||
init,
|
||||
onConfirm,
|
||||
open,
|
||||
onClose,
|
||||
onOpenChange,
|
||||
getPageInfo,
|
||||
propertiesMeta,
|
||||
title,
|
||||
}: EditCollectionModelProps) => {
|
||||
}: EditCollectionModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const onConfirmOnCollection = useCallback(
|
||||
(view: Collection) => {
|
||||
onConfirm(view)
|
||||
.then(() => {
|
||||
onClose();
|
||||
onOpenChange(false);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
},
|
||||
[onClose, onConfirm]
|
||||
[onConfirm, onOpenChange]
|
||||
);
|
||||
const onCancel = useCallback(() => {
|
||||
onOpenChange(false);
|
||||
}, [onOpenChange]);
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper
|
||||
width={600}
|
||||
style={{
|
||||
padding: '40px',
|
||||
background: 'var(--affine-background-primary-color)',
|
||||
}}
|
||||
>
|
||||
<ModalCloseButton top={12} right={12} onClick={onClose} />
|
||||
{init ? (
|
||||
<EditCollection
|
||||
propertiesMeta={propertiesMeta}
|
||||
title={title}
|
||||
onConfirmText={t['com.affine.editCollection.save']()}
|
||||
init={init}
|
||||
getPageInfo={getPageInfo}
|
||||
onCancel={onClose}
|
||||
onConfirm={onConfirmOnCollection}
|
||||
/>
|
||||
) : null}
|
||||
</ModalWrapper>
|
||||
<Modal
|
||||
open={open}
|
||||
onOpenChange={onOpenChange}
|
||||
width={600}
|
||||
contentOptions={{
|
||||
style: { padding: '40px' },
|
||||
}}
|
||||
>
|
||||
{init ? (
|
||||
<EditCollection
|
||||
propertiesMeta={propertiesMeta}
|
||||
title={title}
|
||||
onConfirmText={t['com.affine.editCollection.save']()}
|
||||
init={init}
|
||||
getPageInfo={getPageInfo}
|
||||
onCancel={onCancel}
|
||||
onConfirm={onConfirmOnCollection}
|
||||
/>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -301,14 +298,14 @@ export const SaveCollectionButton = ({
|
||||
>
|
||||
{t['com.affine.editCollection.saveCollection']()}
|
||||
</Button>
|
||||
<EditCollectionModel
|
||||
<EditCollectionModal
|
||||
title={t['com.affine.editCollection.saveCollection']()}
|
||||
propertiesMeta={propertiesMeta}
|
||||
init={init}
|
||||
onConfirm={onConfirm}
|
||||
open={show}
|
||||
getPageInfo={getPageInfo}
|
||||
onClose={() => changeShow(false)}
|
||||
onOpenChange={changeShow}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
export { SettingModal, type SettingModalProps } from './modal';
|
||||
export { SettingHeader } from './setting-header';
|
||||
export { SettingRow } from './setting-row';
|
||||
export * from './storage-progess';
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
import { Modal, ModalCloseButton, ModalWrapper } from '@affine/component';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export interface SettingModalProps {
|
||||
open: boolean;
|
||||
setOpen: (value: boolean) => void;
|
||||
}
|
||||
|
||||
export const SettingModal = ({
|
||||
children,
|
||||
open,
|
||||
setOpen,
|
||||
}: PropsWithChildren<SettingModalProps>) => {
|
||||
const handleClose = useCallback(() => {
|
||||
setOpen(false);
|
||||
}, [setOpen]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
wrapperPosition={['center', 'center']}
|
||||
data-testid="setting-modal"
|
||||
>
|
||||
<ModalWrapper
|
||||
width={1080}
|
||||
height={760}
|
||||
style={{
|
||||
maxHeight: '85vh',
|
||||
maxWidth: '70vw',
|
||||
overflow: 'hidden',
|
||||
display: 'flex',
|
||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
||||
boxShadow: 'var(--affine-popover-shadow)',
|
||||
}}
|
||||
>
|
||||
<ModalCloseButton top={16} right={20} onClick={handleClose} />
|
||||
{children}
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1,57 +1,24 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import { Button, IconButton } from '@toeverything/components/button';
|
||||
import {
|
||||
ConfirmModal,
|
||||
type ConfirmModalProps,
|
||||
} from '@toeverything/components/modal';
|
||||
|
||||
import { Modal, ModalWrapper } from '../../..';
|
||||
import { ButtonContainer, Content, Header, StyleTips, Title } from './style';
|
||||
|
||||
export type PublicLinkDisableProps = {
|
||||
open: boolean;
|
||||
onConfirmDisable: () => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const PublicLinkDisableModal = ({
|
||||
open,
|
||||
onConfirmDisable,
|
||||
onClose,
|
||||
}: PublicLinkDisableProps) => {
|
||||
export const PublicLinkDisableModal = (props: ConfirmModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={onClose}>
|
||||
<ModalWrapper width={480}>
|
||||
<Header>
|
||||
<Title>{t['com.affine.publicLinkDisableModal.title']()}</Title>
|
||||
<IconButton onClick={onClose}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Header>
|
||||
<Content>
|
||||
<StyleTips>
|
||||
{t['com.affine.publicLinkDisableModal.description']()}
|
||||
</StyleTips>
|
||||
<ButtonContainer>
|
||||
<div>
|
||||
<Button onClick={onClose} block>
|
||||
{t['com.affine.publicLinkDisableModal.button.cancel']()}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
<Button
|
||||
data-testid="confirm-enable-affine-cloud-button"
|
||||
type="error"
|
||||
block
|
||||
onClick={() => {
|
||||
onConfirmDisable();
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
{t['com.affine.publicLinkDisableModal.button.disable']()}
|
||||
</Button>
|
||||
</div>
|
||||
</ButtonContainer>
|
||||
</Content>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
<ConfirmModal
|
||||
width={480}
|
||||
title={t['com.affine.publicLinkDisableModal.title']()}
|
||||
description={t['com.affine.publicLinkDisableModal.description']()}
|
||||
cancelText={t['com.affine.publicLinkDisableModal.button.cancel']()}
|
||||
confirmButtonOptions={{
|
||||
type: 'error',
|
||||
['data-testid' as string]: 'confirm-enable-affine-cloud-button',
|
||||
children: t['com.affine.publicLinkDisableModal.button.disable'](),
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { styled } from '../../..';
|
||||
|
||||
export const Header = styled('div')({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
paddingRight: '20px',
|
||||
paddingTop: '20px',
|
||||
paddingLeft: '24px',
|
||||
alignItems: 'center',
|
||||
});
|
||||
|
||||
export const Content = styled('div')({
|
||||
padding: '12px 24px 20px 24px',
|
||||
});
|
||||
|
||||
export const Title = styled('div')({
|
||||
fontSize: 'var(--affine-font-h6)',
|
||||
lineHeight: '26px',
|
||||
fontWeight: 600,
|
||||
});
|
||||
|
||||
export const StyleTips = styled('div')(() => {
|
||||
return {
|
||||
userSelect: 'none',
|
||||
marginBottom: '20px',
|
||||
};
|
||||
});
|
||||
export const ButtonContainer = styled('div')(() => {
|
||||
return {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
gap: '20px',
|
||||
paddingTop: '20px',
|
||||
};
|
||||
});
|
||||
@@ -208,7 +208,10 @@ export const AffineSharePage = (props: ShareMenuProps) => {
|
||||
block
|
||||
type="danger"
|
||||
className={styles.menuItemStyle}
|
||||
onClick={() => setShowDisable(true)}
|
||||
onSelect={e => {
|
||||
e.preventDefault();
|
||||
setShowDisable(true);
|
||||
}}
|
||||
>
|
||||
<div className={styles.disableSharePage}>
|
||||
{t['Disable Public Link']()}
|
||||
@@ -216,10 +219,8 @@ export const AffineSharePage = (props: ShareMenuProps) => {
|
||||
</MenuItem>
|
||||
<PublicLinkDisableModal
|
||||
open={showDisable}
|
||||
onConfirmDisable={onDisablePublic}
|
||||
onClose={() => {
|
||||
setShowDisable(false);
|
||||
}}
|
||||
onConfirm={onDisablePublic}
|
||||
onOpenChange={setShowDisable}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
@@ -231,7 +232,11 @@ export const SharePage = (props: ShareMenuProps) => {
|
||||
if (props.workspace.flavour === WorkspaceFlavour.LOCAL) {
|
||||
return <LocalSharePage {...props} />;
|
||||
} else if (props.workspace.flavour === WorkspaceFlavour.AFFINE_CLOUD) {
|
||||
return <AffineSharePage {...props} />;
|
||||
return (
|
||||
<>
|
||||
<AffineSharePage {...props} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
throw new Error('Unreachable');
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ export const modalStyle = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
backgroundColor: 'var(--affine-background-secondary-color)',
|
||||
borderRadius: '16px',
|
||||
overflow: 'hidden',
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { ArrowLeftSmallIcon, ArrowRightSmallIcon } from '@blocksuite/icons';
|
||||
import { Modal, type ModalProps } from '@toeverything/components/modal';
|
||||
import clsx from 'clsx';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Modal, ModalCloseButton, ModalWrapper } from '../..';
|
||||
import {
|
||||
arrowStyle,
|
||||
buttonDisableStyle,
|
||||
@@ -25,140 +25,133 @@ import {
|
||||
videoStyle,
|
||||
} from './index.css';
|
||||
|
||||
export interface TourModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const TourModal = ({ open, onClose }: TourModalProps) => {
|
||||
export const TourModal = (props: ModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const [step, setStep] = useState(-1);
|
||||
const handleClose = () => {
|
||||
setStep(-1);
|
||||
onClose();
|
||||
};
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
wrapperPosition={['center', 'center']}
|
||||
hideBackdrop
|
||||
width={545}
|
||||
contentOptions={{
|
||||
['data-testid' as string]: 'onboarding-modal',
|
||||
style: {
|
||||
minHeight: '480px',
|
||||
padding: 0,
|
||||
},
|
||||
}}
|
||||
overlayOptions={{
|
||||
style: {
|
||||
background: 'transparent',
|
||||
},
|
||||
}}
|
||||
closeButtonOptions={{
|
||||
// @ts-expect-error - fix upstream type
|
||||
'data-testid': 'onboarding-modal-close-button',
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
<ModalWrapper
|
||||
width={545}
|
||||
style={{ minHeight: '480px' }}
|
||||
data-testid="onboarding-modal"
|
||||
>
|
||||
<ModalCloseButton
|
||||
top={6}
|
||||
right={10}
|
||||
onClick={handleClose}
|
||||
data-testid="onboarding-modal-close-button"
|
||||
/>
|
||||
<div className={modalStyle}>
|
||||
<div className={titleContainerStyle}>
|
||||
{step !== -1 && (
|
||||
<div
|
||||
className={clsx(titleStyle, {
|
||||
[slideToRightStyle]: step === 0,
|
||||
[formSlideToLeftStyle]: step === 1,
|
||||
})}
|
||||
>
|
||||
{t['com.affine.onboarding.title2']()}
|
||||
</div>
|
||||
)}
|
||||
<div className={modalStyle}>
|
||||
<div className={titleContainerStyle}>
|
||||
{step !== -1 && (
|
||||
<div
|
||||
className={clsx(titleStyle, {
|
||||
[slideToLeftStyle]: step === 1,
|
||||
[formSlideToRightStyle]: step === 0,
|
||||
[slideToRightStyle]: step === 0,
|
||||
[formSlideToLeftStyle]: step === 1,
|
||||
})}
|
||||
>
|
||||
{t['com.affine.onboarding.title1']()}
|
||||
{t['com.affine.onboarding.title2']()}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={clsx(titleStyle, {
|
||||
[slideToLeftStyle]: step === 1,
|
||||
[formSlideToRightStyle]: step === 0,
|
||||
})}
|
||||
>
|
||||
{t['com.affine.onboarding.title1']()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={containerStyle}>
|
||||
<div
|
||||
className={clsx(arrowStyle, { [buttonDisableStyle]: step !== 1 })}
|
||||
onClick={() => step === 1 && setStep(0)}
|
||||
data-testid="onboarding-modal-pre-button"
|
||||
>
|
||||
<ArrowLeftSmallIcon />
|
||||
</div>
|
||||
<div className={videoContainerStyle}>
|
||||
<div className={videoSlideStyle}>
|
||||
{step !== -1 && (
|
||||
<video
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
className={clsx(videoStyle, {
|
||||
[slideToRightStyle]: step === 0,
|
||||
[formSlideToLeftStyle]: step === 1,
|
||||
})}
|
||||
data-testid="onboarding-modal-editing-video"
|
||||
>
|
||||
<source src="/editingVideo.mp4" type="video/mp4" />
|
||||
<source src="/editingVideo.webm" type="video/webm" />
|
||||
</video>
|
||||
)}
|
||||
<div className={containerStyle}>
|
||||
<div
|
||||
className={clsx(arrowStyle, { [buttonDisableStyle]: step !== 1 })}
|
||||
onClick={() => step === 1 && setStep(0)}
|
||||
data-testid="onboarding-modal-pre-button"
|
||||
>
|
||||
<ArrowLeftSmallIcon />
|
||||
</div>
|
||||
<div className={videoContainerStyle}>
|
||||
<div className={videoSlideStyle}>
|
||||
{step !== -1 && (
|
||||
<video
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
className={clsx(videoStyle, {
|
||||
[slideToLeftStyle]: step === 1,
|
||||
[formSlideToRightStyle]: step === 0,
|
||||
[slideToRightStyle]: step === 0,
|
||||
[formSlideToLeftStyle]: step === 1,
|
||||
})}
|
||||
data-testid="onboarding-modal-switch-video"
|
||||
data-testid="onboarding-modal-editing-video"
|
||||
>
|
||||
<source src="/switchVideo.mp4" type="video/mp4" />
|
||||
<source src="/switchVideo.webm" type="video/webm" />
|
||||
<source src="/editingVideo.mp4" type="video/mp4" />
|
||||
<source src="/editingVideo.webm" type="video/webm" />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={clsx(arrowStyle, { [buttonDisableStyle]: step === 1 })}
|
||||
onClick={() => setStep(1)}
|
||||
data-testid="onboarding-modal-next-button"
|
||||
>
|
||||
<ArrowRightSmallIcon />
|
||||
)}
|
||||
<video
|
||||
autoPlay
|
||||
muted
|
||||
loop
|
||||
className={clsx(videoStyle, {
|
||||
[slideToLeftStyle]: step === 1,
|
||||
[formSlideToRightStyle]: step === 0,
|
||||
})}
|
||||
data-testid="onboarding-modal-switch-video"
|
||||
>
|
||||
<source src="/switchVideo.mp4" type="video/mp4" />
|
||||
<source src="/switchVideo.webm" type="video/webm" />
|
||||
</video>
|
||||
</div>
|
||||
</div>
|
||||
<ul className={tabContainerStyle}>
|
||||
<li
|
||||
className={clsx(tabStyle, {
|
||||
[tabActiveStyle]: step !== 1,
|
||||
})}
|
||||
onClick={() => setStep(0)}
|
||||
></li>
|
||||
<li
|
||||
className={clsx(tabStyle, { [tabActiveStyle]: step === 1 })}
|
||||
onClick={() => setStep(1)}
|
||||
></li>
|
||||
</ul>
|
||||
<div className={descriptionContainerStyle}>
|
||||
{step !== -1 && (
|
||||
<div
|
||||
className={clsx(descriptionStyle, {
|
||||
[slideToRightStyle]: step === 0,
|
||||
[formSlideToLeftStyle]: step === 1,
|
||||
})}
|
||||
>
|
||||
{t['com.affine.onboarding.videoDescription2']()}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={clsx(descriptionStyle, {
|
||||
[slideToLeftStyle]: step === 1,
|
||||
[formSlideToRightStyle]: step === 0,
|
||||
})}
|
||||
>
|
||||
{t['com.affine.onboarding.videoDescription1']()}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(arrowStyle, { [buttonDisableStyle]: step === 1 })}
|
||||
onClick={() => setStep(1)}
|
||||
data-testid="onboarding-modal-next-button"
|
||||
>
|
||||
<ArrowRightSmallIcon />
|
||||
</div>
|
||||
</div>
|
||||
</ModalWrapper>
|
||||
<ul className={tabContainerStyle}>
|
||||
<li
|
||||
className={clsx(tabStyle, {
|
||||
[tabActiveStyle]: step !== 1,
|
||||
})}
|
||||
onClick={() => setStep(0)}
|
||||
></li>
|
||||
<li
|
||||
className={clsx(tabStyle, { [tabActiveStyle]: step === 1 })}
|
||||
onClick={() => setStep(1)}
|
||||
></li>
|
||||
</ul>
|
||||
<div className={descriptionContainerStyle}>
|
||||
{step !== -1 && (
|
||||
<div
|
||||
className={clsx(descriptionStyle, {
|
||||
[slideToRightStyle]: step === 0,
|
||||
[formSlideToLeftStyle]: step === 1,
|
||||
})}
|
||||
>
|
||||
{t['com.affine.onboarding.videoDescription2']()}
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className={clsx(descriptionStyle, {
|
||||
[slideToLeftStyle]: step === 1,
|
||||
[formSlideToRightStyle]: step === 0,
|
||||
})}
|
||||
>
|
||||
{t['com.affine.onboarding.videoDescription1']()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,12 +2,10 @@ export * from './components/list-skeleton';
|
||||
export * from './styles';
|
||||
export * from './ui/breadcrumbs';
|
||||
export * from './ui/button';
|
||||
export * from './ui/confirm';
|
||||
export * from './ui/empty';
|
||||
export * from './ui/input';
|
||||
export * from './ui/layout';
|
||||
export * from './ui/menu';
|
||||
export * from './ui/modal';
|
||||
export * from './ui/mui';
|
||||
export * from './ui/popper';
|
||||
export * from './ui/scrollbar';
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button } from '@toeverything/components/button';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
import type { ModalProps } from '../modal';
|
||||
import { Modal, ModalCloseButton } from '../modal';
|
||||
import {
|
||||
StyledColumnButtonWrapper,
|
||||
StyledConfirmContent,
|
||||
StyledConfirmTitle,
|
||||
StyledModalWrapper,
|
||||
StyledRowButtonWrapper,
|
||||
} from './styles';
|
||||
|
||||
export type ConfirmProps = {
|
||||
title?: string;
|
||||
content?: string;
|
||||
confirmText?: string;
|
||||
cancelText?: string;
|
||||
// TODO: Confirm button's color should depend on confirm type
|
||||
confirmType?: 'primary' | 'warning' | 'error';
|
||||
buttonDirection?: 'row' | 'column';
|
||||
onConfirm?: () => void;
|
||||
onCancel?: () => void;
|
||||
cancelButtonTestId?: string;
|
||||
confirmButtonTestId?: string;
|
||||
} & Omit<ModalProps, 'children'>;
|
||||
|
||||
export const Confirm = ({
|
||||
title,
|
||||
content,
|
||||
confirmText,
|
||||
confirmType,
|
||||
onConfirm,
|
||||
onCancel,
|
||||
buttonDirection = 'row',
|
||||
cancelText = 'Cancel',
|
||||
open,
|
||||
cancelButtonTestId = '',
|
||||
confirmButtonTestId = '',
|
||||
}: ConfirmProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
const cancelText_ = useMemo<string>(() => {
|
||||
return cancelText === 'Cancel'
|
||||
? t['com.affine.confirmModal.button.cancel']()
|
||||
: cancelText;
|
||||
}, [cancelText, t]);
|
||||
|
||||
const handleCancel = useCallback(() => {
|
||||
onCancel?.();
|
||||
}, [onCancel]);
|
||||
const handleConfirm = useCallback(() => {
|
||||
onConfirm?.();
|
||||
}, [onConfirm]);
|
||||
|
||||
return (
|
||||
<Modal open={open} disablePortal={false}>
|
||||
<StyledModalWrapper>
|
||||
<ModalCloseButton onClick={handleCancel} />
|
||||
<StyledConfirmTitle>{title}</StyledConfirmTitle>
|
||||
<StyledConfirmContent>{content}</StyledConfirmContent>
|
||||
{buttonDirection === 'row' ? (
|
||||
<StyledRowButtonWrapper>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
size="large"
|
||||
style={{ marginRight: '24px' }}
|
||||
data-testid={cancelButtonTestId}
|
||||
>
|
||||
{cancelText_}
|
||||
</Button>
|
||||
<Button
|
||||
type={confirmType}
|
||||
onClick={handleConfirm}
|
||||
size="large"
|
||||
data-testid={confirmButtonTestId}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
</StyledRowButtonWrapper>
|
||||
) : (
|
||||
<StyledColumnButtonWrapper>
|
||||
<Button
|
||||
type={confirmType}
|
||||
onClick={handleConfirm}
|
||||
style={{ width: '284px', height: '38px', textAlign: 'center' }}
|
||||
data-testid={confirmButtonTestId}
|
||||
>
|
||||
{confirmText}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleCancel}
|
||||
style={{
|
||||
marginTop: '16px',
|
||||
width: '284px',
|
||||
height: '38px',
|
||||
textAlign: 'center',
|
||||
}}
|
||||
data-testid={cancelButtonTestId}
|
||||
>
|
||||
{cancelText_}
|
||||
</Button>
|
||||
</StyledColumnButtonWrapper>
|
||||
)}
|
||||
</StyledModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Confirm;
|
||||
@@ -1 +0,0 @@
|
||||
export * from './confirm';
|
||||
@@ -1,46 +0,0 @@
|
||||
import { displayFlex, styled } from '../../styles';
|
||||
import { ModalWrapper } from '../modal';
|
||||
|
||||
export const StyledModalWrapper = styled(ModalWrapper)(() => {
|
||||
return {
|
||||
minWidth: '460px',
|
||||
maxWidth: '560px',
|
||||
maxHeight: '292px',
|
||||
padding: '44px 84px 32px 84px',
|
||||
overflow: 'auto',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledConfirmTitle = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-h6)',
|
||||
fontWeight: 600,
|
||||
textAlign: 'center',
|
||||
lineHeight: '28px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledConfirmContent = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
textAlign: 'center',
|
||||
marginTop: '12px',
|
||||
color: 'var(--affine-text-primary-color)',
|
||||
lineHeight: '26px',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledColumnButtonWrapper = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('center', 'center'),
|
||||
flexDirection: 'column',
|
||||
marginTop: '32px',
|
||||
};
|
||||
});
|
||||
export const StyledRowButtonWrapper = styled('div')(() => {
|
||||
return {
|
||||
...displayFlex('center', 'center'),
|
||||
flexDirection: 'row',
|
||||
marginTop: '32px',
|
||||
};
|
||||
});
|
||||
@@ -1,72 +0,0 @@
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
import { Button, type ButtonType } from '@toeverything/components/button';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { Modal, type ModalProps } from './modal';
|
||||
import { ModalCloseButton } from './modal-close-button';
|
||||
import { ModalWrapper } from './modal-wrapper';
|
||||
import {
|
||||
StyledModalContent,
|
||||
StyledModalFooter,
|
||||
StyledModalTitle,
|
||||
} from './styles';
|
||||
|
||||
export interface BaseModalProps
|
||||
extends Omit<ModalProps, 'onClose' | 'children'> {
|
||||
title?: string;
|
||||
content?: string;
|
||||
confirmText?: string;
|
||||
confirmType?: ButtonType;
|
||||
onClose: () => void;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
}
|
||||
|
||||
export const ConfirmModal = ({
|
||||
open,
|
||||
onClose,
|
||||
confirmText,
|
||||
confirmType = 'primary',
|
||||
onCancel,
|
||||
onConfirm,
|
||||
title,
|
||||
content,
|
||||
}: BaseModalProps) => {
|
||||
const t = useAFFiNEI18N();
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
onClose();
|
||||
}, [onClose]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
wrapperPosition={['center', 'center']}
|
||||
data-testid="auth-modal"
|
||||
>
|
||||
<ModalWrapper
|
||||
width={480}
|
||||
minHeight={194}
|
||||
style={{
|
||||
overflow: 'hidden',
|
||||
backgroundColor: 'var(--affine-white)',
|
||||
boxShadow: 'var(--affine-popover-shadow)',
|
||||
padding: '20px 24px 0',
|
||||
}}
|
||||
>
|
||||
<ModalCloseButton top={22} right={20} onClick={handleClose} />
|
||||
<StyledModalTitle>{title}</StyledModalTitle>
|
||||
<StyledModalContent>{content}</StyledModalContent>
|
||||
<StyledModalFooter>
|
||||
<Button onClick={onCancel} style={{ marginRight: 20 }}>
|
||||
{t['com.affine.confirmModal.button.cancel']()}
|
||||
</Button>
|
||||
<Button type={confirmType} onClick={onConfirm}>
|
||||
{confirmText || t['Confirm']()}
|
||||
</Button>
|
||||
</StyledModalFooter>
|
||||
</ModalWrapper>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
@@ -1,8 +0,0 @@
|
||||
import Modal from './modal';
|
||||
|
||||
export * from './confirm-modal';
|
||||
export * from './modal';
|
||||
export * from './modal-close-button';
|
||||
export * from './modal-wrapper';
|
||||
|
||||
export default Modal;
|
||||
@@ -1,39 +0,0 @@
|
||||
import { CloseIcon } from '@blocksuite/icons';
|
||||
import {
|
||||
IconButton,
|
||||
type IconButtonProps,
|
||||
} from '@toeverything/components/button';
|
||||
import type { HTMLAttributes } from 'react';
|
||||
|
||||
export type ModalCloseButtonProps = {
|
||||
top?: number;
|
||||
right?: number;
|
||||
absolute?: boolean;
|
||||
} & Omit<IconButtonProps, 'children'> &
|
||||
HTMLAttributes<HTMLButtonElement>;
|
||||
|
||||
export const ModalCloseButton = ({
|
||||
absolute = true,
|
||||
right,
|
||||
top,
|
||||
...props
|
||||
}: ModalCloseButtonProps) => {
|
||||
return (
|
||||
<IconButton
|
||||
style={
|
||||
absolute
|
||||
? {
|
||||
position: 'absolute',
|
||||
top: top ?? 24,
|
||||
right: right ?? 40,
|
||||
zIndex: 1,
|
||||
}
|
||||
: {}
|
||||
}
|
||||
data-testid="modal-close-button"
|
||||
{...props}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import { styled } from '../../styles';
|
||||
|
||||
export const ModalWrapper = styled('div')<{
|
||||
width?: CSSProperties['width'];
|
||||
height?: CSSProperties['height'];
|
||||
minHeight?: CSSProperties['minHeight'];
|
||||
}>(({ width, height, minHeight }) => {
|
||||
return {
|
||||
width,
|
||||
height,
|
||||
minHeight,
|
||||
backgroundColor: 'var(--affine-background-overlay-panel-color)',
|
||||
boxShadow: 'var(--affine-shadow-3)',
|
||||
borderRadius: '12px',
|
||||
position: 'relative',
|
||||
maxHeight: 'calc(100vh - 32px)',
|
||||
};
|
||||
});
|
||||
|
||||
export default ModalWrapper;
|
||||
@@ -1,59 +0,0 @@
|
||||
import type { ModalProps as ModalUnstyledOwnProps } from '@mui/base/Modal';
|
||||
import Fade from '@mui/material/Fade';
|
||||
|
||||
import { StyledBackdrop, StyledModal } from './styles';
|
||||
const Backdrop = ({
|
||||
open,
|
||||
...other
|
||||
}: {
|
||||
open?: boolean;
|
||||
className: string;
|
||||
}) => {
|
||||
return (
|
||||
<Fade in={open}>
|
||||
<StyledBackdrop {...other} />
|
||||
</Fade>
|
||||
);
|
||||
};
|
||||
|
||||
export type ModalProps = {
|
||||
wrapperPosition?: ['top' | 'bottom' | 'center', 'left' | 'right' | 'center'];
|
||||
} & ModalUnstyledOwnProps;
|
||||
|
||||
const transformConfig = {
|
||||
top: 'flex-start',
|
||||
bottom: 'flex-end',
|
||||
center: 'center',
|
||||
left: 'flex-start',
|
||||
right: 'flex-end',
|
||||
};
|
||||
|
||||
export const Modal = (props: ModalProps) => {
|
||||
const {
|
||||
wrapperPosition = ['center', 'center'],
|
||||
open,
|
||||
children,
|
||||
...otherProps
|
||||
} = props;
|
||||
const [vertical, horizontal] = wrapperPosition;
|
||||
// Fixme: This is a workaround for Mui bug
|
||||
// Refs: https://github.com/mui/material-ui/issues/33748
|
||||
if (!open) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledModal
|
||||
{...otherProps}
|
||||
open={open}
|
||||
slots={{ backdrop: Backdrop }}
|
||||
alignItems={transformConfig[vertical]}
|
||||
justifyContent={transformConfig[horizontal]}
|
||||
disableEnforceFocus
|
||||
>
|
||||
<Fade in={open}>{children}</Fade>
|
||||
</StyledModal>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
@@ -1,65 +0,0 @@
|
||||
import { Modal as ModalUnstyled } from '@mui/base/Modal';
|
||||
import type { CSSProperties } from 'react';
|
||||
|
||||
import { styled } from '../../styles';
|
||||
|
||||
export const StyledBackdrop = styled('div')(() => {
|
||||
return {
|
||||
zIndex: '-1',
|
||||
position: 'fixed',
|
||||
right: '0',
|
||||
bottom: '0',
|
||||
top: '0',
|
||||
left: '0',
|
||||
backgroundColor: 'var(--affine-background-modal-color)',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModal = styled(ModalUnstyled, {
|
||||
shouldForwardProp: prop => {
|
||||
return !['justifyContent', 'alignItems'].includes(prop as string);
|
||||
},
|
||||
})<{
|
||||
alignItems: CSSProperties['alignItems'];
|
||||
justifyContent: CSSProperties['justifyContent'];
|
||||
}>(({ alignItems, justifyContent }) => {
|
||||
return {
|
||||
width: '100vw',
|
||||
height: '100vh',
|
||||
display: 'flex',
|
||||
alignItems,
|
||||
justifyContent,
|
||||
position: 'fixed',
|
||||
left: '0',
|
||||
top: '0',
|
||||
zIndex: 'var(--affine-z-index-modal)',
|
||||
WebkitAppRegion: 'no-drag',
|
||||
'*': {
|
||||
WebkitTapHighlightColor: 'transparent',
|
||||
outline: 'none',
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalFooter = styled('div')(() => {
|
||||
return {
|
||||
marginTop: 40,
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
alignItems: 'center',
|
||||
};
|
||||
});
|
||||
|
||||
export const StyledModalTitle = styled('div')(() => {
|
||||
return {
|
||||
fontWeight: 600,
|
||||
fontSize: 'var(--affine-font-h-6)',
|
||||
};
|
||||
});
|
||||
export const StyledModalContent = styled('div')(() => {
|
||||
return {
|
||||
fontSize: 'var(--affine-font-base)',
|
||||
lineHeight: '24px',
|
||||
marginTop: '12px',
|
||||
};
|
||||
});
|
||||
Reference in New Issue
Block a user