diff --git a/packages/app/src/components/Header/header.tsx b/packages/app/src/components/Header/header.tsx
new file mode 100644
index 0000000000..3be73a1413
--- /dev/null
+++ b/packages/app/src/components/Header/header.tsx
@@ -0,0 +1,127 @@
+import React, { PropsWithChildren, useEffect, useState } from 'react';
+import {
+ LogoIcon,
+ MoreIcon,
+ ExportIcon,
+ Export2Markdown,
+ Export2HTML,
+ RightArrow,
+} from './icons';
+import {
+ StyledHeader,
+ StyledTitle,
+ StyledTitleWrapper,
+ StyledLogo,
+ StyledHeaderRightSide,
+ IconButton,
+ StyledHeaderContainer,
+ StyledBrowserWarning,
+ StyledCloseButton,
+ StyledMenuItemWrapper,
+} from './styles';
+import { useEditor } from '@/providers/editor-provider';
+import EditorModeSwitch from '@/components/editor-mode-switch';
+import { EdgelessIcon, PaperIcon } from '../editor-mode-switch/icons';
+import ThemeModeSwitch from '@/components/theme-mode-switch';
+import { useModal } from '@/providers/global-modal-provider';
+import CloseIcon from '@mui/icons-material/Close';
+import { getWarningMessage, shouldShowWarning } from './utils';
+import { Menu, MenuItem } from '@/ui/menu';
+
+const PopoverContent = () => {
+ const { editor, mode, setMode } = useEditor();
+ return (
+ <>
+
+
+ >
+ );
+};
+
+const BrowserWarning = ({
+ show,
+ onClose,
+}: {
+ show: boolean;
+ onClose: () => void;
+}) => {
+ return (
+
+ {getWarningMessage()}
+
+
+
+
+ );
+};
+
+export const Header = ({ children }: PropsWithChildren<{}>) => {
+ const [showWarning, setShowWarning] = useState(shouldShowWarning());
+
+ return (
+
+ {
+ setShowWarning(false);
+ }}
+ />
+
+ {children}
+
+
+ } placement="bottom-end">
+
+
+
+
+
+
+
+ );
+};
+
+export default Header;
diff --git a/packages/app/src/components/Header/index.tsx b/packages/app/src/components/Header/index.tsx
index 33c9d0495c..9ecc82e6c6 100644
--- a/packages/app/src/components/Header/index.tsx
+++ b/packages/app/src/components/Header/index.tsx
@@ -1,165 +1,2 @@
-import React, { useEffect, useState } from 'react';
-import {
- LogoIcon,
- MoreIcon,
- ExportIcon,
- Export2Markdown,
- Export2HTML,
- RightArrow,
-} from './icons';
-import {
- StyledHeader,
- StyledTitle,
- StyledTitleWrapper,
- StyledLogo,
- StyledHeaderRightSide,
- IconButton,
- StyledHeaderContainer,
- StyledBrowserWarning,
- StyledCloseButton,
- StyledMenuItemWrapper,
-} from './styles';
-import { useEditor } from '@/providers/editor-provider';
-import EditorModeSwitch from '@/components/editor-mode-switch';
-import { EdgelessIcon, PaperIcon } from '../editor-mode-switch/icons';
-import ThemeModeSwitch from '@/components/theme-mode-switch';
-import { useModal } from '@/providers/global-modal-provider';
-import CloseIcon from '@mui/icons-material/Close';
-import { getWarningMessage, shouldShowWarning } from './utils';
-import { Menu, MenuItem } from '@/ui/menu';
-
-const PopoverContent = () => {
- const { editor, mode, setMode } = useEditor();
- return (
- <>
-
-
- >
- );
-};
-
-const BrowserWarning = ({
- show,
- onClose,
-}: {
- show: boolean;
- onClose: () => void;
-}) => {
- return (
-
- {getWarningMessage()}
-
-
-
-
- );
-};
-
-export const Header = () => {
- const [title, setTitle] = useState('');
- const [isHover, setIsHover] = useState(false);
- const [showWarning, setShowWarning] = useState(shouldShowWarning());
-
- const { contactModalHandler } = useModal();
- const { editor } = useEditor();
-
- useEffect(() => {
- console.log('header', editor);
-
- if (editor?.model) {
- setTitle(editor.model.title ?? '');
- editor.model.propsUpdated.on(() => {
- setTitle(editor.model.title);
- });
- }
- }, [editor]);
- return (
-
- {
- setShowWarning(false);
- }}
- />
-
- {/* {*/}
- {/* contactModalHandler(true);*/}
- {/* }}*/}
- {/*>*/}
- {/* */}
- {/**/}
- {title ? (
- {
- setIsHover(true);
- }}
- onMouseLeave={() => {
- setIsHover(false);
- }}
- >
-
- {title}
-
- ) : null}
-
-
-
- } placement="bottom-end">
-
-
-
-
-
-
-
- );
-};
+export * from './header';
+export * from './page-header';
diff --git a/packages/app/src/components/Header/page-header.tsx b/packages/app/src/components/Header/page-header.tsx
new file mode 100644
index 0000000000..65dfa8e04a
--- /dev/null
+++ b/packages/app/src/components/Header/page-header.tsx
@@ -0,0 +1,47 @@
+import React, { useEffect, useState } from 'react';
+import { StyledTitle, StyledTitleWrapper } from './styles';
+import { useEditor } from '@/providers/editor-provider';
+import EditorModeSwitch from '@/components/editor-mode-switch';
+
+import Header from './header';
+
+export const PageHeader = () => {
+ const [title, setTitle] = useState('');
+ const [isHover, setIsHover] = useState(false);
+
+ const { editor } = useEditor();
+
+ useEffect(() => {
+ if (editor?.model) {
+ setTitle(editor.model.title ?? '');
+ editor.model.propsUpdated.on(() => {
+ setTitle(editor.model.title);
+ });
+ }
+ }, [editor]);
+
+ return (
+
+ {title ? (
+ {
+ setIsHover(true);
+ }}
+ onMouseLeave={() => {
+ setIsHover(false);
+ }}
+ >
+
+ {title}
+
+ ) : null}
+
+ );
+};
+
+export default PageHeader;
diff --git a/packages/app/src/components/workspace-slider-bar/index.tsx b/packages/app/src/components/workspace-slider-bar/index.tsx
index 7a4ef8361c..4cd6065462 100644
--- a/packages/app/src/components/workspace-slider-bar/index.tsx
+++ b/packages/app/src/components/workspace-slider-bar/index.tsx
@@ -1,22 +1,26 @@
import React, { useState } from 'react';
+import Router from 'next/router';
import {
StyledArrowButton,
StyledListItem,
+ StyledNewPageButton,
StyledSliderBar,
StyledSubListItem,
- StyledWrapper,
} from './style';
import { Arrow } from './icons';
-
export const WorkSpaceSliderBar = () => {
const [show, setShow] = useState(false);
return (
<>
-
-
Quick search
- All pages
+ {
+ Router.push('/all-page');
+ }}
+ >
+ All pages
+
Favourites
document 1, this is a paper icondocument 1
@@ -25,6 +29,8 @@ export const WorkSpaceSliderBar = () => {
document 4
Import
Bin
+
+ New Page
(
zIndex: theme.zIndex.modal,
padding: show ? '24px 12px' : '24px 0',
overflowX: 'hidden',
+ flexShrink: 0,
};
}
);
-export const StyledWrapper = styled.div(() => {
- return {
- // padding: '24px 12px',
- // height: '100%',
- // overflowY: 'auto',
- };
-});
+
export const StyledArrowButton = styled.button<{ isShow: boolean }>(
({ theme, isShow }) => {
return {
@@ -54,6 +49,8 @@ export const StyledListItem = styled.button(({ theme }) => {
marginTop: '12px',
fontSize: theme.font.sm,
color: theme.colors.popoverColor,
+ padding: '0 12px',
+ borderRadius: '5px',
...displayFlex('flex-start', 'center'),
':hover': {
color: theme.colors.primaryColor,
@@ -62,13 +59,22 @@ export const StyledListItem = styled.button(({ theme }) => {
};
});
+export const StyledNewPageButton = styled(StyledListItem)(({ theme }) => {
+ return {
+ position: 'absolute',
+ bottom: '24px',
+ left: '0',
+ right: '0',
+ margin: 'auto',
+ };
+});
+
export const StyledSubListItem = styled.button(({ theme }) => {
return {
width: '296px',
height: '32px',
marginTop: '4px',
fontSize: theme.font.sm,
- fontWeight: 400,
color: theme.colors.popoverColor,
paddingLeft: '45px',
lineHeight: '32px',
@@ -77,7 +83,6 @@ export const StyledSubListItem = styled.button(({ theme }) => {
':hover': {
color: theme.colors.primaryColor,
backgroundColor: theme.colors.hoverBackground,
- fontWeight: 500,
},
};
});
diff --git a/packages/app/src/pages/_app.tsx b/packages/app/src/pages/_app.tsx
index 9b15465982..b64264e83c 100644
--- a/packages/app/src/pages/_app.tsx
+++ b/packages/app/src/pages/_app.tsx
@@ -11,9 +11,8 @@ import '@fontsource/space-mono';
import '@fontsource/poppins';
import '../utils/print-build-info';
import { styled } from '@/styles';
-import type { ReactNode, PropsWithChildren, FC } from 'react';
-import { cloneElement } from 'react';
-
+import ProviderComposer from '@/components/provider-composer';
+import ConfirmProvider from '@/providers/confirm-provider';
const ThemeProvider = dynamic(() => import('@/providers/themeProvider'), {
ssr: false,
});
@@ -24,21 +23,15 @@ const StyledPage = styled('div')(({ theme }) => {
backgroundColor: theme.colors.pageBackground,
transition: 'background-color .5s',
display: 'flex',
+ flexGrow: '1',
};
});
-const ProviderComposer: FC<
- PropsWithChildren<{
- contexts: any;
- }>
-> = ({ contexts, children }) =>
- contexts.reduceRight(
- (kids: ReactNode, parent: any) =>
- cloneElement(parent, {
- children: kids,
- }),
- children
- );
+const StyledWrapper = styled('div')(({ theme }) => {
+ return {
+ flexGrow: 1,
+ };
+});
function MyApp({ Component, pageProps }: AppProps) {
return (
@@ -49,11 +42,14 @@ function MyApp({ Component, pageProps }: AppProps) {
,
,
,
+ ,
]}
>
-
+
+
+
>
diff --git a/packages/app/src/pages/all-page.tsx b/packages/app/src/pages/all-page.tsx
new file mode 100644
index 0000000000..dd40b8188f
--- /dev/null
+++ b/packages/app/src/pages/all-page.tsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { Header } from '@/components/Header';
+import { styled } from '@/styles';
+import { Table, TableCell, TableHead, TableRow, TableBody } from '../ui/table';
+import { useConfirm } from '@/providers/confirm-provider';
+const StyledTableContainer = styled.div(() => {
+ return {
+ height: 'calc(100vh - 60px)',
+ padding: '78px 72px',
+ overflowY: 'auto',
+ };
+});
+export const AllPage = () => {
+ const { confirm } = useConfirm();
+ return (
+ <>
+
+
+
+
+
+
+ Documents
+ Created
+ Uploaded
+
+
+
+
+ {new Array(100).fill(0).map((_, index) => {
+ return (
+
+
+ This is a long, very long, extremely long, incredibly long,
+ exceedingly long, very long document title
+
+ 2022-11-02 18:30
+ 2022-11-02 18:30
+ ...
+
+ );
+ })}
+
+
+
+ >
+ );
+};
+
+export default AllPage;
diff --git a/packages/app/src/pages/index.tsx b/packages/app/src/pages/index.tsx
index 2ceee74813..33c87c954f 100644
--- a/packages/app/src/pages/index.tsx
+++ b/packages/app/src/pages/index.tsx
@@ -1,6 +1,6 @@
import type { NextPage } from 'next';
import { styled } from '@/styles';
-import { Header } from '@/components/Header';
+import { PageHeader } from '@/components/Header';
import { FAQ } from '@/components/faq';
import EdgelessToolbar from '@/components/edgeless-toolbar';
import MobileModal from '@/components/mobile-modal';
@@ -14,26 +14,17 @@ const StyledEditorContainer = styled('div')(({ theme }) => {
};
});
-const StyledWrapper = styled('div')(({ theme }) => {
- return {
- height: '100vh',
- backgroundColor: theme.colors.pageBackground,
- transition: 'background-color .5s',
- flexGrow: 1,
- };
-});
-
const Home: NextPage = () => {
return (
-
-
+ <>
+
-
+ >
);
};
diff --git a/packages/app/src/providers/confirm-provider.tsx b/packages/app/src/providers/confirm-provider.tsx
new file mode 100644
index 0000000000..4b7b374d42
--- /dev/null
+++ b/packages/app/src/providers/confirm-provider.tsx
@@ -0,0 +1,52 @@
+import { createContext, useContext, useState, ReactNode } from 'react';
+import type { PropsWithChildren } from 'react';
+import { Confirm, ConfirmProps } from '@/ui/confirm';
+
+type ConfirmContextValue = {
+ confirm: (props: ConfirmProps) => void;
+};
+type ConfirmContextProps = PropsWithChildren<{}>;
+
+export const ConfirmContext = createContext({
+ confirm: () => {},
+});
+
+export const useConfirm = () => useContext(ConfirmContext);
+
+export const ConfirmProvider = ({
+ children,
+}: PropsWithChildren) => {
+ const [confirmRecord, setConfirmRecord] = useState>(
+ {}
+ );
+ return (
+ {
+ const confirmId = String(Date.now());
+ setConfirmRecord(oldConfirmRecord => {
+ return {
+ ...oldConfirmRecord,
+ [confirmId]: (
+ {
+ delete confirmRecord[confirmId];
+ setConfirmRecord({ ...confirmRecord });
+ }}
+ />
+ ),
+ };
+ });
+ },
+ }}
+ >
+ {children}
+ {Object.entries(confirmRecord).map(([confirmId, confirmNode]) => {
+ return {confirmNode}
;
+ })}
+
+ );
+};
+
+export default ConfirmProvider;
diff --git a/packages/app/src/styles/theme.ts b/packages/app/src/styles/theme.ts
index e54ba4a4fd..18ca3b7163 100644
--- a/packages/app/src/styles/theme.ts
+++ b/packages/app/src/styles/theme.ts
@@ -20,6 +20,7 @@ export const getLightTheme = (
tooltipBackground: '#6880FF',
codeBackground: '#f2f5f9',
warningBackground: '#FFF9C7',
+ errorBackground: '#FFDED8',
textColor: '#3A4C5C',
edgelessTextColor: '#3A4C5C',
@@ -36,11 +37,19 @@ export const getLightTheme = (
borderColor: '#D0D7E3',
disableColor: '#C0C0C0',
warningColor: '#906616',
+ errorColor: '#EB4335',
},
font: {
xs: '12px',
sm: '16px',
base: '18px',
+ h1: '30px',
+ h2: '28px',
+ h3: '26px',
+ h4: '24px',
+ h5: '22px',
+ h6: '20px',
+
family: `Avenir Next, Poppins, ${basicFontFamily}`,
family2: `Space Mono, ${basicFontFamily}`,
lineHeightBase: '26px',
@@ -85,6 +94,7 @@ export const getDarkTheme = (
? lightTheme.colors.codeBackground
: '#505662',
warningBackground: '#FFF9C7',
+ errorBackground: '#FFDED8',
textColor: '#fff',
edgelessTextColor: '#3A4C5C',
@@ -102,6 +112,7 @@ export const getDarkTheme = (
borderColor: '#4D4C53',
disableColor: '#4b4b4b',
warningColor: '#906616',
+ errorColor: '#EB4335',
},
shadow: {
popover:
diff --git a/packages/app/src/styles/types.ts b/packages/app/src/styles/types.ts
index 155842eebc..0a69af4b47 100644
--- a/packages/app/src/styles/types.ts
+++ b/packages/app/src/styles/types.ts
@@ -25,6 +25,7 @@ export interface AffineTheme {
hoverBackground: string;
codeBackground: string;
warningBackground: string;
+ errorBackground: string;
// Use for the page`s text
textColor: string;
// Use for the editor`s text, because in edgeless mode text is different form other
@@ -43,11 +44,18 @@ export interface AffineTheme {
borderColor: string;
disableColor: string;
warningColor: string;
+ errorColor: string;
};
font: {
xs: string; // tiny
sm: string; // small
base: string;
+ h1: string;
+ h2: string;
+ h3: string;
+ h4: string;
+ h5: string;
+ h6: string;
family: string;
family2: string;
diff --git a/packages/app/src/ui/confirm/confirm.tsx b/packages/app/src/ui/confirm/confirm.tsx
new file mode 100644
index 0000000000..c759ebfe98
--- /dev/null
+++ b/packages/app/src/ui/confirm/confirm.tsx
@@ -0,0 +1,66 @@
+import { useState } from 'react';
+import { Modal, ModalCloseButton, ModalProps } from '../modal';
+import {
+ StyledButtonWrapper,
+ StyledConfirmContent,
+ StyledConfirmTitle,
+ StyledModalWrapper,
+ StyledButton,
+} from '@/ui/confirm/styles';
+
+export type ConfirmProps = {
+ title?: string;
+ content?: string;
+ confirmText?: string;
+ // TODO: Confirm button's color should depend on confirm type
+ confirmType?: 'primary' | 'warning' | 'danger';
+ onConfirm?: () => void;
+ onCancel?: () => void;
+} & Omit;
+
+export const Confirm = ({
+ title,
+ content,
+ confirmText,
+ confirmType,
+ onConfirm,
+ onCancel,
+}: ConfirmProps) => {
+ const [open, setOpen] = useState(true);
+ return (
+
+
+ {
+ setOpen(false);
+ onCancel?.();
+ }}
+ />
+ {title}
+ {content}
+
+
+ {
+ setOpen(false);
+ onCancel?.();
+ }}
+ >
+ Cancel
+
+ {
+ setOpen(false);
+ onConfirm?.();
+ }}
+ >
+ {confirmText}
+
+
+
+
+ );
+};
+
+export default Confirm;
diff --git a/packages/app/src/ui/confirm/index.ts b/packages/app/src/ui/confirm/index.ts
new file mode 100644
index 0000000000..5d2064b5f1
--- /dev/null
+++ b/packages/app/src/ui/confirm/index.ts
@@ -0,0 +1 @@
+export * from './confirm';
diff --git a/packages/app/src/ui/confirm/styles.ts b/packages/app/src/ui/confirm/styles.ts
new file mode 100644
index 0000000000..d285ecdad7
--- /dev/null
+++ b/packages/app/src/ui/confirm/styles.ts
@@ -0,0 +1,98 @@
+import { displayFlex, styled, AffineTheme } from '@/styles';
+import { ConfirmProps } from '@/ui/confirm/confirm';
+
+export const StyledModalWrapper = styled.div(({ theme }) => {
+ return {
+ width: '460px',
+ height: '240px',
+ padding: '0 60px',
+ background: theme.colors.popoverBackground,
+ borderRadius: '28px',
+ position: 'relative',
+ };
+});
+
+export const StyledConfirmTitle = styled.div(({ theme }) => {
+ return {
+ fontSize: theme.font.h6,
+ fontWeight: 600,
+ textAlign: 'center',
+ marginTop: '45px',
+ color: theme.colors.popoverColor,
+ };
+});
+
+export const StyledConfirmContent = styled.div(({ theme }) => {
+ return {
+ fontSize: theme.font.base,
+ textAlign: 'center',
+ marginTop: '12px',
+ color: theme.colors.textColor,
+ };
+});
+
+export const StyledButtonWrapper = styled.div(() => {
+ return {
+ ...displayFlex('center', 'center'),
+ marginTop: '32px',
+ };
+});
+
+const getButtonColors = (
+ theme: AffineTheme,
+ confirmType: ConfirmProps['confirmType']
+) => {
+ switch (confirmType) {
+ case 'primary':
+ return {
+ background: theme.colors.primaryColor,
+ color: '#fff',
+ borderColor: theme.colors.primaryColor,
+ };
+ case 'warning':
+ return {
+ background: theme.colors.warningBackground,
+ color: theme.colors.warningColor,
+ borderColor: theme.colors.warningBackground,
+ ':hover': {
+ borderColor: theme.colors.warningColor,
+ },
+ };
+ case 'danger':
+ return {
+ background: theme.colors.errorBackground,
+ color: theme.colors.errorColor,
+ borderColor: theme.colors.errorBackground,
+ ':hover': {
+ borderColor: theme.colors.errorColor,
+ },
+ };
+ default:
+ return {
+ color: theme.colors.popoverColor,
+ borderColor: theme.colors.borderColor,
+ ':hover': {
+ borderColor: theme.colors.primaryColor,
+ color: theme.colors.primaryColor,
+ },
+ };
+ }
+};
+
+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/modal/index.tsx b/packages/app/src/ui/modal/index.tsx
index 936be1b987..0052b129f1 100644
--- a/packages/app/src/ui/modal/index.tsx
+++ b/packages/app/src/ui/modal/index.tsx
@@ -1,4 +1,6 @@
import Modal from './modal';
+export * from './modal-close-button';
export * from './modal';
+
export default Modal;
diff --git a/packages/app/src/ui/modal/modal-close-button.tsx b/packages/app/src/ui/modal/modal-close-button.tsx
new file mode 100644
index 0000000000..4b1d2bad91
--- /dev/null
+++ b/packages/app/src/ui/modal/modal-close-button.tsx
@@ -0,0 +1,25 @@
+import { HTMLAttributes } from 'react';
+import { StyledCloseButton } from './style';
+import { CloseIcon } from '@blocksuite/icons';
+
+export type ModalCloseButtonProps = {
+ top?: number;
+ right?: number;
+ triggerSize?: [number, number];
+ size?: [number, number];
+ iconSize?: [number, number];
+} & HTMLAttributes;
+
+export const ModalCloseButton = ({
+ iconSize = [24, 24],
+ ...props
+}: ModalCloseButtonProps) => {
+ const [iconWidth, iconHeight] = iconSize;
+ return (
+
+
+
+ );
+};
+
+export default ModalCloseButton;
diff --git a/packages/app/src/ui/modal/style.ts b/packages/app/src/ui/modal/style.ts
index 3b37e3c0f2..999f254030 100644
--- a/packages/app/src/ui/modal/style.ts
+++ b/packages/app/src/ui/modal/style.ts
@@ -1,5 +1,6 @@
-import { displayFlex, fixedCenter, styled } from '@/styles';
+import { absoluteCenter, displayFlex, fixedCenter, styled } from '@/styles';
import ModalUnstyled from '@mui/base/ModalUnstyled';
+import { ModalCloseButtonProps } from '@/ui/modal/modal-close-button';
export const StyledBackdrop = styled.div<{ open?: boolean }>(({ open }) => {
return {
@@ -28,3 +29,40 @@ export const StyledModal = styled(ModalUnstyled)(({ theme }) => {
},
};
});
+
+export const StyledCloseButton = styled.button<
+ Pick
+>(({ theme, triggerSize = [], size = [32, 32], top, right }) => {
+ const [triggerWidth, triggerHeight] = triggerSize;
+ const [width, height] = size;
+
+ return {
+ width: triggerWidth ?? width * 2,
+ height: triggerHeight ?? height * 2,
+ color: theme.colors.iconColor,
+ cursor: 'pointer',
+ ...displayFlex('center', 'center'),
+ position: 'absolute',
+ top: top ?? 0,
+ right: right ?? 0,
+
+ // TODO: we need to add @emotion/babel-plugin
+ '::after': {
+ content: '""',
+ width,
+ height,
+ borderRadius: '6px',
+ ...absoluteCenter({ horizontal: true, vertical: true }),
+ },
+ ':hover': {
+ color: theme.colors.primaryColor,
+ '::after': {
+ background: theme.colors.hoverBackground,
+ },
+ },
+ svg: {
+ position: 'relative',
+ zIndex: 1,
+ },
+ };
+});
diff --git a/packages/app/src/ui/table/index.ts b/packages/app/src/ui/table/index.ts
new file mode 100644
index 0000000000..f9c589659b
--- /dev/null
+++ b/packages/app/src/ui/table/index.ts
@@ -0,0 +1,12 @@
+// import Table from '@mui/material/Table';
+// import TableBody from '@mui/material/TableBody';
+import TableCell from '@mui/material/TableCell';
+// import TableHead from '@mui/material/TableHead';
+// import TableRow from '@mui/material/TableRow';
+//
+
+export * from './table';
+export * from './table-body';
+export * from './table-cell';
+export * from './table-head';
+export * from './table-row';
diff --git a/packages/app/src/ui/table/interface.ts b/packages/app/src/ui/table/interface.ts
new file mode 100644
index 0000000000..30e8795e50
--- /dev/null
+++ b/packages/app/src/ui/table/interface.ts
@@ -0,0 +1,8 @@
+import { CSSProperties } from 'react';
+
+export type TableCellProps = {
+ align?: 'left' | 'right' | 'center';
+ ellipsis?: boolean;
+ proportion?: number;
+ style?: CSSProperties;
+};
diff --git a/packages/app/src/ui/table/styles.ts b/packages/app/src/ui/table/styles.ts
new file mode 100644
index 0000000000..39d16aa5db
--- /dev/null
+++ b/packages/app/src/ui/table/styles.ts
@@ -0,0 +1,66 @@
+import { styled, textEllipsis } from '@/styles';
+import { TableCellProps } from './interface';
+
+export const StyledTable = styled.table<{ tableLayout: 'auto' | 'fixed' }>(
+ ({ theme, tableLayout }) => {
+ return {
+ fontSize: theme.font.base,
+ color: theme.colors.textColor,
+ tableLayout,
+ width: '100%',
+ borderCollapse: 'separate',
+ borderSpacing: '0 25px',
+ };
+ }
+);
+
+export const StyledTableBody = styled.tbody(({ theme }) => {
+ return {
+ fontWeight: 400,
+ };
+});
+
+export const StyledTableCell = styled.td<
+ Pick
+>(({ theme, align = 'left', ellipsis = false, proportion }) => {
+ const width = proportion ? `${proportion * 100}%` : 'auto';
+ return {
+ width,
+ height: '54px',
+ lineHeight: '54px',
+ padding: '0 30px',
+ boxSizing: 'border-box',
+ textAlign: align,
+ ...(ellipsis ? textEllipsis(1) : {}),
+ overflowWrap: 'break-word',
+ };
+});
+
+export const StyledTableHead = styled.thead(({ theme }) => {
+ return {
+ fontWeight: 500,
+ };
+});
+
+export const StyledTableRow = styled.tr(({ theme }) => {
+ return {
+ marginBottom: '25px',
+ td: {
+ transition: 'background .15s',
+ },
+ 'td:first-child': {
+ borderTopLeftRadius: '10px',
+ borderBottomLeftRadius: '10px',
+ },
+ 'td:last-child': {
+ borderTopRightRadius: '10px',
+ borderBottomRightRadius: '10px',
+ },
+
+ ':hover': {
+ td: {
+ background: theme.colors.hoverBackground,
+ },
+ },
+ };
+});
diff --git a/packages/app/src/ui/table/table-body.tsx b/packages/app/src/ui/table/table-body.tsx
new file mode 100644
index 0000000000..2954415bf7
--- /dev/null
+++ b/packages/app/src/ui/table/table-body.tsx
@@ -0,0 +1,8 @@
+import { PropsWithChildren } from 'react';
+import { StyledTableBody } from '@/ui/table/styles';
+
+export const TableBody = ({ children }: PropsWithChildren<{}>) => {
+ return {children};
+};
+
+export default TableBody;
diff --git a/packages/app/src/ui/table/table-cell.tsx b/packages/app/src/ui/table/table-cell.tsx
new file mode 100644
index 0000000000..5dfe025a5c
--- /dev/null
+++ b/packages/app/src/ui/table/table-cell.tsx
@@ -0,0 +1,12 @@
+import { PropsWithChildren, useLayoutEffect } from 'react';
+import { TableCellProps } from './interface';
+import { StyledTableCell } from './styles';
+
+export const TableCell = ({
+ children,
+ ...props
+}: PropsWithChildren) => {
+ return {children};
+};
+
+export default TableCell;
diff --git a/packages/app/src/ui/table/table-head.tsx b/packages/app/src/ui/table/table-head.tsx
new file mode 100644
index 0000000000..33e95ce6e3
--- /dev/null
+++ b/packages/app/src/ui/table/table-head.tsx
@@ -0,0 +1,8 @@
+import { PropsWithChildren } from 'react';
+import { StyledTableHead } from './styles';
+
+export const TableHead = ({ children }: PropsWithChildren<{}>) => {
+ return {children};
+};
+
+export default TableHead;
diff --git a/packages/app/src/ui/table/table-row.tsx b/packages/app/src/ui/table/table-row.tsx
new file mode 100644
index 0000000000..c421f81868
--- /dev/null
+++ b/packages/app/src/ui/table/table-row.tsx
@@ -0,0 +1,8 @@
+import { PropsWithChildren } from 'react';
+import { StyledTableRow } from './styles';
+
+export const TableRow = ({ children }: PropsWithChildren<{}>) => {
+ return {children};
+};
+
+export default TableRow;
diff --git a/packages/app/src/ui/table/table.tsx b/packages/app/src/ui/table/table.tsx
new file mode 100644
index 0000000000..47bea8362e
--- /dev/null
+++ b/packages/app/src/ui/table/table.tsx
@@ -0,0 +1,23 @@
+import { PropsWithChildren, Children, ReactNode } from 'react';
+import { StyledTable } from './styles';
+
+const childrenHasEllipsis = (children: ReactNode | ReactNode[]): boolean => {
+ return Children.toArray(children).some(child => {
+ if (typeof child === 'object' && 'props' in child) {
+ if (!child.props.ellipsis && child.props.children) {
+ return childrenHasEllipsis(child.props.children);
+ }
+ return child.props.ellipsis ?? false;
+ }
+
+ return false;
+ });
+};
+
+export const Table = ({ children }: PropsWithChildren<{}>) => {
+ const tableLayout = childrenHasEllipsis(children) ? 'fixed' : 'auto';
+
+ return {children};
+};
+
+export default Table;