mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-02 01:49:51 +08:00
feat(core): doc database properties (#8520)
fix AF-1454
1. move inline tags editor to components
2. add progress component
3. adjust doc properties styles for desktop
4. subscribe bs database links and display in doc info
5. move update/create dates to doc info
6. a trivial e2e test
<div class='graphite__hidden'>
<div>🎥 Video uploaded on Graphite:</div>
<a href="https://app.graphite.dev/media/video/T2klNLEk0wxLh4NRDzhk/eed266c1-fdac-4f0e-baa9-4aa00d14a2e8.mp4">
<img src="https://app.graphite.dev/api/v1/graphite/video/thumbnail/T2klNLEk0wxLh4NRDzhk/eed266c1-fdac-4f0e-baa9-4aa00d14a2e8.mp4">
</a>
</div>
<video src="https://graphite-user-uploaded-assets-prod.s3.amazonaws.com/T2klNLEk0wxLh4NRDzhk/eed266c1-fdac-4f0e-baa9-4aa00d14a2e8.mp4">10月23日.mp4</video>
This commit is contained in:
@@ -129,15 +129,13 @@ export const propertyValueContainer = style({
|
||||
color: cssVarV2('text/placeholder'),
|
||||
},
|
||||
selectors: {
|
||||
'&[data-readonly="false"]': {
|
||||
'&[data-readonly="false"][data-hoverable="true"]': {
|
||||
cursor: 'pointer',
|
||||
},
|
||||
'&[data-readonly="false"]:hover': {
|
||||
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
|
||||
},
|
||||
'&[data-readonly="false"]:focus-within': {
|
||||
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
|
||||
},
|
||||
'&[data-readonly="false"][data-hoverable="true"]:is(:hover, :focus-within)':
|
||||
{
|
||||
backgroundColor: cssVarV2('layer/background/hoverOverlay'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -162,3 +160,67 @@ globalStyle(`${tableButton} svg`, {
|
||||
globalStyle(`${tableButton}:hover svg`, {
|
||||
color: cssVarV2('icon/primary'),
|
||||
});
|
||||
|
||||
export const section = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 8,
|
||||
});
|
||||
|
||||
export const sectionHeader = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
padding: '4px 6px',
|
||||
minHeight: 30,
|
||||
});
|
||||
|
||||
export const sectionHeaderTrigger = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
flex: 1,
|
||||
});
|
||||
|
||||
export const sectionHeaderIcon = style({
|
||||
width: 16,
|
||||
height: 16,
|
||||
fontSize: 16,
|
||||
color: cssVarV2('icon/primary'),
|
||||
});
|
||||
|
||||
export const sectionHeaderName = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
fontSize: cssVar('fontSm'),
|
||||
fontWeight: 500,
|
||||
whiteSpace: 'nowrap',
|
||||
selectors: {
|
||||
'&[data-collapsed="true"]': {
|
||||
color: cssVarV2('text/secondary'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const sectionCollapsedIcon = style({
|
||||
transition: 'all 0.2s ease-in-out',
|
||||
color: cssVarV2('icon/primary'),
|
||||
fontSize: 20,
|
||||
selectors: {
|
||||
'&[data-collapsed="true"]': {
|
||||
transform: 'rotate(90deg)',
|
||||
color: cssVarV2('icon/secondary'),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const sectionContent = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
selectors: {
|
||||
'&[hidden]': {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -3,7 +3,8 @@ import { FrameIcon } from '@blocksuite/icons/rc';
|
||||
import { useDraggable, useDropTarget } from '../dnd';
|
||||
import { MenuItem } from '../menu';
|
||||
import {
|
||||
PropertyCollapsible,
|
||||
PropertyCollapsibleContent,
|
||||
PropertyCollapsibleSection,
|
||||
PropertyName,
|
||||
PropertyRoot,
|
||||
PropertyValue,
|
||||
@@ -100,9 +101,9 @@ export const HideEmptyProperty = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const BasicPropertyCollapsible = () => {
|
||||
export const BasicPropertyCollapsibleContent = () => {
|
||||
return (
|
||||
<PropertyCollapsible collapsible>
|
||||
<PropertyCollapsibleContent collapsible>
|
||||
<PropertyRoot>
|
||||
<PropertyName name="Always show" icon={<FrameIcon />} />
|
||||
<PropertyValue>Value</PropertyValue>
|
||||
@@ -115,13 +116,24 @@ export const BasicPropertyCollapsible = () => {
|
||||
<PropertyName name="Hide" icon={<FrameIcon />} />
|
||||
<PropertyValue>Value</PropertyValue>
|
||||
</PropertyRoot>
|
||||
</PropertyCollapsible>
|
||||
</PropertyCollapsibleContent>
|
||||
);
|
||||
};
|
||||
|
||||
export const BasicPropertyCollapsibleSection = () => {
|
||||
return (
|
||||
<PropertyCollapsibleSection
|
||||
icon={<FrameIcon />}
|
||||
title="Collapsible Section"
|
||||
>
|
||||
<BasicPropertyCollapsibleContent />
|
||||
</PropertyCollapsibleSection>
|
||||
);
|
||||
};
|
||||
|
||||
export const PropertyCollapsibleCustomButton = () => {
|
||||
return (
|
||||
<PropertyCollapsible
|
||||
<PropertyCollapsibleContent
|
||||
collapsible
|
||||
collapseButtonText={({ hide, isCollapsed }) =>
|
||||
`${isCollapsed ? 'Show' : 'Hide'} ${hide} properties`
|
||||
@@ -139,6 +151,6 @@ export const PropertyCollapsibleCustomButton = () => {
|
||||
<PropertyName name="Hide" icon={<FrameIcon />} />
|
||||
<PropertyValue>Value</PropertyValue>
|
||||
</PropertyRoot>
|
||||
</PropertyCollapsible>
|
||||
</PropertyCollapsibleContent>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import type { Edge } from '@atlaskit/pragmatic-drag-and-drop-hitbox/closest-edge';
|
||||
import { ArrowDownSmallIcon, ArrowUpSmallIcon } from '@blocksuite/icons/rc';
|
||||
import {
|
||||
ArrowDownSmallIcon,
|
||||
ArrowUpSmallIcon,
|
||||
ToggleExpandIcon,
|
||||
} from '@blocksuite/icons/rc';
|
||||
import * as Collapsible from '@radix-ui/react-collapsible';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
createContext,
|
||||
@@ -24,7 +29,92 @@ const PropertyTableContext = createContext<{
|
||||
showAllHide: boolean;
|
||||
} | null>(null);
|
||||
|
||||
export const PropertyCollapsible = forwardRef<
|
||||
export const PropertyCollapsibleSection = forwardRef<
|
||||
HTMLDivElement,
|
||||
PropsWithChildren<{
|
||||
defaultCollapsed?: boolean;
|
||||
icon?: ReactNode;
|
||||
title: ReactNode;
|
||||
suffix?: ReactNode;
|
||||
collapsed?: boolean;
|
||||
onCollapseChange?: (collapsed: boolean) => void;
|
||||
}> &
|
||||
HTMLProps<HTMLDivElement>
|
||||
>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
defaultCollapsed = false,
|
||||
collapsed,
|
||||
onCollapseChange,
|
||||
icon,
|
||||
title,
|
||||
suffix,
|
||||
className,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [internalCollapsed, setInternalCollapsed] =
|
||||
useState(defaultCollapsed);
|
||||
|
||||
const handleCollapse = useCallback(
|
||||
(open: boolean) => {
|
||||
setInternalCollapsed(!open);
|
||||
onCollapseChange?.(!open);
|
||||
},
|
||||
[onCollapseChange]
|
||||
);
|
||||
|
||||
const finalCollapsed =
|
||||
collapsed !== undefined ? collapsed : internalCollapsed;
|
||||
|
||||
return (
|
||||
<Collapsible.Root
|
||||
{...props}
|
||||
ref={ref}
|
||||
className={clsx(styles.section, className)}
|
||||
open={!finalCollapsed}
|
||||
onOpenChange={handleCollapse}
|
||||
data-testid="property-collapsible-section"
|
||||
>
|
||||
<div
|
||||
className={styles.sectionHeader}
|
||||
data-testid="property-collapsible-section-header"
|
||||
>
|
||||
<Collapsible.Trigger
|
||||
role="button"
|
||||
data-testid="property-collapsible-section-trigger"
|
||||
className={styles.sectionHeaderTrigger}
|
||||
>
|
||||
{icon && <div className={styles.sectionHeaderIcon}>{icon}</div>}
|
||||
<div
|
||||
data-collapsed={finalCollapsed}
|
||||
className={styles.sectionHeaderName}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
<ToggleExpandIcon
|
||||
className={styles.sectionCollapsedIcon}
|
||||
data-collapsed={finalCollapsed}
|
||||
/>
|
||||
</Collapsible.Trigger>
|
||||
{suffix}
|
||||
</div>
|
||||
<Collapsible.Content
|
||||
data-testid="property-collapsible-section-content"
|
||||
className={styles.sectionContent}
|
||||
>
|
||||
{children}
|
||||
</Collapsible.Content>
|
||||
</Collapsible.Root>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
PropertyCollapsibleSection.displayName = 'PropertyCollapsibleSection';
|
||||
|
||||
export const PropertyCollapsibleContent = forwardRef<
|
||||
HTMLDivElement,
|
||||
PropsWithChildren<{
|
||||
collapsible?: boolean;
|
||||
@@ -124,7 +214,7 @@ export const PropertyCollapsible = forwardRef<
|
||||
}
|
||||
);
|
||||
|
||||
PropertyCollapsible.displayName = 'PropertyCollapsible';
|
||||
PropertyCollapsibleContent.displayName = 'PropertyCollapsible';
|
||||
|
||||
const PropertyRootContext = createContext<{
|
||||
mountValue: (payload: { isEmpty: boolean }) => () => void;
|
||||
@@ -249,28 +339,38 @@ export const PropertyName = ({
|
||||
|
||||
export const PropertyValue = forwardRef<
|
||||
HTMLDivElement,
|
||||
{ readonly?: boolean; isEmpty?: boolean } & HTMLProps<HTMLDivElement>
|
||||
>(({ children, className, readonly, isEmpty, ...props }, ref) => {
|
||||
const context = useContext(PropertyRootContext);
|
||||
{
|
||||
readonly?: boolean;
|
||||
isEmpty?: boolean;
|
||||
hoverable?: boolean;
|
||||
} & HTMLProps<HTMLDivElement>
|
||||
>(
|
||||
(
|
||||
{ children, className, readonly, isEmpty, hoverable = true, ...props },
|
||||
ref
|
||||
) => {
|
||||
const context = useContext(PropertyRootContext);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (context) {
|
||||
return context.mountValue({ isEmpty: !!isEmpty });
|
||||
}
|
||||
return;
|
||||
}, [context, isEmpty]);
|
||||
useLayoutEffect(() => {
|
||||
if (context) {
|
||||
return context.mountValue({ isEmpty: !!isEmpty });
|
||||
}
|
||||
return;
|
||||
}, [context, isEmpty]);
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(styles.propertyValueContainer, className)}
|
||||
data-readonly={readonly ? 'true' : 'false'}
|
||||
data-empty={isEmpty ? 'true' : 'false'}
|
||||
data-property-value
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx(styles.propertyValueContainer, className)}
|
||||
data-readonly={readonly ? 'true' : 'false'}
|
||||
data-empty={isEmpty ? 'true' : 'false'}
|
||||
data-hoverable={hoverable ? 'true' : 'false'}
|
||||
data-property-value
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
PropertyValue.displayName = 'PropertyValue';
|
||||
|
||||
Reference in New Issue
Block a user