💄 style: Update Sync Style

This commit is contained in:
canisminor1990 2024-05-05 00:19:50 +08:00
parent 4d573ab697
commit 3aa8be4a13
7 changed files with 293 additions and 227 deletions

View file

@ -4,7 +4,6 @@ import { ActionIcon, Logo, MobileNavBar } from '@lobehub/ui';
import { MessageSquarePlus } from 'lucide-react';
import { useRouter } from 'next/navigation';
import { memo } from 'react';
import { Flexbox } from 'react-layout-kit';
import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
import SyncStatusInspector from '@/features/SyncStatusInspector';
@ -16,25 +15,23 @@ import { mobileHeaderSticky } from '@/styles/mobileHeader';
const Header = memo(() => {
const [createSession] = useSessionStore((s) => [s.createSession]);
const router = useRouter();
const { enableWebrtc, showCreateSession } = useServerConfigStore(featureFlagsSelectors);
const { showCreateSession, enableWebrtc } = useServerConfigStore(featureFlagsSelectors);
return (
<MobileNavBar
left={
<Flexbox align={'center'} gap={8} horizontal style={{ marginLeft: 8 }}>
<UserAvatar onClick={() => router.push('/me')} size={32} />
<Logo type={'text'} />
{enableWebrtc && <SyncStatusInspector placement={'bottom'} />}
</Flexbox>
}
center={<Logo type={'text'} />}
left={<UserAvatar onClick={() => router.push('/me')} size={32} />}
right={
showCreateSession && (
<ActionIcon
icon={MessageSquarePlus}
onClick={() => createSession()}
size={MOBILE_HEADER_ICON_SIZE}
/>
)
<>
{enableWebrtc && <SyncStatusInspector mobile />}
{showCreateSession && (
<ActionIcon
icon={MessageSquarePlus}
onClick={() => createSession()}
size={MOBILE_HEADER_ICON_SIZE}
/>
)}
</>
}
style={mobileHeaderSticky}
/>

View file

@ -26,6 +26,7 @@ const useStyles = createStyles(
background: ${inverseTheme ? rgba(token.colorTextTertiary, 0.15) : token.colorFillTertiary};
border-radius: ${token.borderRadius}px;
box-shadow: 0 0 0 1px ${rgba(token.colorBorder, 0.1)} inset;
}
`,
);
@ -67,7 +68,7 @@ const HotKeys = memo<HotKeysProps>(({ keys, desc, inverseTheme }) => {
if (!desc) return content;
return (
<Flexbox gap={16} horizontal>
<Flexbox align={'center'} gap={4} style={{ paddingBottom: 4 }}>
{desc}
{content}
</Flexbox>

View file

@ -1,22 +1,25 @@
import { Icon, Tag } from '@lobehub/ui';
import { Badge, Button, Popover } from 'antd';
import { TooltipPlacement } from 'antd/es/tooltip';
import { Icon } from '@lobehub/ui';
import { Button, Popover } from 'antd';
import { LucideCloudCog, LucideCloudy } from 'lucide-react';
import Link from 'next/link';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { useOpenSettings } from '@/hooks/useInterceptingRoutes';
import { SettingsTabs } from '@/store/global/initialState';
import { useUserStore } from '@/store/user';
import { syncSettingsSelectors } from '@/store/user/selectors';
import { DisableTag } from './SyncTags';
interface DisableSyncProps {
mobile?: boolean;
noPopover?: boolean;
placement?: TooltipPlacement;
}
const DisableSync = memo<DisableSyncProps>(({ noPopover, placement = 'bottomLeft' }) => {
const DisableSync = memo<DisableSyncProps>(({ noPopover, mobile }) => {
const { t } = useTranslation('common');
const openSettings = useOpenSettings();
const [haveConfig, setSettings] = useUserStore((s) => [
!!syncSettingsSelectors.webrtcConfig(s).channelName,
s.setSettings,
@ -26,52 +29,53 @@ const DisableSync = memo<DisableSyncProps>(({ noPopover, placement = 'bottomLeft
setSettings({ sync: { webrtc: { enabled: true } } });
};
const tag = (
<div>
<Tag>
<Badge status="default" />
{t('sync.status.disabled')}
</Tag>
</div>
if (noPopover) return <DisableTag mobile={mobile} />;
const title = (
<Flexbox gap={8} horizontal>
<Icon icon={LucideCloudy} />
{t('sync.disabled.title')}
</Flexbox>
);
return noPopover ? (
tag
) : (
const content = (
<Flexbox gap={12} width={mobile ? 240 : 320}>
{t('sync.disabled.desc')}
{haveConfig ? (
<Flexbox gap={8} horizontal={!mobile}>
<Button
block
icon={<Icon icon={LucideCloudCog} />}
onClick={() => openSettings(SettingsTabs.Sync)}
>
{t('sync.disabled.actions.settings')}
</Button>
<Button block onClick={enableSync} type={'primary'}>
{t('sync.disabled.actions.enable')}
</Button>
</Flexbox>
) : (
<Button
block
icon={<Icon icon={LucideCloudCog} />}
onClick={() => openSettings(SettingsTabs.Sync)}
type={'primary'}
>
{t('sync.disabled.actions.settings')}
</Button>
)}
</Flexbox>
);
return (
<Popover
arrow={false}
content={
<Flexbox gap={12} width={320}>
{t('sync.disabled.desc')}
{haveConfig ? (
<Flexbox gap={8} horizontal>
<Link href={'/settings/sync'}>
<Button block icon={<Icon icon={LucideCloudCog} />}>
{t('sync.disabled.actions.settings')}
</Button>
</Link>
<Button block onClick={enableSync} type={'primary'}>
{t('sync.disabled.actions.enable')}
</Button>
</Flexbox>
) : (
<Link href={'/settings/sync'}>
<Button block icon={<Icon icon={LucideCloudCog} />} type={'primary'}>
{t('sync.disabled.actions.settings')}
</Button>
</Link>
)}
</Flexbox>
}
placement={placement}
title={
<Flexbox gap={8} horizontal>
<Icon icon={LucideCloudy} />
{t('sync.disabled.title')}
</Flexbox>
}
content={content}
placement={'bottomLeft'}
title={title}
trigger={['click']}
>
{tag}
<DisableTag mobile={mobile} />
</Popover>
);
});

View file

@ -1,21 +1,18 @@
import { ActionIcon, Avatar, Icon } from '@lobehub/ui';
import { Divider, Popover, Switch, Tag, Typography } from 'antd';
import { ActionIcon, Avatar, Icon, Snippet, Tag } from '@lobehub/ui';
import { Divider, Popover, Switch, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { TooltipPlacement } from 'antd/es/tooltip';
import isEqual from 'fast-deep-equal';
import { LucideCloudy, LucideLaptop, LucideSmartphone, SettingsIcon } from 'lucide-react';
import Link from 'next/link';
import { LucideCloudCog, LucideLaptop, LucideSmartphone } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { Flexbox } from 'react-layout-kit';
import { useOpenSettings } from '@/hooks/useInterceptingRoutes';
import { SettingsTabs } from '@/store/global/initialState';
import { useUserStore } from '@/store/user';
import { syncSettingsSelectors } from '@/store/user/selectors';
import { pathString } from '@/utils/url';
import EnableTag from './EnableTag';
const { Text } = Typography;
import { EnableTag } from './SyncTags';
const useStyles = createStyles(({ css, token, prefixCls }) => ({
text: css`
@ -26,18 +23,19 @@ const useStyles = createStyles(({ css, token, prefixCls }) => ({
}
`,
title: css`
color: ${token.colorTextTertiary};
flex: none;
color: ${token.colorTextSecondary};
`,
}));
interface EnableSyncProps {
hiddenActions?: boolean;
placement?: TooltipPlacement;
mobile?: boolean;
}
const EnableSync = memo<EnableSyncProps>(({ hiddenActions, placement = 'bottomLeft' }) => {
const EnableSync = memo<EnableSyncProps>(({ hiddenActions, mobile }) => {
const { t } = useTranslation('common');
const openSettings = useOpenSettings();
const { styles, theme } = useStyles();
const [syncStatus, isSyncing, channelName, enableWebRTC, setSettings] = useUserStore((s) => [
s.syncStatus,
@ -53,82 +51,80 @@ const EnableSync = memo<EnableSyncProps>(({ hiddenActions, placement = 'bottomLe
setSettings({ sync: { webrtc: { enabled } } });
};
const title = (
<Flexbox align={'center'} distribution={'space-between'} horizontal style={{ minWidth: 240 }}>
{t('sync.title')}
<Flexbox align={'center'} gap={8} horizontal>
{!hiddenActions && <Switch checked={enableWebRTC} onChange={switchSync} size={'small'} />}
{!hiddenActions && (
<ActionIcon
icon={LucideCloudCog}
onClick={() => openSettings(SettingsTabs.Sync)}
size={{ blockSize: 24, fontSize: 16 }}
title={t('sync.actions.settings')}
/>
)}
</Flexbox>
</Flexbox>
);
const content = (
<Flexbox gap={16} style={{ minWidth: 240 }}>
<Flexbox gap={4} width={'100%'}>
<div className={styles.title}>
{[t('sync.channel'), mobile && t(`sync.status.${syncStatus}`)]
.filter(Boolean)
.join(' · ')}
</div>
<Snippet language={'text'} style={{ flex: 1 }} type={'block'}>
{String(channelName)}
</Snippet>
</Flexbox>
<Divider dashed style={{ margin: 0 }} />
<Flexbox gap={12}>
{users.map((user) => (
<Flexbox align={'center'} gap={12} horizontal key={user.clientID}>
<Avatar
avatar={
<Icon
color={theme.colorBgLayout}
icon={user.isMobile ? LucideSmartphone : LucideLaptop}
size={{ fontSize: 24 }}
/>
}
background={theme.colorPrimary}
shape={'square'}
size={36}
style={{ flex: 'none' }}
/>
<Flexbox>
<Flexbox gap={8} horizontal>
{user.name || user.id}
{user.current && (
<Flexbox horizontal>
<Tag>{t('sync.awareness.current')}</Tag>
</Flexbox>
)}
</Flexbox>
<Typography.Text style={{ fontSize: 12 }} type={'secondary'}>
{[user.os, user.browser].join(' · ')}
</Typography.Text>
</Flexbox>
</Flexbox>
))}
</Flexbox>
</Flexbox>
);
return (
<Popover
arrow={false}
content={
<Flexbox gap={16}>
<Flexbox align={'center'} gap={24} horizontal>
<Flexbox
align={'center'}
className={styles.title}
gap={4}
horizontal
style={{ paddingInlineEnd: 12 }}
>
{t('sync.channel')}
<Text className={styles.text} copyable>
{channelName}
</Text>
</Flexbox>
</Flexbox>
<Divider dashed style={{ margin: 0 }} />
<Flexbox gap={12}>
{users.map((user) => (
<Flexbox gap={12} horizontal key={user.clientID}>
<Avatar
avatar={
<Icon
color={theme.purple}
icon={user.isMobile ? LucideSmartphone : LucideLaptop}
size={{ fontSize: 24 }}
/>
}
background={theme.purple1}
shape={'square'}
/>
<Flexbox>
<Flexbox gap={8} horizontal>
{user.name || user.id}
{user.current && (
<Flexbox horizontal>
<Tag bordered={false} color={'blue'}>
{t('sync.awareness.current')}
</Tag>
</Flexbox>
)}
</Flexbox>
<Typography.Text type={'secondary'}>
{user.os} · {user.browser}
</Typography.Text>
</Flexbox>
</Flexbox>
))}
</Flexbox>
</Flexbox>
}
placement={placement}
title={
<Flexbox distribution={'space-between'} horizontal>
<Flexbox align={'center'} gap={8} horizontal>
<Icon icon={LucideCloudy} />
{t('sync.title')}
{!hiddenActions && (
<Switch checked={enableWebRTC} onChange={switchSync} size={'small'} />
)}
</Flexbox>
{!hiddenActions && (
<Link href={pathString('/settings/sync')}>
<ActionIcon icon={SettingsIcon} title={t('sync.actions.settings')} />
</Link>
)}
</Flexbox>
}
content={content}
placement={mobile ? 'bottom' : 'bottomLeft'}
title={title}
trigger={['click']}
>
<div>
<EnableTag isSyncing={isSyncing} status={syncStatus} />
</div>
<EnableTag isSyncing={isSyncing} mobile={mobile} status={syncStatus} />
</Popover>
);
});

View file

@ -1,66 +0,0 @@
import { Icon, Tooltip } from '@lobehub/ui';
import { Badge, Tag } from 'antd';
import { LucideCloudy, LucideRefreshCw, LucideRouter, LucideWifiOff } from 'lucide-react';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
import { PeerSyncStatus } from '@/types/sync';
const EnableTag = memo<{ isSyncing: boolean; status: PeerSyncStatus }>(({ status, isSyncing }) => {
const { t } = useTranslation('common');
switch (status) {
case PeerSyncStatus.Connecting: {
return (
<Tag
bordered={false}
color={'blue'}
icon={<Badge color={'blue'} status="processing" />}
style={{ display: 'flex', gap: 4 }}
>
{t('sync.status.connecting')}
</Tag>
);
}
case PeerSyncStatus.Synced: {
return (
<Tag bordered={false} color={'green'} icon={<Icon icon={LucideCloudy} />}>
{t('sync.status.synced')}
</Tag>
);
}
case PeerSyncStatus.Ready: {
return (
<Tag bordered={false} color={'blue'} icon={<Icon icon={LucideRouter} />}>
{t('sync.status.ready')}
</Tag>
);
}
case PeerSyncStatus.Syncing: {
return (
<Tag
bordered={false}
color={'blue'}
icon={<Icon icon={LucideRefreshCw} spin={isSyncing} />}
>
{t('sync.status.syncing')}
</Tag>
);
}
case PeerSyncStatus.Unconnected: {
return (
<Tooltip title={t('sync.unconnected.tip')}>
<Tag bordered={false} color={'red'} icon={<Icon icon={LucideWifiOff} />}>
{t('sync.status.unconnected')}
</Tag>
</Tooltip>
);
}
}
});
export default EnableTag;

View file

@ -0,0 +1,137 @@
import { ActionIcon, DivProps, Icon, Tooltip } from '@lobehub/ui';
import { Tag } from 'antd';
import { useTheme } from 'antd-style';
import {
LucideCloudCog,
LucideCloudy,
LucideIcon,
LucideRefreshCw,
LucideRouter,
LucideWifiOff,
Radio,
} from 'lucide-react';
import { CSSProperties, memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { MOBILE_HEADER_ICON_SIZE } from '@/const/layoutTokens';
import { PeerSyncStatus } from '@/types/sync';
export const TAG_STYLE: CSSProperties = {
borderRadius: 12,
cursor: 'pointer',
display: 'block',
};
export interface EnableTagProps extends DivProps {
isSyncing: boolean;
mobile?: boolean;
status: PeerSyncStatus;
}
export const EnableTag = memo<EnableTagProps>(({ status, isSyncing, mobile, style, ...rest }) => {
const theme = useTheme();
const { t } = useTranslation('common');
const config = useMemo(() => {
switch (status) {
case PeerSyncStatus.Connecting: {
return {
icon: Radio,
style: {
background: theme.colorWarningBg,
color: theme.colorWarning,
},
title: t('sync.status.connecting'),
};
}
case PeerSyncStatus.Synced: {
return {
icon: LucideCloudy,
style: {
background: theme.colorSuccessBg,
color: theme.colorSuccess,
},
title: t('sync.status.synced'),
};
}
case PeerSyncStatus.Ready: {
return {
icon: LucideRouter,
style: {
background: theme.colorInfoBg,
color: theme.colorInfo,
},
title: t('sync.status.ready'),
};
}
case PeerSyncStatus.Syncing: {
return {
icon: LucideRefreshCw,
style: {
background: theme.colorWarningBg,
color: theme.colorWarning,
},
title: t('sync.status.syncing'),
};
}
case PeerSyncStatus.Unconnected: {
return {
icon: LucideWifiOff,
style: {
background: theme.colorErrorBg,
color: theme.colorError,
},
title: t('sync.status.unconnected'),
tooltip: t('sync.unconnected.tip'),
};
}
}
}, [status, t, theme]) as {
icon: LucideIcon;
style: CSSProperties;
title: string;
tooltip?: string;
};
if (mobile)
return (
<ActionIcon
color={config.style.color === theme.colorInfo ? undefined : config.style.color}
icon={config.icon}
loading={isSyncing}
size={MOBILE_HEADER_ICON_SIZE}
{...rest}
/>
);
const tag = (
<Tag
bordered={false}
icon={<Icon icon={config.icon} spin={isSyncing} />}
style={{ ...TAG_STYLE, ...config.style, ...style }}
{...rest}
>
{config.title}
</Tag>
);
if (!config.tooltip) return tag;
return <Tooltip title={config.tooltip}>{tag}</Tooltip>;
});
export const DisableTag = memo<DivProps & { mobile?: boolean }>(({ style, mobile, ...rest }) => {
const { t } = useTranslation('common');
if (mobile) return <ActionIcon icon={LucideCloudCog} size={MOBILE_HEADER_ICON_SIZE} {...rest} />;
return (
<Tag bordered={false} style={{ ...TAG_STYLE, ...style }} {...rest}>
{t('sync.status.disabled')}
</Tag>
);
});

View file

@ -1,4 +1,3 @@
import { TooltipPlacement } from 'antd/es/tooltip';
import { memo } from 'react';
import { useUserStore } from '@/store/user';
@ -9,19 +8,17 @@ import EnableSync from './EnableSync';
interface SyncStatusTagProps {
hiddenActions?: boolean;
hiddenEnableGuide?: boolean;
placement?: TooltipPlacement;
mobile?: boolean;
}
const SyncStatusTag = memo<SyncStatusTagProps>(
({ hiddenActions, placement, hiddenEnableGuide }) => {
const [enableSync] = useUserStore((s) => [s.syncEnabled]);
const SyncStatusTag = memo<SyncStatusTagProps>(({ hiddenActions, hiddenEnableGuide, mobile }) => {
const [enableSync] = useUserStore((s) => [s.syncEnabled]);
return enableSync ? (
<EnableSync hiddenActions={hiddenActions} placement={placement} />
) : (
<DisableSync noPopover={hiddenEnableGuide} placement={placement} />
);
},
);
return enableSync ? (
<EnableSync hiddenActions={hiddenActions} mobile={mobile} />
) : (
<DisableSync mobile={mobile} noPopover={hiddenEnableGuide} />
);
});
export default SyncStatusTag;