diff --git a/packages/frontend/core/src/components/affine/onboarding/onboarding.tsx b/packages/frontend/core/src/components/affine/onboarding/onboarding.tsx index 8648116def..dfa83e3f02 100644 --- a/packages/frontend/core/src/components/affine/onboarding/onboarding.tsx +++ b/packages/frontend/core/src/components/affine/onboarding/onboarding.tsx @@ -1,43 +1,76 @@ -import type { CSSProperties } from 'react'; +import { type CSSProperties, useCallback, useState } from 'react'; import { articles } from './articles'; import { PaperSteps } from './paper-steps'; import * as styles from './style.css'; +import type { ArticleId, ArticleOption } from './types'; interface OnboardingProps { onOpenApp?: () => void; } export const Onboarding = (_: OnboardingProps) => { + const [status, setStatus] = useState<{ + activeId: ArticleId | null; + unfoldingId: ArticleId | null; + }>({ activeId: null, unfoldingId: null }); + + const onFoldChange = useCallback((id: ArticleId, v: boolean) => { + setStatus(s => { + return { + activeId: v ? null : s.activeId, + unfoldingId: v ? null : id, + }; + }); + }, []); + + const onFoldChanged = useCallback((id: ArticleId, v: boolean) => { + setStatus(s => { + return { + activeId: v ? null : id, + unfoldingId: s.unfoldingId, + }; + }); + }, []); + return (
- {Object.entries(articles).map(([id, article]) => { - const { enterOptions, location } = article; - const style = { - '--fromX': `${enterOptions.fromX}vw`, - '--fromY': `${enterOptions.fromY}vh`, - '--fromZ': `${enterOptions.fromZ}px`, - '--toZ': `${enterOptions.toZ}px`, - '--fromRotateX': `${enterOptions.fromRotateX}deg`, - '--fromRotateY': `${enterOptions.fromRotateY}deg`, - '--fromRotateZ': `${enterOptions.fromRotateZ}deg`, - '--toRotateZ': `${enterOptions.toRotateZ}deg`, + {(Object.entries(articles) as Array<[ArticleId, ArticleOption]>).map( + ([id, article]) => { + const { enterOptions, location } = article; + const style = { + zIndex: status.unfoldingId === id ? 1 : 0, - '--delay': `${enterOptions.delay}ms`, - '--duration': enterOptions.duration, - '--easing': enterOptions.easing, + '--fromX': `${enterOptions.fromX}vw`, + '--fromY': `${enterOptions.fromY}vh`, + '--fromZ': `${enterOptions.fromZ}px`, + '--toZ': `${enterOptions.toZ}px`, + '--fromRotateX': `${enterOptions.fromRotateX}deg`, + '--fromRotateY': `${enterOptions.fromRotateY}deg`, + '--fromRotateZ': `${enterOptions.fromRotateZ}deg`, + '--toRotateZ': `${enterOptions.toRotateZ}deg`, - '--offset-x': `${location.x || 0}px`, - '--offset-y': `${location.y || 0}px`, - } as CSSProperties; + '--delay': `${enterOptions.delay}ms`, + '--duration': enterOptions.duration, + '--easing': enterOptions.easing, - return ( -
- -
- ); - })} + '--offset-x': `${location.x || 0}px`, + '--offset-y': `${location.y || 0}px`, + } as CSSProperties; + + return ( +
+ +
+ ); + } + )}
); diff --git a/packages/frontend/core/src/components/affine/onboarding/paper-steps.tsx b/packages/frontend/core/src/components/affine/onboarding/paper-steps.tsx index c9dcabc56c..5432f14bbf 100644 --- a/packages/frontend/core/src/components/affine/onboarding/paper-steps.tsx +++ b/packages/frontend/core/src/components/affine/onboarding/paper-steps.tsx @@ -1,18 +1,51 @@ -import { useCallback } from 'react'; +import { useCallback, useState } from 'react'; import { AnimateIn } from './steps/animate-in'; -import type { ArticleOption } from './types'; +import { Unfolding } from './steps/unfolding'; +import type { ArticleId, OnboardingStep } from './types'; +import { type ArticleOption } from './types'; interface PaperStepsProps { show?: boolean; article: ArticleOption; + onFoldChange?: (id: ArticleId, v: boolean) => void; + onFoldChanged?: (id: ArticleId, v: boolean) => void; } -export const PaperSteps = ({ show, article }: PaperStepsProps) => { - const onFinished = useCallback(() => { - console.log('onFinished'); +export const PaperSteps = ({ + show, + article, + onFoldChange, + onFoldChanged, +}: PaperStepsProps) => { + const [stage, setStage] = useState('enter'); + + const onEntered = useCallback(() => { + setStage('unfold'); }, []); + const _onFoldChange = useCallback( + (v: boolean) => { + onFoldChange?.(article.id, v); + }, + [onFoldChange, article.id] + ); + + const _onFoldChanged = useCallback( + (v: boolean) => { + onFoldChanged?.(article.id, v); + }, + [onFoldChanged, article.id] + ); + if (!show) return null; - return ; + return stage === 'enter' ? ( + + ) : stage === 'unfold' ? ( + + ) : null; }; diff --git a/packages/frontend/core/src/components/affine/onboarding/steps/unfolding.css.ts b/packages/frontend/core/src/components/affine/onboarding/steps/unfolding.css.ts new file mode 100644 index 0000000000..3c4aa5e05b --- /dev/null +++ b/packages/frontend/core/src/components/affine/onboarding/steps/unfolding.css.ts @@ -0,0 +1,79 @@ +import { keyframes, style } from '@vanilla-extract/css'; + +import { onboardingVars, paperLocation } from '../style.css'; + +const unfolding = onboardingVars.unfolding; + +const shadowIn = keyframes({ + from: { boxShadow: `0px 0px 0px rgba(0, 0, 0, 0)` }, + to: { boxShadow: `0px 0px 4px rgba(66, 65, 73, 0.14)` }, +}); +const borderIn = keyframes({ + from: { opacity: 0 }, + to: { opacity: 1 }, +}); +const fadeOut = keyframes({ + from: { opacity: 1 }, + to: { opacity: 0 }, +}); + +export const unfoldingWrapper = style([ + paperLocation, + { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + + transform: 'rotate(var(--toRotateZ))', + cursor: 'pointer', + + backgroundColor: onboardingVars.paper.bg, + borderRadius: onboardingVars.paper.r, + width: onboardingVars.paper.w, + height: onboardingVars.paper.h, + + // animate in + boxShadow: `0px 0px 0px rgba(0, 0, 0, 0)`, + animation: `${shadowIn} 0.5s ease forwards`, + + transition: `all 0.23s ease, width ${unfolding.sizeTransition}, height ${unfolding.sizeTransition}, transform ${unfolding.transformTransition}`, + + '::before': { + // hack border + content: '""', + position: 'absolute', + inset: 0, + border: `1px solid ${onboardingVars.paper.borderColor}`, + borderRadius: 'inherit', + animation: `${borderIn} 0.5s ease forwards`, + pointerEvents: 'none', + }, + + selectors: { + '&[data-fold="false"]': { + vars: { + '--toRotateZ': '0deg', + }, + width: onboardingVars.article.w, + height: onboardingVars.article.h, + left: `calc(0 - ${onboardingVars.article.w} / 2)`, + top: `calc(0 - ${onboardingVars.article.h} / 2)`, + }, + }, + }, +]); + +export const unfoldingContent = style({ + width: onboardingVars.paper.w, + height: onboardingVars.paper.h, + + padding: '16px', + overflow: 'hidden', + fontFamily: 'var(--affine-font-family)', + + selectors: { + '&.leave': { + animation: `${fadeOut} 0.1s ease forwards`, + }, + }, +}); diff --git a/packages/frontend/core/src/components/affine/onboarding/steps/unfolding.tsx b/packages/frontend/core/src/components/affine/onboarding/steps/unfolding.tsx new file mode 100644 index 0000000000..f4465a6d14 --- /dev/null +++ b/packages/frontend/core/src/components/affine/onboarding/steps/unfolding.tsx @@ -0,0 +1,49 @@ +import clsx from 'clsx'; +import { useCallback, useRef, useState } from 'react'; + +import type { ArticleOption } from '../types'; +import * as styles from './unfolding.css'; + +interface UnfoldingProps { + onChange?: (e: boolean) => void; + onChanged?: (e: boolean) => void; + article: ArticleOption; +} + +export const Unfolding = ({ article, onChange, onChanged }: UnfoldingProps) => { + const [fold, setFold] = useState(true); + const ref = useRef(null); + + const toggleFold = useCallback(() => { + setFold(!fold); + return !fold; + }, [fold]); + + const onPaperClick = useCallback(() => { + const isFold = toggleFold(); + onChange?.(isFold); + + if (ref.current) { + const handler = () => { + onChanged?.(isFold); + }; + ref.current.addEventListener('transitionend', handler, { once: true }); + return () => ref.current?.removeEventListener('transitionend', handler); + } + + return null; + }, [toggleFold, onChange, onChanged]); + + return ( +
+
+ {article.brief} +
+
+ ); +}; diff --git a/packages/frontend/core/src/components/affine/onboarding/style.css.ts b/packages/frontend/core/src/components/affine/onboarding/style.css.ts index dfc0cfbfb8..3c4c010f2d 100644 --- a/packages/frontend/core/src/components/affine/onboarding/style.css.ts +++ b/packages/frontend/core/src/components/affine/onboarding/style.css.ts @@ -16,10 +16,24 @@ export const onboardingVars = { bg: 'var(--affine-pure-white)', // textColor: 'var(--affine-light-text-primary-color)', textColor: '#121212', + borderColor: '#E3E2E4', + }, + unfolding: { + sizeTransition: '0.3s ease', + transformTransition: '0.3s ease', }, web: { bg: '#fafafa', // TODO: use var }, + + article: { + w: '1200px', + h: '800px', + }, + edgeless: { + w: '1200px', + h: '800px', + }, }; export const perspective = style({