refactor: tooltip

pull/6220/head
tangjinzhou 2023-01-30 22:28:00 +08:00
parent 337d958c67
commit 1d01df4b85
14 changed files with 291 additions and 360 deletions

View File

@ -10,7 +10,7 @@ import RightOutlined from '@ant-design/icons-vue/RightOutlined';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import devWarning from '../vc-util/devWarning';
import omit from '../_util/omit';
import getPlacements from '../tooltip/placements';
import getPlacements from '../_util/placements';
export type DropdownProps = Partial<ExtractPropTypes<ReturnType<typeof dropdownProps>>>;

View File

@ -10,7 +10,7 @@ import './pagination/style';
// import './badge/style';
import './tabs/style';
import './input/style';
import './tooltip/style';
// import './tooltip/style';
import './popover/style';
import './popconfirm/style';
// import './menu/style';

View File

@ -41,7 +41,7 @@ import type { ComponentToken as NotificationComponentToken } from '../../notific
// import type { ComponentToken as TabsComponentToken } from '../../tabs/style';
// import type { ComponentToken as TagComponentToken } from '../../tag/style';
// import type { ComponentToken as TimelineComponentToken } from '../../timeline/style';
// import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style';
import type { ComponentToken as TooltipComponentToken } from '../../tooltip/style';
// import type { ComponentToken as TransferComponentToken } from '../../transfer/style';
// import type { ComponentToken as TypographyComponentToken } from '../../typography/style';
// import type { ComponentToken as UploadComponentToken } from '../../upload/style';
@ -106,7 +106,7 @@ export interface ComponentTokenMap {
Modal?: ModalComponentToken;
Message?: MessageComponentToken;
// Upload?: UploadComponentToken;
// Tooltip?: TooltipComponentToken;
Tooltip?: TooltipComponentToken;
// Table?: TableComponentToken;
// Space?: SpaceComponentToken;
// Progress?: ProgressComponentToken;

View File

@ -1,19 +1,26 @@
import type { ExtractPropTypes } from 'vue';
import { computed, watch, defineComponent, onMounted, ref } from 'vue';
import type { CSSProperties, ExtractPropTypes } from 'vue';
import { computed, watch, defineComponent, ref } from 'vue';
import VcTooltip from '../vc-tooltip';
import classNames from '../_util/classNames';
import PropTypes from '../_util/vue-types';
import warning from '../_util/warning';
import { getStyle, filterEmpty, isValidElement, initDefaultProps } from '../_util/props-util';
import {
getStyle,
filterEmpty,
isValidElement,
initDefaultProps,
isFragment,
} from '../_util/props-util';
import { cloneElement } from '../_util/vnode';
export type { TriggerType, TooltipPlacement } from './abstractTooltipProps';
import abstractTooltipProps from './abstractTooltipProps';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import getPlacements from './placements';
import getPlacements from '../_util/placements';
import firstNotUndefined from '../_util/firstNotUndefined';
import raf from '../_util/raf';
import { parseColor } from './util';
export type { AdjustOverflow, PlacementsConfig } from './placements';
export type { AdjustOverflow, PlacementsConfig } from '../_util/placements';
import useStyle from './style';
// https://github.com/react-component/tooltip
// https://github.com/yiminghe/dom-align
@ -26,10 +33,12 @@ export interface TooltipAlignConfig {
useCssBottom?: boolean;
useCssTransform?: boolean;
}
const splitObject = (obj: any, keys: string[]) => {
const picked = {};
const omitted = { ...obj };
const splitObject = <T extends CSSProperties>(
obj: T,
keys: (keyof T)[],
): Record<'picked' | 'omitted', T> => {
const picked: T = {} as T;
const omitted: T = { ...obj };
keys.forEach(key => {
if (obj && key in obj) {
picked[key] = obj[key];
@ -74,29 +83,32 @@ export default defineComponent({
slots: ['title'],
// emits: ['update:visible', 'visibleChange'],
setup(props, { slots, emit, attrs, expose }) {
const { prefixCls, getPopupContainer, direction } = useConfigInject('tooltip', props);
if (process.env.NODE_ENV !== 'production') {
[
['visible', 'open'],
['onVisibleChange', 'onOpenChange'],
].forEach(([deprecatedName, newName]) => {
warning(
props[deprecatedName] === undefined,
'Tooltip',
`\`${deprecatedName}\` is deprecated, please use \`${newName}\` instead.`,
);
});
}
const visible = ref(firstNotUndefined([props.visible, props.defaultVisible]));
const { prefixCls, getPopupContainer, direction } = useConfigInject('tooltip', props);
const mergedOpen = computed(() => props.open ?? props.visible);
const innerOpen = ref(firstNotUndefined([props.open, props.visible]));
const tooltip = ref();
onMounted(() => {
warning(
props.defaultVisible === undefined,
'Tooltip',
`'defaultVisible' is deprecated, please use 'v-model:visible'`,
);
});
let rafId: any;
watch(
() => props.visible,
val => {
raf.cancel(rafId);
rafId = raf(() => {
visible.value = !!val;
});
},
);
watch(mergedOpen, val => {
raf.cancel(rafId);
rafId = raf(() => {
innerOpen.value = !!val;
});
});
const isNoTitle = () => {
const title = props.title ?? slots.title;
return !title && title !== 0;
@ -104,12 +116,14 @@ export default defineComponent({
const handleVisibleChange = (val: boolean) => {
const noTitle = isNoTitle();
if (props.visible === undefined) {
visible.value = noTitle ? false : val;
if (mergedOpen.value === undefined) {
innerOpen.value = noTitle ? false : val;
}
if (!noTitle) {
emit('update:visible', val);
emit('visibleChange', val);
emit('update:open', val);
emit('openChange', val);
}
};
@ -117,7 +131,7 @@ export default defineComponent({
return tooltip.value.getPopupDomNode();
};
expose({ getPopupDomNode, visible, forcePopupAlign: () => tooltip.value?.forcePopupAlign() });
expose({ getPopupDomNode, open, forcePopupAlign: () => tooltip.value?.forcePopupAlign() });
const tooltipPlacements = computed(() => {
const { builtinPlacements, arrowPointAtCenter, autoAdjustOverflow } = props;
@ -139,7 +153,8 @@ export default defineComponent({
((elementType.__ANT_BUTTON === true || elementType === 'button') &&
isTrueProps(ele.props.disabled)) ||
(elementType.__ANT_SWITCH === true &&
(isTrueProps(ele.props.disabled) || isTrueProps(ele.props.loading)))
(isTrueProps(ele.props.disabled) || isTrueProps(ele.props.loading))) ||
(elementType.__ANT_RADIO === true && isTrueProps(ele.props.disabled))
) {
// Pick some layout related style properties up to span
// Prevent layout bugs like https://github.com/ant-design/ant-design/issues/5254
@ -153,14 +168,14 @@ export default defineComponent({
'display',
'zIndex',
]);
const spanStyle = {
const spanStyle: CSSProperties = {
display: 'inline-block', // default inline-block is important
...picked,
cursor: 'not-allowed',
lineHeight: 1, // use the true height of child nodes
width: ele.props && ele.props.block ? '100%' : null,
width: ele.props && ele.props.block ? '100%' : undefined,
};
const buttonStyle = {
const buttonStyle: CSSProperties = {
...omitted,
pointerEvents: 'none',
};
@ -190,46 +205,50 @@ export default defineComponent({
//
const placement = Object.keys(placements).find(
key =>
placements[key].points[0] === align.points[0] &&
placements[key].points[1] === align.points[1],
placements[key].points[0] === align.points?.[0] &&
placements[key].points[1] === align.points?.[1],
);
if (!placement) {
return;
if (placement) {
//
const rect = domNode.getBoundingClientRect();
const transformOrigin = {
top: '50%',
left: '50%',
};
if (placement.indexOf('top') >= 0 || placement.indexOf('Bottom') >= 0) {
transformOrigin.top = `${rect.height - align.offset[1]}px`;
} else if (placement.indexOf('Top') >= 0 || placement.indexOf('bottom') >= 0) {
transformOrigin.top = `${-align.offset[1]}px`;
}
if (placement.indexOf('left') >= 0 || placement.indexOf('Right') >= 0) {
transformOrigin.left = `${rect.width - align.offset[0]}px`;
} else if (placement.indexOf('right') >= 0 || placement.indexOf('Left') >= 0) {
transformOrigin.left = `${-align.offset[0]}px`;
}
domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`;
}
//
const rect = domNode.getBoundingClientRect();
const transformOrigin = {
top: '50%',
left: '50%',
};
if (placement.indexOf('top') >= 0 || placement.indexOf('Bottom') >= 0) {
transformOrigin.top = `${rect.height - align.offset[1]}px`;
} else if (placement.indexOf('Top') >= 0 || placement.indexOf('bottom') >= 0) {
transformOrigin.top = `${-align.offset[1]}px`;
}
if (placement.indexOf('left') >= 0 || placement.indexOf('Right') >= 0) {
transformOrigin.left = `${rect.width - align.offset[0]}px`;
} else if (placement.indexOf('right') >= 0 || placement.indexOf('Left') >= 0) {
transformOrigin.left = `${-align.offset[0]}px`;
}
domNode.style.transformOrigin = `${transformOrigin.left} ${transformOrigin.top}`;
};
const colorInfo = computed(() => parseColor(prefixCls.value, props.color));
const injectFromPopover = computed(() => (attrs as any)['data-popover-inject']);
const [wrapSSR, hashId] = useStyle(
prefixCls,
computed(() => !injectFromPopover.value),
);
return () => {
const { openClassName, overlayClassName } = props;
let children = filterEmpty(slots.default?.()) ?? null;
children = children.length === 1 ? children[0] : children;
let tempVisible = visible.value;
let tempVisible = innerOpen.value;
// Hide tooltip when there is no title
if (props.visible === undefined && isNoTitle()) {
if (mergedOpen.value === undefined && isNoTitle()) {
tempVisible = false;
}
if (!children) {
return null;
}
const child = getDisabledCompatibleChildren(
isValidElement(children) ? children : <span>{children}</span>,
isValidElement(children) && !isFragment(children) ? children : <span>{children}</span>,
);
const childCls = classNames({
[openClassName || `${prefixCls.value}-open`]: true,
@ -242,6 +261,7 @@ export default defineComponent({
},
colorInfo.value.className,
hashId.value,
);
const formattedOverlayInnerStyle = {
...props.overlayInnerStyle,
@ -261,7 +281,7 @@ export default defineComponent({
onVisibleChange: handleVisibleChange,
onPopupAlign,
};
return (
return wrapSSR(
<VcTooltip
{...vcTooltipProps}
v-slots={{
@ -271,8 +291,8 @@ export default defineComponent({
overlay: getOverlay,
}}
>
{visible.value ? cloneElement(child, { class: childCls }) : child}
</VcTooltip>
{innerOpen.value ? cloneElement(child, { class: childCls }) : child}
</VcTooltip>,
);
};
},

View File

@ -1,9 +1,10 @@
import type { CSSProperties, PropType } from 'vue';
import type { AlignType, BuildInPlacements } from '../vc-trigger/interface';
import type { AdjustOverflow } from './placements';
import type { AdjustOverflow } from '../_util/placements';
export type TriggerType = 'hover' | 'focus' | 'click' | 'contextmenu';
import type { PresetColorType } from '../_util/colors';
import type { LiteralUnion } from '../_util/type';
import { objectType } from '../_util/type';
export type TooltipPlacement =
| 'top'
| 'left'
@ -20,16 +21,14 @@ export type TooltipPlacement =
export default () => ({
trigger: [String, Array] as PropType<TriggerType | TriggerType[]>,
open: { type: Boolean, default: undefined },
/** @deprecated Please use `open` instead. */
visible: { type: Boolean, default: undefined },
defaultVisible: { type: Boolean, default: undefined },
placement: String as PropType<TooltipPlacement>,
color: String as PropType<LiteralUnion<PresetColorType>>,
transitionName: String,
overlayStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
overlayInnerStyle: {
type: Object as PropType<CSSProperties>,
default: undefined as CSSProperties,
},
overlayStyle: objectType<CSSProperties>(),
overlayInnerStyle: objectType<CSSProperties>(),
overlayClassName: String,
openClassName: String,
prefixCls: String,
@ -42,15 +41,13 @@ export default () => ({
default: undefined as boolean | AdjustOverflow,
},
destroyTooltipOnHide: { type: Boolean, default: undefined },
align: {
type: Object as PropType<AlignType>,
default: undefined as AlignType,
},
builtinPlacements: {
type: Object as PropType<BuildInPlacements>,
default: undefined as BuildInPlacements,
},
align: objectType<AlignType>(),
builtinPlacements: objectType<BuildInPlacements>(),
children: Array,
/** @deprecated Please use `onOpenChange` instead. */
onVisibleChange: Function as PropType<(vis: boolean) => void>,
/** @deprecated Please use `onUpdate:open` instead. */
'onUpdate:visible': Function as PropType<(vis: boolean) => void>,
onOpenChange: Function as PropType<(vis: boolean) => void>,
'onUpdate:open': Function as PropType<(vis: boolean) => void>,
});

View File

@ -62,7 +62,7 @@ export default defineComponent({
</script>
<style scoped>
#components-a-tooltip-demo-color .ant-btn {
:deep(#components-a-tooltip-demo-color) .ant-btn {
margin-right: 8px;
margin-bottom: 8px;
}

View File

@ -111,7 +111,7 @@ export default defineComponent({
});
</script>
<style scoped>
#components-a-tooltip-demo-placement .ant-btn {
:deep(#components-a-tooltip-demo-placement) .ant-btn {
width: 70px;
text-align: center;
padding: 0;

View File

@ -22,28 +22,27 @@ A simple text popup tip.
The following APIs are shared by Tooltip, Popconfirm, Popover.
| Property | Description | Type | Default |
| --- | --- | --- | --- |
| align | this value will be merged into placement's config, please refer to the settings [dom-align](https://github.com/yiminghe/dom-align) | Object | - |
| arrowPointAtCenter | Whether the arrow is pointed at the center of target | boolean | `false` |
| autoAdjustOverflow | Whether to adjust popup placement automatically when popup is off screen | boolean | `true` |
| color | The background color | string | - |
| defaultVisible | Whether the floating tooltip card is visible by default | boolean | `false` |
| destroyTooltipOnHide | Whether to destroy tooltip on hide | boolean | false |
| getPopupContainer | The DOM container of the tip, the default behavior is to create a `div` element in `body`. | Function(triggerNode) | () => document.body |
| mouseEnterDelay | Delay in seconds, before tooltip is shown on mouse enter | number | 0.1 |
| mouseLeaveDelay | Delay in seconds, before tooltip is hidden on mouse leave | number | 0.1 |
| overlayClassName | Class name of the tooltip card | string | - |
| overlayStyle | Style of the tooltip card | object | - |
| placement | The position of the tooltip relative to the target, which can be one of `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | `top` |
| trigger | Tooltip trigger mode | `hover` \| `focus` \| `click` \| `contextmenu` | `hover` |
| visible(v-model) | Whether the floating tooltip card is visible or not | boolean | `false` |
| Property | Description | Type | Default | Version |
| --- | --- | --- | --- | --- |
| align | this value will be merged into placement's config, please refer to the settings [dom-align](https://github.com/yiminghe/dom-align) | Object | - | |
| arrowPointAtCenter | Whether the arrow is pointed at the center of target | boolean | `false` | |
| autoAdjustOverflow | Whether to adjust popup placement automatically when popup is off screen | boolean | `true` | |
| color | The background color | string | - | |
| destroyTooltipOnHide | Whether to destroy tooltip on hide | boolean | false | |
| getPopupContainer | The DOM container of the tip, the default behavior is to create a `div` element in `body`. | (triggerNode: HTMLElement) => HTMLElement | () => document.body | |
| mouseEnterDelay | Delay in seconds, before tooltip is shown on mouse enter | number | 0.1 | |
| mouseLeaveDelay | Delay in seconds, before tooltip is hidden on mouse leave | number | 0.1 | |
| overlayClassName | Class name of the tooltip card | string | - | |
| overlayStyle | Style of the tooltip card | object | - | |
| placement | The position of the tooltip relative to the target, which can be one of `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | `top` | |
| trigger | Tooltip trigger mode | `hover` \| `focus` \| `click` \| `contextmenu` | `hover` | |
| open(v-model) | Whether the floating tooltip card is open or not, Use `visible` under 4.0.0 | boolean | `false` | 4.0.0 |
### events
| Events Name | Description | Arguments | |
| Events Name | Description | Arguments | Version |
| --- | --- | --- | --- |
| visibleChange | Callback executed when visibility of the tooltip card is changed | (visible) => void | - |
| openChange | Callback executed when visibility of the tooltip card is changed | (visible) => void | 4.0 |
## Note

View File

@ -23,28 +23,27 @@ cover: https://gw.alipayobjects.com/zos/alicdn/Vyyeu8jq2/Tooltp.svg
以下 API 为 Tooltip、Popconfirm、Popover 共享的 API。
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| align | 该值将合并到 placement 的配置中,设置参考 [dom-align](https://github.com/yiminghe/dom-align) | Object | 无 |
| arrowPointAtCenter | 箭头是否指向目标元素中心 | boolean | `false` |
| autoAdjustOverflow | 气泡被遮挡时自动调整位置 | boolean | `true` |
| color | 背景颜色 | string | 无 |
| defaultVisible | 默认是否显隐 | boolean | false |
| destroyTooltipOnHide | 隐藏后是否销毁 tooltip | boolean | false |
| getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | Function(triggerNode) | () => document.body |
| mouseEnterDelay | 鼠标移入后延时多少才显示 Tooltip单位秒 | number | 0.1 |
| mouseLeaveDelay | 鼠标移出后延时多少才隐藏 Tooltip单位秒 | number | 0.1 |
| overlayClassName | 卡片类名 | string | 无 |
| overlayStyle | 卡片样式 | object | 无 |
| placement | 气泡框位置,可选 `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | top |
| trigger | 触发行为,可选 `hover/focus/click/contextmenu` | string | hover |
| visible(v-model) | 用于手动控制浮层显隐 | boolean | false |
| 参数 | 说明 | 类型 | 默认值 | 版本 |
| --- | --- | --- | --- | --- |
| align | 该值将合并到 placement 的配置中,设置参考 [dom-align](https://github.com/yiminghe/dom-align) | Object | 无 | |
| arrowPointAtCenter | 箭头是否指向目标元素中心 | boolean | `false` | |
| autoAdjustOverflow | 气泡被遮挡时自动调整位置 | boolean | `true` | |
| color | 背景颜色 | string | 无 | |
| destroyTooltipOnHide | 隐藏后是否销毁 tooltip | boolean | false | |
| getPopupContainer | 浮层渲染父节点,默认渲染到 body 上 | (triggerNode: HTMLElement) => HTMLElement | () => document.body | |
| mouseEnterDelay | 鼠标移入后延时多少才显示 Tooltip单位秒 | number | 0.1 | |
| mouseLeaveDelay | 鼠标移出后延时多少才隐藏 Tooltip单位秒 | number | 0.1 | |
| overlayClassName | 卡片类名 | string | 无 | |
| overlayStyle | 卡片样式 | object | 无 | |
| placement | 气泡框位置,可选 `top` `left` `right` `bottom` `topLeft` `topRight` `bottomLeft` `bottomRight` `leftTop` `leftBottom` `rightTop` `rightBottom` | string | top | |
| trigger | 触发行为,可选 `hover/focus/click/contextmenu` | string | hover | |
| open(v-model) | 用于手动控制浮层显隐, 小于 4.0.0 使用 `visible` | boolean | false | 4.0 |
### 事件
| 事件名称 | 说明 | 回调参数 |
| ------------- | -------------- | ----------------- |
| visibleChange | 显示隐藏的回调 | (visible) => void |
| 事件名称 | 说明 | 回调参数 | 版本 |
| ---------- | -------------- | ----------------- | ---- |
| openChange | 显示隐藏的回调 | (visible) => void | 4.0 |
## 注意

View File

@ -1,225 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@tooltip-prefix-cls: ~'@{ant-prefix}-tooltip';
@tooltip-arrow-shadow-width: 3px;
@tooltip-arrow-rotate-width: sqrt(@tooltip-arrow-width * @tooltip-arrow-width * 2) +
@tooltip-arrow-shadow-width * 2;
@tooltip-arrow-offset-vertical: 5px; // 8 - 3px
@tooltip-arrow-offset-horizontal: 13px; // 16 - 3px
// Base class
.@{tooltip-prefix-cls} {
.reset-component();
position: absolute;
z-index: @zindex-tooltip;
display: block;
width: max-content;
width: intrinsic;
max-width: @tooltip-max-width;
visibility: visible;
&-content {
position: relative;
}
&-hidden {
display: none;
}
&-placement-top,
&-placement-topLeft,
&-placement-topRight {
padding-bottom: @tooltip-distance;
}
&-placement-right,
&-placement-rightTop,
&-placement-rightBottom {
padding-left: @tooltip-distance;
}
&-placement-bottom,
&-placement-bottomLeft,
&-placement-bottomRight {
padding-top: @tooltip-distance;
}
&-placement-left,
&-placement-leftTop,
&-placement-leftBottom {
padding-right: @tooltip-distance;
}
// Wrapper for the tooltip content
&-inner {
min-width: 30px;
min-height: 32px;
padding: 6px 8px;
color: @tooltip-color;
text-align: left;
text-decoration: none;
word-wrap: break-word;
background-color: @tooltip-bg;
border-radius: @tooltip-border-radius;
box-shadow: @box-shadow-base;
}
// Arrows
&-arrow {
position: absolute;
z-index: 2;
display: block;
width: @tooltip-arrow-rotate-width;
height: @tooltip-arrow-rotate-width;
overflow: hidden;
background: transparent;
pointer-events: none;
&-content {
// Use linear gradient to mix box shadow of tooltip inner
--antd-arrow-background-color: linear-gradient(
to right bottom,
fadeout(@tooltip-bg, 10%),
@tooltip-bg
);
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
width: @tooltip-arrow-width;
height: @tooltip-arrow-width;
margin: auto;
background-color: transparent;
content: '';
pointer-events: auto;
.roundedArrow(@tooltip-arrow-width, 5px);
}
}
&-placement-top &-arrow,
&-placement-topLeft &-arrow,
&-placement-topRight &-arrow {
bottom: 0;
transform: translateY(100%);
&-content {
box-shadow: @tooltip-arrow-shadow-width @tooltip-arrow-shadow-width 7px fade(@black, 7%);
transform: translateY((-@tooltip-arrow-rotate-width / 2)) rotate(45deg);
}
}
&-placement-top &-arrow {
left: 50%;
transform: translateY(100%) translateX(-50%);
}
&-placement-topLeft &-arrow {
left: @tooltip-arrow-offset-horizontal;
}
&-placement-topRight &-arrow {
right: @tooltip-arrow-offset-horizontal;
}
&-placement-right &-arrow,
&-placement-rightTop &-arrow,
&-placement-rightBottom &-arrow {
left: 0;
transform: translateX(-100%);
&-content {
box-shadow: -@tooltip-arrow-shadow-width @tooltip-arrow-shadow-width 7px fade(@black, 7%);
transform: translateX((@tooltip-arrow-rotate-width / 2)) rotate(135deg);
}
}
&-placement-right &-arrow {
top: 50%;
transform: translateX(-100%) translateY(-50%);
}
&-placement-rightTop &-arrow {
top: @tooltip-arrow-offset-vertical;
}
&-placement-rightBottom &-arrow {
bottom: @tooltip-arrow-offset-vertical;
}
&-placement-left &-arrow,
&-placement-leftTop &-arrow,
&-placement-leftBottom &-arrow {
right: 0;
transform: translateX(100%);
&-content {
box-shadow: @tooltip-arrow-shadow-width -@tooltip-arrow-shadow-width 7px fade(@black, 7%);
transform: translateX((-@tooltip-arrow-rotate-width / 2)) rotate(315deg);
}
}
&-placement-left &-arrow {
top: 50%;
transform: translateX(100%) translateY(-50%);
}
&-placement-leftTop &-arrow {
top: @tooltip-arrow-offset-vertical;
}
&-placement-leftBottom &-arrow {
bottom: @tooltip-arrow-offset-vertical;
}
&-placement-bottom &-arrow,
&-placement-bottomLeft &-arrow,
&-placement-bottomRight &-arrow {
top: 0;
transform: translateY(-100%);
&-content {
box-shadow: -@tooltip-arrow-shadow-width -@tooltip-arrow-shadow-width 7px fade(@black, 7%);
transform: translateY((@tooltip-arrow-rotate-width / 2)) rotate(225deg);
}
}
&-placement-bottom &-arrow {
left: 50%;
transform: translateY(-100%) translateX(-50%);
}
&-placement-bottomLeft &-arrow {
left: @tooltip-arrow-offset-horizontal;
}
&-placement-bottomRight &-arrow {
right: @tooltip-arrow-offset-horizontal;
}
}
.generator-tooltip-preset-color(@i: length(@preset-colors)) when (@i > 0) {
.generator-tooltip-preset-color(@i - 1);
@color: extract(@preset-colors, @i);
@lightColor: '@{color}-6';
.@{tooltip-prefix-cls}-@{color} {
.@{tooltip-prefix-cls}-inner {
background-color: @@lightColor;
}
.@{tooltip-prefix-cls}-arrow {
&-content::before {
background: @@lightColor;
}
}
}
}
.generator-tooltip-preset-color();
@import './rtl';

View File

@ -0,0 +1,157 @@
import { initZoomMotion } from '../../_style/motion';
import type { FullToken, GenerateStyle, UseComponentStyleResult } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import { genPresetColor, resetComponent } from '../../_style';
import getArrowStyle, { MAX_VERTICAL_CONTENT_RADIUS } from '../../_style/placementArrow';
import { Ref } from 'vue';
export interface ComponentToken {
zIndexPopup: number;
colorBgDefault: string;
}
interface TooltipToken extends FullToken<'Tooltip'> {
// default variables
tooltipMaxWidth: number;
tooltipColor: string;
tooltipBg: string;
tooltipBorderRadius: number;
tooltipRadiusOuter: number;
}
const genTooltipStyle: GenerateStyle<TooltipToken> = token => {
const {
componentCls, // ant-tooltip
tooltipMaxWidth,
tooltipColor,
tooltipBg,
tooltipBorderRadius,
zIndexPopup,
controlHeight,
boxShadowSecondary,
paddingSM,
paddingXS,
tooltipRadiusOuter,
} = token;
return [
{
[componentCls]: {
...resetComponent(token),
position: 'absolute',
zIndex: zIndexPopup,
display: 'block',
'&': [{ width: 'max-content' }, { width: 'intrinsic' }],
maxWidth: tooltipMaxWidth,
visibility: 'visible',
'&-hidden': {
display: 'none',
},
'--antd-arrow-background-color': tooltipBg,
// Wrapper for the tooltip content
[`${componentCls}-inner`]: {
minWidth: controlHeight,
minHeight: controlHeight,
padding: `${paddingSM / 2}px ${paddingXS}px`,
color: tooltipColor,
textAlign: 'start',
textDecoration: 'none',
wordWrap: 'break-word',
backgroundColor: tooltipBg,
borderRadius: tooltipBorderRadius,
boxShadow: boxShadowSecondary,
},
// Limit left and right placement radius
[[
`&-placement-left`,
`&-placement-leftTop`,
`&-placement-leftBottom`,
`&-placement-right`,
`&-placement-rightTop`,
`&-placement-rightBottom`,
].join(',')]: {
[`${componentCls}-inner`]: {
borderRadius: Math.min(tooltipBorderRadius, MAX_VERTICAL_CONTENT_RADIUS),
},
},
[`${componentCls}-content`]: {
position: 'relative',
},
// generator for preset color
...genPresetColor(token, (colorKey, { darkColor }) => ({
[`&${componentCls}-${colorKey}`]: {
[`${componentCls}-inner`]: {
backgroundColor: darkColor,
},
[`${componentCls}-arrow`]: {
'--antd-arrow-background-color': darkColor,
},
},
})),
// RTL
'&-rtl': {
direction: 'rtl',
},
},
},
// Arrow Style
getArrowStyle<TooltipToken>(
mergeToken<TooltipToken>(token, {
borderRadiusOuter: tooltipRadiusOuter,
}),
{
colorBg: 'var(--antd-arrow-background-color)',
showArrowCls: '',
contentRadius: tooltipBorderRadius,
limitVerticalRadius: true,
},
),
// Pure Render
{
[`${componentCls}-pure`]: {
position: 'relative',
maxWidth: 'none',
},
},
];
};
// ============================== Export ==============================
export default (prefixCls: Ref<string>, injectStyle: Ref<boolean>): UseComponentStyleResult => {
const useOriginHook = genComponentStyleHook(
'Tooltip',
token => {
// Popover use Tooltip as internal component. We do not need to handle this.
if (injectStyle.value === false) {
return [];
}
const { borderRadius, colorTextLightSolid, colorBgDefault, borderRadiusOuter } = token;
const TooltipToken = mergeToken<TooltipToken>(token, {
// default variables
tooltipMaxWidth: 250,
tooltipColor: colorTextLightSolid,
tooltipBorderRadius: borderRadius,
tooltipBg: colorBgDefault,
tooltipRadiusOuter: borderRadiusOuter > 4 ? 4 : borderRadiusOuter,
});
return [genTooltipStyle(TooltipToken), initZoomMotion(token, 'zoom-big-fast')];
},
({ zIndexPopupBase, colorBgSpotlight }) => ({
zIndexPopup: zIndexPopupBase + 70,
colorBgDefault: colorBgSpotlight,
}),
);
return useOriginHook(prefixCls);
};

View File

@ -1,2 +0,0 @@
import '../../style/index.less';
import './index.less';

View File

@ -1,14 +0,0 @@
@tooltip-prefix-cls: ~'@{ant-prefix}-tooltip';
// Base class
.@{tooltip-prefix-cls} {
&-rtl {
direction: rtl;
}
// Wrapper for the tooltip content
&-inner {
.@{tooltip-prefix-cls}-rtl & {
text-align: right;
}
}
}