mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 12:55:00 +00:00
feat: fav page references (#2422)
Co-authored-by: Himself65 <himself65@outlook.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
} from '@blocksuite/icons';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { useAtom } from 'jotai';
|
||||
import type { PropsWithChildren } from 'react';
|
||||
import { type PropsWithChildren, useState } from 'react';
|
||||
|
||||
import { AppSidebar, AppSidebarFallback, appSidebarOpenAtom } from '.';
|
||||
import { AddPageButton } from './add-page-button';
|
||||
@@ -79,6 +79,7 @@ export const Fallback = () => {
|
||||
};
|
||||
|
||||
export const WithItems: StoryFn = () => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
return (
|
||||
<Container>
|
||||
<AppSidebar>
|
||||
@@ -111,11 +112,22 @@ export const WithItems: StoryFn = () => {
|
||||
<SidebarScrollableContainer>
|
||||
<CategoryDivider label="Favorites" />
|
||||
<MenuLinkItem
|
||||
collapsed={collapsed}
|
||||
onCollapsedChange={setCollapsed}
|
||||
icon={<SettingsIcon />}
|
||||
href="/test"
|
||||
onClick={() => alert('opened')}
|
||||
>
|
||||
Settings
|
||||
Collapsible Item
|
||||
</MenuLinkItem>
|
||||
<MenuLinkItem
|
||||
collapsed={!collapsed}
|
||||
onCollapsedChange={setCollapsed}
|
||||
icon={<SettingsIcon />}
|
||||
href="/test"
|
||||
onClick={() => alert('opened')}
|
||||
>
|
||||
Collapsible Item
|
||||
</MenuLinkItem>
|
||||
<MenuLinkItem
|
||||
icon={<SettingsIcon />}
|
||||
|
||||
@@ -8,7 +8,7 @@ export const root = style({
|
||||
minHeight: '30px',
|
||||
userSelect: 'none',
|
||||
cursor: 'pointer',
|
||||
padding: '0 12px',
|
||||
padding: '0 8px 0 12px',
|
||||
fontSize: 'var(--affine-font-sm)',
|
||||
selectors: {
|
||||
'&:hover': {
|
||||
@@ -27,6 +27,11 @@ export const root = style({
|
||||
// make this a variable?
|
||||
'linear-gradient(0deg, rgba(0, 0, 0, 0.04), rgba(0, 0, 0, 0.04)), rgba(0, 0, 0, 0.04);',
|
||||
},
|
||||
'&[data-collapsible="true"]': {
|
||||
width: 'calc(100% + 8px)',
|
||||
transform: 'translateX(-8px)',
|
||||
paddingLeft: '8px',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -37,11 +42,49 @@ export const content = style({
|
||||
});
|
||||
|
||||
export const icon = style({
|
||||
marginRight: '14px',
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: '20px',
|
||||
});
|
||||
|
||||
export const collapsedIconContainer = style({
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: '2px',
|
||||
transition: 'transform 0.2s',
|
||||
selectors: {
|
||||
'&[data-collapsed="true"]': {
|
||||
transform: 'rotate(-90deg)',
|
||||
},
|
||||
'&:hover': {
|
||||
background: 'var(--affine-hover-color)',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const iconsContainer = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
width: '28px',
|
||||
selectors: {
|
||||
'&[data-collapsible="true"]': {
|
||||
width: '40px',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const collapsedIcon = style({
|
||||
transition: 'transform 0.2s ease-in-out',
|
||||
selectors: {
|
||||
'&[data-collapsed="true"]': {
|
||||
transform: 'rotate(-90deg)',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const spacer = style({
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SettingsIcon } from '@blocksuite/icons';
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { MenuItem, MenuLinkItem } from '.';
|
||||
|
||||
@@ -9,6 +10,7 @@ export default {
|
||||
} satisfies Meta;
|
||||
|
||||
export const Default: StoryFn = () => {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
return (
|
||||
<main style={{ width: '240px' }}>
|
||||
<MenuItem icon={<SettingsIcon />} onClick={() => alert('opened')}>
|
||||
@@ -29,6 +31,14 @@ export const Default: StoryFn = () => {
|
||||
>
|
||||
Primary Item
|
||||
</MenuLinkItem>
|
||||
<MenuItem
|
||||
collapsed={collapsed}
|
||||
onCollapsedChange={setCollapsed}
|
||||
icon={<SettingsIcon />}
|
||||
onClick={() => alert('opened')}
|
||||
>
|
||||
Collapsible Item
|
||||
</MenuItem>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ArrowDownSmallIcon } from '@blocksuite/icons';
|
||||
import clsx from 'clsx';
|
||||
import type { LinkProps } from 'next/link';
|
||||
import Link from 'next/link';
|
||||
@@ -9,6 +10,8 @@ interface MenuItemProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
icon?: React.ReactElement;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
collapsed?: boolean; // true, false, undefined. undefined means no collapse
|
||||
onCollapsedChange?: (collapsed: boolean) => void;
|
||||
}
|
||||
|
||||
interface MenuLinkItemProps extends MenuItemProps, Pick<LinkProps, 'href'> {}
|
||||
@@ -19,8 +22,14 @@ export function MenuItem({
|
||||
active,
|
||||
children,
|
||||
disabled,
|
||||
collapsed,
|
||||
onCollapsedChange,
|
||||
...props
|
||||
}: MenuItemProps) {
|
||||
const collapsible = collapsed !== undefined;
|
||||
if (collapsible && !onCollapsedChange) {
|
||||
throw new Error('onCollapsedChange is required when collapsed is defined');
|
||||
}
|
||||
return (
|
||||
<div
|
||||
{...props}
|
||||
@@ -28,11 +37,31 @@ export function MenuItem({
|
||||
onClick={onClick}
|
||||
data-active={active}
|
||||
data-disabled={disabled}
|
||||
data-collapsible={collapsible}
|
||||
>
|
||||
{icon &&
|
||||
React.cloneElement(icon, {
|
||||
className: clsx([styles.icon, icon.props.className]),
|
||||
})}
|
||||
<div className={styles.iconsContainer} data-collapsible={collapsible}>
|
||||
{collapsible && (
|
||||
<div
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault(); // for links
|
||||
onCollapsedChange?.(!collapsed);
|
||||
}}
|
||||
data-testid="fav-collapsed-button"
|
||||
className={styles.collapsedIconContainer}
|
||||
>
|
||||
<ArrowDownSmallIcon
|
||||
className={styles.collapsedIcon}
|
||||
data-collapsed={collapsed}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{icon &&
|
||||
React.cloneElement(icon, {
|
||||
className: clsx([styles.icon, icon.props.className]),
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -17,7 +17,7 @@ export const root = style({
|
||||
});
|
||||
|
||||
export const icon = style({
|
||||
marginRight: '14px',
|
||||
marginRight: '8px',
|
||||
color: 'var(--affine-icon-color)',
|
||||
fontSize: '20px',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user