mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-09-07 09:21:24 +08:00
refactor: tour modal (#2297)
This commit is contained in:
@@ -67,7 +67,7 @@ test('affine onboarding button', async ({ page }) => {
|
|||||||
'[data-testid=onboarding-modal-editing-video]'
|
'[data-testid=onboarding-modal-editing-video]'
|
||||||
);
|
);
|
||||||
expect(await editingVideo.isVisible()).toEqual(true);
|
expect(await editingVideo.isVisible()).toEqual(true);
|
||||||
await page.getByTestId('onboarding-modal-ok-button').click();
|
await page.getByTestId('onboarding-modal-close-button').click();
|
||||||
|
|
||||||
expect(await onboardingModal.isVisible()).toEqual(false);
|
expect(await onboardingModal.isVisible()).toEqual(false);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,38 +7,90 @@ export const modalStyle = style({
|
|||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
padding: '12px 36px',
|
backgroundColor: 'var(--affine-background-secondary-color)',
|
||||||
backgroundColor: 'var(--affine-white)',
|
borderRadius: '16px',
|
||||||
borderRadius: '12px',
|
|
||||||
boxShadow: 'var(--affine-popover-shadow)',
|
|
||||||
});
|
});
|
||||||
export const titleStyle = style({
|
export const titleStyle = style({
|
||||||
fontSize: 'var(--affine-font-h6)',
|
fontSize: 'var(--affine-font-h6)',
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
|
marginTop: '12px',
|
||||||
|
});
|
||||||
|
export const containerStyle = style({
|
||||||
|
paddingTop: '25px',
|
||||||
|
width: '100%',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'space-between',
|
||||||
});
|
});
|
||||||
export const videoContainerStyle = style({
|
export const videoContainerStyle = style({
|
||||||
paddingTop: '15px',
|
paddingTop: '25px',
|
||||||
|
height: '300px',
|
||||||
|
width: 'calc(100% - 72px)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexGrow: 1,
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
position: 'relative',
|
||||||
|
});
|
||||||
|
export const videoSlideStyle = style({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
});
|
});
|
||||||
export const videoStyle = style({
|
export const videoStyle = style({
|
||||||
|
position: 'absolute',
|
||||||
objectFit: 'fill',
|
objectFit: 'fill',
|
||||||
height: '300px',
|
height: '300px',
|
||||||
width: '100%',
|
border: '1px solid var(--affine-border-color)',
|
||||||
|
transition: 'opacity 0.5s ease-in-out',
|
||||||
});
|
});
|
||||||
export const buttonContainerStyle = style({
|
export const videoActiveStyle = style({
|
||||||
|
opacity: 0,
|
||||||
|
});
|
||||||
|
export const arrowStyle = style({
|
||||||
|
wordBreak: 'break-all',
|
||||||
|
wordWrap: 'break-word',
|
||||||
|
width: '36px',
|
||||||
|
fontSize: '32px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
height: '240px',
|
||||||
|
flexGrow: 0.2,
|
||||||
|
cursor: 'pointer',
|
||||||
|
});
|
||||||
|
export const descriptionStyle = style({
|
||||||
marginTop: '15px',
|
marginTop: '15px',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'flex-end',
|
padding: '0 56px',
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
lineHeight: '18px',
|
||||||
|
marginBottom: '20px',
|
||||||
});
|
});
|
||||||
export const buttonStyle = style({
|
export const tabStyle = style({
|
||||||
borderRadius: '8px',
|
width: '40px',
|
||||||
backgroundColor: 'var(--affine-primary-color)',
|
height: '20px',
|
||||||
color: 'var(--affine-white)',
|
content: '""',
|
||||||
height: '32px',
|
borderBottom: '2px solid var(--affine-text-primary-color)',
|
||||||
padding: '4 20px',
|
opacity: 0.2,
|
||||||
|
margin: '0 10px 20px 0',
|
||||||
|
transition: 'all 0.15s ease-in-out',
|
||||||
':hover': {
|
':hover': {
|
||||||
backgroundColor: 'var(--affine-primary-color)',
|
opacity: 1,
|
||||||
color: 'var(--affine-white)',
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
export const tabActiveStyle = style({
|
||||||
|
opacity: 1,
|
||||||
|
});
|
||||||
|
export const tabContainerStyle = style({
|
||||||
|
width: '100%',
|
||||||
|
marginTop: '20px',
|
||||||
|
position: 'relative',
|
||||||
|
height: '2px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,13 +1,22 @@
|
|||||||
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
|
import { ArrowLeftSmallIcon, ArrowRightSmallIcon } from '@blocksuite/icons';
|
||||||
|
import clsx from 'clsx';
|
||||||
import type { FC } from 'react';
|
import type { FC } from 'react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
|
|
||||||
import { Button, Modal, ModalCloseButton, ModalWrapper } from '../..';
|
import { Modal, ModalCloseButton, ModalWrapper } from '../..';
|
||||||
import {
|
import {
|
||||||
buttonContainerStyle,
|
arrowStyle,
|
||||||
buttonStyle,
|
containerStyle,
|
||||||
|
descriptionStyle,
|
||||||
modalStyle,
|
modalStyle,
|
||||||
|
tabActiveStyle,
|
||||||
|
tabContainerStyle,
|
||||||
|
tabStyle,
|
||||||
titleStyle,
|
titleStyle,
|
||||||
|
videoActiveStyle,
|
||||||
videoContainerStyle,
|
videoContainerStyle,
|
||||||
|
videoSlideStyle,
|
||||||
videoStyle,
|
videoStyle,
|
||||||
} from './index.css';
|
} from './index.css';
|
||||||
|
|
||||||
@@ -17,6 +26,7 @@ type TourModalProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const TourModal: FC<TourModalProps> = ({ open, onClose }) => {
|
export const TourModal: FC<TourModalProps> = ({ open, onClose }) => {
|
||||||
|
const t = useAFFiNEI18N();
|
||||||
const [step, setStep] = useState(0);
|
const [step, setStep] = useState(0);
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setStep(0);
|
setStep(0);
|
||||||
@@ -27,73 +37,85 @@ export const TourModal: FC<TourModalProps> = ({ open, onClose }) => {
|
|||||||
open={open}
|
open={open}
|
||||||
onClose={handleClose}
|
onClose={handleClose}
|
||||||
wrapperPosition={['center', 'center']}
|
wrapperPosition={['center', 'center']}
|
||||||
|
hideBackdrop
|
||||||
>
|
>
|
||||||
<ModalWrapper width={545} height={442} data-testid="onboarding-modal">
|
<ModalWrapper
|
||||||
|
width={545}
|
||||||
|
style={{ minHeight: '480px' }}
|
||||||
|
data-testid="onboarding-modal"
|
||||||
|
>
|
||||||
<ModalCloseButton
|
<ModalCloseButton
|
||||||
top={10}
|
top={6}
|
||||||
right={10}
|
right={10}
|
||||||
onClick={handleClose}
|
onClick={handleClose}
|
||||||
data-testid="onboarding-modal-close-button"
|
data-testid="onboarding-modal-close-button"
|
||||||
/>
|
/>
|
||||||
{step === 0 && (
|
<div className={modalStyle}>
|
||||||
<div className={modalStyle}>
|
<div className={titleStyle}>
|
||||||
<div className={titleStyle}>Hyper merged whiteboard and docs</div>
|
{step === 1
|
||||||
<div className={videoContainerStyle}>
|
? t['com.affine.onboarding.title2']()
|
||||||
<video
|
: t['com.affine.onboarding.title1']()}
|
||||||
autoPlay
|
|
||||||
muted
|
|
||||||
loop
|
|
||||||
className={videoStyle}
|
|
||||||
data-testid="onboarding-modal-switch-video"
|
|
||||||
>
|
|
||||||
<source src="/switchVideo.mp4" type="video/mp4" />
|
|
||||||
<source src="/switchVideo.webm" type="video/webm" />
|
|
||||||
Easily switch between Page mode for structured document creation
|
|
||||||
and Whiteboard mode for the freeform visual expression of
|
|
||||||
creative ideas.
|
|
||||||
</video>
|
|
||||||
</div>
|
|
||||||
<div className={buttonContainerStyle}>
|
|
||||||
<Button
|
|
||||||
className={buttonStyle}
|
|
||||||
onClick={() => setStep(1)}
|
|
||||||
data-testid="onboarding-modal-next-button"
|
|
||||||
>
|
|
||||||
Next
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className={containerStyle}>
|
||||||
{step === 1 && (
|
<div
|
||||||
<div className={modalStyle}>
|
className={arrowStyle}
|
||||||
<div className={titleStyle}>
|
onClick={() => setStep(0)}
|
||||||
Intuitive & robust block-based editing
|
data-testid="onboarding-modal-pre-button"
|
||||||
|
>
|
||||||
|
<ArrowLeftSmallIcon />
|
||||||
</div>
|
</div>
|
||||||
<div className={videoContainerStyle}>
|
<div className={videoContainerStyle}>
|
||||||
<video
|
<div className={videoSlideStyle}>
|
||||||
autoPlay
|
<video
|
||||||
muted
|
autoPlay
|
||||||
loop
|
muted
|
||||||
className={videoStyle}
|
loop
|
||||||
data-testid="onboarding-modal-editing-video"
|
className={clsx(videoStyle, {
|
||||||
>
|
[videoActiveStyle]: step === 0,
|
||||||
<source src="/editingVideo.mp4" type="video/mp4" />
|
})}
|
||||||
<source src="/editingVideo.webm" type="video/webm" />
|
data-testid="onboarding-modal-editing-video"
|
||||||
Create structured documents with ease, using a modular interface
|
>
|
||||||
to drag and drop blocks of text, images, and other content.
|
<source src="/editingVideo.mp4" type="video/mp4" />
|
||||||
</video>
|
<source src="/editingVideo.webm" type="video/webm" />
|
||||||
|
</video>
|
||||||
|
<video
|
||||||
|
autoPlay
|
||||||
|
muted
|
||||||
|
loop
|
||||||
|
className={clsx(videoStyle, {
|
||||||
|
[videoActiveStyle]: step === 1,
|
||||||
|
})}
|
||||||
|
data-testid="onboarding-modal-switch-video"
|
||||||
|
>
|
||||||
|
<source src="/switchVideo.mp4" type="video/mp4" />
|
||||||
|
<source src="/switchVideo.webm" type="video/webm" />
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={buttonContainerStyle}>
|
<div
|
||||||
<Button
|
className={arrowStyle}
|
||||||
className={buttonStyle}
|
onClick={() => setStep(1)}
|
||||||
onClick={handleClose}
|
data-testid="onboarding-modal-next-button"
|
||||||
data-testid="onboarding-modal-ok-button"
|
>
|
||||||
>
|
<ArrowRightSmallIcon />
|
||||||
Okay, I Like It !
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
<ul className={tabContainerStyle}>
|
||||||
|
<li
|
||||||
|
className={clsx(tabStyle, { [tabActiveStyle]: step === 0 })}
|
||||||
|
onClick={() => setStep(0)}
|
||||||
|
></li>
|
||||||
|
<li
|
||||||
|
className={clsx(tabStyle, { [tabActiveStyle]: step === 1 })}
|
||||||
|
onClick={() => setStep(1)}
|
||||||
|
></li>
|
||||||
|
</ul>
|
||||||
|
<div className={descriptionStyle}>
|
||||||
|
{step === 1
|
||||||
|
? t['com.affine.onboarding.videoDescription2']()
|
||||||
|
: t['com.affine.onboarding.videoDescription1']()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const ModalWrapper = styled('div')<{
|
|||||||
height,
|
height,
|
||||||
minHeight,
|
minHeight,
|
||||||
backgroundColor: 'var(--affine-background-secondary-color)',
|
backgroundColor: 'var(--affine-background-secondary-color)',
|
||||||
boxShadow: 'var(--affine-popover-shadow)',
|
boxShadow: 'var(--affine-shadow-3)',
|
||||||
borderRadius: '16px',
|
borderRadius: '16px',
|
||||||
position: 'relative',
|
position: 'relative',
|
||||||
maxHeight: 'calc(100vh - 32px)',
|
maxHeight: 'calc(100vh - 32px)',
|
||||||
|
|||||||
@@ -267,5 +267,9 @@
|
|||||||
"dark": "dark",
|
"dark": "dark",
|
||||||
"system": "system",
|
"system": "system",
|
||||||
"com.affine.pageMode": "Page Mode",
|
"com.affine.pageMode": "Page Mode",
|
||||||
"com.affine.edgelessMode": "Edgeless Mode"
|
"com.affine.edgelessMode": "Edgeless Mode",
|
||||||
|
"com.affine.onboarding.title1": "Hyper merged whiteboard and docs",
|
||||||
|
"com.affine.onboarding.title2": "Intuitive & robust block-based editing",
|
||||||
|
"com.affine.onboarding.videoDescription1": "Easily switch between Page mode for structured document creation and Whiteboard mode for the freeform visual expression of creative ideas.",
|
||||||
|
"com.affine.onboarding.videoDescription2": "Create structured documents with ease, using a modular interface to drag and drop blocks of text, images, and other content."
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user