feat(core): mobile renderer for explorer (#7942)

This commit is contained in:
CatsJuice
2024-08-29 06:09:45 +00:00
parent b96ad57568
commit f37051dc87
8 changed files with 210 additions and 82 deletions

View File

@@ -64,3 +64,30 @@ export const collapseIcon = style({
},
},
});
// ------------- mobile -------------
export const mobileRoot = style([
root,
{
height: 25,
padding: '0 16px',
selectors: {
'&[data-collapsible="true"]:hover': {
backgroundColor: 'transparent',
},
'&[data-collapsible="true"]:active': {
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
},
},
},
]);
export const mobileLabel = style([
label,
{
color: cssVarV2('text/primary'),
fontSize: 20,
lineHeight: '25px',
letterSpacing: -0.45,
fontWeight: 400,
},
]);

View File

@@ -9,6 +9,7 @@ export type CategoryDividerProps = PropsWithChildren<
label: string;
className?: string;
collapsed?: boolean;
mobile?: boolean;
setCollapsed?: (collapsed: boolean) => void;
} & {
[key: `data-${string}`]: unknown;
@@ -22,6 +23,7 @@ export const CategoryDivider = forwardRef(
children,
className,
collapsed,
mobile,
setCollapsed,
...otherProps
}: CategoryDividerProps,
@@ -31,14 +33,15 @@ export const CategoryDivider = forwardRef(
return (
<div
className={clsx([styles.root, className])}
className={clsx(mobile ? styles.mobileRoot : styles.root, className)}
ref={ref}
onClick={() => setCollapsed?.(!collapsed)}
data-mobile={mobile}
data-collapsed={collapsed}
data-collapsible={collapsible}
{...otherProps}
>
<div className={styles.label}>
<div className={mobile ? styles.mobileLabel : styles.label}>
{label}
{collapsible ? (
<ToggleCollapseIcon

View File

@@ -8,6 +8,7 @@ import { ExplorerSection } from './entities/explore-section';
import { ExplorerService } from './services/explorer';
export { ExplorerService } from './services/explorer';
export type { CollapsibleSectionName } from './types';
export { ExplorerMobileContext } from './views/mobile.context';
export { ExplorerCollections } from './views/sections/collections';
export { ExplorerFavorites } from './views/sections/favorites';
export { ExplorerMigrationFavorites } from './views/sections/migration-favorites';

View File

@@ -13,3 +13,8 @@ export const header = style({
},
},
});
// mobile
export const mobileContent = style({
paddingTop: 8,
});

View File

@@ -7,11 +7,18 @@ import {
type ReactNode,
type RefObject,
useCallback,
useContext,
} from 'react';
import { ExplorerService } from '../../services/explorer';
import type { CollapsibleSectionName } from '../../types';
import { content, header, root } from './collapsible-section.css';
import { ExplorerMobileContext } from '../mobile.context';
import {
content,
header,
mobileContent,
root,
} from './collapsible-section.css';
interface CollapsibleSectionProps extends PropsWithChildren {
name: CollapsibleSectionName;
@@ -43,6 +50,7 @@ export const CollapsibleSection = ({
contentClassName,
}: CollapsibleSectionProps) => {
const mobile = useContext(ExplorerMobileContext);
const section = useService(ExplorerService).sections[name];
const collapsed = useLiveData(section.collapsed$);
@@ -62,6 +70,7 @@ export const CollapsibleSection = ({
data-testid={testId}
>
<CategoryDivider
mobile={mobile}
data-testid={headerTestId}
label={title}
setCollapsed={setCollapsed}
@@ -69,9 +78,11 @@ export const CollapsibleSection = ({
ref={headerRef}
className={clsx(header, headerClassName)}
>
{actions}
{mobile ? null : actions}
</CategoryDivider>
<Collapsible.Content className={clsx(content, contentClassName)}>
<Collapsible.Content
className={clsx(mobile ? mobileContent : content, contentClassName)}
>
{children}
</Collapsible.Content>
</Collapsible.Root>

View File

@@ -0,0 +1,8 @@
import { createContext } from 'react';
/**
* To enable mobile manually
* > Using `environment.isMobile` directly will affect current web entry on mobile
* > So we control it manually for now
*/
export const ExplorerMobileContext = createContext(false);

View File

@@ -36,6 +36,14 @@ export const itemRoot = style({
},
},
});
export const itemMain = style({
display: 'flex',
alignItems: 'center',
width: 0,
flex: 1,
position: 'relative',
gap: 12,
});
export const itemRenameAnchor = style({
pointerEvents: 'none',
position: 'absolute',
@@ -55,31 +63,26 @@ export const itemContent = style({
export const postfix = style({
display: 'flex',
alignItems: 'center',
right: '4px',
right: 0,
position: 'absolute',
opacity: 0,
pointerEvents: 'none',
selectors: {
[`${itemRoot}:hover &`]: {
justifySelf: 'flex-end',
position: 'initial',
opacity: 1,
pointerEvents: 'all',
pointerEvents: 'initial',
position: 'initial',
},
},
});
export const icon = style({
color: cssVarV2('icon/primary'),
fontSize: '20px',
});
export const emojiIcon = style({
width: '20px',
height: '20px',
export const iconContainer = style({
display: 'flex',
justifyContent: 'center',
alignContent: 'center',
alignItems: 'center',
width: 20,
height: 20,
color: cssVarV2('icon/primary'),
fontSize: cssVar('--affine-font-sm'),
fontSize: 20,
});
export const collapsedIconContainer = style({
width: '16px',
@@ -103,13 +106,6 @@ export const collapsedIconContainer = style({
},
},
});
export const iconsContainer = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'flex-start',
width: '44px',
flexShrink: 0,
});
export const collapsedIcon = style({
transition: 'transform 0.2s ease-in-out',
selectors: {
@@ -182,3 +178,67 @@ export const draggedOverEffect = style({
},
},
});
// ---------- mobile ----------
export const mobileItemRoot = style([
itemRoot,
{
padding: '8px',
borderRadius: 0,
flexDirection: 'row-reverse',
gap: 12,
selectors: {
'&:hover': {
background: 'transparent',
},
'&:active': {
background: cssVar('hoverColor'),
},
'&[data-active="true"]': {
background: 'transparent',
},
},
':after': {
content: '',
width: `calc(100% + ${levelIndent})`,
height: 0.5,
background: cssVar('borderColor'),
bottom: 0,
position: 'absolute',
right: 0,
},
},
]);
export const mobileItemMain = style([itemMain, {}]);
export const mobileIconContainer = style([
iconContainer,
{
width: 32,
height: 32,
fontSize: 24,
},
]);
export const mobileCollapsedIconContainer = style([
collapsedIconContainer,
{
fontSize: 16,
},
]);
export const mobileItemContent = style([
itemContent,
{
fontSize: 17,
lineHeight: '22px',
letterSpacing: -0.43,
fontWeight: 400,
},
]);
export const mobileContentContainer = style([
contentContainer,
{
marginTop: 0,
},
]);

View File

@@ -37,6 +37,7 @@ import {
useState,
} from 'react';
import { ExplorerMobileContext } from '../mobile.context';
import { ExplorerTreeContext } from './context';
import { DropEffect } from './drop-effect';
import * as styles from './node.css';
@@ -108,6 +109,7 @@ export const ExplorerTreeNode = ({
onDrop?: (data: DropTargetDropEvent<AffineDNDData>) => void;
dropEffect?: ExplorerTreeNodeDropEffect;
} & { [key in `data-${string}`]?: any }) => {
const mobile = useContext(ExplorerMobileContext);
const t = useI18n();
const cid = useId();
const context = useContext(ExplorerTreeContext);
@@ -306,48 +308,43 @@ export const ExplorerTreeNode = ({
const content = (
<div
onClick={handleClick}
className={styles.itemRoot}
className={mobile ? styles.mobileItemRoot : styles.itemRoot}
data-active={active}
data-disabled={disabled}
>
<div className={styles.iconsContainer}>
<div
data-disabled={disabled}
onClick={handleCollapsedChange}
data-testid="explorer-collapsed-button"
className={styles.collapsedIconContainer}
className={
mobile
? styles.mobileCollapsedIconContainer
: styles.collapsedIconContainer
}
>
<ArrowDownSmallIcon
className={styles.collapsedIcon}
data-collapsed={collapsed !== false}
/>
</div>
{emoji ? (
<div className={styles.emojiIcon}>{emoji}</div>
) : (
Icon && (
<div className={clsx(mobile ? styles.mobileItemMain : styles.itemMain)}>
<div
className={mobile ? styles.mobileIconContainer : styles.iconContainer}
>
{emoji ??
(Icon && (
<Icon
className={styles.icon}
draggedOver={draggedOver && !isSelfDraggedOver}
treeInstruction={treeInstruction}
collapsed={collapsed}
/>
)
)}
))}
</div>
{renameable && renaming && (
<RenameModal
open
width={sidebarWidth - 32}
onOpenChange={setRenaming}
onRename={handleRename}
currentName={rawName ?? ''}
>
<div className={styles.itemRenameAnchor} />
</RenameModal>
)}
<div className={styles.itemContent}>{name}</div>
<div className={mobile ? styles.mobileItemContent : styles.itemContent}>
{name}
</div>
{postfix}
<div
@@ -378,6 +375,19 @@ export const ExplorerTreeNode = ({
)}
</div>
</div>
{renameable && renaming && (
<RenameModal
open
width={sidebarWidth - 32}
onOpenChange={setRenaming}
onRename={handleRename}
currentName={rawName ?? ''}
>
<div className={styles.itemRenameAnchor} />
</RenameModal>
)}
</div>
);
return (
@@ -391,7 +401,10 @@ export const ExplorerTreeNode = ({
{...otherProps}
>
<div
className={clsx(styles.contentContainer, styles.draggedOverEffect)}
className={clsx(
mobile ? styles.mobileContentContainer : styles.contentContainer,
styles.draggedOverEffect
)}
data-open={!collapsed}
data-self-dragged-over={isSelfDraggedOver}
ref={dropTargetRef}