mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-07-23 05:25:53 +08:00
init: the first public commit for AFFiNE
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
import { cloneElement, useMemo, useCallback, type ReactElement } from 'react';
|
||||
import { styled } from '@toeverything/components/ui';
|
||||
import {
|
||||
CommentIcon,
|
||||
LayoutIcon,
|
||||
SettingsIcon,
|
||||
} from '@toeverything/components/icons';
|
||||
import { LayoutSettings } from '../Layout';
|
||||
import { Comments } from '../Comments';
|
||||
import { useActiveComment } from '../Comments/use-comments';
|
||||
import { SettingsPanel } from '../Settings';
|
||||
import { TabItemTitle } from './TabItemTitle';
|
||||
import { useTabs } from './use-tabs';
|
||||
|
||||
const _defaultTabsKeys = ['layout', 'comment', 'settings'] as const;
|
||||
|
||||
export const ContainerTabs = () => {
|
||||
const { activeCommentId, resolveComment } = useActiveComment();
|
||||
|
||||
const getSettingsTabsData = useCallback((): SettingsTabItemType[] => {
|
||||
return [
|
||||
{
|
||||
type: 'layout',
|
||||
text: 'Layout',
|
||||
icon: (
|
||||
<IconWrapper>
|
||||
<LayoutIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
panel: <LayoutSettings />,
|
||||
},
|
||||
{
|
||||
type: 'comment',
|
||||
text: 'Comment',
|
||||
icon: (
|
||||
<IconWrapper>
|
||||
<CommentIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
panel: (
|
||||
<StyledSidebarContent>
|
||||
<Comments
|
||||
activeCommentId={activeCommentId}
|
||||
resolveComment={resolveComment}
|
||||
/>
|
||||
</StyledSidebarContent>
|
||||
),
|
||||
},
|
||||
{
|
||||
type: 'settings',
|
||||
text: 'Settings',
|
||||
icon: (
|
||||
<IconWrapper>
|
||||
<SettingsIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
panel: <SettingsPanel />,
|
||||
},
|
||||
];
|
||||
}, [activeCommentId, resolveComment]);
|
||||
|
||||
const settingsTabsData = useMemo(() => {
|
||||
return getSettingsTabsData();
|
||||
}, [getSettingsTabsData]);
|
||||
|
||||
const [activeTab, setActiveTab] = useTabs(
|
||||
_defaultTabsKeys as unknown as string[],
|
||||
'settings'
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledTabsTitlesContainer>
|
||||
{settingsTabsData.map(tab => {
|
||||
const { type, text, icon } = tab;
|
||||
return (
|
||||
<TabItemTitle
|
||||
title={text}
|
||||
icon={icon}
|
||||
isActive={activeTab === type}
|
||||
onClick={() => {
|
||||
setActiveTab(type);
|
||||
}}
|
||||
key={type}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</StyledTabsTitlesContainer>
|
||||
<StyledTabsPanelsContainer>
|
||||
{settingsTabsData.map(tab => {
|
||||
const { type, panel } = tab;
|
||||
return type === activeTab
|
||||
? cloneElement(panel, { key: type })
|
||||
: null;
|
||||
})}
|
||||
</StyledTabsPanelsContainer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const StyledTabsTitlesContainer = styled('div')(({ theme }) => {
|
||||
return {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
marginTop: 24,
|
||||
marginBottom: 24,
|
||||
marginLeft: theme.affine.spacing.smSpacing,
|
||||
marginRight: theme.affine.spacing.smSpacing,
|
||||
};
|
||||
});
|
||||
|
||||
const StyledTabsPanelsContainer = styled('div')(({ theme }) => {
|
||||
return {
|
||||
height: 'calc(100vh - 80px)',
|
||||
borderTop: `1px solid ${theme.affine.palette.tagHover}`,
|
||||
};
|
||||
});
|
||||
|
||||
type SettingsTabsTypes = typeof _defaultTabsKeys[number];
|
||||
|
||||
type SettingsTabItemType = {
|
||||
type: SettingsTabsTypes;
|
||||
text: string;
|
||||
icon: ReactElement;
|
||||
panel: ReactElement;
|
||||
};
|
||||
|
||||
const StyledSidebarContent = styled('div')(({ theme }) => {
|
||||
return {
|
||||
marginLeft: theme.affine.spacing.lgSpacing,
|
||||
};
|
||||
});
|
||||
|
||||
const IconWrapper = styled('div')({
|
||||
fontSize: 0,
|
||||
|
||||
'& > svg': {
|
||||
fontSize: '20px',
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { styled, ListItem, ListIcon } from '@toeverything/components/ui';
|
||||
|
||||
type TabItemTitleProps = {
|
||||
icon: ReactNode;
|
||||
title: string;
|
||||
isActive?: boolean;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const TabItemTitle = ({
|
||||
icon,
|
||||
title,
|
||||
isActive,
|
||||
onClick,
|
||||
}: TabItemTitleProps) => {
|
||||
return (
|
||||
<Container active={isActive} onClick={onClick}>
|
||||
<IconWrapper>{icon}</IconWrapper>
|
||||
<StyledTitleText>{title}</StyledTitleText>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
const Container = styled(ListItem)({
|
||||
width: '110px',
|
||||
height: '32px',
|
||||
});
|
||||
|
||||
const IconWrapper = styled(ListIcon)({
|
||||
marginRight: '4px',
|
||||
});
|
||||
|
||||
const StyledTitleText = styled('span')(({ theme }) => {
|
||||
return {
|
||||
color: theme.affine.palette.menu,
|
||||
};
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export { ContainerTabs } from './ContainerTabs';
|
||||
@@ -0,0 +1,58 @@
|
||||
import { useRef, useEffect, useState, useMemo } from 'react';
|
||||
|
||||
function usePrevious<T>(value: T) {
|
||||
const ref = useRef<T>();
|
||||
useEffect(() => {
|
||||
ref.current = value;
|
||||
});
|
||||
return ref.current;
|
||||
}
|
||||
|
||||
export function useTabs<K extends string>(tabs: K[], defaultTab?: K | null) {
|
||||
const state = useState<K | null>();
|
||||
const [selectedTab, setSelectedTab] = state;
|
||||
|
||||
const activeIndex = useMemo(() => {
|
||||
if (selectedTab) {
|
||||
return tabs.indexOf(selectedTab);
|
||||
}
|
||||
return -1;
|
||||
}, [selectedTab, tabs]);
|
||||
|
||||
const previousActiveIndex = usePrevious(activeIndex);
|
||||
|
||||
useEffect(() => {
|
||||
if (tabs.length === 0) {
|
||||
setSelectedTab(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
selectedTab === null ||
|
||||
(selectedTab && tabs.includes(selectedTab))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
typeof previousActiveIndex === 'number' &&
|
||||
previousActiveIndex >= 0 &&
|
||||
(tabs[previousActiveIndex] || tabs[previousActiveIndex - 1])
|
||||
) {
|
||||
setSelectedTab(
|
||||
tabs[previousActiveIndex] || tabs[previousActiveIndex - 1]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (defaultTab === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
setSelectedTab(
|
||||
defaultTab && tabs.includes(defaultTab) ? defaultTab : tabs[0]
|
||||
);
|
||||
}, [defaultTab, previousActiveIndex, selectedTab, setSelectedTab, tabs]);
|
||||
|
||||
return state;
|
||||
}
|
||||
Reference in New Issue
Block a user