fix(mobile): doc property styles (#8760)
fix AF-1582 fix AF-1671 - mobile doc info dialog styles - added ConfigModal for editing property values in modal, including: - workspace properties: text, number, tags - db properties: text, number, label, link
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
import { Button, Modal } from '@affine/component';
|
||||
import { useI18n } from '@affine/i18n';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
type CSSProperties,
|
||||
forwardRef,
|
||||
type HTMLProps,
|
||||
type ReactNode,
|
||||
} from 'react';
|
||||
|
||||
import { PageHeader } from '../page-header';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
interface ConfigModalProps {
|
||||
onBack?: () => void;
|
||||
onDone?: () => void;
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
title?: ReactNode;
|
||||
children: ReactNode;
|
||||
variant?: 'popup' | 'page';
|
||||
}
|
||||
|
||||
/**
|
||||
* A modal with a page header for configuring something on mobile (preferable to be fullscreen)
|
||||
*/
|
||||
export const ConfigModal = ({
|
||||
onBack,
|
||||
onDone,
|
||||
open,
|
||||
onOpenChange,
|
||||
title,
|
||||
children,
|
||||
variant = 'page',
|
||||
}: ConfigModalProps) => {
|
||||
const t = useI18n();
|
||||
return (
|
||||
<Modal
|
||||
onOpenChange={onOpenChange}
|
||||
open={open}
|
||||
fullScreen={variant === 'page'}
|
||||
width="100%"
|
||||
minHeight={0}
|
||||
animation="slideBottom"
|
||||
withoutCloseButton
|
||||
contentOptions={{
|
||||
onClick: e => {
|
||||
e.stopPropagation();
|
||||
},
|
||||
className:
|
||||
variant === 'page'
|
||||
? styles.pageModalContent
|
||||
: styles.popupModalContent,
|
||||
}}
|
||||
>
|
||||
{variant === 'page' ? (
|
||||
<PageHeader
|
||||
back={!!onBack}
|
||||
backAction={onBack}
|
||||
suffix={
|
||||
onDone ? (
|
||||
<Button
|
||||
style={{
|
||||
fontSize: 17,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
className={styles.doneButton}
|
||||
variant="plain"
|
||||
onClick={onDone}
|
||||
>
|
||||
{t['Done']()}
|
||||
</Button>
|
||||
) : undefined
|
||||
}
|
||||
>
|
||||
<div className={styles.pageTitle}>{title}</div>
|
||||
</PageHeader>
|
||||
) : null}
|
||||
<div
|
||||
className={
|
||||
variant === 'page' ? styles.pageContent : styles.popupContent
|
||||
}
|
||||
>
|
||||
{variant === 'page' ? null : (
|
||||
<div className={styles.popupTitle}>{title}</div>
|
||||
)}
|
||||
{children}
|
||||
{variant === 'popup' && onDone ? (
|
||||
<Button
|
||||
variant="primary"
|
||||
className={styles.bottomDoneButton}
|
||||
onClick={onDone}
|
||||
>
|
||||
{t['Done']()}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export const ConfigRow = forwardRef<HTMLDivElement, HTMLProps<HTMLDivElement>>(
|
||||
function ConfigRow({ children, className, ...attrs }, ref) {
|
||||
return (
|
||||
<div className={clsx(styles.rowItem, className)} ref={ref} {...attrs}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
export interface SettingGroupProps
|
||||
extends Omit<HTMLProps<HTMLDivElement>, 'title'> {
|
||||
title?: ReactNode;
|
||||
contentClassName?: string;
|
||||
contentStyle?: CSSProperties;
|
||||
}
|
||||
|
||||
export const ConfigRowGroup = forwardRef<HTMLDivElement, SettingGroupProps>(
|
||||
function ConfigRowGroup(
|
||||
{ children, title, className, contentClassName, contentStyle, ...attrs },
|
||||
ref
|
||||
) {
|
||||
return (
|
||||
<div className={clsx(styles.group, className)} ref={ref} {...attrs}>
|
||||
{title ? <div className={styles.groupTitle}>{title}</div> : null}
|
||||
<div
|
||||
className={clsx(styles.groupContent, contentClassName)}
|
||||
style={contentStyle}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
ConfigModal.RowGroup = ConfigRowGroup;
|
||||
ConfigModal.Row = ConfigRow;
|
||||
@@ -0,0 +1,89 @@
|
||||
import {
|
||||
bodyEmphasized,
|
||||
footnoteRegular,
|
||||
} from '@toeverything/theme/typography';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const pageModalContent = style({
|
||||
padding: 0,
|
||||
overflowY: 'auto',
|
||||
backgroundColor: cssVarV2('layer/background/secondary'),
|
||||
});
|
||||
|
||||
export const popupModalContent = style({});
|
||||
|
||||
export const pageTitle = style([
|
||||
bodyEmphasized,
|
||||
{
|
||||
color: cssVarV2('text/primary'),
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
]);
|
||||
|
||||
export const popupTitle = style([
|
||||
bodyEmphasized,
|
||||
{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
color: cssVarV2('text/primary'),
|
||||
},
|
||||
]);
|
||||
|
||||
export const pageContent = style({
|
||||
padding: '24px 16px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 16,
|
||||
});
|
||||
|
||||
export const popupContent = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 16,
|
||||
});
|
||||
|
||||
export const doneButton = style([
|
||||
bodyEmphasized,
|
||||
{
|
||||
color: cssVarV2('button/primary'),
|
||||
},
|
||||
]);
|
||||
|
||||
export const bottomDoneButton = style({
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const group = style({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
width: '100%',
|
||||
});
|
||||
|
||||
export const groupTitle = style([
|
||||
footnoteRegular,
|
||||
{
|
||||
color: cssVarV2('text/tertiary'),
|
||||
padding: 4,
|
||||
},
|
||||
]);
|
||||
|
||||
export const groupContent = style({
|
||||
background: cssVarV2('layer/background/primary'),
|
||||
borderRadius: 12,
|
||||
padding: 4,
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 8,
|
||||
});
|
||||
|
||||
export const rowItem = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
padding: '8px 4px',
|
||||
});
|
||||
2
packages/frontend/core/src/components/mobile/index.ts
Normal file
2
packages/frontend/core/src/components/mobile/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './config-modal';
|
||||
export * from './page-header';
|
||||
@@ -0,0 +1,115 @@
|
||||
import { IconButton, SafeArea } from '@affine/component';
|
||||
import { ArrowLeftSmallIcon } from '@blocksuite/icons/rc';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
forwardRef,
|
||||
type HtmlHTMLAttributes,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
} from 'react';
|
||||
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export interface PageHeaderProps
|
||||
extends Omit<HtmlHTMLAttributes<HTMLHeadElement>, 'prefix'> {
|
||||
/**
|
||||
* whether to show back button
|
||||
*/
|
||||
back?: boolean;
|
||||
/**
|
||||
* Override back button action
|
||||
*/
|
||||
backAction?: () => void;
|
||||
|
||||
/**
|
||||
* prefix content, shown after back button(if exists)
|
||||
*/
|
||||
prefix?: ReactNode;
|
||||
|
||||
/**
|
||||
* suffix content
|
||||
*/
|
||||
suffix?: ReactNode;
|
||||
|
||||
/**
|
||||
* Weather to center the content
|
||||
* @default true
|
||||
*/
|
||||
centerContent?: boolean;
|
||||
|
||||
prefixClassName?: string;
|
||||
prefixStyle?: React.CSSProperties;
|
||||
suffixClassName?: string;
|
||||
suffixStyle?: React.CSSProperties;
|
||||
}
|
||||
export const PageHeader = forwardRef<HTMLDivElement, PageHeaderProps>(
|
||||
function PageHeader(
|
||||
{
|
||||
back,
|
||||
backAction,
|
||||
prefix,
|
||||
suffix,
|
||||
children,
|
||||
className,
|
||||
centerContent = true,
|
||||
prefixClassName,
|
||||
prefixStyle,
|
||||
suffixClassName,
|
||||
suffixStyle,
|
||||
...attrs
|
||||
},
|
||||
ref
|
||||
) {
|
||||
const handleRouteBack = useCallback(() => {
|
||||
backAction ? backAction() : history.back();
|
||||
}, [backAction]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SafeArea
|
||||
top
|
||||
ref={ref}
|
||||
className={clsx(styles.root, className)}
|
||||
data-testid="mobile-page-header"
|
||||
{...attrs}
|
||||
>
|
||||
<header className={styles.inner}>
|
||||
<section
|
||||
className={clsx(styles.prefix, prefixClassName)}
|
||||
style={prefixStyle}
|
||||
>
|
||||
{back ? (
|
||||
<IconButton
|
||||
size={24}
|
||||
style={{ padding: 10 }}
|
||||
onClick={handleRouteBack}
|
||||
icon={<ArrowLeftSmallIcon />}
|
||||
data-testid="page-header-back"
|
||||
/>
|
||||
) : null}
|
||||
{prefix}
|
||||
</section>
|
||||
|
||||
<section
|
||||
className={clsx(styles.content, { center: centerContent })}
|
||||
>
|
||||
{children}
|
||||
</section>
|
||||
|
||||
<section
|
||||
className={clsx(styles.suffix, suffixClassName)}
|
||||
style={suffixStyle}
|
||||
>
|
||||
{suffix}
|
||||
</section>
|
||||
</header>
|
||||
</SafeArea>
|
||||
|
||||
{/* Spacer */}
|
||||
<SafeArea top>
|
||||
<div className={styles.headerSpacer} />
|
||||
</SafeArea>
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
@@ -0,0 +1,52 @@
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
|
||||
export const root = style({
|
||||
width: '100%',
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
zIndex: 1,
|
||||
backgroundColor: cssVarV2('layer/background/secondary'),
|
||||
});
|
||||
export const headerSpacer = style({
|
||||
height: 44,
|
||||
});
|
||||
export const inner = style({
|
||||
height: 44,
|
||||
padding: '0 6px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
});
|
||||
export const content = style({
|
||||
selectors: {
|
||||
'&.center': {
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
width: 'fit-content',
|
||||
maxWidth: 'calc(100% - 12px - 88px - 16px)',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
'&:not(.center)': {
|
||||
width: 0,
|
||||
flex: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
export const spacer = style({
|
||||
width: 0,
|
||||
flex: 1,
|
||||
});
|
||||
export const prefix = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 0,
|
||||
});
|
||||
export const suffix = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 6,
|
||||
});
|
||||
Reference in New Issue
Block a user