diff --git a/packages/app/src/components/header/header.tsx b/packages/app/src/components/header/header.tsx index 12eb65b40b..f4ecf3671d 100644 --- a/packages/app/src/components/header/header.tsx +++ b/packages/app/src/components/header/header.tsx @@ -16,13 +16,16 @@ import { } from '@blocksuite/icons'; import { useEditor } from '@/providers/editor-provider'; import ThemeModeSwitch from '@/components/theme-mode-switch'; -import { IconButton } from '@/ui/button'; +import { IconButton, Button } from '@/ui/button'; import CloseIcon from '@mui/icons-material/Close'; import { getWarningMessage, shouldShowWarning } from './utils'; import { Menu, MenuItem } from '@/ui/menu'; +import { useRouter } from 'next/router'; +import { useConfirm } from '@/providers/confirm-provider'; const PopoverContent = () => { const { editor, mode, setMode } = useEditor(); + return ( <> ) => { - const [showWarning, setShowWarning] = useState(shouldShowWarning()); +const HeaderRight = () => { + const { pageList, toggleDeletePage, permanentlyDeletePage } = useEditor(); + const { confirm } = useConfirm(); + const router = useRouter(); + const currentPageMeta = pageList.find(p => p.id === router.query.pageId); + const isTrash = !!currentPageMeta?.trash; + if (isTrash) { + const { id } = currentPageMeta; + return ( + <> + + + + ); + } + return ( + <> + + } placement="bottom-end"> + + + + + + ); +}; + +export const Header = ({ children }: PropsWithChildren<{}>) => { + const { pageList } = useEditor(); + + const [showWarning, setShowWarning] = useState(shouldShowWarning()); + const router = useRouter(); + const currentPageMeta = pageList.find(p => p.id === router.query.pageId); + const isTrash = !!currentPageMeta?.trash; + console.log('isTrash', isTrash); return ( ) => { {children} - - } placement="bottom-end"> - - - - + diff --git a/packages/app/src/components/header/page-header.tsx b/packages/app/src/components/header/page-header.tsx index 4521dd122e..9de86010e1 100644 --- a/packages/app/src/components/header/page-header.tsx +++ b/packages/app/src/components/header/page-header.tsx @@ -1,14 +1,21 @@ import React, { useEffect, useState } from 'react'; -import { StyledTitle, StyledTitleWrapper } from './styles'; +import { + StyledSearchArrowWrapper, + StyledSwitchWrapper, + StyledTitle, + StyledTitleWrapper, +} from './styles'; +import { IconButton } from '@/ui/button'; +import { Content } from '@/ui/layout'; import { useEditor } from '@/providers/editor-provider'; import EditorModeSwitch from '@/components/editor-mode-switch'; import { MiddleIconArrowDownSmallIcon } from '@blocksuite/icons'; import { useModal } from '@/providers/global-modal-provider'; -import IconButton from '@/ui/button/icon-button'; + import Header from './header'; export const PageHeader = () => { - const [title, setTitle] = useState(''); + const [title, setTitle] = useState('Untitled'); const [isHover, setIsHover] = useState(false); const { editor } = useEditor(); @@ -24,35 +31,35 @@ export const PageHeader = () => { return (
- {title ? ( - { - setIsHover(true); - }} - onMouseLeave={() => { - setIsHover(false); - }} - > - - {title} - - { + setIsHover(true); + }} + onMouseLeave={() => { + setIsHover(false); + }} + > + + + + + {title} + + { triggerQuickSearchModal(true); }} - /> - - - ) : null} + > + + + + +
); }; diff --git a/packages/app/src/components/header/styles.ts b/packages/app/src/components/header/styles.ts index 399ac197a7..d6baee2926 100644 --- a/packages/app/src/components/header/styles.ts +++ b/packages/app/src/components/header/styles.ts @@ -40,10 +40,9 @@ export const StyledTitle = styled('div')(({ theme }) => ({ export const StyledTitleWrapper = styled('div')({ maxWidth: '720px', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', + height: '100%', position: 'relative', + ...displayFlex('center', 'center'), }); export const StyledHeaderRightSide = styled('div')({ @@ -89,3 +88,25 @@ export const StyledCloseButton = styled.div(({ theme }) => { }, }; }); + +export const StyledSwitchWrapper = styled.div(() => { + return { + position: 'absolute', + right: '100%', + top: 0, + bottom: 0, + margin: 'auto', + ...displayFlex('center', 'center'), + }; +}); + +export const StyledSearchArrowWrapper = styled.div(() => { + return { + position: 'absolute', + left: 'calc(100% + 4px)', + top: 0, + bottom: 0, + margin: 'auto', + ...displayFlex('center', 'center'), + }; +}); diff --git a/packages/app/src/components/page-list/empty.tsx b/packages/app/src/components/page-list/empty.tsx new file mode 100644 index 0000000000..f995f71fb7 --- /dev/null +++ b/packages/app/src/components/page-list/empty.tsx @@ -0,0 +1,12 @@ +import React from 'react'; + +export const Empty = () => { + return ( +
+

Tips: Click Add to Favourites/Trash and the page will appear here.

+

(Designer is grappling with designing)

+
+ ); +}; + +export default Empty; diff --git a/packages/app/src/components/page-list/index.tsx b/packages/app/src/components/page-list/index.tsx new file mode 100644 index 0000000000..f07aaba8fa --- /dev/null +++ b/packages/app/src/components/page-list/index.tsx @@ -0,0 +1,97 @@ +import { PageMeta, useEditor } from '@/providers/editor-provider'; +import { + MiddleFavouritedStatus2Icon, + MiddleFavouritesIcon, +} from '@blocksuite/icons'; +import { + StyledFavoriteButton, + StyledTableContainer, + StyledTableRow, + StyledTitleContent, + StyledTitleWrapper, +} from './styles'; +import { Table, TableBody, TableCell, TableHead, TableRow } from '@/ui/table'; +import { OperationCell, TrashOperationCell } from './operation-cell'; +import Empty from './empty'; +import Link from 'next/link'; +import React from 'react'; +const FavoriteTag = ({ pageMeta }: { pageMeta: PageMeta }) => { + const { toggleFavoritePage } = useEditor(); + + return ( + { + toggleFavoritePage(pageMeta.id); + }} + > + {pageMeta.favorite ? ( + + ) : ( + + )} + + ); +}; + +export const PageList = ({ + pageList, + showFavoriteTag = false, + isTrash = false, +}: { + pageList: PageMeta[]; + showFavoriteTag?: boolean; + isTrash?: boolean; +}) => { + if (pageList.length === 0) { + return ; + } + return ( + + + + + Documents + Created + + {isTrash ? 'Uploaded' : 'Moved to Trash'} + + + + + + {pageList.map((pageMeta, index) => { + return ( + + + + + + {pageMeta.title || pageMeta.id} + + + {showFavoriteTag && } + + + {pageMeta.createDate} + {pageMeta.createDate} + + {isTrash ? ( + + ) : ( + + )} + + + ); + })} + +
+
+ ); +}; + +export default PageList; diff --git a/packages/app/src/components/page-list/operation-cell.tsx b/packages/app/src/components/page-list/operation-cell.tsx new file mode 100644 index 0000000000..da06c1121c --- /dev/null +++ b/packages/app/src/components/page-list/operation-cell.tsx @@ -0,0 +1,96 @@ +import { PageMeta, useEditor } from '@/providers/editor-provider'; +import { useConfirm } from '@/providers/confirm-provider'; +import { Menu, MenuItem } from '@/ui/menu'; +import { Wrapper } from '@/ui/layout'; +import { IconButton } from '@/ui/button'; +import { + MoreVertical_24pxIcon, + RestoreIcon, + TrashDeleteforeverIcon, +} from '@blocksuite/icons'; +import React from 'react'; + +export const OperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => { + const { id, favorite } = pageMeta; + const { openPage, toggleFavoritePage, toggleDeletePage } = useEditor(); + const { confirm } = useConfirm(); + + const OperationMenu = ( + <> + { + toggleFavoritePage(id); + }} + > + {favorite ? 'Remove' : 'Add'} to favourites + + { + openPage(id); + }} + > + Open in new tab + + { + confirm({ + title: 'Delete', + content: + 'Deleted items will be moved to Trash Bin. Do you confirm?', + confirmText: 'Delete', + confirmType: 'danger', + }).then(confirm => { + confirm && toggleDeletePage(id); + }); + }} + > + Delete + + + ); + return ( + + + + + + + + ); +}; + +export const TrashOperationCell = ({ pageMeta }: { pageMeta: PageMeta }) => { + const { id } = pageMeta; + const { permanentlyDeletePage, toggleDeletePage } = useEditor(); + const { confirm } = useConfirm(); + + return ( + + { + toggleDeletePage(id); + }} + > + + + { + confirm({ + title: 'Permanently delete', + content: + "Once deleted, you can't undo this action. Do you confirm?", + confirmText: 'Delete', + confirmType: 'danger', + }).then(confirm => { + confirm && permanentlyDeletePage(id); + }); + }} + > + + + + ); +}; diff --git a/packages/app/src/components/page-list/styles.ts b/packages/app/src/components/page-list/styles.ts new file mode 100644 index 0000000000..46909847d5 --- /dev/null +++ b/packages/app/src/components/page-list/styles.ts @@ -0,0 +1,55 @@ +import { displayFlex, styled, textEllipsis } from '@/styles'; +import { TableRow } from '@/ui/table'; + +export const StyledTableContainer = styled.div(() => { + return { + height: 'calc(100vh - 60px)', + padding: '78px 72px', + overflowY: 'auto', + }; +}); +export const StyledTitleWrapper = styled.div(({ theme }) => { + return { + ...displayFlex('flex-start', 'center'), + a: { + color: 'inherit', + }, + 'a:visited': { + color: 'unset', + }, + 'a:hover': { + color: theme.colors.primaryColor, + }, + }; +}); +export const StyledTitleContent = styled.div(({ theme }) => { + return { + maxWidth: '90%', + marginRight: '18px', + ...textEllipsis(1), + }; +}); +export const StyledFavoriteButton = styled.button<{ favorite: boolean }>( + ({ theme, favorite }) => { + return { + width: '32px', + height: '32px', + justifyContent: 'center', + alignItems: 'center', + display: 'none', + color: favorite ? theme.colors.primaryColor : theme.colors.iconColor, + '&:hover': { + color: theme.colors.primaryColor, + }, + }; + } +); +export const StyledTableRow = styled(TableRow)(({ theme }) => { + return { + '&:hover': { + '.favorite-button': { + display: 'flex', + }, + }, + }; +}); diff --git a/packages/app/src/components/workspace-slider-bar/index.tsx b/packages/app/src/components/workspace-slider-bar/index.tsx index 4b266ce21c..ba42f697f7 100644 --- a/packages/app/src/components/workspace-slider-bar/index.tsx +++ b/packages/app/src/components/workspace-slider-bar/index.tsx @@ -8,14 +8,51 @@ import { StyledSubListItem, } from './style'; import { Arrow } from './icons'; +import Collapse from '@mui/material/Collapse'; +import { MiddleIconArrowDownSmallIcon } from '@blocksuite/icons'; import Link from 'next/link'; import { useEditor } from '@/providers/editor-provider'; import { useModal } from '@/providers/global-modal-provider'; + +import { IconButton } from '@/ui/button'; + +const FavoriteList = ({ showList }: { showList: boolean }) => { + const { pageList, openPage } = useEditor(); + const router = useRouter(); + + const favoriteList = pageList.filter(p => p.favorite); + return ( + + {favoriteList.map((pageMeta, index) => { + const active = router.query.pageId === pageMeta.id; + return ( + { + if (active) { + return; + } + openPage(pageMeta.id); + }} + > + {pageMeta.title || pageMeta.id} + + ); + })} + {favoriteList.length === 0 && ( + No item + )} + + ); +}; export const WorkSpaceSliderBar = () => { const { triggerQuickSearchModal } = useModal(); const [show, setShow] = useState(false); + const [showSubFavorite, setShowSubFavorite] = useState(false); const { createPage } = useEditor(); const router = useRouter(); + return ( <> @@ -38,18 +75,40 @@ export const WorkSpaceSliderBar = () => { > Back to Doc - - All pages + + + All pages + - Favourites - - document 1, this is a paper icondocument 1 - - document 2 - document 4 - Import - Bin + + + Favourites + + { + setShowSubFavorite(!showSubFavorite); + }} + > + + + + + Import + + + + Trash + + { createPage(); diff --git a/packages/app/src/components/workspace-slider-bar/style.ts b/packages/app/src/components/workspace-slider-bar/style.ts index 458c9a013a..a6fc9d072f 100644 --- a/packages/app/src/components/workspace-slider-bar/style.ts +++ b/packages/app/src/components/workspace-slider-bar/style.ts @@ -5,7 +5,7 @@ export const StyledSliderBar = styled.div<{ show: boolean }>( return { width: show ? '320px' : '0', height: '100vh', - background: '#FBFBFC', + background: theme.mode === 'dark' ? '#272727' : '#FBFBFC', boxShadow: theme.shadow.modal, transition: 'width .15s', position: 'relative', @@ -42,22 +42,25 @@ export const StyledArrowButton = styled.button<{ isShow: boolean }>( } ); -export const StyledListItem = styled.button(({ theme }) => { - return { - width: '296px', - height: '32px', - marginTop: '12px', - fontSize: theme.font.sm, - color: theme.colors.popoverColor, - padding: '0 12px', - borderRadius: '5px', - ...displayFlex('flex-start', 'center'), - ':hover': { - color: theme.colors.primaryColor, - backgroundColor: theme.colors.hoverBackground, - }, - }; -}); +export const StyledListItem = styled.button<{ active?: boolean }>( + ({ theme, active }) => { + return { + width: '296px', + height: '32px', + marginTop: '12px', + fontSize: theme.font.sm, + color: active ? theme.colors.primaryColor : theme.colors.popoverColor, + backgroundColor: active ? theme.colors.hoverBackground : 'unset', + paddingLeft: '12px', + borderRadius: '5px', + ...displayFlex('flex-start', 'center'), + ':hover': { + color: theme.colors.primaryColor, + backgroundColor: theme.colors.hoverBackground, + }, + }; + } +); export const StyledNewPageButton = styled(StyledListItem)(({ theme }) => { return { @@ -69,20 +72,32 @@ export const StyledNewPageButton = styled(StyledListItem)(({ theme }) => { }; }); -export const StyledSubListItem = styled.button(({ theme }) => { +export const StyledSubListItem = styled.button<{ + disable?: boolean; + active?: boolean; +}>(({ theme, disable, active }) => { return { width: '296px', height: '32px', marginTop: '4px', fontSize: theme.font.sm, - color: theme.colors.popoverColor, + color: disable + ? theme.colors.iconColor + : active + ? theme.colors.primaryColor + : theme.colors.popoverColor, + backgroundColor: active ? theme.colors.hoverBackground : 'unset', + + cursor: disable ? 'not-allowed' : 'pointer', paddingLeft: '45px', lineHeight: '32px', textAlign: 'start', ...textEllipsis(1), - ':hover': { - color: theme.colors.primaryColor, - backgroundColor: theme.colors.hoverBackground, - }, + ':hover': disable + ? {} + : { + color: theme.colors.primaryColor, + backgroundColor: theme.colors.hoverBackground, + }, }; }); diff --git a/packages/app/src/pages/affine.tsx b/packages/app/src/pages/affine.tsx index a0c3ee1cd1..1fd7340538 100644 --- a/packages/app/src/pages/affine.tsx +++ b/packages/app/src/pages/affine.tsx @@ -3,6 +3,11 @@ import { ThemeModeSwitch } from '@/components/theme-mode-switch'; import { Loading } from '@/components/loading'; import Modal from '@/ui/modal'; import { useState } from 'react'; +import { Button } from '@/ui/button'; +import { + MiddleFavouritedStatus2Icon, + MiddleFavouritesIcon, +} from '@blocksuite/icons'; export const StyledHeader = styled('div')({ height: '60px', width: '100vw', @@ -35,6 +40,20 @@ const Affine = () => {
hi
+ + + + + + + + ); }; diff --git a/packages/app/src/pages/all-page.tsx b/packages/app/src/pages/all-page.tsx deleted file mode 100644 index 2cef69aa69..0000000000 --- a/packages/app/src/pages/all-page.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import React from 'react'; -import { Header } from '@/components/header'; -import { displayFlex, styled, textEllipsis } from '@/styles'; -import { Table, TableCell, TableHead, TableRow, TableBody } from '../ui/table'; -import { useConfirm } from '@/providers/confirm-provider'; -import { IconButton } from '@/ui/button'; -import { MoreVertical_24pxIcon } from '@blocksuite/icons'; -import { Menu, MenuItem } from '@/ui/menu'; -import { PageMeta, useEditor } from '@/providers/editor-provider'; -import { Wrapper } from '@/ui/layout'; - -import { - MiddleFavouritesIcon, - MiddleFavouritedStatus2Icon, -} from '@blocksuite/icons'; - -const StyledTableContainer = styled.div(() => { - return { - height: 'calc(100vh - 60px)', - padding: '78px 72px', - overflowY: 'auto', - }; -}); -const StyledTitleWrapper = styled.div(({ theme }) => { - return { - ...displayFlex('flex-start', 'center'), - 'favorite-heart': {}, - }; -}); -const StyledTitleContent = styled.div(({ theme }) => { - return { - maxWidth: '90%', - marginRight: '18px', - ...textEllipsis(1), - }; -}); -const StyledFavoriteButton = styled.button<{ favorite: boolean }>( - ({ theme, favorite }) => { - return { - width: '32px', - height: '32px', - justifyContent: 'center', - alignItems: 'center', - display: 'none', - color: favorite ? theme.colors.primaryColor : theme.colors.iconColor, - '&:hover': { - color: theme.colors.primaryColor, - }, - }; - } -); -const StyledTableRow = styled(TableRow)(({ theme }) => { - return { - '&:hover': { - '.favorite-button': { - display: 'flex', - }, - }, - }; -}); - -const OperationArea = ({ pageMeta }: { pageMeta: PageMeta }) => { - const { id, favorite } = pageMeta; - const { openPage, toggleFavoritePage, toggleDeletePage } = useEditor(); - - const OperationMenu = ( - <> - { - toggleFavoritePage(id); - }} - > - {favorite ? 'Remove' : 'Add'} to favourites - - { - openPage(id); - }} - > - Open in new tab - - { - toggleDeletePage(id); - }} - > - Delete - - - ); - return ( - - - - - - - - ); -}; - -export const AllPage = () => { - const { confirm } = useConfirm(); - const { pageList, toggleFavoritePage } = useEditor(); - return ( - <> -
- - - - - - Documents - Created - Uploaded - - - - - {pageList.map((pageMeta, index) => { - return ( - - - - - {pageMeta.title || pageMeta.id} - - { - toggleFavoritePage(pageMeta.id); - }} - > - {pageMeta.favorite ? ( - - ) : ( - - )} - - - - {pageMeta.createDate} - {pageMeta.createDate} - - - - - ); - })} - -
-
- - ); -}; - -export default AllPage; diff --git a/packages/app/src/pages/page-list/all.tsx b/packages/app/src/pages/page-list/all.tsx new file mode 100644 index 0000000000..fa80c8b56b --- /dev/null +++ b/packages/app/src/pages/page-list/all.tsx @@ -0,0 +1,18 @@ +import { Header } from '@/components/header'; +import { useEditor } from '@/providers/editor-provider'; +import { PageList } from '@/components/page-list'; + +export const All = () => { + const { pageList: allPageList } = useEditor(); + return ( + <> +
+ !p.trash)} + showFavoriteTag={true} + /> + + ); +}; + +export default All; diff --git a/packages/app/src/pages/page-list/favorite.tsx b/packages/app/src/pages/page-list/favorite.tsx new file mode 100644 index 0000000000..5d5c0c542d --- /dev/null +++ b/packages/app/src/pages/page-list/favorite.tsx @@ -0,0 +1,15 @@ +import { Header } from '@/components/header'; +import { useEditor } from '@/providers/editor-provider'; +import { PageList } from '@/components/page-list'; + +export const Favorite = () => { + const { pageList: allPageList } = useEditor(); + return ( + <> +
+ p.favorite && !p.trash)} /> + + ); +}; + +export default Favorite; diff --git a/packages/app/src/pages/page-list/trash.tsx b/packages/app/src/pages/page-list/trash.tsx new file mode 100644 index 0000000000..4a139e1c76 --- /dev/null +++ b/packages/app/src/pages/page-list/trash.tsx @@ -0,0 +1,15 @@ +import { Header } from '@/components/header'; +import { useEditor } from '@/providers/editor-provider'; +import { PageList } from '@/components/page-list'; + +export const Trash = () => { + const { pageList: allPageList } = useEditor(); + return ( + <> +
+ p.trash)} isTrash={true} /> + + ); +}; + +export default Trash; diff --git a/packages/app/src/providers/confirm-provider.tsx b/packages/app/src/providers/confirm-provider.tsx index 4b7b374d42..6f8a2049c3 100644 --- a/packages/app/src/providers/confirm-provider.tsx +++ b/packages/app/src/providers/confirm-provider.tsx @@ -3,12 +3,12 @@ import type { PropsWithChildren } from 'react'; import { Confirm, ConfirmProps } from '@/ui/confirm'; type ConfirmContextValue = { - confirm: (props: ConfirmProps) => void; + confirm: (props: ConfirmProps) => Promise; }; type ConfirmContextProps = PropsWithChildren<{}>; export const ConfirmContext = createContext({ - confirm: () => {}, + confirm: () => Promise.resolve(false), }); export const useConfirm = () => useContext(ConfirmContext); @@ -22,21 +22,33 @@ export const ConfirmProvider = ({ return ( { - const confirmId = String(Date.now()); - setConfirmRecord(oldConfirmRecord => { - return { - ...oldConfirmRecord, - [confirmId]: ( - { - delete confirmRecord[confirmId]; - setConfirmRecord({ ...confirmRecord }); - }} - /> - ), + confirm: ({ onClose, onCancel, onConfirm, ...props }: ConfirmProps) => { + return new Promise((resolve, reject) => { + const confirmId = String(Date.now()); + const closeHandler = () => { + delete confirmRecord[confirmId]; + setConfirmRecord({ ...confirmRecord }); }; + setConfirmRecord(oldConfirmRecord => { + return { + ...oldConfirmRecord, + [confirmId]: ( + { + closeHandler(); + onCancel?.(); + resolve(false); + }} + onConfirm={() => { + closeHandler(); + onConfirm?.(); + resolve(true); + }} + /> + ), + }; + }); }); }, }} diff --git a/packages/app/src/providers/editor-provider/editor-provider.tsx b/packages/app/src/providers/editor-provider/editor-provider.tsx index 1690dad88e..b22b14622e 100644 --- a/packages/app/src/providers/editor-provider/editor-provider.tsx +++ b/packages/app/src/providers/editor-provider/editor-provider.tsx @@ -39,6 +39,7 @@ type EditorHandlers = { favoritePage: (pageId: string) => void; unFavoritePage: (pageId: string) => void; toggleFavoritePage: (pageId: string) => void; + permanentlyDeletePage: (pageId: string) => void; }; type EditorContextProps = PropsWithChildren<{}>; @@ -59,6 +60,7 @@ export const EditorContext = createContext({ favoritePage: () => {}, unFavoritePage: () => {}, toggleFavoritePage: () => {}, + permanentlyDeletePage: () => {}, }); export const useEditor = () => useContext(EditorContext); @@ -108,19 +110,23 @@ export const EditorProvider = ({ }); }, deletePage: pageId => { - workspace?.setPage(pageId, { trash: true }); + workspace?.setPageMeta(pageId, { trash: true }); }, recyclePage: pageId => { - workspace?.setPage(pageId, { trash: false }); + workspace?.setPageMeta(pageId, { trash: false }); }, toggleDeletePage: pageId => { const pageMeta = workspace?.meta.pages.find(p => p.id === pageId); if (pageMeta) { - workspace?.setPage(pageId, { trash: !pageMeta.trash }); + workspace?.setPageMeta(pageId, { trash: !pageMeta.trash }); } }, favoritePage: pageId => { - workspace?.setPage(pageId, { favorite: true }); + workspace?.setPageMeta(pageId, { favorite: true }); + }, + permanentlyDeletePage: pageId => { + // TODO: workspace.meta.removePage or workspace.removePage? + workspace?.meta.removePage(pageId); }, unFavoritePage: pageId => { workspace?.setPageMeta(pageId, { favorite: true }); diff --git a/packages/app/src/providers/themeProvider.tsx b/packages/app/src/providers/themeProvider.tsx index ce397182ba..f821da5d0a 100644 --- a/packages/app/src/providers/themeProvider.tsx +++ b/packages/app/src/providers/themeProvider.tsx @@ -1,9 +1,13 @@ +import { createContext, useContext, useEffect, useState } from 'react'; import { ThemeProvider as EmotionThemeProvider, Global, css, } from '@emotion/react'; -import { createContext, useContext, useEffect, useState } from 'react'; +import { + ThemeProvider as MuiThemeProvider, + createTheme as MuiCreateTheme, +} from '@mui/material/styles'; import type { PropsWithChildren } from 'react'; import { Theme, @@ -26,6 +30,7 @@ export const ThemeContext = createContext({ }); export const useTheme = () => useContext(ThemeContext); +const muiTheme = MuiCreateTheme(); export const ThemeProvider = ({ defaultTheme = 'light', @@ -87,16 +92,21 @@ export const ThemeProvider = ({ // }, [mode]); return ( - - - {children} - + // Use MuiThemeProvider is just because some Transitions in Mui components need it + + + + + {children} + + + ); }; diff --git a/packages/app/src/ui/button/button.tsx b/packages/app/src/ui/button/button.tsx new file mode 100644 index 0000000000..2f961724c7 --- /dev/null +++ b/packages/app/src/ui/button/button.tsx @@ -0,0 +1,61 @@ +import { + HTMLAttributes, + cloneElement, + ReactElement, + Children, + CSSProperties, + forwardRef, + PropsWithChildren, +} from 'react'; +import { StyledButton } from './styles'; + +import { ButtonProps } from './interface'; +import { getSize } from './utils'; + +export const Button = forwardRef( + ( + { + size = 'default', + disabled = false, + hoverBackground, + hoverColor, + hoverStyle, + shape = 'default', + icon, + type = 'default', + children, + bold = false, + ...props + }, + ref + ) => { + const { iconSize } = getSize(size); + + return ( + + {icon && + cloneElement(Children.only(icon), { + width: iconSize, + height: iconSize, + className: `affine-button-icon ${icon.props.className ?? ''}`, + })} + {children && {children}} + + ); + } +); +Button.displayName = 'Button'; + +export default Button; diff --git a/packages/app/src/ui/button/icon-button.tsx b/packages/app/src/ui/button/icon-button.tsx index 922f838425..da40fbda57 100644 --- a/packages/app/src/ui/button/icon-button.tsx +++ b/packages/app/src/ui/button/icon-button.tsx @@ -11,7 +11,7 @@ import { StyledIconButton } from './styles'; const SIZE_SMALL = 'small' as const; const SIZE_MIDDLE = 'middle' as const; const SIZE_NORMAL = 'normal' as const; -// TODO: Designer is not sure about the size of the icon button +// TODO: IconButton should merge into Button, but it has not been designed yet const SIZE_CONFIG = { [SIZE_SMALL]: { iconSize: 16, diff --git a/packages/app/src/ui/button/index.ts b/packages/app/src/ui/button/index.ts index 51688a7c80..765395ea09 100644 --- a/packages/app/src/ui/button/index.ts +++ b/packages/app/src/ui/button/index.ts @@ -1 +1,2 @@ export * from './icon-button'; +export * from './button'; diff --git a/packages/app/src/ui/button/interface.ts b/packages/app/src/ui/button/interface.ts new file mode 100644 index 0000000000..0086a35f87 --- /dev/null +++ b/packages/app/src/ui/button/interface.ts @@ -0,0 +1,23 @@ +import { + CSSProperties, + HTMLAttributes, + PropsWithChildren, + ReactElement, +} from 'react'; + +export const SIZE_SMALL = 'small' as const; +export const SIZE_MIDDLE = 'middle' as const; +export const SIZE_DEFAULT = 'default' as const; + +export type ButtonProps = PropsWithChildren & + Omit, 'type'> & { + size?: typeof SIZE_SMALL | typeof SIZE_MIDDLE | typeof SIZE_DEFAULT; + disabled?: boolean; + hoverBackground?: CSSProperties['background']; + hoverColor?: CSSProperties['color']; + hoverStyle?: CSSProperties; + icon?: ReactElement; + shape?: 'default' | 'round' | 'circle'; + type?: 'primary' | 'warning' | 'danger' | 'default'; + bold?: boolean; + }; diff --git a/packages/app/src/ui/button/styles.ts b/packages/app/src/ui/button/styles.ts index abf791ada7..99773a7876 100644 --- a/packages/app/src/ui/button/styles.ts +++ b/packages/app/src/ui/button/styles.ts @@ -1,5 +1,7 @@ import { absoluteCenter, displayInlineFlex, styled } from '@/styles'; import { CSSProperties } from 'react'; +import { ButtonProps } from '@/ui/button/interface'; +import { getSize, getButtonColors } from './utils'; export const StyledIconButton = styled.button<{ width: number; @@ -53,3 +55,67 @@ export const StyledIconButton = styled.button<{ }; } ); +export const StyledButton = styled.button< + Pick< + ButtonProps, + | 'size' + | 'disabled' + | 'hoverBackground' + | 'hoverColor' + | 'hoverStyle' + | 'shape' + | 'type' + | 'bold' + > +>( + ({ + theme, + size = 'default', + disabled, + hoverBackground, + hoverColor, + hoverStyle, + bold = false, + shape = 'default', + type = 'default', + }) => { + const { fontSize, borderRadius, padding, height } = getSize(size); + + return { + height, + paddingLeft: padding, + paddingRight: padding, + border: '1px solid', + ...displayInlineFlex('flex-start', 'center'), + position: 'relative', + ...(disabled ? { cursor: 'not-allowed', pointerEvents: 'none' } : {}), + transition: 'background .15s', + // TODO: Implement circle shape + borderRadius: shape === 'default' ? borderRadius : height / 2, + fontSize, + fontWeight: bold ? '500' : '400', + '.affine-button-icon': { + color: theme.colors.iconColor, + }, + '>span': { + marginLeft: '5px', + }, + // @ts-ignore + ...getButtonColors(theme, type, { + hoverBackground, + hoverColor, + hoverStyle, + }), + + // + // ':hover': { + // color: hoverColor ?? theme.colors.primaryColor, + // background: hoverBackground ?? theme.colors.hoverBackground, + // '.affine-button-icon':{ + // + // } + // ...(hoverStyle ?? {}), + // }, + }; + } +); diff --git a/packages/app/src/ui/button/utils.ts b/packages/app/src/ui/button/utils.ts new file mode 100644 index 0000000000..fddfa5df6e --- /dev/null +++ b/packages/app/src/ui/button/utils.ts @@ -0,0 +1,104 @@ +import { AffineTheme } from '@/styles'; +import { + SIZE_SMALL, + SIZE_MIDDLE, + SIZE_DEFAULT, + ButtonProps, +} from './interface'; + +// TODO: Designer is not sure about the size, Now, is just use default size +export const SIZE_CONFIG = { + [SIZE_SMALL]: { + iconSize: 16, + fontSize: 16, + borderRadius: 6, + height: 26, + padding: 24, + }, + [SIZE_MIDDLE]: { + iconSize: 20, + fontSize: 16, + borderRadius: 6, + height: 32, + padding: 24, + }, + [SIZE_DEFAULT]: { + iconSize: 24, + fontSize: 16, + height: 38, + padding: 24, + borderRadius: 6, + }, +} as const; + +export const getSize = ( + size: typeof SIZE_SMALL | typeof SIZE_MIDDLE | typeof SIZE_DEFAULT +) => { + return SIZE_CONFIG[size]; +}; + +export const getButtonColors = ( + theme: AffineTheme, + type: ButtonProps['type'], + extend?: { + hoverBackground: ButtonProps['hoverBackground']; + hoverColor: ButtonProps['hoverColor']; + hoverStyle: ButtonProps['hoverStyle']; + } +) => { + switch (type) { + case 'primary': + return { + background: theme.colors.primaryColor, + color: '#fff', + borderColor: theme.colors.primaryColor, + '.affine-button-icon': { + color: '#fff', + }, + }; + case 'warning': + return { + background: theme.colors.warningBackground, + color: theme.colors.warningColor, + borderColor: theme.colors.warningBackground, + '.affine-button-icon': { + color: theme.colors.warningColor, + }, + ':hover': { + borderColor: theme.colors.warningColor, + color: extend?.hoverColor, + background: extend?.hoverBackground, + ...extend?.hoverStyle, + }, + }; + case 'danger': + return { + background: theme.colors.errorBackground, + color: theme.colors.errorColor, + borderColor: theme.colors.errorBackground, + '.affine-button-icon': { + color: theme.colors.errorColor, + }, + ':hover': { + borderColor: theme.colors.errorColor, + color: extend?.hoverColor, + background: extend?.hoverBackground, + ...extend?.hoverStyle, + }, + }; + default: + return { + color: theme.colors.popoverColor, + borderColor: theme.colors.borderColor, + ':hover': { + borderColor: theme.colors.primaryColor, + color: extend?.hoverColor ?? theme.colors.primaryColor, + '.affine-button-icon': { + color: extend?.hoverColor ?? theme.colors.primaryColor, + background: extend?.hoverBackground, + ...extend?.hoverStyle, + }, + }, + }; + } +}; diff --git a/packages/app/src/ui/confirm/confirm.tsx b/packages/app/src/ui/confirm/confirm.tsx index c759ebfe98..d3de35adb1 100644 --- a/packages/app/src/ui/confirm/confirm.tsx +++ b/packages/app/src/ui/confirm/confirm.tsx @@ -5,9 +5,8 @@ import { StyledConfirmContent, StyledConfirmTitle, StyledModalWrapper, - StyledButton, } from '@/ui/confirm/styles'; - +import { Button } from '@/ui/button'; export type ConfirmProps = { title?: string; content?: string; @@ -40,23 +39,28 @@ export const Confirm = ({ {content} - { setOpen(false); onCancel?.(); }} + style={{ marginRight: '24px' }} > Cancel - - + diff --git a/packages/app/src/ui/confirm/styles.ts b/packages/app/src/ui/confirm/styles.ts index a0ac9fa87b..03d1ef5bca 100644 --- a/packages/app/src/ui/confirm/styles.ts +++ b/packages/app/src/ui/confirm/styles.ts @@ -78,21 +78,3 @@ const getButtonColors = ( }; } }; - -export const StyledButton = styled.button>( - ({ theme, confirmType }) => { - return { - width: '100px', - height: '38px', - borderRadius: '19px', - border: '1px solid', - ...getButtonColors(theme, confirmType), - fontSize: theme.font.sm, - fontWeight: 500, - - '&:first-of-type': { - marginRight: '24px', - }, - }; - } -); diff --git a/packages/app/src/ui/table/table-cell.tsx b/packages/app/src/ui/table/table-cell.tsx index 3514d2fb23..c6aed80987 100644 --- a/packages/app/src/ui/table/table-cell.tsx +++ b/packages/app/src/ui/table/table-cell.tsx @@ -4,7 +4,6 @@ import { StyledTableCell } from './styles'; export const TableCell = ({ children, - ellipsis, ...props }: PropsWithChildren< TableCellProps & HTMLAttributes