mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-02-13 21:05:19 +00:00
feat(component): shortcut style for tooltip (#7721)
 - New `shortcut` prop for `<Tooltip />` - single key ```tsx <Tooltip shortcut="T" /> ``` - multiple ```tsx <Tooltip shortcut={["⌘", "K"]} /> ``` - Round tooltip's arrow - Use new design system colors - Replace some usage - App sidebar switch - Editor mode switch - New tab (new)
This commit is contained in:
@@ -1,11 +1,46 @@
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import { style } from '@vanilla-extract/css';
|
||||
export const tooltipContent = style({
|
||||
backgroundColor: cssVar('tooltip'),
|
||||
color: cssVar('white'),
|
||||
backgroundColor: cssVarV2('tooltips/background'),
|
||||
color: cssVarV2('tooltips/foreground'),
|
||||
padding: '5px 12px',
|
||||
fontSize: cssVar('fontSm'),
|
||||
lineHeight: '22px',
|
||||
borderRadius: '4px',
|
||||
maxWidth: '280px',
|
||||
});
|
||||
|
||||
export const withShortcut = style({
|
||||
display: 'flex',
|
||||
gap: 10,
|
||||
});
|
||||
export const withShortcutContent = style({
|
||||
whiteSpace: 'nowrap',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
});
|
||||
|
||||
export const shortcut = style({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 2,
|
||||
});
|
||||
export const command = style({
|
||||
background: cssVarV2('tooltips/secondaryBackground'),
|
||||
fontSize: cssVar('fontXs'),
|
||||
fontWeight: 400,
|
||||
lineHeight: '20px',
|
||||
height: 16,
|
||||
minWidth: 16,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '0 4px',
|
||||
borderRadius: 4,
|
||||
selectors: {
|
||||
'&[data-length="1"]': {
|
||||
width: 16,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import type { Meta, StoryFn } from '@storybook/react';
|
||||
import { useState } from 'react';
|
||||
|
||||
import { Button } from '../button';
|
||||
import { RadioGroup } from '../radio';
|
||||
import type { TooltipProps } from './index';
|
||||
import Tooltip from './index';
|
||||
|
||||
@@ -18,6 +20,77 @@ const Template: StoryFn<TooltipProps> = args => (
|
||||
export const Default: StoryFn<TooltipProps> = Template.bind(undefined);
|
||||
Default.args = {};
|
||||
|
||||
export const WithShortCut = () => {
|
||||
const shortCuts = [
|
||||
['Text', 'T'],
|
||||
['Bold', ['⌘', 'B']],
|
||||
['Quick Search', ['⌘', 'K']],
|
||||
['Share', ['⌘', 'Shift', 'S']],
|
||||
['Copy', ['$mod', '$shift', 'C']],
|
||||
] as Array<[string, string | string[]]>;
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 4 }}>
|
||||
{shortCuts.map(([name, shortcut]) => (
|
||||
<Tooltip shortcut={shortcut} content={name} key={name}>
|
||||
<Button>{name}</Button>
|
||||
</Tooltip>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const CustomAlign = () => {
|
||||
const [align, setAlign] = useState('center' as const);
|
||||
const _ = undefined;
|
||||
const positions = [
|
||||
// [top, left, right, bottom, translateX, translateY]
|
||||
[0, 0, _, _, _, _],
|
||||
[0, '50%', _, _, '-50%', _],
|
||||
[0, _, 0, _, _, _],
|
||||
['50%', 0, _, _, _, '-50%'],
|
||||
['50%', _, 0, _, _, '-50%'],
|
||||
[_, 0, _, 0, _, _],
|
||||
[_, '50%', _, 0, '-50%', _],
|
||||
[_, _, 0, 0, _, _],
|
||||
];
|
||||
return (
|
||||
<div>
|
||||
<RadioGroup
|
||||
items={['start', 'center', 'end']}
|
||||
value={align}
|
||||
onChange={setAlign}
|
||||
/>
|
||||
<div
|
||||
style={{
|
||||
width: '100%',
|
||||
height: 200,
|
||||
position: 'relative',
|
||||
border: '1px solid rgba(100,100,100,0.2)',
|
||||
marginTop: 40,
|
||||
}}
|
||||
>
|
||||
{positions.map(pos => {
|
||||
const key = pos.join('-');
|
||||
const style = {
|
||||
position: 'absolute',
|
||||
top: pos[0],
|
||||
left: pos[1],
|
||||
right: pos[2],
|
||||
bottom: pos[3],
|
||||
transform: `translate(${pos[4] ?? 0}, ${pos[5] ?? 0})`,
|
||||
} as const;
|
||||
return (
|
||||
<Tooltip align={align} content="This is a tooltip" key={key}>
|
||||
<Button style={style}>Show tooltip</Button>
|
||||
</Tooltip>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const WithCustomContent: StoryFn<TooltipProps> = args => (
|
||||
<Tooltip
|
||||
content={
|
||||
|
||||
@@ -4,15 +4,36 @@ import type {
|
||||
TooltipProps as RootProps,
|
||||
} from '@radix-ui/react-tooltip';
|
||||
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
|
||||
import { cssVar } from '@toeverything/theme';
|
||||
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||
import clsx from 'clsx';
|
||||
import type { ReactElement, ReactNode } from 'react';
|
||||
import { type ReactElement, type ReactNode } from 'react';
|
||||
|
||||
import { getCommand } from '../../utils/keyboard-mapping';
|
||||
import * as styles from './styles.css';
|
||||
|
||||
export interface TooltipProps {
|
||||
// `children` can not be string, number or even undefined
|
||||
children: ReactElement;
|
||||
content?: ReactNode;
|
||||
/**
|
||||
* When shortcut is provided, will use a single line layout
|
||||
*
|
||||
* ```tsx
|
||||
* <Tooltip shortcut="T" /> // [T]
|
||||
* <Tooltip shortcut="⌘ + K" /> // [⌘ + K]
|
||||
* <Tooltip shortcut={['⌘', 'K']} /> // [⌘] [K]
|
||||
* <Tooltip shortcut={['$mod', 'K']} /> // [⌘] [K] or [Ctrl] [K]
|
||||
* ```
|
||||
*
|
||||
* Mapping:
|
||||
* | Shortcut | macOS | Windows |
|
||||
* |----------|-------|---------|
|
||||
* | `$mod` | `⌘` | `Ctrl` |
|
||||
* | `$alt` | `⌥` | `Alt` |
|
||||
* | `$shift` | `⇧` | `Shift` |
|
||||
*/
|
||||
shortcut?: string | string[];
|
||||
side?: TooltipContentProps['side'];
|
||||
align?: TooltipContentProps['align'];
|
||||
|
||||
@@ -21,11 +42,32 @@ export interface TooltipProps {
|
||||
options?: Omit<TooltipContentProps, 'side' | 'align'>;
|
||||
}
|
||||
|
||||
const TooltipShortcut = ({ shortcut }: { shortcut: string | string[] }) => {
|
||||
const commands = (Array.isArray(shortcut) ? shortcut : [shortcut])
|
||||
.map(cmd => cmd.trim())
|
||||
.map(cmd => getCommand(cmd));
|
||||
|
||||
return (
|
||||
<div className={styles.shortcut}>
|
||||
{commands.map((cmd, index) => (
|
||||
<div
|
||||
key={`${index}-${cmd}`}
|
||||
className={styles.command}
|
||||
data-length={cmd.length}
|
||||
>
|
||||
{cmd}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Tooltip = ({
|
||||
children,
|
||||
content,
|
||||
side = 'top',
|
||||
align = 'center',
|
||||
shortcut,
|
||||
options,
|
||||
rootOptions,
|
||||
portalOptions,
|
||||
@@ -34,6 +76,7 @@ export const Tooltip = ({
|
||||
return children;
|
||||
}
|
||||
const { className, ...contentOptions } = options || {};
|
||||
const { style: contentStyle, ...restContentOptions } = contentOptions;
|
||||
return (
|
||||
<TooltipPrimitive.Provider>
|
||||
<TooltipPrimitive.Root delayDuration={500} {...rootOptions}>
|
||||
@@ -45,15 +88,31 @@ export const Tooltip = ({
|
||||
side={side}
|
||||
align={align}
|
||||
sideOffset={5}
|
||||
style={{ zIndex: 'var(--affine-z-index-popover)' }}
|
||||
{...contentOptions}
|
||||
style={{ zIndex: cssVar('zIndexPopover'), ...contentStyle }}
|
||||
{...restContentOptions}
|
||||
>
|
||||
{content}
|
||||
<TooltipPrimitive.Arrow
|
||||
height={6}
|
||||
width={10}
|
||||
fill="var(--affine-tooltip)"
|
||||
/>
|
||||
{shortcut ? (
|
||||
<div className={styles.withShortcut}>
|
||||
<div className={styles.withShortcutContent}>{content}</div>
|
||||
<TooltipShortcut shortcut={shortcut} />
|
||||
</div>
|
||||
) : (
|
||||
content
|
||||
)}
|
||||
<TooltipPrimitive.Arrow asChild>
|
||||
<svg
|
||||
width="10"
|
||||
height="6"
|
||||
viewBox="0 0 10 6"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M4.11111 5.55C4.55556 6.15 5.44445 6.15 5.88889 5.55L10 0H0L4.11111 5.55Z"
|
||||
fill={cssVarV2('tooltips/background')}
|
||||
/>
|
||||
</svg>
|
||||
</TooltipPrimitive.Arrow>
|
||||
</TooltipPrimitive.Content>
|
||||
</TooltipPrimitive.Portal>
|
||||
</TooltipPrimitive.Root>
|
||||
|
||||
Reference in New Issue
Block a user