feat: add download tips banner (#2151)
This commit is contained in:
@@ -10,12 +10,15 @@ export type Guide = {
|
|||||||
changeLog: boolean;
|
changeLog: boolean;
|
||||||
// should show recording tips
|
// should show recording tips
|
||||||
onBoarding: boolean;
|
onBoarding: boolean;
|
||||||
|
// should show download client tips
|
||||||
|
downloadClientTip: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const guidePrimitiveAtom = atomWithStorage<Guide>('helper-guide', {
|
const guidePrimitiveAtom = atomWithStorage<Guide>('helper-guide', {
|
||||||
quickSearchTips: true,
|
quickSearchTips: true,
|
||||||
changeLog: true,
|
changeLog: true,
|
||||||
onBoarding: true,
|
onBoarding: true,
|
||||||
|
downloadClientTip: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const guideQuickSearchTipsAtom = atom<
|
export const guideQuickSearchTipsAtom = atom<
|
||||||
@@ -67,3 +70,18 @@ export const guideOnboardingAtom = atom<
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
export const guideDownloadClientTipAtom = atom<
|
||||||
|
Guide['downloadClientTip'],
|
||||||
|
[open: boolean],
|
||||||
|
void
|
||||||
|
>(
|
||||||
|
get => {
|
||||||
|
return get(guidePrimitiveAtom).downloadClientTip;
|
||||||
|
},
|
||||||
|
(_, set, open) => {
|
||||||
|
set(guidePrimitiveAtom, tips => ({
|
||||||
|
...tips,
|
||||||
|
downloadClientTip: open,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { DownloadTips } from '@affine/component/affine-banner';
|
||||||
|
import { getEnvironment } from '@affine/env';
|
||||||
|
import { useAtom } from 'jotai';
|
||||||
|
import { useCallback } from 'react';
|
||||||
|
|
||||||
|
import { guideDownloadClientTipAtom } from '../../../atoms/guide';
|
||||||
|
|
||||||
|
export const DownloadClientTip = () => {
|
||||||
|
const env = getEnvironment();
|
||||||
|
const [showDownloadClientTips, setShowDownloadClientTips] = useAtom(
|
||||||
|
guideDownloadClientTipAtom
|
||||||
|
);
|
||||||
|
const onCloseDownloadClient = useCallback(() => {
|
||||||
|
setShowDownloadClientTips(false);
|
||||||
|
}, [setShowDownloadClientTips]);
|
||||||
|
|
||||||
|
if (!showDownloadClientTips || env.isDesktop) {
|
||||||
|
return <></>;
|
||||||
|
}
|
||||||
|
return <DownloadTips onClose={onCloseDownloadClient} />;
|
||||||
|
};
|
||||||
|
export default DownloadClientTip;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
|
import { BrowserWarning } from '@affine/component/affine-banner';
|
||||||
import { appSidebarOpenAtom } from '@affine/component/app-sidebar';
|
import { appSidebarOpenAtom } from '@affine/component/app-sidebar';
|
||||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||||
import { WorkspaceFlavour } from '@affine/workspace/type';
|
import { WorkspaceFlavour } from '@affine/workspace/type';
|
||||||
import { CloseIcon } from '@blocksuite/icons';
|
|
||||||
import type { Page } from '@blocksuite/store';
|
import type { Page } from '@blocksuite/store';
|
||||||
import { useAtom } from 'jotai';
|
import { useAtom } from 'jotai';
|
||||||
import type { FC, HTMLAttributes, PropsWithChildren } from 'react';
|
import type { FC, HTMLAttributes, PropsWithChildren } from 'react';
|
||||||
@@ -14,8 +14,10 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
|
|
||||||
|
import { guideDownloadClientTipAtom } from '../../../atoms/guide';
|
||||||
import { useCurrentMode } from '../../../hooks/current/use-current-mode';
|
import { useCurrentMode } from '../../../hooks/current/use-current-mode';
|
||||||
import type { AffineOfficialWorkspace } from '../../../shared';
|
import type { AffineOfficialWorkspace } from '../../../shared';
|
||||||
|
import { DownloadClientTip } from './download-tips';
|
||||||
import EditPage from './header-right-items/edit-page';
|
import EditPage from './header-right-items/edit-page';
|
||||||
import { EditorOptionMenu } from './header-right-items/editor-option-menu';
|
import { EditorOptionMenu } from './header-right-items/editor-option-menu';
|
||||||
import { HeaderShareMenu } from './header-right-items/share-menu';
|
import { HeaderShareMenu } from './header-right-items/share-menu';
|
||||||
@@ -23,8 +25,6 @@ import SyncUser from './header-right-items/sync-user';
|
|||||||
import TrashButtonGroup from './header-right-items/trash-button-group';
|
import TrashButtonGroup from './header-right-items/trash-button-group';
|
||||||
import UserAvatar from './header-right-items/user-avatar';
|
import UserAvatar from './header-right-items/user-avatar';
|
||||||
import {
|
import {
|
||||||
StyledBrowserWarning,
|
|
||||||
StyledCloseButton,
|
|
||||||
StyledHeader,
|
StyledHeader,
|
||||||
StyledHeaderContainer,
|
StyledHeaderContainer,
|
||||||
StyledHeaderRightSide,
|
StyledHeaderRightSide,
|
||||||
@@ -37,23 +37,6 @@ const SidebarSwitch = lazy(() =>
|
|||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
const BrowserWarning = ({
|
|
||||||
show,
|
|
||||||
onClose,
|
|
||||||
}: {
|
|
||||||
show: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}) => {
|
|
||||||
return (
|
|
||||||
<StyledBrowserWarning show={show}>
|
|
||||||
<OSWarningMessage />
|
|
||||||
<StyledCloseButton onClick={onClose}>
|
|
||||||
<CloseIcon />
|
|
||||||
</StyledCloseButton>
|
|
||||||
</StyledBrowserWarning>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BaseHeaderProps<
|
export type BaseHeaderProps<
|
||||||
Workspace extends AffineOfficialWorkspace = AffineOfficialWorkspace
|
Workspace extends AffineOfficialWorkspace = AffineOfficialWorkspace
|
||||||
> = {
|
> = {
|
||||||
@@ -130,9 +113,15 @@ export const Header = forwardRef<
|
|||||||
PropsWithChildren<HeaderProps> & HTMLAttributes<HTMLDivElement>
|
PropsWithChildren<HeaderProps> & HTMLAttributes<HTMLDivElement>
|
||||||
>((props, ref) => {
|
>((props, ref) => {
|
||||||
const [showWarning, setShowWarning] = useState(false);
|
const [showWarning, setShowWarning] = useState(false);
|
||||||
|
const [showGuideDownloadClientTip, setShowGuideDownloadClientTip] =
|
||||||
|
useState(false);
|
||||||
|
const [shouldShowGuideDownloadClientTip] = useAtom(
|
||||||
|
guideDownloadClientTipAtom
|
||||||
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setShowWarning(shouldShowWarning());
|
setShowWarning(shouldShowWarning());
|
||||||
}, []);
|
setShowGuideDownloadClientTip(shouldShowGuideDownloadClientTip);
|
||||||
|
}, [shouldShowGuideDownloadClientTip]);
|
||||||
const [open] = useAtom(appSidebarOpenAtom);
|
const [open] = useAtom(appSidebarOpenAtom);
|
||||||
const t = useAFFiNEI18N();
|
const t = useAFFiNEI18N();
|
||||||
|
|
||||||
@@ -144,12 +133,18 @@ export const Header = forwardRef<
|
|||||||
data-open={open}
|
data-open={open}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<BrowserWarning
|
{showGuideDownloadClientTip ? (
|
||||||
show={showWarning}
|
<DownloadClientTip />
|
||||||
onClose={() => {
|
) : (
|
||||||
setShowWarning(false);
|
<BrowserWarning
|
||||||
}}
|
show={showWarning}
|
||||||
/>
|
message={<OSWarningMessage />}
|
||||||
|
onClose={() => {
|
||||||
|
setShowWarning(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<StyledHeader
|
<StyledHeader
|
||||||
hasWarning={showWarning}
|
hasWarning={showWarning}
|
||||||
data-testid="editor-header-items"
|
data-testid="editor-header-items"
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { CloseIcon } from '@blocksuite/icons';
|
||||||
|
import type React from 'react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
browserWarningStyle,
|
||||||
|
closeButtonStyle,
|
||||||
|
closeIconStyle,
|
||||||
|
} from './index.css';
|
||||||
|
|
||||||
|
export const BrowserWarning = ({
|
||||||
|
show,
|
||||||
|
onClose,
|
||||||
|
message,
|
||||||
|
}: {
|
||||||
|
show: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
message: React.ReactNode;
|
||||||
|
}) => {
|
||||||
|
if (!show) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className={browserWarningStyle}>
|
||||||
|
{message}
|
||||||
|
<div className={closeButtonStyle} onClick={onClose}>
|
||||||
|
<CloseIcon className={closeIconStyle} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BrowserWarning;
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { AffineLogoSimSBlue1_1Icon, CloseIcon } from '@blocksuite/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
downloadCloseButtonStyle,
|
||||||
|
downloadMessageStyle,
|
||||||
|
downloadTipContainerStyle,
|
||||||
|
downloadTipIconStyle,
|
||||||
|
downloadTipStyle,
|
||||||
|
linkStyle,
|
||||||
|
} from './index.css';
|
||||||
|
|
||||||
|
export const DownloadTips = ({ onClose }: { onClose: () => void }) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={downloadTipContainerStyle}
|
||||||
|
data-testid="download-client-tip"
|
||||||
|
>
|
||||||
|
<div className={downloadTipStyle}>
|
||||||
|
<AffineLogoSimSBlue1_1Icon className={downloadTipIconStyle} />
|
||||||
|
<div className={downloadMessageStyle}>
|
||||||
|
Enjoying the demo?
|
||||||
|
<a
|
||||||
|
className={linkStyle}
|
||||||
|
href="https://github.com/toeverything/AFFiNE/releases"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Download the AFFiNE Client
|
||||||
|
</a>
|
||||||
|
for the full experience.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={downloadCloseButtonStyle}
|
||||||
|
onClick={onClose}
|
||||||
|
data-testid="download-client-tip-close-button"
|
||||||
|
>
|
||||||
|
<CloseIcon className={downloadTipIconStyle} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DownloadTips;
|
||||||
87
packages/component/src/components/affine-banner/index.css.ts
Normal file
87
packages/component/src/components/affine-banner/index.css.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import { keyframes, style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
const slideDown = keyframes({
|
||||||
|
'0%': {
|
||||||
|
height: '0px',
|
||||||
|
},
|
||||||
|
'100%': {
|
||||||
|
height: '44px',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const browserWarningStyle = style({
|
||||||
|
backgroundColor: 'var(--affine-background-warning-color)',
|
||||||
|
color: 'var(--affine-warning-color)',
|
||||||
|
height: '36px',
|
||||||
|
fontSize: 'var(--affine-font-sm)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
});
|
||||||
|
export const closeButtonStyle = style({
|
||||||
|
width: '36px',
|
||||||
|
height: '36px',
|
||||||
|
color: 'var(--affine-icon-color)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'absolute',
|
||||||
|
right: '16px',
|
||||||
|
});
|
||||||
|
export const closeIconStyle = style({
|
||||||
|
width: '15px',
|
||||||
|
height: '15px',
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1,
|
||||||
|
});
|
||||||
|
export const downloadTipContainerStyle = style({
|
||||||
|
backgroundColor: 'var(--affine-primary-color)',
|
||||||
|
color: 'var(--affine-white)',
|
||||||
|
width: '100%',
|
||||||
|
height: '44px',
|
||||||
|
fontSize: 'var(--affine-font-base)',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'relative',
|
||||||
|
animation: `${slideDown} .3s ease-in-out forwards`,
|
||||||
|
});
|
||||||
|
export const downloadTipStyle = style({
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
});
|
||||||
|
export const downloadTipIconStyle = style({
|
||||||
|
color: 'var(--affine-white)',
|
||||||
|
width: '24px',
|
||||||
|
height: '24px',
|
||||||
|
fontSize: '24px',
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1,
|
||||||
|
});
|
||||||
|
export const downloadCloseButtonStyle = style({
|
||||||
|
color: 'var(--affine-white)',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
position: 'absolute',
|
||||||
|
right: '24px',
|
||||||
|
});
|
||||||
|
export const downloadMessageStyle = style({
|
||||||
|
color: 'var(--affine-white)',
|
||||||
|
marginLeft: '8px',
|
||||||
|
});
|
||||||
|
export const linkStyle = style({
|
||||||
|
color: 'var(--affine-white)',
|
||||||
|
textDecoration: 'underline',
|
||||||
|
':hover': {
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
|
':visited': {
|
||||||
|
color: 'var(--affine-white)',
|
||||||
|
textDecoration: 'underline',
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './browser-warning';
|
||||||
|
export * from './download-client';
|
||||||
37
packages/component/src/stories/affine-banner.stories.tsx
Normal file
37
packages/component/src/stories/affine-banner.stories.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import type { StoryFn } from '@storybook/react';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
import { BrowserWarning, DownloadTips } from '../components/affine-banner';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'AFFiNE/Banner',
|
||||||
|
component: BrowserWarning,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Default: StoryFn = () => {
|
||||||
|
const [closed, setIsClosed] = useState(true);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<BrowserWarning
|
||||||
|
message={<span>test</span>}
|
||||||
|
show={closed}
|
||||||
|
onClose={() => {
|
||||||
|
setIsClosed(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Download: StoryFn = () => {
|
||||||
|
const [, setIsClosed] = useState(true);
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<DownloadTips
|
||||||
|
onClose={() => {
|
||||||
|
setIsClosed(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -54,3 +54,21 @@ test('Click right-bottom corner change log icon', async ({ page }) => {
|
|||||||
);
|
);
|
||||||
expect(await normalRightBottomChangeLog.isVisible()).toEqual(true);
|
expect(await normalRightBottomChangeLog.isVisible()).toEqual(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Download client tip', async ({ page }) => {
|
||||||
|
await openHomePage(page);
|
||||||
|
const downloadClientTipItem = page.locator(
|
||||||
|
'[data-testid=download-client-tip]'
|
||||||
|
);
|
||||||
|
await expect(downloadClientTipItem).toBeVisible();
|
||||||
|
const closeButton = page.locator(
|
||||||
|
'[data-testid=download-client-tip-close-button]'
|
||||||
|
);
|
||||||
|
await closeButton.click();
|
||||||
|
await expect(downloadClientTipItem).not.toBeVisible();
|
||||||
|
await page.goto('http://localhost:8080');
|
||||||
|
const currentDownloadClientTipItem = page.locator(
|
||||||
|
'[data-testid=download-client-tip]'
|
||||||
|
);
|
||||||
|
await expect(currentDownloadClientTipItem).not.toBeVisible();
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user