fix(core): adjust new doc list filter style (#12629)

close AF-2678, AF-2677, AF-2674, AF-2655

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
  - Added customizable spacing for dividers.
  - Introduced a flexible filter value menu for easier filter editing.
- Added options to control visibility of creation actions in empty
document views.

- **Improvements**
- Enhanced menu components for filter values with a more declarative and
simplified interface.
  - Improved vertical alignment in some UI containers.
  - Updated divider spacing for more consistent UI appearance.

- **Bug Fixes**
  - Menu popups for filters now appear in correct positions.

- **Removals**
- Removed support for the "zotero" integration type from integration
settings and filters.

- **Style**
  - Updated CSS for better menu positioning and alignment.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Cats Juice
2025-06-23 10:34:00 +00:00
committed by GitHub
parent 24b205ae83
commit ea92e2291d
27 changed files with 197 additions and 238 deletions
@@ -1,3 +1,4 @@
import { assignInlineVars } from '@vanilla-extract/dynamic';
import clsx from 'clsx'; import clsx from 'clsx';
import type { HTMLAttributes, PropsWithChildren } from 'react'; import type { HTMLAttributes, PropsWithChildren } from 'react';
import { forwardRef } from 'react'; import { forwardRef } from 'react';
@@ -8,6 +9,7 @@ export type DividerProps = PropsWithChildren &
Omit<HTMLAttributes<HTMLDivElement>, 'type'> & { Omit<HTMLAttributes<HTMLDivElement>, 'type'> & {
orientation?: DividerOrientation; orientation?: DividerOrientation;
size?: 'thinner' | 'default'; size?: 'thinner' | 'default';
space?: number;
dividerColor?: string; dividerColor?: string;
}; };
@@ -16,6 +18,7 @@ export const Divider = forwardRef<HTMLDivElement, DividerProps>(
{ {
orientation = 'horizontal', orientation = 'horizontal',
size = 'default', size = 'default',
space: propSpace,
dividerColor, dividerColor,
style, style,
className, className,
@@ -23,6 +26,8 @@ export const Divider = forwardRef<HTMLDivElement, DividerProps>(
}, },
ref ref
) => { ) => {
const space = propSpace ?? (orientation === 'horizontal' ? 8 : 2);
return ( return (
<div <div
data-divider data-divider
@@ -38,6 +43,7 @@ export const Divider = forwardRef<HTMLDivElement, DividerProps>(
style={{ style={{
borderColor: dividerColor ? dividerColor : undefined, borderColor: dividerColor ? dividerColor : undefined,
...style, ...style,
...assignInlineVars({ [styles.dividerSpace]: `${space}px` }),
}} }}
{...otherProps} {...otherProps}
/> />
@@ -1,9 +1,15 @@
import { cssVar } from '@toeverything/theme'; import { cssVar } from '@toeverything/theme';
import { style } from '@vanilla-extract/css'; import { createVar, style } from '@vanilla-extract/css';
export const dividerSpace = createVar('dividerSpace');
export const divider = style({ export const divider = style({
vars: {
[dividerSpace]: '8px',
},
borderBottom: `1px solid ${cssVar('borderColor')}`, borderBottom: `1px solid ${cssVar('borderColor')}`,
height: 0, height: 0,
margin: '8px 0', margin: `${dividerSpace} 0`,
width: '100%', width: '100%',
}); });
@@ -11,7 +17,7 @@ export const verticalDivider = style({
borderLeft: `1px solid ${cssVar('borderColor')}`, borderLeft: `1px solid ${cssVar('borderColor')}`,
width: 0, width: 0,
height: '100%', height: '100%',
margin: '0 2px', margin: `0 ${dividerSpace}`,
}); });
export const thinner = style({ export const thinner = style({
@@ -19,11 +19,13 @@ export interface EmptyDocsProps extends UniversalEmptyProps {
* Used for "New doc", if provided, new doc will be created with this tag. * Used for "New doc", if provided, new doc will be created with this tag.
*/ */
tagId?: string; tagId?: string;
allowCreate?: boolean;
} }
export const EmptyDocs = ({ export const EmptyDocs = ({
type = 'all', type = 'all',
tagId, tagId,
allowCreate = true,
...props ...props
}: EmptyDocsProps) => { }: EmptyDocsProps) => {
const t = useI18n(); const t = useI18n();
@@ -56,7 +58,7 @@ export const EmptyDocs = ({
: t['com.affine.empty.docs.all-description']() : t['com.affine.empty.docs.all-description']()
} }
action={ action={
showActionButton ? ( allowCreate && showActionButton ? (
<ActionButton onClick={onCreate} prefix={<AllDocsIcon />}> <ActionButton onClick={onCreate} prefix={<AllDocsIcon />}>
{t['com.affine.empty.docs.action.new-doc']()} {t['com.affine.empty.docs.action.new-doc']()}
</ActionButton> </ActionButton>
@@ -81,12 +81,13 @@ const ExplorerDisplayMenu = ({
</span> </span>
</div> </div>
</MenuSub> </MenuSub>
<Divider size="thinner" /> <Divider space={4} size="thinner" />
<DisplayProperties <DisplayProperties
displayPreference={displayPreference} displayPreference={displayPreference}
onDisplayPreferenceChange={onDisplayPreferenceChange} onDisplayPreferenceChange={onDisplayPreferenceChange}
/> />
<Divider size="thinner" />
<Divider space={4} size="thinner" />
<QuickActionsConfig <QuickActionsConfig
displayPreference={displayPreference} displayPreference={displayPreference}
onDisplayPreferenceChange={onDisplayPreferenceChange} onDisplayPreferenceChange={onDisplayPreferenceChange}
@@ -102,26 +102,30 @@ export const DisplayProperties = ({
); );
})} })}
</div> </div>
<Divider size="thinner" /> {displayPreference.view === 'list' ? (
<section className={styles.sectionLabel}> <>
{t['com.affine.all-docs.display.list-view']()} <Divider space={4} size="thinner" />
</section> <section className={styles.sectionLabel}>
<div className={styles.properties}> {t['com.affine.all-docs.display.list-view']()}
<Button </section>
className={styles.property} <div className={styles.properties}>
data-show={showIcon} <Button
onClick={toggleIcon} className={styles.property}
> data-show={showIcon}
{t['com.affine.all-docs.display.list-view.icon']()} onClick={toggleIcon}
</Button> >
<Button {t['com.affine.all-docs.display.list-view.icon']()}
className={styles.property} </Button>
data-show={showBody} <Button
onClick={toggleBody} className={styles.property}
> data-show={showBody}
{t['com.affine.all-docs.display.list-view.body']()} onClick={toggleBody}
</Button> >
</div> {t['com.affine.all-docs.display.list-view.body']()}
</Button>
</div>
</>
) : null}
</div> </div>
); );
}; };
@@ -11,6 +11,7 @@ import { useLiveData, useService } from '@toeverything/infra';
import { cssVarV2 } from '@toeverything/theme/v2'; import { cssVarV2 } from '@toeverything/theme/v2';
import { memo, useCallback, useContext, useEffect, useMemo } from 'react'; import { memo, useCallback, useContext, useEffect, useMemo } from 'react';
import { EmptyDocs } from '../../affine/empty';
import { ListFloatingToolbar } from '../../page-list/components/list-floating-toolbar'; import { ListFloatingToolbar } from '../../page-list/components/list-floating-toolbar';
import { SystemPropertyTypes } from '../../system-property-types'; import { SystemPropertyTypes } from '../../system-property-types';
import { WorkspacePropertyTypes } from '../../workspace-property-types'; import { WorkspacePropertyTypes } from '../../workspace-property-types';
@@ -228,6 +229,12 @@ export const DocsExplorer = ({
[] []
); );
const isEmpty = masonryItems.length === 0;
if (isEmpty) {
return <EmptyDocs allowCreate={false} style={{ height: '100%' }} />;
}
return ( return (
<> <>
<Masonry <Masonry
@@ -0,0 +1,33 @@
import { Menu, type MenuProps, type MenuRef } from '@affine/component';
import { useEffect, useRef } from 'react';
export const FilterValueMenu = ({
isDraft,
rootOptions,
contentOptions,
onDraftCompleted,
...otherProps
}: { isDraft?: boolean; onDraftCompleted?: () => void } & MenuProps) => {
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return (
<Menu
ref={menuRef}
rootOptions={{
onClose: onDraftCompleted,
...rootOptions,
}}
contentOptions={{
alignOffset: -4,
...contentOptions,
}}
{...otherProps}
/>
);
};
@@ -1,6 +1,7 @@
import { Menu, MenuItem, type MenuRef } from '@affine/component'; import { MenuItem } from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules'; import type { FilterParams } from '@affine/core/modules/collection-rules';
import { useEffect, useRef } from 'react';
import { FilterValueMenu } from '../filter/filter-value-menu';
export const FavoriteFilterValue = ({ export const FavoriteFilterValue = ({
filter, filter,
@@ -13,20 +14,10 @@ export const FavoriteFilterValue = ({
onDraftCompleted?: () => void; onDraftCompleted?: () => void;
onChange?: (filter: FilterParams) => void; onChange?: (filter: FilterParams) => void;
}) => { }) => {
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return ( return (
<Menu <FilterValueMenu
ref={menuRef} isDraft={isDraft}
rootOptions={{ onDraftCompleted={onDraftCompleted}
onClose: onDraftCompleted,
}}
items={ items={
<> <>
<MenuItem <MenuItem
@@ -55,6 +46,6 @@ export const FavoriteFilterValue = ({
} }
> >
<span>{filter.value === 'true' ? 'True' : 'False'}</span> <span>{filter.value === 'true' ? 'True' : 'False'}</span>
</Menu> </FilterValueMenu>
); );
}; };
@@ -1,14 +1,17 @@
import { Menu, MenuItem, type MenuRef } from '@affine/component'; import { MenuItem } from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules'; import type { FilterParams } from '@affine/core/modules/collection-rules';
import type { DocRecord } from '@affine/core/modules/doc'; import type { DocRecord } from '@affine/core/modules/doc';
import { IntegrationTypeIcon } from '@affine/core/modules/integration';
import { INTEGRATION_TYPE_NAME_MAP } from '@affine/core/modules/integration/constant';
import type { IntegrationType } from '@affine/core/modules/integration/type';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { IntegrationsIcon, ReadwiseIcon } from '@blocksuite/icons/rc'; import { IntegrationsIcon, ReadwiseIcon } from '@blocksuite/icons/rc';
import { useLiveData } from '@toeverything/infra'; import { useLiveData } from '@toeverything/infra';
import { useEffect, useRef } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header'; import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property'; import { StackProperty } from '../explorer/docs-view/stack-property';
import type { GroupHeaderProps } from '../explorer/types'; import type { GroupHeaderProps } from '../explorer/types';
import { FilterValueMenu } from '../filter/filter-value-menu';
export const IntegrationTypeFilterValue = ({ export const IntegrationTypeFilterValue = ({
filter, filter,
@@ -22,41 +25,37 @@ export const IntegrationTypeFilterValue = ({
onChange?: (filter: FilterParams) => void; onChange?: (filter: FilterParams) => void;
}) => { }) => {
const t = useI18n(); const t = useI18n();
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return ( return (
<Menu <FilterValueMenu
ref={menuRef} isDraft={isDraft}
rootOptions={{ onDraftCompleted={onDraftCompleted}
onClose: onDraftCompleted, items={Object.entries(INTEGRATION_TYPE_NAME_MAP).map(entries => {
}} const type = entries[0] as IntegrationType;
items={ const i18nKey = entries[1];
<MenuItem return (
onClick={() => { <MenuItem
onChange?.({ key={type}
...filter, onClick={() => {
value: 'readwise', onChange?.({
}); ...filter,
}} value: type,
prefixIcon={<ReadwiseIcon />} });
selected={filter.value === 'readwise'} }}
> prefixIcon={<IntegrationTypeIcon type={type} />}
{t['com.affine.integration.readwise.name']()} selected={filter.value === type}
</MenuItem> >
} {t.t(i18nKey)}
</MenuItem>
);
})}
> >
<span> <span>
{filter.value === 'readwise' {INTEGRATION_TYPE_NAME_MAP[filter.value as IntegrationType]
? t['com.affine.integration.readwise.name']() ? t.t(INTEGRATION_TYPE_NAME_MAP[filter.value as IntegrationType])
: filter.value} : filter.value}
</span> </span>
</Menu> </FilterValueMenu>
); );
}; };
@@ -1,6 +1,7 @@
import { Menu, MenuItem, type MenuRef } from '@affine/component'; import { MenuItem } from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules'; import type { FilterParams } from '@affine/core/modules/collection-rules';
import { useEffect, useRef } from 'react';
import { FilterValueMenu } from '../filter/filter-value-menu';
export const SharedFilterValue = ({ export const SharedFilterValue = ({
filter, filter,
@@ -13,20 +14,10 @@ export const SharedFilterValue = ({
onDraftCompleted?: () => void; onDraftCompleted?: () => void;
onChange?: (filter: FilterParams) => void; onChange?: (filter: FilterParams) => void;
}) => { }) => {
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return ( return (
<Menu <FilterValueMenu
ref={menuRef} isDraft={isDraft}
rootOptions={{ onDraftCompleted={onDraftCompleted}
onClose: onDraftCompleted,
}}
items={ items={
<> <>
<MenuItem <MenuItem
@@ -55,6 +46,6 @@ export const SharedFilterValue = ({
} }
> >
<span>{filter.value === 'true' ? 'True' : 'False'}</span> <span>{filter.value === 'true' ? 'True' : 'False'}</span>
</Menu> </FilterValueMenu>
); );
}; };
@@ -1,18 +1,13 @@
import { import { Checkbox, MenuItem, PropertyValue } from '@affine/component';
Checkbox,
Menu,
MenuItem,
type MenuRef,
PropertyValue,
} from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules'; import type { FilterParams } from '@affine/core/modules/collection-rules';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { CheckBoxCheckLinearIcon } from '@blocksuite/icons/rc'; import { CheckBoxCheckLinearIcon } from '@blocksuite/icons/rc';
import { useCallback, useEffect, useRef } from 'react'; import { useCallback } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header'; import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property'; import { StackProperty } from '../explorer/docs-view/stack-property';
import type { DocListPropertyProps, GroupHeaderProps } from '../explorer/types'; import type { DocListPropertyProps, GroupHeaderProps } from '../explorer/types';
import { FilterValueMenu } from '../filter/filter-value-menu';
import type { PropertyValueProps } from '../properties/types'; import type { PropertyValueProps } from '../properties/types';
import * as styles from './checkbox.css'; import * as styles from './checkbox.css';
@@ -55,20 +50,10 @@ export const CheckboxFilterValue = ({
onDraftCompleted?: () => void; onDraftCompleted?: () => void;
onChange?: (filter: FilterParams) => void; onChange?: (filter: FilterParams) => void;
}) => { }) => {
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return ( return (
<Menu <FilterValueMenu
ref={menuRef} isDraft={isDraft}
rootOptions={{ onDraftCompleted={onDraftCompleted}
onClose: onDraftCompleted,
}}
items={ items={
<> <>
<MenuItem <MenuItem
@@ -97,7 +82,7 @@ export const CheckboxFilterValue = ({
} }
> >
<span>{filter.value === 'true' ? 'True' : 'False'}</span> <span>{filter.value === 'true' ? 'True' : 'False'}</span>
</Menu> </FilterValueMenu>
); );
}; };
@@ -9,3 +9,7 @@ export const userLabelContainer = style({
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
}); });
export const filterValueMenu = style({
top: 'calc(var(--radix-popper-anchor-height) - 18px) !important',
});
@@ -160,6 +160,7 @@ export const CreatedByUpdatedByFilterValue = ({
onChange={handleChange} onChange={handleChange}
ref={menuRef} ref={menuRef}
onEditorClose={onDraftCompleted} onEditorClose={onDraftCompleted}
menuClassName={styles.filterValueMenu}
/> />
); );
}; };
@@ -19,6 +19,7 @@ import {
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header'; import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property'; import { StackProperty } from '../explorer/docs-view/stack-property';
import type { DocListPropertyProps, GroupHeaderProps } from '../explorer/types'; import type { DocListPropertyProps, GroupHeaderProps } from '../explorer/types';
import { FilterValueMenu } from '../filter/filter-value-menu';
import { FilterOptionsGroup } from '../filter/options'; import { FilterOptionsGroup } from '../filter/options';
import type { PropertyValueProps } from '../properties/types'; import type { PropertyValueProps } from '../properties/types';
import * as styles from './date.css'; import * as styles from './date.css';
@@ -123,11 +124,14 @@ const DateSelectorMenu = ({
); );
return ( return (
<Menu <FilterValueMenu
rootOptions={{ rootOptions={{
open, open,
onOpenChange: handleOpenChange, onOpenChange: handleOpenChange,
}} }}
contentOptions={{
style: { padding: '12px 16px' },
}}
items={<DatePicker value={value || undefined} onChange={handleChange} />} items={<DatePicker value={value || undefined} onChange={handleChange} />}
> >
{value ? ( {value ? (
@@ -137,7 +141,7 @@ const DateSelectorMenu = ({
{t['com.affine.filter.empty']()} {t['com.affine.filter.empty']()}
</span> </span>
)} )}
</Menu> </FilterValueMenu>
); );
}; };
@@ -1,7 +1,5 @@
import { import {
Menu,
MenuItem, MenuItem,
type MenuRef,
notify, notify,
PropertyValue, PropertyValue,
type RadioItem, type RadioItem,
@@ -12,11 +10,12 @@ import { useI18n } from '@affine/i18n';
import type { DocMode } from '@blocksuite/affine/model'; import type { DocMode } from '@blocksuite/affine/model';
import { EdgelessIcon, PageIcon } from '@blocksuite/icons/rc'; import { EdgelessIcon, PageIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useMemo } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header'; import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property'; import { StackProperty } from '../explorer/docs-view/stack-property';
import type { DocListPropertyProps, GroupHeaderProps } from '../explorer/types'; import type { DocListPropertyProps, GroupHeaderProps } from '../explorer/types';
import { FilterValueMenu } from '../filter/filter-value-menu';
import type { PropertyValueProps } from '../properties/types'; import type { PropertyValueProps } from '../properties/types';
import { PropertyRadioGroup } from '../properties/widgets/radio-group'; import { PropertyRadioGroup } from '../properties/widgets/radio-group';
import * as styles from './doc-primary-mode.css'; import * as styles from './doc-primary-mode.css';
@@ -89,20 +88,11 @@ export const DocPrimaryModeFilterValue = ({
onChange?: (filter: FilterParams) => void; onChange?: (filter: FilterParams) => void;
}) => { }) => {
const t = useI18n(); const t = useI18n();
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return ( return (
<Menu <FilterValueMenu
ref={menuRef} isDraft={isDraft}
rootOptions={{ onDraftCompleted={onDraftCompleted}
onClose: onDraftCompleted,
}}
items={ items={
<> <>
<MenuItem <MenuItem
@@ -131,7 +121,7 @@ export const DocPrimaryModeFilterValue = ({
} }
> >
<span>{filter.value === 'edgeless' ? t['Edgeless']() : t['Page']()}</span> <span>{filter.value === 'edgeless' ? t['Edgeless']() : t['Page']()}</span>
</Menu> </FilterValueMenu>
); );
}; };
@@ -1,20 +1,15 @@
import { import { MenuItem, PropertyValue, type RadioItem } from '@affine/component';
Menu,
MenuItem,
type MenuRef,
PropertyValue,
type RadioItem,
} from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules'; import type { FilterParams } from '@affine/core/modules/collection-rules';
import { type DocRecord, DocService } from '@affine/core/modules/doc'; import { type DocRecord, DocService } from '@affine/core/modules/doc';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { EdgelessIcon } from '@blocksuite/icons/rc'; import { EdgelessIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useMemo } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header'; import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property'; import { StackProperty } from '../explorer/docs-view/stack-property';
import type { GroupHeaderProps } from '../explorer/types'; import type { GroupHeaderProps } from '../explorer/types';
import { FilterValueMenu } from '../filter/filter-value-menu';
import type { PropertyValueProps } from '../properties/types'; import type { PropertyValueProps } from '../properties/types';
import { PropertyRadioGroup } from '../properties/widgets/radio-group'; import { PropertyRadioGroup } from '../properties/widgets/radio-group';
import * as styles from './edgeless-theme.css'; import * as styles from './edgeless-theme.css';
@@ -97,20 +92,11 @@ export const EdgelessThemeFilterValue = ({
onChange?: (filter: FilterParams) => void; onChange?: (filter: FilterParams) => void;
}) => { }) => {
const t = useI18n(); const t = useI18n();
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return ( return (
<Menu <FilterValueMenu
ref={menuRef} isDraft={isDraft}
rootOptions={{ onDraftCompleted={onDraftCompleted}
onClose: onDraftCompleted,
}}
items={ items={
<> <>
<MenuItem <MenuItem
@@ -156,7 +142,7 @@ export const EdgelessThemeFilterValue = ({
? t['com.affine.themeSettings.light']() ? t['com.affine.themeSettings.light']()
: t['com.affine.themeSettings.dark']()} : t['com.affine.themeSettings.dark']()}
</span> </span>
</Menu> </FilterValueMenu>
); );
}; };
@@ -3,7 +3,6 @@ import {
DatePicker, DatePicker,
Menu, Menu,
MenuItem, MenuItem,
type MenuRef,
PropertyValue, PropertyValue,
} from '@affine/component'; } from '@affine/component';
import { MobileJournalConflictList } from '@affine/core/mobile/pages/workspace/detail/menu/journal-conflicts'; import { MobileJournalConflictList } from '@affine/core/mobile/pages/workspace/detail/menu/journal-conflicts';
@@ -25,6 +24,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header'; import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property'; import { StackProperty } from '../explorer/docs-view/stack-property';
import type { DocListPropertyProps, GroupHeaderProps } from '../explorer/types'; import type { DocListPropertyProps, GroupHeaderProps } from '../explorer/types';
import { FilterValueMenu } from '../filter/filter-value-menu';
import type { PropertyValueProps } from '../properties/types'; import type { PropertyValueProps } from '../properties/types';
import * as styles from './journal.css'; import * as styles from './journal.css';
@@ -192,20 +192,10 @@ export const JournalFilterValue = ({
onDraftCompleted?: () => void; onDraftCompleted?: () => void;
onChange?: (filter: FilterParams) => void; onChange?: (filter: FilterParams) => void;
}) => { }) => {
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return ( return (
<Menu <FilterValueMenu
ref={menuRef} isDraft={isDraft}
rootOptions={{ onDraftCompleted={onDraftCompleted}
onClose: onDraftCompleted,
}}
items={ items={
<> <>
<MenuItem <MenuItem
@@ -234,7 +224,7 @@ export const JournalFilterValue = ({
} }
> >
<span>{filter.value === 'true' ? 'True' : 'False'}</span> <span>{filter.value === 'true' ? 'True' : 'False'}</span>
</Menu> </FilterValueMenu>
); );
}; };
@@ -1,4 +1,4 @@
import { Input, Menu, type MenuRef, PropertyValue } from '@affine/component'; import { Input, PropertyValue } from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules'; import type { FilterParams } from '@affine/core/modules/collection-rules';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { NumberIcon } from '@blocksuite/icons/rc'; import { NumberIcon } from '@blocksuite/icons/rc';
@@ -8,13 +8,13 @@ import {
type ChangeEventHandler, type ChangeEventHandler,
useCallback, useCallback,
useEffect, useEffect,
useRef,
useState, useState,
} from 'react'; } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header'; import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property'; import { StackProperty } from '../explorer/docs-view/stack-property';
import type { GroupHeaderProps } from '../explorer/types'; import type { GroupHeaderProps } from '../explorer/types';
import { FilterValueMenu } from '../filter/filter-value-menu';
import type { PropertyValueProps } from '../properties/types'; import type { PropertyValueProps } from '../properties/types';
import * as styles from './number.css'; import * as styles from './number.css';
@@ -77,15 +77,8 @@ export const NumberFilterValue = ({
}) => { }) => {
const [tempValue, setTempValue] = useState(filter.value || ''); const [tempValue, setTempValue] = useState(filter.value || '');
const [valueMenuOpen, setValueMenuOpen] = useState(false); const [valueMenuOpen, setValueMenuOpen] = useState(false);
const menuRef = useRef<MenuRef>(null);
const t = useI18n(); const t = useI18n();
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
useEffect(() => { useEffect(() => {
// update temp value with new filter value // update temp value with new filter value
setTempValue(filter.value || ''); setTempValue(filter.value || '');
@@ -126,8 +119,7 @@ export const NumberFilterValue = ({
}, [isDraft, filter.method, onDraftCompleted]); }, [isDraft, filter.method, onDraftCompleted]);
return filter.method !== 'is-not-empty' && filter.method !== 'is-empty' ? ( return filter.method !== 'is-not-empty' && filter.method !== 'is-empty' ? (
<Menu <FilterValueMenu
ref={menuRef}
rootOptions={{ rootOptions={{
open: valueMenuOpen, open: valueMenuOpen,
onOpenChange: setValueMenuOpen, onOpenChange: setValueMenuOpen,
@@ -135,7 +127,6 @@ export const NumberFilterValue = ({
}} }}
contentOptions={{ contentOptions={{
onPointerDownOutside: submitTempValue, onPointerDownOutside: submitTempValue,
sideOffset: -28,
}} }}
items={ items={
<Input <Input
@@ -163,7 +154,7 @@ export const NumberFilterValue = ({
{t['com.affine.filter.empty']()} {t['com.affine.filter.empty']()}
</span> </span>
)} )}
</Menu> </FilterValueMenu>
) : null; ) : null;
}; };
@@ -1,21 +1,16 @@
import { import { MenuItem, PropertyValue, type RadioItem } from '@affine/component';
Menu,
MenuItem,
type MenuRef,
PropertyValue,
type RadioItem,
} from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules'; import type { FilterParams } from '@affine/core/modules/collection-rules';
import { type DocRecord, DocService } from '@affine/core/modules/doc'; import { type DocRecord, DocService } from '@affine/core/modules/doc';
import { EditorSettingService } from '@affine/core/modules/editor-setting'; import { EditorSettingService } from '@affine/core/modules/editor-setting';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { LongerIcon } from '@blocksuite/icons/rc'; import { LongerIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { useCallback, useEffect, useMemo, useRef } from 'react'; import { useCallback, useMemo } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header'; import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property'; import { StackProperty } from '../explorer/docs-view/stack-property';
import type { GroupHeaderProps } from '../explorer/types'; import type { GroupHeaderProps } from '../explorer/types';
import { FilterValueMenu } from '../filter/filter-value-menu';
import type { PropertyValueProps } from '../properties/types'; import type { PropertyValueProps } from '../properties/types';
import { PropertyRadioGroup } from '../properties/widgets/radio-group'; import { PropertyRadioGroup } from '../properties/widgets/radio-group';
import { container } from './page-width.css'; import { container } from './page-width.css';
@@ -99,20 +94,11 @@ export const PageWidthFilterValue = ({
onChange?: (filter: FilterParams) => void; onChange?: (filter: FilterParams) => void;
}) => { }) => {
const t = useI18n(); const t = useI18n();
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return ( return (
<Menu <FilterValueMenu
ref={menuRef} isDraft={isDraft}
rootOptions={{ onDraftCompleted={onDraftCompleted}
onClose: onDraftCompleted,
}}
items={ items={
<> <>
<MenuItem <MenuItem
@@ -153,7 +139,7 @@ export const PageWidthFilterValue = ({
'com.affine.settings.editorSettings.page.default-page-width.standard' 'com.affine.settings.editorSettings.page.default-page-width.standard'
]()} ]()}
</span> </span>
</Menu> </FilterValueMenu>
); );
}; };
@@ -24,3 +24,6 @@ export const groupHeaderLabel = style({
alignItems: 'center', alignItems: 'center',
gap: 4, gap: 4,
}); });
export const filterValueMenu = style({
top: 'calc(var(--radix-popper-anchor-height) - 18px) !important',
});
@@ -118,6 +118,7 @@ export const TagsFilterValue = ({
selectedTags={selectedTags} selectedTags={selectedTags}
onSelectTag={handleSelectTag} onSelectTag={handleSelectTag}
onDeselectTag={handleDeselectTag} onDeselectTag={handleDeselectTag}
menuClassName={styles.filterValueMenu}
tagMode="inline-tag" tagMode="inline-tag"
ref={menuRef} ref={menuRef}
onEditorClose={onDraftCompleted} onEditorClose={onDraftCompleted}
@@ -1,20 +1,15 @@
import { import { Checkbox, MenuItem, PropertyValue } from '@affine/component';
Checkbox,
Menu,
MenuItem,
type MenuRef,
PropertyValue,
} from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules'; import type { FilterParams } from '@affine/core/modules/collection-rules';
import { type DocRecord, DocService } from '@affine/core/modules/doc'; import { type DocRecord, DocService } from '@affine/core/modules/doc';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { TemplateIcon } from '@blocksuite/icons/rc'; import { TemplateIcon } from '@blocksuite/icons/rc';
import { useLiveData, useService } from '@toeverything/infra'; import { useLiveData, useService } from '@toeverything/infra';
import { type ChangeEvent, useCallback, useEffect, useRef } from 'react'; import { type ChangeEvent, useCallback } from 'react';
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header'; import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property'; import { StackProperty } from '../explorer/docs-view/stack-property';
import type { GroupHeaderProps } from '../explorer/types'; import type { GroupHeaderProps } from '../explorer/types';
import { FilterValueMenu } from '../filter/filter-value-menu';
import type { PropertyValueProps } from '../properties/types'; import type { PropertyValueProps } from '../properties/types';
import * as styles from './template.css'; import * as styles from './template.css';
@@ -95,20 +90,10 @@ export const TemplateFilterValue = ({
onDraftCompleted?: () => void; onDraftCompleted?: () => void;
onChange?: (filter: FilterParams) => void; onChange?: (filter: FilterParams) => void;
}) => { }) => {
const menuRef = useRef<MenuRef>(null);
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
return ( return (
<Menu <FilterValueMenu
ref={menuRef} isDraft={isDraft}
rootOptions={{ onDraftCompleted={onDraftCompleted}
onClose: onDraftCompleted,
}}
items={ items={
<> <>
<MenuItem <MenuItem
@@ -137,6 +122,6 @@ export const TemplateFilterValue = ({
} }
> >
<span>{filter.value === 'true' ? 'True' : 'False'}</span> <span>{filter.value === 'true' ? 'True' : 'False'}</span>
</Menu> </FilterValueMenu>
); );
}; };
@@ -1,4 +1,4 @@
import { Input, Menu, type MenuRef, PropertyValue } from '@affine/component'; import { Input, PropertyValue } from '@affine/component';
import type { FilterParams } from '@affine/core/modules/collection-rules'; import type { FilterParams } from '@affine/core/modules/collection-rules';
import { useI18n } from '@affine/i18n'; import { useI18n } from '@affine/i18n';
import { TextIcon, TextTypeIcon } from '@blocksuite/icons/rc'; import { TextIcon, TextTypeIcon } from '@blocksuite/icons/rc';
@@ -15,6 +15,7 @@ import {
import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header'; import { PlainTextDocGroupHeader } from '../explorer/docs-view/group-header';
import { StackProperty } from '../explorer/docs-view/stack-property'; import { StackProperty } from '../explorer/docs-view/stack-property';
import type { GroupHeaderProps } from '../explorer/types'; import type { GroupHeaderProps } from '../explorer/types';
import { FilterValueMenu } from '../filter/filter-value-menu';
import { ConfigModal } from '../mobile'; import { ConfigModal } from '../mobile';
import type { PropertyValueProps } from '../properties/types'; import type { PropertyValueProps } from '../properties/types';
import * as styles from './text.css'; import * as styles from './text.css';
@@ -188,15 +189,8 @@ export const TextFilterValue = ({
}) => { }) => {
const [tempValue, setTempValue] = useState(filter.value || ''); const [tempValue, setTempValue] = useState(filter.value || '');
const [valueMenuOpen, setValueMenuOpen] = useState(false); const [valueMenuOpen, setValueMenuOpen] = useState(false);
const menuRef = useRef<MenuRef>(null);
const t = useI18n(); const t = useI18n();
useEffect(() => {
if (isDraft) {
menuRef.current?.changeOpen(true);
}
}, [isDraft]);
useEffect(() => { useEffect(() => {
// update temp value with new filter value // update temp value with new filter value
setTempValue(filter.value || ''); setTempValue(filter.value || '');
@@ -237,8 +231,8 @@ export const TextFilterValue = ({
}, [isDraft, filter.method, onDraftCompleted]); }, [isDraft, filter.method, onDraftCompleted]);
return filter.method !== 'is-not-empty' && filter.method !== 'is-empty' ? ( return filter.method !== 'is-not-empty' && filter.method !== 'is-empty' ? (
<Menu <FilterValueMenu
ref={menuRef} isDraft={isDraft}
rootOptions={{ rootOptions={{
open: valueMenuOpen, open: valueMenuOpen,
onOpenChange: setValueMenuOpen, onOpenChange: setValueMenuOpen,
@@ -246,7 +240,6 @@ export const TextFilterValue = ({
}} }}
contentOptions={{ contentOptions={{
onPointerDownOutside: submitTempValue, onPointerDownOutside: submitTempValue,
sideOffset: -28,
}} }}
items={ items={
<Input <Input
@@ -272,7 +265,7 @@ export const TextFilterValue = ({
{t['com.affine.filter.empty']()} {t['com.affine.filter.empty']()}
</span> </span>
)} )}
</Menu> </FilterValueMenu>
) : null; ) : null;
}; };
@@ -45,4 +45,5 @@ export const container = style({
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
gap: 4, gap: 4,
alignItems: 'center',
}); });
@@ -9,7 +9,7 @@ import { nanoid } from 'nanoid';
import type { WorkspacePropertyType } from '../../workspace-property'; import type { WorkspacePropertyType } from '../../workspace-property';
const integrationType = f.enum('readwise', 'zotero'); const integrationType = f.enum('readwise');
export const AFFiNE_WORKSPACE_DB_SCHEMA = { export const AFFiNE_WORKSPACE_DB_SCHEMA = {
folders: { folders: {
@@ -13,7 +13,7 @@ import type { IntegrationProperty, IntegrationType } from './type';
// name // name
export const INTEGRATION_TYPE_NAME_MAP: Record<IntegrationType, I18nString> = { export const INTEGRATION_TYPE_NAME_MAP: Record<IntegrationType, I18nString> = {
readwise: 'com.affine.integration.name.readwise', readwise: 'com.affine.integration.name.readwise',
zotero: 'Zotero', // zotero: 'Zotero',
}; };
// schema // schema
@@ -50,7 +50,7 @@ export const INTEGRATION_PROPERTY_SCHEMA: {
icon: HistoryIcon, icon: HistoryIcon,
}, },
}, },
zotero: {}, // zotero: {},
}; };
// icon // icon
@@ -59,5 +59,5 @@ export const INTEGRATION_ICON_MAP: Record<
React.ComponentType<SVGProps<SVGSVGElement>> React.ComponentType<SVGProps<SVGSVGElement>>
> = { > = {
readwise: ReadwiseLogoDuotoneIcon, readwise: ReadwiseLogoDuotoneIcon,
zotero: () => null, // zotero: () => null,
}; };
@@ -9,7 +9,6 @@ export type IntegrationType = NonNullable<DocIntegrationRef['type']>;
export type IntegrationDocPropertiesMap = { export type IntegrationDocPropertiesMap = {
readwise: ReadwiseDocProperties; readwise: ReadwiseDocProperties;
zotero: never;
}; };
export type IntegrationProperty<T extends IntegrationType> = { export type IntegrationProperty<T extends IntegrationType> = {