From 3ef8e2db83ea496e05c282bdec340f954493a6d8 Mon Sep 17 00:00:00 2001 From: JimmFly Date: Fri, 10 Mar 2023 14:17:26 +0800 Subject: [PATCH] feat: add novice guide for quick search arrow button (#1493) --- apps/web/src/atoms/first-load.ts | 5 ++ .../affine/sidebar-switch/index.tsx | 16 +++- .../components/blocksuite/header/index.tsx | 62 +++++++++++++-- .../header/quick-search-button/index.tsx | 1 - .../components/blocksuite/header/styles.ts | 21 +++++ .../pure/quick-search-modal/index.tsx | 4 +- .../pure/quick-search-modal/style.ts | 4 +- .../pure/workspace-slider-bar/index.tsx | 1 + apps/web/src/hooks/__tests__/index.spec.tsx | 19 +++++ .../web/src/hooks/affine/use-is-first-load.ts | 12 +++ apps/web/src/templates/Welcome-to-AFFiNE.md | 16 ++-- .../component/src/ui/popper/PopoverArrow.tsx | 19 +++-- .../src/ui/tooltip/QuickSearch-tips.tsx | 76 +++++++++++++++++++ packages/component/src/ui/tooltip/Tooltip.tsx | 1 - packages/component/src/ui/tooltip/index.tsx | 1 + tests/quick-search.spec.ts | 14 ++++ 16 files changed, 238 insertions(+), 34 deletions(-) create mode 100644 apps/web/src/atoms/first-load.ts create mode 100644 apps/web/src/hooks/affine/use-is-first-load.ts create mode 100644 packages/component/src/ui/tooltip/QuickSearch-tips.tsx diff --git a/apps/web/src/atoms/first-load.ts b/apps/web/src/atoms/first-load.ts new file mode 100644 index 0000000000..67282bd83c --- /dev/null +++ b/apps/web/src/atoms/first-load.ts @@ -0,0 +1,5 @@ +import { atom } from 'jotai'; +import { atomWithStorage } from 'jotai/utils'; + +export const isFirstLoadAtom = atomWithStorage('isFirstLoad', true); +export const openTipsAtom = atom(false); diff --git a/apps/web/src/components/affine/sidebar-switch/index.tsx b/apps/web/src/components/affine/sidebar-switch/index.tsx index 4cdc7bb487..2433081cc1 100644 --- a/apps/web/src/components/affine/sidebar-switch/index.tsx +++ b/apps/web/src/components/affine/sidebar-switch/index.tsx @@ -1,7 +1,11 @@ import { Tooltip } from '@affine/component'; import { useTranslation } from '@affine/i18n'; -import React, { useCallback, useState } from 'react'; +import { useCallback, useState } from 'react'; +import { + useIsFirstLoad, + useOpenTips, +} from '../../../hooks/affine/use-is-first-load'; import { useSidebarStatus } from '../../../hooks/affine/use-sidebar-status'; import { SidebarSwitchIcon } from './icons'; import { StyledSidebarSwitch } from './style'; @@ -17,6 +21,8 @@ export const SidebarSwitch = ({ }: SidebarSwitchProps) => { const [open, setOpen] = useSidebarStatus(); const [tooltipVisible, setTooltipVisible] = useState(false); + const [isFirstLoad, setIsFirstLoad] = useIsFirstLoad(); + const [, setOpenTips] = useOpenTips(); const { t } = useTranslation(); tooltipContent = tooltipContent || (open ? t('Collapse sidebar') : t('Expand sidebar')); @@ -34,7 +40,13 @@ export const SidebarSwitch = ({ onClick={useCallback(() => { setOpen(!open); setTooltipVisible(false); - }, [open, setOpen])} + if (isFirstLoad) { + setIsFirstLoad(false); + setTimeout(() => { + setOpenTips(true); + }, 200); + } + }, [isFirstLoad, open, setIsFirstLoad, setOpen, setOpenTips])} onMouseEnter={useCallback(() => { setTooltipVisible(true); }, [])} diff --git a/apps/web/src/components/blocksuite/header/index.tsx b/apps/web/src/components/blocksuite/header/index.tsx index ecdc0c87d2..ca6d7d3bd4 100644 --- a/apps/web/src/components/blocksuite/header/index.tsx +++ b/apps/web/src/components/blocksuite/header/index.tsx @@ -1,9 +1,12 @@ -import { Content } from '@affine/component'; +import { Content, QuickSearchTips } from '@affine/component'; +import { getEnvironment } from '@affine/env'; +import { ArrowDownSmallIcon } from '@blocksuite/icons'; import { assertExists } from '@blocksuite/store'; import { useSetAtom } from 'jotai'; import React from 'react'; import { openQuickSearchModalAtom } from '../../../atoms'; +import { useOpenTips } from '../../../hooks/affine/use-is-first-load'; import { usePageMeta } from '../../../hooks/use-page-meta'; import { BlockSuiteWorkspace } from '../../../shared'; import { PageNotFoundError } from '../../affine/affine-error-eoundary'; @@ -11,6 +14,8 @@ import { EditorModeSwitch } from './editor-mode-switch'; import Header from './header'; import { QuickSearchButton } from './quick-search-button'; import { + StyledQuickSearchTipButton, + StyledQuickSearchTipContent, StyledSearchArrowWrapper, StyledSwitchWrapper, StyledTitle, @@ -43,6 +48,39 @@ export const BlockSuiteEditorHeader: React.FC = ({ assertExists(pageMeta); const title = pageMeta.title; const { trash: isTrash } = pageMeta; + const [openTips, setOpenTips] = useOpenTips(); + const isMac = () => { + const env = getEnvironment(); + return env.isBrowser && env.isMacOs; + }; + const tipsContent = () => { + return ( + +
+ Click button + { + + + + } + or use + {isMac() ? ' ⌘ + K' : ' Ctrl + K'} to activate Quick Search. Then you + can search keywords or quickly open recently viewed pages. +
+ setOpenTips(false)} + > + Got it + +
+ ); + }; return (
= ({ /> {title || 'Untitled'} - - { - setOpenQuickSearch(true); - }} - /> - + + + { + setOpenQuickSearch(true); + }} + /> + + )} diff --git a/apps/web/src/components/blocksuite/header/quick-search-button/index.tsx b/apps/web/src/components/blocksuite/header/quick-search-button/index.tsx index bad22d00af..7e26cf42ae 100644 --- a/apps/web/src/components/blocksuite/header/quick-search-button/index.tsx +++ b/apps/web/src/components/blocksuite/header/quick-search-button/index.tsx @@ -1,7 +1,6 @@ import { IconButton, IconButtonProps } from '@affine/component'; import { styled } from '@affine/component'; import { ArrowDownSmallIcon } from '@blocksuite/icons'; -import React from 'react'; const StyledIconButtonWithAnimate = styled(IconButton)(({ theme }) => { return { diff --git a/apps/web/src/components/blocksuite/header/styles.ts b/apps/web/src/components/blocksuite/header/styles.ts index 110d6bad5f..631a8a51da 100644 --- a/apps/web/src/components/blocksuite/header/styles.ts +++ b/apps/web/src/components/blocksuite/header/styles.ts @@ -113,3 +113,24 @@ export const StyledPageListTittleWrapper = styled(StyledTitle)(({ theme }) => { }, }; }); +export const StyledQuickSearchTipButton = styled('div')(({ theme }) => { + return { + ...displayFlex('center', 'center'), + marginTop: '12px', + color: '#FFFFFF', + width: '60px', + height: ' 26px', + fontSize: theme.font.sm, + lineHeight: '22px', + background: theme.colors.primaryColor, + borderRadius: '8px', + textAlign: 'center', + cursor: 'pointer', + }; +}); +export const StyledQuickSearchTipContent = styled('div')(({ theme }) => { + return { + ...displayFlex('center', 'flex-end'), + flexDirection: 'column', + }; +}); diff --git a/apps/web/src/components/pure/quick-search-modal/index.tsx b/apps/web/src/components/pure/quick-search-modal/index.tsx index 381b736f70..75499df27c 100644 --- a/apps/web/src/components/pure/quick-search-modal/index.tsx +++ b/apps/web/src/components/pure/quick-search-modal/index.tsx @@ -92,8 +92,8 @@ export const QuickSearchModal: React.FC = ({ width={620} style={{ maxHeight: '80vh', - minHeight: isPublicAndNoQuery() ? '72px' : '350px', - top: '12vh', + minHeight: isPublicAndNoQuery() ? '72px' : '412px', + top: '80px', }} > { return { - minHeight: '220px', - maxHeight: '55vh', + minHeight: '280px', + maxHeight: '70vh', width: '100%', overflow: 'auto', marginBottom: '10px', diff --git a/apps/web/src/components/pure/workspace-slider-bar/index.tsx b/apps/web/src/components/pure/workspace-slider-bar/index.tsx index da8e12c6d8..a968594a66 100644 --- a/apps/web/src/components/pure/workspace-slider-bar/index.tsx +++ b/apps/web/src/components/pure/workspace-slider-bar/index.tsx @@ -124,6 +124,7 @@ export const WorkSpaceSliderBar: React.FC = ({ testid="sliderBar-arrowButton-collapse" /> + { ]); }); }); +describe('useIsFirstLoad', () => { + test('basic', async () => { + const firstLoad = renderHook(() => useIsFirstLoad()); + const setFirstLoad = firstLoad.result.current[1]; + expect(firstLoad.result.current[0]).toEqual(true); + setFirstLoad(false); + firstLoad.rerender(); + expect(firstLoad.result.current[0]).toEqual(false); + }); + test('useOpenTips', async () => { + const openTips = renderHook(() => useOpenTips()); + const setOpenTips = openTips.result.current[1]; + expect(openTips.result.current[0]).toEqual(false); + setOpenTips(true); + openTips.rerender(); + expect(openTips.result.current[0]).toEqual(true); + }); +}); diff --git a/apps/web/src/hooks/affine/use-is-first-load.ts b/apps/web/src/hooks/affine/use-is-first-load.ts new file mode 100644 index 0000000000..3af0a9c198 --- /dev/null +++ b/apps/web/src/hooks/affine/use-is-first-load.ts @@ -0,0 +1,12 @@ +import { useAtom } from 'jotai'; + +import { isFirstLoadAtom, openTipsAtom } from '../../atoms/first-load'; + +export function useIsFirstLoad() { + const [isFirstLoad, setIsFirstLoad] = useAtom(isFirstLoadAtom); + return [isFirstLoad, setIsFirstLoad] as const; +} +export function useOpenTips() { + const [openTips, setOpenTips] = useAtom(openTipsAtom); + return [openTips, setOpenTips] as const; +} diff --git a/apps/web/src/templates/Welcome-to-AFFiNE.md b/apps/web/src/templates/Welcome-to-AFFiNE.md index 6845e1ff08..278e6fa826 100644 --- a/apps/web/src/templates/Welcome-to-AFFiNE.md +++ b/apps/web/src/templates/Welcome-to-AFFiNE.md @@ -1,12 +1,10 @@ # Welcome to AFFiNE -👋 Quick Start -============== +# 👋 Quick Start > Your content should be as feature-rich as you like. We offer you an easy and simple approach to writing your content, with advanced tools that stay out of the way when you don't need them. -AFFiNE - not just a note taking app ------------------------------------ +## AFFiNE - not just a note taking app There are lots of apps out there, so why choose AFFiNE? @@ -18,18 +16,16 @@ AFFiNE is **privacy focused** - with a local-first approach, keep control of you AFFiNE is **open source** - you can check us out on [GitHub: toeverything/AFFiNE](https://github.com/toeverything/affine) -Let's get started! ------------------- +## Let's get started! -* Create a new page and begin editing - in `Paper` or `Edgeless` +- Create a new page and begin editing - in `Paper` or `Edgeless` ![](https://cdn.affine.pro/38928b822e78a8cf6cc1278c3cd7563967dfefcf744c2517a60c7bfe.gif) -* Head over to `Workspace Settings` to find your `General` settings, enable multiplayer with `Collaboration`, share your docs with `Publish` and download your data with `Export` +- Head over to `Workspace Settings` to find your `General` settings, enable multiplayer with `Collaboration`, share your docs with `Publish` and download your data with `Export` ![](https://cdn.affine.pro/34043644b47f7bab79255a80e1b11d6cb173ec9d2eb0e3208c7badd2.png) -Looking for more support? -------------- +## Looking for more support? Why not come and join the awesome [AFFiNE Community](https://community.affine.pro)? Whether you want to share new ideas or interact with other like minded individuals - we look forward to having you. diff --git a/packages/component/src/ui/popper/PopoverArrow.tsx b/packages/component/src/ui/popper/PopoverArrow.tsx index 1f49d5da52..e112a0f9ac 100644 --- a/packages/component/src/ui/popper/PopoverArrow.tsx +++ b/packages/component/src/ui/popper/PopoverArrow.tsx @@ -1,4 +1,4 @@ -import { forwardRef } from 'react'; +import { CSSProperties, forwardRef } from 'react'; import { styled } from '../../styles'; import { PopperArrowProps } from './interface'; @@ -9,7 +9,10 @@ export const PopperArrow = forwardRef( } ); -const getArrowStyle = (placement: PopperArrowProps['placement']) => { +const getArrowStyle = ( + placement: PopperArrowProps['placement'], + backgroundColor: CSSProperties['backgroundColor'] +) => { if (placement.indexOf('bottom') === 0) { return { top: 0, @@ -19,7 +22,7 @@ const getArrowStyle = (placement: PopperArrowProps['placement']) => { height: '1em', '&::before': { borderWidth: '0 1em 1em 1em', - borderColor: `transparent transparent #98ACBD transparent`, + borderColor: `transparent transparent ${backgroundColor} transparent`, }, }; } @@ -33,7 +36,7 @@ const getArrowStyle = (placement: PopperArrowProps['placement']) => { height: '1em', '&::before': { borderWidth: '1em 1em 0 1em', - borderColor: `#98ACBD transparent transparent transparent`, + borderColor: `${backgroundColor} transparent transparent transparent`, }, }; } @@ -45,7 +48,7 @@ const getArrowStyle = (placement: PopperArrowProps['placement']) => { width: '1em', '&::before': { borderWidth: '1em 0 1em 1em', - borderColor: `transparent transparent transparent #98ACBD`, + borderColor: `transparent transparent transparent ${backgroundColor}`, }, }; } @@ -57,7 +60,7 @@ const getArrowStyle = (placement: PopperArrowProps['placement']) => { width: '1em', '&::before': { borderWidth: '1em 1em 1em 0', - borderColor: `transparent #98ACBD transparent transparent`, + borderColor: `transparent ${backgroundColor} transparent transparent`, }, }; } @@ -69,7 +72,7 @@ const getArrowStyle = (placement: PopperArrowProps['placement']) => { const StyledArrow = styled('span')<{ placement: PopperArrowProps['placement']; -}>(({ placement }) => { +}>(({ placement, theme }) => { return { position: 'absolute', fontSize: '7px', @@ -88,6 +91,6 @@ const StyledArrow = styled('span')<{ bottom: 0, }, - ...getArrowStyle(placement), + ...getArrowStyle(placement, theme.colors.tooltipBackground), }; }); diff --git a/packages/component/src/ui/tooltip/QuickSearch-tips.tsx b/packages/component/src/ui/tooltip/QuickSearch-tips.tsx new file mode 100644 index 0000000000..e40e260fdd --- /dev/null +++ b/packages/component/src/ui/tooltip/QuickSearch-tips.tsx @@ -0,0 +1,76 @@ +import type { TooltipProps } from '@mui/material'; + +import { css, displayFlex, styled } from '../../styles'; +import { Popper, type PopperProps } from '../popper'; +import StyledPopperContainer from '../shared/Container'; +const StyledTooltip = styled(StyledPopperContainer)(({ theme }) => { + return { + width: '390px', + minHeight: '92px', + boxShadow: theme.shadow.tooltip, + padding: '12px', + backgroundColor: theme.colors.hoverBackground, + transform: 'all 0.15s', + color: theme.colors.primaryColor, + ...displayFlex('center', 'center'), + border: `1px solid ${theme.colors.primaryColor}`, + fontSize: theme.font.sm, + lineHeight: '22px', + fontWeight: 500, + }; +}); + +const StyledCircleContainer = styled('div')(({ theme }) => { + return css` + position: relative; + content: ''; + top: 50%; + left: 50%; + transform: translate(0%, 0%); + width: 0; + height: 40px; + border-right: 1px solid ${theme.colors.primaryColor}; + &::after { + content: ''; + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, -100%); + width: 12px; + height: 12px; + border-radius: 50%; + border: 1px solid ${theme.colors.primaryColor}; + } + &::before { + content: ''; + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%, -150%); + width: 6px; + height: 6px; + border-radius: 50%; + background-color: ${theme.colors.primaryColor}; + border: 1px solid ${theme.colors.primaryColor}; + } + `; +}); + +export const QuickSearchTips = ( + props: PopperProps & Omit +) => { + const { content, placement = 'top', children } = props; + return ( + + + {content} + + } + > + {children} + + ); +}; diff --git a/packages/component/src/ui/tooltip/Tooltip.tsx b/packages/component/src/ui/tooltip/Tooltip.tsx index fe626433ff..b9a4e0c9f5 100644 --- a/packages/component/src/ui/tooltip/Tooltip.tsx +++ b/packages/component/src/ui/tooltip/Tooltip.tsx @@ -19,7 +19,6 @@ export const Tooltip = (props: PopperProps & Omit) => { return ( {content}} > {children} diff --git a/packages/component/src/ui/tooltip/index.tsx b/packages/component/src/ui/tooltip/index.tsx index 7594a8f06c..8c2b3834ea 100644 --- a/packages/component/src/ui/tooltip/index.tsx +++ b/packages/component/src/ui/tooltip/index.tsx @@ -1 +1,2 @@ +export * from './QuickSearch-tips'; export * from './Tooltip'; diff --git a/tests/quick-search.spec.ts b/tests/quick-search.spec.ts index 4375cafcf7..3b4fee50ae 100644 --- a/tests/quick-search.spec.ts +++ b/tests/quick-search.spec.ts @@ -150,3 +150,17 @@ test.describe('Focus event for quick search', () => { await titleIsFocused(page); }); }); +test.describe('Novice guidance for quick search', () => { + test('When opening the website for the first time, the first folding sidebar will appear novice guide', async ({ + page, + }) => { + const quickSearchTips = page.locator('[data-testid=quick-search-tips]'); + await expect(quickSearchTips).not.toBeVisible(); + await page.getByTestId('sliderBar-arrowButton-collapse').click(); + const sliderBarArea = page.getByTestId('sliderBar'); + await expect(sliderBarArea).not.toBeVisible(); + await expect(quickSearchTips).toBeVisible(); + await page.locator('[data-testid=quick-search-got-it]').click(); + await expect(quickSearchTips).not.toBeVisible(); + }); +});