feat(core): adjust ui for new design (#5322)

feat(core): add bg and hover state for onboarding

feat(core): adjust onboarding styles for web

feat(core): add get started page for onboarding
This commit is contained in:
Cats Juice 2023-12-19 10:28:11 +00:00
parent 07f10f55bf
commit 197d1d4136
No known key found for this signature in database
GPG Key ID: 1C1E76924FAFDDE4
19 changed files with 340 additions and 67 deletions

View File

@ -14,11 +14,16 @@ export const tooltip = style({
fontSize: '20px',
lineHeight: '28px',
fontWeight: 600,
textShadow: '0px 0px 4px rgba(66, 65, 73, 0.14)',
color: 'white',
opacity: 0,
animation: `${fadeIn} 1s ease forwards`,
animationDelay: onboardingVars.animateIn.tooltipShowUpDelay,
color: '#121212',
selectors: {
'[data-is-desktop="true"] &': {
color: 'white',
textShadow: '0px 0px 4px rgba(66, 65, 73, 0.14)',
},
},
});
export const next = style({

View File

@ -3,6 +3,7 @@ import type { OnboardingBlockOption } from '../types';
import bookmark1png from './assets/article-0-bookmark-1.png';
import bookmark2png from './assets/article-0-bookmark-2.png';
import embed1png from './assets/article-0-embed-1.png';
import { BlogLink } from './blog-link';
export const article0: Array<OnboardingBlockOption> = [
{
@ -245,6 +246,7 @@ export const article0: Array<OnboardingBlockOption> = [
work. Obviously if you attend one of these, you should stop. But what
else can you do?
</p>
<BlogLink />
</>
),
offset: { x: 1200, y: -1600 },

View File

@ -7,6 +7,7 @@ import bookmark1png from './assets/article-1-bookmark-1.png';
import illustration1png from './assets/article-1-illustration-1.png';
import Article1Illustration2 from './assets/article-1-illustration-2';
import { hr, link, quote } from './blocks.css';
import { BlogLink } from './blog-link';
export const article1: Array<OnboardingBlockOption> = [
{
@ -256,6 +257,7 @@ export const article1: Array<OnboardingBlockOption> = [
intricate algorithm, designing a user interface, or figuring out how
to lead a team towards some goal are also creative efforts.)
</p>
<BlogLink />
</>
),
offset: { x: 900, y: -950 },

View File

@ -4,6 +4,7 @@ import illustration1png from './assets/article-2-illustration-1.jpg';
import illustration2png from './assets/article-2-illustration-2.jpg';
import note1png from './assets/article-2-note-1.png';
import note2png from './assets/article-2-note-2.png';
import { BlogLink } from './blog-link';
export const article2: Array<OnboardingBlockOption> = [
{
@ -140,6 +141,7 @@ export const article2: Array<OnboardingBlockOption> = [
media can also be an echo chamber that can push us to unwittingly
become more extreme in our beliefs.
</p>
<BlogLink />
</>
),
offset: { x: 150, y: -680 },

View File

@ -5,6 +5,7 @@ import illustration2jpg from './assets/article-3-illustration-2.jpg';
import illustration3jpg from './assets/article-3-illustration-3.jpg';
import illustration4jpg from './assets/article-3-illustration-4.jpg';
import illustration5jpg from './assets/article-3-illustration-5.jpg';
import { BlogLink } from './blog-link';
export const article3: Array<OnboardingBlockOption> = [
{
@ -191,6 +192,7 @@ export const article3: Array<OnboardingBlockOption> = [
game world in creative and flexible ways, leading to fresh and
unexpected discoveries.
</p>
<BlogLink />
</>
),
offset: { x: 450, y: -1400 },

View File

@ -4,6 +4,7 @@ import bookmark1png from './assets/article-4-bookmark-1.png';
import bookmark2png from './assets/article-4-bookmark-2.png';
import illustration1jpg from './assets/article-4-illustration-1.jpg';
import illustration2jpg from './assets/article-4-illustration-2.jpg';
import { BlogLink } from './blog-link';
export const article4: Array<OnboardingBlockOption> = [
{
@ -180,6 +181,7 @@ export const article4: Array<OnboardingBlockOption> = [
degree as in the previous one. Psychology is not applied biology, nor
is biology applied chemistry.
</p>
<BlogLink />
</>
),

View File

@ -0,0 +1,9 @@
import { link } from './blocks.css';
export const BlogLink = () => {
return (
<a className={link} href="https://affine.pro/blog">
Check other articles
</a>
);
};

View File

@ -36,7 +36,7 @@ const paperLocations = {
/** paper enter animation config */
const paperEnterAnimationOriginal = {
'0': {
curveCenter: 4,
curveCenter: 3,
curve: 292,
delay: 800,
fromZ: 1230,
@ -54,7 +54,7 @@ const paperEnterAnimationOriginal = {
},
'1': {
curveCenter: 4,
curve: 280,
curve: 390,
delay: 0,
fromZ: 3697,
fromX: 25,
@ -71,9 +71,9 @@ const paperEnterAnimationOriginal = {
},
'2': {
curveCenter: 3,
curve: 660,
curve: 1240,
delay: 1700,
fromZ: 57379,
fromZ: 27379,
fromX: 2,
fromY: -77,
fromRotateX: 0,
@ -87,8 +87,8 @@ const paperEnterAnimationOriginal = {
easing: 'ease',
},
'3': {
curveCenter: 4,
curve: 260,
curveCenter: 1,
curve: 300,
delay: 1500,
fromZ: 4303,
fromX: -37,
@ -104,8 +104,8 @@ const paperEnterAnimationOriginal = {
easing: 'ease',
},
'4': {
curveCenter: 3,
curve: 270,
curveCenter: 4,
curve: 470,
delay: 1571,
fromZ: 1876,
fromX: 65,

File diff suppressed because one or more lines are too long

View File

@ -35,7 +35,11 @@ export const Onboarding = ({ onOpenApp }: OnboardingProps) => {
}, []);
return (
<div className={styles.onboarding} data-is-desktop={environment.isDesktop}>
<div
className={styles.onboarding}
data-is-desktop={environment.isDesktop}
data-is-window={!!status.activeId || !!status.unfoldingId}
>
<div className={styles.offsetOrigin}>
{(Object.entries(articles) as Array<[ArticleId, ArticleOption]>).map(
([id, article]) => {

View File

@ -55,7 +55,6 @@ export const PaperSteps = ({
useEffect(() => {
if (stage === 'unfold' && status.unfoldingId === article.id) {
console.log('unfold', article.id);
setFold(false);
}
}, [article.id, stage, status.unfoldingId]);

View File

@ -12,8 +12,8 @@ interface AnimateInProps {
onFinished?: () => void;
}
const easing = 'spring(5, 100, 10, 0)';
const segments = 4;
const easing = 'spring(3.2, 100, 10, 0)';
const segments = 6;
const animeSync = (params: Parameters<typeof anime>[0]) => {
return new Promise(resolve => {

View File

@ -1,4 +1,4 @@
import { globalStyle, style } from '@vanilla-extract/css';
import { globalStyle, keyframes, style } from '@vanilla-extract/css';
import { onboardingVars } from '../style.css';
@ -7,9 +7,9 @@ export const edgelessSwitchWindow = style({
borderRadius: onboardingVars.paper.r,
backgroundColor: onboardingVars.paper.bg,
position: 'relative',
transition: `width ${onboardingVars.window.transition.size}, height ${onboardingVars.window.transition.size}`,
transition: `width ${onboardingVars.window.transition.size}, height ${onboardingVars.window.transition.size}, border-radius ${onboardingVars.window.transition.size}`,
overflow: 'hidden',
boxShadow: 'var(--affine-shadow-2)',
boxShadow: onboardingVars.web.windowShadow,
fontFamily: 'var(--affine-font-family)',
color: onboardingVars.paper.textColor,
@ -18,11 +18,43 @@ export const edgelessSwitchWindow = style({
'&[data-mode="edgeless"]': {
width: onboardingVars.edgeless.w,
height: onboardingVars.edgeless.h,
borderRadius: onboardingVars.edgeless.r,
},
'&[data-mode="page"]': {
width: onboardingVars.article.w,
height: onboardingVars.article.h,
borderRadius: onboardingVars.article.r,
},
'&[data-mode="well-done"]': {
width: onboardingVars.wellDone.w,
height: onboardingVars.wellDone.h,
borderRadius: onboardingVars.wellDone.r,
},
},
});
export const orbit = style({
width: '200%',
height: '100%',
display: 'flex',
transition: 'transform 0.4s ease',
willChange: 'transform',
selectors: {
'[data-mode="well-done"] &': {
transform: 'translateX(-50%)',
},
},
});
export const orbitItem = style({
width: '50%',
height: '100%',
flexShrink: 0,
flexGrow: 0,
overflow: 'hidden',
});
export const doc = style({
selectors: {
// grid background
'&::before': {
content: '""',
@ -39,12 +71,56 @@ export const edgelessSwitchWindow = style({
pointerEvents: 'none',
transition: 'opacity 0.3s ease',
},
'&[data-mode="edgeless"]::before': {
'[data-mode="edgeless"] &::before': {
opacity: 1,
},
},
});
export const wellDone = style({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
textAlign: 'center',
userSelect: 'none',
});
const wellDoneSlideIn = keyframes({
from: {
transform: 'translateX(100px)',
opacity: 0,
},
to: {
transform: 'translateX(0)',
opacity: 1,
},
});
export const wellDoneEnterAnim = style({
opacity: 0,
selectors: {
'[data-mode="well-done"] &': {
animation: `${wellDoneSlideIn} 0.25s cubic-bezier(0.25, 0.1, 0.25, 1) forwards`,
},
'&:nth-child(1)': { animationDelay: '0.1s' },
'&:nth-child(2)': { animationDelay: '0.15s' },
'&:nth-child(3)': { animationDelay: '0.2s' },
'&:nth-child(4)': { animationDelay: '0.25s' },
},
});
export const wellDoneTitle = style({
fontSize: 28,
lineHeight: '36px',
fontWeight: '600',
});
export const wellDoneContent = style({
fontSize: 15,
lineHeight: '24px',
fontWeight: '400',
});
export const toolbar = style({
position: 'absolute',
bottom: '20px',
@ -77,11 +153,11 @@ export const canvas = style({
'[data-mode="edgeless"] &': {
cursor: 'grab',
},
'.grabbing[data-mode="edgeless"] &': {
'[data-mode="edgeless"] .grabbing &': {
cursor: 'grabbing',
transition: 'none',
},
'.scaling[data-mode="edgeless"] &': {
'[data-mode="edgeless"] .scaling &': {
transition: 'none',
},
},

View File

@ -1,4 +1,5 @@
import { Button } from '@affine/component';
import clsx from 'clsx';
import { debounce } from 'lodash-es';
import {
type CSSProperties,
@ -8,6 +9,7 @@ import {
useState,
} from 'react';
import Logo from '../assets/logo';
import { OnboardingBlock } from '../switch-widgets/block';
import { EdgelessSwitchButtons } from '../switch-widgets/switch';
import { ToolbarSVG } from '../switch-widgets/toolbar';
@ -39,7 +41,8 @@ export const EdgelessSwitch = ({
onBack,
onNext,
}: EdgelessSwitchProps) => {
const windowRef = useRef<HTMLDivElement>(null);
// const windowRef = useRef<HTMLDivElement>(null);
const docRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLDivElement>(null);
const mouseDownRef = useRef(false);
const prevStateRef = useRef<EdgelessSwitchState | null>(
@ -59,12 +62,12 @@ export const EdgelessSwitch = ({
const onSwitchToPageMode = useCallback(() => setMode('page'), []);
const onSwitchToEdgelessMode = useCallback(() => setMode('edgeless'), []);
const toggleGrabbing = useCallback((v: boolean) => {
if (!windowRef.current) return;
windowRef.current.classList.toggle('grabbing', v);
if (!docRef.current) return;
docRef.current.classList.toggle('grabbing', v);
}, []);
const turnOnScaling = useCallback(() => {
if (!windowRef.current) return;
windowRef.current.classList.add('scaling');
if (!docRef.current) return;
docRef.current.classList.add('scaling');
}, []);
const enableScrollWithDelay = useCallback(() => {
@ -86,20 +89,21 @@ export const EdgelessSwitch = ({
}, []);
const onNextClick = useCallback(() => {
if (mode === 'page') setMode('edgeless');
else if (mode === 'edgeless') setMode('well-done');
else onNext?.();
}, [mode, onNext]);
useEffect(() => {
turnOffScalingRef.current = debounce(() => {
if (!windowRef.current) return;
windowRef.current.classList.remove('scaling');
if (!docRef.current) return;
docRef.current.classList.remove('scaling');
}, 100);
}, []);
useEffect(() => {
if (mode === 'page') return;
const canvas = canvasRef.current;
const win = windowRef.current;
const win = docRef.current;
if (!win || !canvas) return;
const onWheel = (e: WheelEvent) => {
@ -197,40 +201,71 @@ export const EdgelessSwitch = ({
return (
<div
ref={windowRef}
data-mode={mode}
data-scroll={scrollable}
className={styles.edgelessSwitchWindow}
style={canvasStyle}
>
<div className={styles.canvas} ref={canvasRef}>
<div className={styles.page}>
{
/* render blocks */
article.blocks.map((block, key) => {
return <OnboardingBlock key={key} mode={mode} {...block} />;
})
}
<div className={styles.orbit}>
<div
ref={docRef}
className={clsx(styles.orbitItem, styles.doc)}
data-scroll={scrollable}
>
<div className={styles.canvas} ref={canvasRef}>
<div className={styles.page}>
{
/* render blocks */
article.blocks.map((block, key) => {
return <OnboardingBlock key={key} mode={mode} {...block} />;
})
}
</div>
</div>
<div data-no-drag className={styles.noDragWrapper}>
<header className={styles.header}>
<Button size="extraLarge" onClick={onBack}>
Back
</Button>
<EdgelessSwitchButtons
mode={mode}
onSwitchToPageMode={onSwitchToPageMode}
onSwitchToEdgelessMode={onSwitchToEdgelessMode}
/>
<Button size="extraLarge" type="primary" onClick={onNextClick}>
Next
</Button>
</header>
<div className={styles.toolbar}>
<ToolbarSVG />
</div>
</div>
</div>
</div>
<div data-no-drag className={styles.noDragWrapper}>
<header className={styles.header}>
<Button size="extraLarge" onClick={onBack}>
Back
<div className={clsx(styles.orbitItem, styles.wellDone)}>
<div
className={styles.wellDoneEnterAnim}
onDoubleClick={() => setMode('edgeless')}
>
<Logo />
</div>
<h1 className={clsx(styles.wellDoneTitle, styles.wellDoneEnterAnim)}>
Well Done !
</h1>
<p className={clsx(styles.wellDoneContent, styles.wellDoneEnterAnim)}>
You have the flexibility to switch between Page and Edgeless
<br /> Mode at any point during content creation.
</p>
<Button
className={styles.wellDoneEnterAnim}
onClick={onNextClick}
type="primary"
size="extraLarge"
style={{ marginTop: 40 }}
>
Get Start
</Button>
<EdgelessSwitchButtons
mode={mode}
onSwitchToPageMode={onSwitchToPageMode}
onSwitchToEdgelessMode={onSwitchToEdgelessMode}
/>
<Button size="extraLarge" type="primary" onClick={onNextClick}>
Next
</Button>
</header>
<div className={styles.toolbar}>
<ToolbarSVG />
</div>
</div>
</div>

View File

@ -20,11 +20,16 @@ const fadeOut = keyframes({
export const unfoldingWrapper = style([
paperLocation,
{
vars: {
'--hover-offset-y': '0px',
'--hover-scale': '1',
},
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
transform: 'rotate(var(--toRotateZ))',
transform:
'rotate(var(--toRotateZ)) translateY(var(--hover-offset-y)) scale(var(--hover-scale))',
cursor: 'pointer',
backgroundColor: onboardingVars.paper.bg,
@ -38,6 +43,13 @@ export const unfoldingWrapper = style([
transition: `all 0.23s ease, width ${unfolding.sizeTransition}, height ${unfolding.sizeTransition}, transform ${unfolding.transformTransition}`,
':hover': {
vars: {
'--hover-offset-y': '-10px',
'--hover-scale': '1.03',
},
},
'::before': {
// hack border
content: '""',
@ -53,6 +65,9 @@ export const unfoldingWrapper = style([
'&[data-fold="false"]': {
vars: {
'--toRotateZ': '0deg',
// reset hover to avoid flickering
'--hover-offset-y': '0px',
'--hover-scale': '1',
},
width: onboardingVars.article.w,
height: onboardingVars.article.h,

View File

@ -1,4 +1,4 @@
import { globalStyle, style } from '@vanilla-extract/css';
import { globalStyle, keyframes, style } from '@vanilla-extract/css';
// in case that we need to support dark mode later
export const onboardingVars = {
@ -23,16 +23,25 @@ export const onboardingVars = {
transformTransition: '0.3s ease',
},
web: {
bg: '#fafafa',
bg: '#F4F4F5',
windowShadow:
'1px 18px 39px 0px rgba(0, 0, 0, 0.15), 5px 71px 71px 0px rgba(0, 0, 0, 0.09), 12px 160px 96px 0px rgba(0, 0, 0, 0.05), 20px 284px 114px 0px rgba(0, 0, 0, 0.01), 32px 443px 124px 0px rgba(0, 0, 0, 0.00)',
},
article: {
w: '1200px',
h: '800px',
r: '8px',
},
edgeless: {
w: '1200px',
h: '800px',
r: '8px',
},
wellDone: {
w: '800px',
h: '600px',
r: '12px',
},
canvas: {
@ -62,6 +71,11 @@ export const perspective = style({
transformStyle: 'preserve-3d',
});
export const fadeIn = keyframes({
from: { opacity: 0 },
to: { opacity: 1 },
});
export const onboarding = style([
perspective,
{
@ -80,9 +94,17 @@ export const onboarding = style([
inset: 0,
background: onboardingVars.web.bg,
transform: 'translateZ(-1000px) scale(2)',
transition: 'opacity 0.3s ease',
},
'&[data-is-desktop="true"]::after': {
content: 'unset',
animation: `${fadeIn} 0.8s linear`,
// content: 'unset',
background:
// 'linear-gradient(180deg, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0) 99.58%)',
'linear-gradient(180deg, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0) 80%)',
},
'&[data-is-window="true"][data-is-desktop="true"]::after': {
opacity: 0,
},
},
},

View File

@ -47,7 +47,7 @@ export const OnboardingBlock = ({
const blockStyles = {
...baseStyles,
...style,
...customStyle?.[mode],
...customStyle?.[mode === 'page' ? 'page' : 'edgeless'],
} as CSSProperties;
return (
@ -58,7 +58,7 @@ export const OnboardingBlock = ({
}}
className={onboardingBlock}
data-mode={mode}
data-bg-mode={bg && mode === 'edgeless'}
data-bg-mode={bg && mode !== 'page'}
data-invisible={mode === 'page' && edgelessOnly}
>
{children}

View File

@ -109,7 +109,7 @@ export const onboardingBlock = style([
'&:last-child': {
marginBottom: 0,
},
'&[data-mode="edgeless"]': {
'&[data-mode="edgeless"], &[data-mode="well-done"]': {
transition: `all ${onboardingVars.block.transition} var(--enter-delay)`,
},
'&[data-mode="page"]': {

View File

@ -3,7 +3,7 @@ import type { PropsWithChildren, ReactNode } from 'react';
export type OnboardingStep = 'enter' | 'unfold' | 'edgeless-switch';
export type ArticleId = '0' | '1' | '2' | '3' | '4';
export type EdgelessSwitchMode = 'edgeless' | 'page';
export type EdgelessSwitchMode = 'edgeless' | 'page' | 'well-done';
/**
* Paper enter animation options
@ -77,10 +77,7 @@ export interface OnboardingBlockOption extends PropsWithChildren {
style?: CSSProperties;
/** customize style for different mode */
customStyle?: {
page?: CSSProperties;
edgeless?: CSSProperties;
};
customStyle?: Partial<Record<EdgelessSwitchMode, CSSProperties>>;
/** attach a sub block to current block */
sub?: OnboardingBlockOption;