refactor: dropdown

This commit is contained in:
tangjinzhou 2023-02-01 22:16:23 +08:00
parent 989bedda47
commit 47c84cdbad
24 changed files with 904 additions and 880 deletions

View File

@ -51,8 +51,8 @@ export function objectType<T>(defaultVal?: any) {
return { type: Object as PropType<T>, default: defaultVal as T };
}
export function booleanType<T>(defaultVal?: any) {
return { type: Boolean as PropType<T>, default: defaultVal as T };
export function booleanType(defaultVal?: any) {
return { type: Boolean, default: defaultVal as boolean };
}
export function someType<T>(types: any[], defaultVal?: any) {

View File

@ -1,12 +1,13 @@
import type { ExtractPropTypes, HTMLAttributes } from 'vue';
import { defineComponent } from 'vue';
import { computed, defineComponent } from 'vue';
import Button from '../button';
import classNames from '../_util/classNames';
import Dropdown from './dropdown';
import classNames from '../_util/classNames';
import { initDefaultProps } from '../_util/props-util';
import { dropdownButtonProps } from './props';
import EllipsisOutlined from '@ant-design/icons-vue/EllipsisOutlined';
import useConfigInject from '../config-provider/hooks/useConfigInject';
import useStyle from './style';
const ButtonGroup = Button.Group;
export type DropdownButtonProps = Partial<ExtractPropTypes<ReturnType<typeof dropdownButtonProps>>>;
@ -27,20 +28,25 @@ export default defineComponent({
const handleVisibleChange = (val: boolean) => {
emit('update:visible', val);
emit('visibleChange', val);
emit('update:open', val);
emit('openChange', val);
};
const { prefixCls, direction, getPopupContainer } = useConfigInject('dropdown-button', props);
const { prefixCls, direction, getPopupContainer } = useConfigInject('dropdown', props);
const buttonPrefixCls = computed(() => `${prefixCls.value}-button`);
const [wrapSSR, hashId] = useStyle(prefixCls);
return () => {
const {
type = 'default',
disabled,
danger,
loading,
htmlType,
class: className = '',
overlay = slots.overlay?.(),
trigger,
align,
open,
visible,
onVisibleChange: _onVisibleChange,
placement = direction.value === 'rtl' ? 'bottomLeft' : 'bottomRight',
@ -53,7 +59,7 @@ export default defineComponent({
overlayStyle,
destroyPopupOnHide,
onClick,
'onUpdate:visible': _updateVisible,
'onUpdate:open': _updateVisible,
...restProps
} = { ...props, ...attrs } as DropdownButtonProps & HTMLAttributes;
@ -63,10 +69,10 @@ export default defineComponent({
trigger: disabled ? [] : trigger,
placement,
getPopupContainer: getPopupContainer?.value,
onVisibleChange: handleVisibleChange,
onOpenChange: handleVisibleChange,
mouseEnterDelay,
mouseLeaveDelay,
visible,
open: open ?? visible,
overlayClassName,
overlayStyle,
destroyPopupOnHide,
@ -74,6 +80,7 @@ export default defineComponent({
const leftButton = (
<Button
danger={danger}
type={type}
disabled={disabled}
loading={loading}
@ -85,15 +92,18 @@ export default defineComponent({
></Button>
);
const rightButton = <Button type={type} icon={icon} />;
const rightButton = <Button danger={danger} type={type} icon={icon} />;
return (
<ButtonGroup {...restProps} class={classNames(prefixCls.value, className)}>
return wrapSSR(
<ButtonGroup
{...restProps}
class={classNames(buttonPrefixCls.value, className, hashId.value)}
>
{slots.leftButton ? slots.leftButton({ button: leftButton }) : leftButton}
<Dropdown {...dropdownProps} v-slots={{ overlay: () => overlay }}>
{slots.rightButton ? slots.rightButton({ button: rightButton }) : rightButton}
</Dropdown>
</ButtonGroup>
</ButtonGroup>,
);
};
},

View File

@ -11,6 +11,9 @@ import useConfigInject from '../config-provider/hooks/useConfigInject';
import devWarning from '../vc-util/devWarning';
import omit from '../_util/omit';
import getPlacements from '../_util/placements';
import warning from '../_util/warning';
import useStyle from './style';
import { useProvideOverride } from '../menu/src/OverrideContext';
export type DropdownProps = Partial<ExtractPropTypes<ReturnType<typeof dropdownProps>>>;
@ -31,18 +34,53 @@ const Dropdown = defineComponent({
'dropdown',
props,
);
const [wrapSSR, hashId] = useStyle(prefixCls);
// Warning for deprecated usage
if (process.env.NODE_ENV !== 'production') {
[
['visible', 'open'],
['onVisibleChange', 'onOpenChange'],
['onUpdate:visible', 'onUpdate:open'],
].forEach(([deprecatedName, newName]) => {
warning(
props[deprecatedName] === undefined,
'Dropdown',
`\`${deprecatedName}\` is deprecated which will be removed in next major version, please use \`${newName}\` instead.`,
);
});
}
const transitionName = computed(() => {
const { placement = '', transitionName } = props;
if (transitionName !== undefined) {
return transitionName;
}
if (placement.indexOf('top') >= 0) {
if (placement.includes('top')) {
return `${rootPrefixCls.value}-slide-down`;
}
return `${rootPrefixCls.value}-slide-up`;
});
useProvideOverride({
prefixCls: computed(() => `${prefixCls.value}-menu`),
expandIcon: computed(() => {
return (
<span class={`${prefixCls.value}-menu-submenu-arrow`}>
<RightOutlined class={`${prefixCls.value}-menu-submenu-arrow-icon`} />
</span>
);
}),
mode: computed(() => 'vertical'),
selectable: computed(() => false),
onClick: () => {},
validator: ({ mode }) => {
// Warning if use other mode
warning(
!mode || mode === 'vertical',
'Dropdown',
`mode="${mode}" is not supported for Dropdown's Menu.`,
);
},
});
const renderOverlay = () => {
// rc-dropdown already can process the function of overlay, but we have check logic here.
// So we need render the element to check and pass back to rc-dropdown.
@ -104,6 +142,8 @@ const Dropdown = defineComponent({
const handleVisibleChange = (val: boolean) => {
emit('update:visible', val);
emit('visibleChange', val);
emit('update:open', val);
emit('openChange', val);
};
return () => {
@ -125,13 +165,13 @@ const Dropdown = defineComponent({
),
);
const overlayClassNameCustomized = classNames(overlayClassName, {
const overlayClassNameCustomized = classNames(overlayClassName, hashId.value, {
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
});
const triggerActions = disabled ? [] : trigger;
let alignPoint: boolean;
if (triggerActions && triggerActions.indexOf('contextmenu') !== -1) {
if (triggerActions && triggerActions.includes('contextmenu')) {
alignPoint = true;
}
@ -156,14 +196,13 @@ const Dropdown = defineComponent({
},
['overlay', 'onUpdate:visible'],
);
return (
return wrapSSR(
<RcDropdown {...dropdownProps} v-slots={{ overlay: renderOverlay }}>
{dropdownTrigger}
</RcDropdown>
</RcDropdown>,
);
};
},
});
Dropdown.Button = DropdownButton;
export default Dropdown;

View File

@ -17,7 +17,7 @@ When there are more than a few options to choose from, you can wrap them in a `D
| Property | Description | Type | Default | |
| --- | --- | --- | --- | --- |
| arrow | Whether the dropdown arrow should be visible | boolean \| { pointAtCenter: boolean } | false | 3.3.0 |
| arrow | Whether the dropdown arrow should be open | boolean \| { pointAtCenter: boolean } | false | 3.3.0 |
| destroyPopupOnHide | Whether destroy dropdown when hidden | boolean | false | |
| disabled | whether the dropdown menu is disabled | boolean | - | |
| getPopupContainer | to set the container of the dropdown menu. The default is to create a `div` element in `body`, you can reset it to the scrolling area and make a relative reposition. [example](https://codepen.io/afc163/pen/zEjNOy?editors=0010) | Function(triggerNode) | `() => document.body` | |
@ -26,13 +26,13 @@ When there are more than a few options to choose from, you can wrap them in a `D
| overlayStyle | Style of the dropdown root element | object | - | |
| placement | placement of pop menu: `bottomLeft` `bottom` `bottomRight` `topLeft` `top` `topRight` | String | `bottomLeft` | |
| trigger | the trigger mode which executes the drop-down action, hover doesn't work on mobile device | Array&lt;`click`\|`hover`\|`contextmenu`> | `['hover']` | |
| visible(v-model) | whether the dropdown menu is visible | boolean | - | |
| open(v-model) | whether the dropdown menu is open | boolean | - | 4.0 |
### events
| Events Name | Description | Arguments |
| --- | --- | --- |
| visibleChange | a callback function takes an argument: `visible`, is executed when the visible state is changed. Not trigger when hidden by click item | function(visible) |
| Events Name | Description | Arguments | Version |
| --- | --- | --- | --- |
| openChange | a callback function takes an argument: `open`, is executed when the open state is changed. Not trigger when hidden by click item | function(open) | 4.0 |
You should use [Menu](/components/menu/) as `overlay`. The menu items and dividers are also available by using `Menu.Item` and `Menu.Divider`.
@ -52,11 +52,11 @@ You should use [Menu](/components/menu/) as `overlay`. The menu items and divide
| size | size of the button, the same as [Button](/components/button) | string | `default` | |
| trigger | the trigger mode which executes the drop-down action | Array&lt;`click`\|`hover`\|`contextmenu`> | `['hover']` | |
| type | type of the button, the same as [Button](/components/button) | string | `default` | |
| visible(v-model) | whether the dropdown menu is visible | boolean | - | |
| open(v-model) | whether the dropdown menu is open | boolean | - | |
### Dropdown.Button events
| Events Name | Description | Arguments |
| --- | --- | --- |
| click | a callback function, the same as [Button](/components/button), which will be executed when you click the button on the left | Function |
| visibleChange | a callback function takes an argument: `visible`, is executed when the visible state is changed. Not trigger when hidden by click item | Function |
| Events Name | Description | Arguments | Version |
| --- | --- | --- | --- |
| click | a callback function, the same as [Button](/components/button), which will be executed when you click the button on the left | Function | |
| openChange | a callback function takes an argument: `open`, is executed when the open state is changed. Not trigger when hidden by click item | Function | 4.0 |

View File

@ -30,7 +30,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
| overlayStyle | 下拉根元素的样式 | object | - | |
| placement | 菜单弹出位置 | `bottomLeft` \| `bottom` \| `bottomRight` \| `topLeft` \| `top` \| `topRight` | `bottomLeft` | |
| trigger | 触发下拉的行为, 移动端不支持 hover | Array&lt;`click`\|`hover`\|`contextmenu`> | `['hover']` | |
| visible(v-model) | 菜单是否显示 | boolean | - | |
| open(v-model) | 菜单是否显示 | boolean | - | |
`overlay` 菜单使用 [Menu](/components/menu-cn/),还包括菜单项 `Menu.Item`,分割线 `Menu.Divider`
@ -40,9 +40,9 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
### 事件
| 事件名称 | 说明 | 回调参数 |
| --- | --- | --- |
| visibleChange | 菜单显示状态改变时调用,参数为 visible。点击菜单按钮导致的消失不会触发 | function(visible) |
| 事件名称 | 说明 | 回调参数 | 版本 |
| --- | --- | --- | --- |
| openChange | 菜单显示状态改变时调用,参数为 visible。点击菜单按钮导致的消失不会触发 | function(open) | 4.0 |
### Dropdown.Button
@ -56,11 +56,11 @@ cover: https://gw.alipayobjects.com/zos/alicdn/eedWN59yJ/Dropdown.svg
| size | 按钮大小,和 [Button](/components/button-cn/) 一致 | string | 'default' | |
| trigger | 触发下拉的行为 | Array&lt;`click`\|`hover`\|`contextmenu`> | `['hover']` | |
| type | 按钮类型,和 [Button](/components/button-cn/) 一致 | string | 'default' | |
| visible(v-model) | 菜单是否显示 | boolean | - | |
| open(v-model) | 菜单是否显示 | boolean | - | |
### Dropdown.Button 事件
| 事件名称 | 说明 | 回调参数 |
| --- | --- | --- |
| 事件名称 | 说明 | 回调参数 | 版本 |
| --- | --- | --- | --- |
| click | 点击左侧按钮的回调,和 [Button](/components/button-cn/) 一致 | Function |
| visibleChange | 菜单显示状态改变时调用,参数为 visible。点击菜单按钮导致的消失不会触发 | function(visible) |
| openChange | 菜单显示状态改变时调用,参数为 visible。点击菜单按钮导致的消失不会触发 | function(open) | 4.0 |

View File

@ -4,7 +4,7 @@ import PropTypes from '../_util/vue-types';
import buttonTypes from '../button/buttonTypes';
import type { MouseEventHandler } from '../_util/EventInterface';
import type { MenuProps } from '../menu';
import { objectType } from '../_util/type';
import { booleanType, eventType, objectType, someType } from '../_util/type';
export type Align = {
points?: [string, string];
@ -25,18 +25,19 @@ export type DropdownArrowOptions = {
pointAtCenter?: boolean;
};
const dropdownProps = () => ({
arrow: {
type: [Boolean, Object] as PropType<boolean | DropdownArrowOptions>,
default: undefined,
},
arrow: someType<boolean | DropdownArrowOptions>([Boolean, Object]),
trigger: {
type: [Array, String] as PropType<Trigger[] | Trigger>,
},
menu: objectType<MenuProps>(),
overlay: PropTypes.any,
visible: { type: Boolean, default: undefined },
disabled: { type: Boolean, default: undefined },
align: { type: Object as PropType<Align> },
/** @deprecated Please use `open` instead */
visible: booleanType(),
open: booleanType(),
disabled: booleanType(),
danger: booleanType(),
autofocus: booleanType(),
align: objectType<Align>(),
getPopupContainer: Function as PropType<(triggerNode: HTMLElement) => HTMLElement>,
prefixCls: String,
transitionName: String,
@ -51,19 +52,27 @@ const dropdownProps = () => ({
| 'bottomRight'
>,
overlayClassName: String,
overlayStyle: { type: Object as PropType<CSSProperties>, default: undefined as CSSProperties },
forceRender: { type: Boolean, default: undefined },
overlayStyle: objectType<CSSProperties>(),
forceRender: booleanType(),
mouseEnterDelay: Number,
mouseLeaveDelay: Number,
openClassName: String,
minOverlayWidthMatchTrigger: { type: Boolean, default: undefined },
destroyPopupOnHide: { type: Boolean, default: undefined },
minOverlayWidthMatchTrigger: booleanType(),
destroyPopupOnHide: booleanType(),
/** @deprecated Please use `onOpenChange` instead */
onVisibleChange: {
type: Function as PropType<(val: boolean) => void>,
},
/** @deprecated Please use `onUpdate:open` instead */
'onUpdate:visible': {
type: Function as PropType<(val: boolean) => void>,
},
onOpenChange: {
type: Function as PropType<(val: boolean) => void>,
},
'onUpdate:open': {
type: Function as PropType<(val: boolean) => void>,
},
});
const buttonTypesProps = buttonTypes();
@ -73,14 +82,12 @@ const dropdownButtonProps = () => ({
size: String as PropType<'small' | 'large'>,
htmlType: buttonTypesProps.htmlType,
href: String,
disabled: { type: Boolean, default: undefined },
disabled: booleanType(),
prefixCls: String,
icon: PropTypes.any,
title: String,
loading: buttonTypesProps.loading,
onClick: {
type: Function as PropType<MouseEventHandler>,
},
onClick: eventType<MouseEventHandler>(),
});
export { dropdownProps, dropdownButtonProps };

View File

@ -0,0 +1,26 @@
import type { DropdownToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
const genButtonStyle: GenerateStyle<DropdownToken> = token => {
const { componentCls, antCls, paddingXS, opacityLoading } = token;
return {
[`${componentCls}-button`]: {
whiteSpace: 'nowrap',
[`&${antCls}-btn-group > ${antCls}-btn`]: {
[`&-loading, &-loading + ${antCls}-btn`]: {
cursor: 'default',
pointerEvents: 'none',
opacity: opacityLoading,
},
[`&:last-child:not(:first-child):not(${antCls}-btn-icon-only)`]: {
paddingInline: paddingXS,
},
},
},
};
};
export default genButtonStyle;

View File

@ -1,394 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@import './status';
@dropdown-prefix-cls: ~'@{ant-prefix}-dropdown';
.@{dropdown-prefix-cls} {
.reset-component();
position: absolute;
top: -9999px;
left: -9999px;
z-index: @zindex-dropdown;
display: block;
&::before {
position: absolute;
top: -@popover-distance + @popover-arrow-width;
right: 0;
bottom: -@popover-distance + @popover-arrow-width;
left: -7px;
z-index: -9999;
opacity: 0.0001;
content: ' ';
}
&-wrap {
position: relative;
.@{ant-prefix}-btn > .@{iconfont-css-prefix}-down {
font-size: 10px;
}
.@{iconfont-css-prefix}-down::before {
transition: transform @animation-duration-base;
}
}
&-wrap-open {
.@{iconfont-css-prefix}-down::before {
transform: rotate(180deg);
}
}
&-hidden,
&-menu-hidden,
&-menu-submenu-hidden {
display: none;
}
// Offset the popover to account for the dropdown arrow
&-show-arrow&-placement-topLeft,
&-show-arrow&-placement-top,
&-show-arrow&-placement-topRight {
padding-bottom: @popover-distance;
}
&-show-arrow&-placement-bottomLeft,
&-show-arrow&-placement-bottom,
&-show-arrow&-placement-bottomRight {
padding-top: @popover-distance;
}
// Arrows
// .popover-arrow is outer, .popover-arrow:after is inner
&-arrow {
position: absolute;
z-index: 1; // lift it up so the menu wouldn't cask shadow on it
display: block;
width: @popover-arrow-width;
height: @popover-arrow-width;
background: linear-gradient(
135deg,
transparent 40%,
@popover-bg 40%
); // Use linear-gradient to prevent arrow from covering text
.roundedArrow(@popover-arrow-width, 5px, @popover-bg);
}
&-placement-top > &-arrow,
&-placement-topLeft > &-arrow,
&-placement-topRight > &-arrow {
bottom: @popover-arrow-width * sqrt((1 / 2)) + 2px;
box-shadow: 3px 3px 7px -3px fade(@black, 10%);
transform: rotate(45deg);
}
&-placement-top > &-arrow {
left: 50%;
transform: translateX(-50%) rotate(45deg);
}
&-placement-topLeft > &-arrow {
left: 16px;
}
&-placement-topRight > &-arrow {
right: 16px;
}
&-placement-bottom > &-arrow,
&-placement-bottomLeft > &-arrow,
&-placement-bottomRight > &-arrow {
top: (@popover-arrow-width + 2px) * sqrt((1 / 2));
box-shadow: 2px 2px 5px -2px fade(@black, 10%);
transform: rotate(-135deg) translateY(-0.5px);
}
&-placement-bottom > &-arrow {
left: 50%;
transform: translateX(-50%) rotate(-135deg) translateY(-0.5px);
}
&-placement-bottomLeft > &-arrow {
left: 16px;
}
&-placement-bottomRight > &-arrow {
right: 16px;
}
&-menu {
position: relative;
margin: 0;
padding: @dropdown-edge-child-vertical-padding 0;
text-align: left;
list-style-type: none;
background-color: @dropdown-menu-bg;
background-clip: padding-box;
border-radius: @border-radius-base;
outline: none;
box-shadow: @box-shadow-base;
&-item-group-title {
padding: 5px @control-padding-horizontal;
color: @text-color-secondary;
transition: all @animation-duration-slow;
}
&-submenu-popup {
position: absolute;
z-index: @zindex-dropdown;
background: transparent;
box-shadow: none;
transform-origin: 0 0;
ul,
li {
list-style: none;
}
ul {
margin-right: 0.3em;
margin-left: 0.3em;
}
}
// ======================= Item Content =======================
&-item {
position: relative;
display: flex;
align-items: center;
}
&-item-icon {
min-width: 12px;
margin-right: 8px;
font-size: @font-size-sm;
}
&-title-content {
flex: auto;
white-space: nowrap;
> a {
color: inherit;
transition: all @animation-duration-slow;
&:hover {
color: inherit;
}
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
content: '';
}
}
}
// =========================== Item ===========================
&-item,
&-submenu-title {
clear: both;
margin: 0;
padding: @dropdown-vertical-padding @control-padding-horizontal;
color: @text-color;
font-weight: normal;
font-size: @dropdown-font-size;
line-height: @dropdown-line-height;
cursor: pointer;
transition: all @animation-duration-slow;
&:first-child {
& when (@dropdown-edge-child-vertical-padding = 0) {
border-radius: @border-radius-base @border-radius-base 0 0;
}
}
&:last-child {
& when (@dropdown-edge-child-vertical-padding = 0) {
border-radius: 0 0 @border-radius-base @border-radius-base;
}
}
&-selected {
color: @dropdown-selected-color;
background-color: @dropdown-selected-bg;
}
&:hover,
&&-active {
background-color: @item-hover-bg;
}
&-disabled {
color: @disabled-color;
cursor: not-allowed;
&:hover {
color: @disabled-color;
background-color: @dropdown-menu-submenu-disabled-bg;
cursor: not-allowed;
}
a {
pointer-events: none;
}
}
&-divider {
height: 1px;
margin: 4px 0;
overflow: hidden;
line-height: 0;
background-color: @border-color-split;
}
.@{dropdown-prefix-cls}-menu-submenu-expand-icon {
position: absolute;
right: @padding-xs;
.@{dropdown-prefix-cls}-menu-submenu-arrow-icon {
margin-right: 0 !important;
color: @text-color-secondary;
font-size: 10px;
font-style: normal;
}
}
}
&-item-group-list {
margin: 0 8px;
padding: 0;
list-style: none;
}
&-submenu-title {
padding-right: @control-padding-horizontal + @font-size-sm;
}
&-submenu-vertical {
position: relative;
}
&-submenu-vertical > & {
position: absolute;
top: 0;
left: 100%;
min-width: 100%;
margin-left: 4px;
transform-origin: 0 0;
}
&-submenu&-submenu-disabled .@{dropdown-prefix-cls}-menu-submenu-title {
&,
.@{dropdown-prefix-cls}-menu-submenu-arrow-icon {
color: @disabled-color;
background-color: @dropdown-menu-submenu-disabled-bg;
cursor: not-allowed;
}
}
// https://github.com/ant-design/ant-design/issues/19264
&-submenu-selected &-submenu-title {
color: @primary-color;
}
}
&.@{ant-prefix}-slide-down-enter.@{ant-prefix}-slide-down-enter-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-down-appear.@{ant-prefix}-slide-down-appear-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-down-enter.@{ant-prefix}-slide-down-enter-active&-placement-bottom,
&.@{ant-prefix}-slide-down-appear.@{ant-prefix}-slide-down-appear-active&-placement-bottom,
&.@{ant-prefix}-slide-down-enter.@{ant-prefix}-slide-down-enter-active&-placement-bottomRight,
&.@{ant-prefix}-slide-down-appear.@{ant-prefix}-slide-down-appear-active&-placement-bottomRight {
animation-name: antSlideUpIn;
}
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-top,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-top,
&.@{ant-prefix}-slide-up-enter.@{ant-prefix}-slide-up-enter-active&-placement-topRight,
&.@{ant-prefix}-slide-up-appear.@{ant-prefix}-slide-up-appear-active&-placement-topRight {
animation-name: antSlideDownIn;
}
&.@{ant-prefix}-slide-down-leave.@{ant-prefix}-slide-down-leave-active&-placement-bottomLeft,
&.@{ant-prefix}-slide-down-leave.@{ant-prefix}-slide-down-leave-active&-placement-bottom,
&.@{ant-prefix}-slide-down-leave.@{ant-prefix}-slide-down-leave-active&-placement-bottomRight {
animation-name: antSlideUpOut;
}
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topLeft,
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-top,
&.@{ant-prefix}-slide-up-leave.@{ant-prefix}-slide-up-leave-active&-placement-topRight {
animation-name: antSlideDownOut;
}
}
.@{dropdown-prefix-cls}-trigger,
.@{dropdown-prefix-cls}-link,
.@{dropdown-prefix-cls}-button {
> .@{iconfont-css-prefix}.@{iconfont-css-prefix}-down {
font-size: 10px;
vertical-align: baseline;
}
}
.@{dropdown-prefix-cls}-button {
white-space: nowrap;
&.@{ant-prefix}-btn-group > .@{ant-prefix}-btn {
&-loading,
&-loading + .@{ant-prefix}-btn {
cursor: default;
pointer-events: none;
}
&-loading + .@{ant-prefix}-btn::before {
display: block;
}
&:last-child:not(:first-child):not(.@{ant-prefix}-btn-icon-only) {
padding-right: @padding-xs;
padding-left: @padding-xs;
}
}
}
// https://github.com/ant-design/ant-design/issues/4903
.@{dropdown-prefix-cls}-menu-dark {
&,
.@{dropdown-prefix-cls}-menu {
background: @menu-dark-bg;
}
.@{dropdown-prefix-cls}-menu-item,
.@{dropdown-prefix-cls}-menu-submenu-title,
.@{dropdown-prefix-cls}-menu-item > a,
.@{dropdown-prefix-cls}-menu-item > .@{iconfont-css-prefix} + span > a {
color: @text-color-secondary-dark;
.@{dropdown-prefix-cls}-menu-submenu-arrow::after {
color: @text-color-secondary-dark;
}
&:hover {
color: @text-color-inverse;
background: transparent;
}
}
.@{dropdown-prefix-cls}-menu-item-selected {
&,
&:hover,
> a {
color: @text-color-inverse;
background: @primary-color;
}
}
}
@import './rtl';

View File

@ -0,0 +1,458 @@
import { getArrowOffset } from '../../_style/placementArrow';
import {
initMoveMotion,
initSlideMotion,
initZoomMotion,
slideDownIn,
slideDownOut,
slideUpIn,
slideUpOut,
} from '../../_style/motion';
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook, mergeToken } from '../../theme/internal';
import genButtonStyle from './button';
import genStatusStyle from './status';
import { genFocusStyle, resetComponent, roundedArrow } from '../../_style';
export interface ComponentToken {
zIndexPopup: number;
}
export interface DropdownToken extends FullToken<'Dropdown'> {
rootPrefixCls: string;
dropdownArrowDistance: number;
dropdownArrowOffset: number;
dropdownPaddingVertical: number;
dropdownEdgeChildPadding: number;
menuCls: string;
}
// =============================== Base ===============================
const genBaseStyle: GenerateStyle<DropdownToken> = token => {
const {
componentCls,
menuCls,
zIndexPopup,
dropdownArrowDistance,
dropdownArrowOffset,
sizePopupArrow,
antCls,
iconCls,
motionDurationMid,
dropdownPaddingVertical,
fontSize,
dropdownEdgeChildPadding,
colorTextDisabled,
fontSizeIcon,
controlPaddingHorizontal,
colorBgElevated,
boxShadowPopoverArrow,
} = token;
return [
{
[componentCls]: {
...resetComponent(token),
position: 'absolute',
top: -9999,
left: {
_skip_check_: true,
value: -9999,
},
zIndex: zIndexPopup,
display: 'block',
// A placeholder out of dropdown visible range to avoid close when user moving
'&::before': {
position: 'absolute',
insetBlock: -dropdownArrowDistance + sizePopupArrow / 2,
// insetInlineStart: -7, // FIXME: Seems not work for hidden element
zIndex: -9999,
opacity: 0.0001,
content: '""',
},
[`${componentCls}-wrap`]: {
position: 'relative',
[`${antCls}-btn > ${iconCls}-down`]: {
fontSize: fontSizeIcon,
},
[`${iconCls}-down::before`]: {
transition: `transform ${motionDurationMid}`,
},
},
[`${componentCls}-wrap-open`]: {
[`${iconCls}-down::before`]: {
transform: `rotate(180deg)`,
},
},
[`
&-hidden,
&-menu-hidden,
&-menu-submenu-hidden
`]: {
display: 'none',
},
// =============================================================
// == Arrow ==
// =============================================================
// Offset the popover to account for the dropdown arrow
[`
&-show-arrow${componentCls}-placement-topLeft,
&-show-arrow${componentCls}-placement-top,
&-show-arrow${componentCls}-placement-topRight
`]: {
paddingBottom: dropdownArrowDistance,
},
[`
&-show-arrow${componentCls}-placement-bottomLeft,
&-show-arrow${componentCls}-placement-bottom,
&-show-arrow${componentCls}-placement-bottomRight
`]: {
paddingTop: dropdownArrowDistance,
},
// Note: .popover-arrow is outer, .popover-arrow:after is inner
[`${componentCls}-arrow`]: {
position: 'absolute',
zIndex: 1, // lift it up so the menu wouldn't cask shadow on it
display: 'block',
...roundedArrow(
sizePopupArrow,
token.borderRadiusXS,
token.borderRadiusOuter,
colorBgElevated,
boxShadowPopoverArrow,
),
},
[`
&-placement-top > ${componentCls}-arrow,
&-placement-topLeft > ${componentCls}-arrow,
&-placement-topRight > ${componentCls}-arrow
`]: {
bottom: dropdownArrowDistance,
transform: 'translateY(100%) rotate(180deg)',
},
[`&-placement-top > ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: '50%',
},
transform: 'translateX(-50%) translateY(100%) rotate(180deg)',
},
[`&-placement-topLeft > ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
[`&-placement-topRight > ${componentCls}-arrow`]: {
right: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
[`
&-placement-bottom > ${componentCls}-arrow,
&-placement-bottomLeft > ${componentCls}-arrow,
&-placement-bottomRight > ${componentCls}-arrow
`]: {
top: dropdownArrowDistance,
transform: `translateY(-100%)`,
},
[`&-placement-bottom > ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: '50%',
},
transform: `translateY(-100%) translateX(-50%)`,
},
[`&-placement-bottomLeft > ${componentCls}-arrow`]: {
left: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
[`&-placement-bottomRight > ${componentCls}-arrow`]: {
right: {
_skip_check_: true,
value: dropdownArrowOffset,
},
},
// =============================================================
// == Motion ==
// =============================================================
// When position is not enough for dropdown, the placement will revert.
// We will handle this with revert motion name.
[`&${antCls}-slide-down-enter${antCls}-slide-down-enter-active${componentCls}-placement-bottomLeft,
&${antCls}-slide-down-appear${antCls}-slide-down-appear-active${componentCls}-placement-bottomLeft,
&${antCls}-slide-down-enter${antCls}-slide-down-enter-active${componentCls}-placement-bottom,
&${antCls}-slide-down-appear${antCls}-slide-down-appear-active${componentCls}-placement-bottom,
&${antCls}-slide-down-enter${antCls}-slide-down-enter-active${componentCls}-placement-bottomRight,
&${antCls}-slide-down-appear${antCls}-slide-down-appear-active${componentCls}-placement-bottomRight`]:
{
animationName: slideUpIn,
},
[`&${antCls}-slide-up-enter${antCls}-slide-up-enter-active${componentCls}-placement-topLeft,
&${antCls}-slide-up-appear${antCls}-slide-up-appear-active${componentCls}-placement-topLeft,
&${antCls}-slide-up-enter${antCls}-slide-up-enter-active${componentCls}-placement-top,
&${antCls}-slide-up-appear${antCls}-slide-up-appear-active${componentCls}-placement-top,
&${antCls}-slide-up-enter${antCls}-slide-up-enter-active${componentCls}-placement-topRight,
&${antCls}-slide-up-appear${antCls}-slide-up-appear-active${componentCls}-placement-topRight`]:
{
animationName: slideDownIn,
},
[`&${antCls}-slide-down-leave${antCls}-slide-down-leave-active${componentCls}-placement-bottomLeft,
&${antCls}-slide-down-leave${antCls}-slide-down-leave-active${componentCls}-placement-bottom,
&${antCls}-slide-down-leave${antCls}-slide-down-leave-active${componentCls}-placement-bottomRight`]:
{
animationName: slideUpOut,
},
[`&${antCls}-slide-up-leave${antCls}-slide-up-leave-active${componentCls}-placement-topLeft,
&${antCls}-slide-up-leave${antCls}-slide-up-leave-active${componentCls}-placement-top,
&${antCls}-slide-up-leave${antCls}-slide-up-leave-active${componentCls}-placement-topRight`]:
{
animationName: slideDownOut,
},
},
},
{
// =============================================================
// == Menu ==
// =============================================================
[`${componentCls} ${menuCls}`]: {
position: 'relative',
margin: 0,
},
[`${menuCls}-submenu-popup`]: {
position: 'absolute',
zIndex: zIndexPopup,
background: 'transparent',
boxShadow: 'none',
transformOrigin: '0 0',
'ul,li': {
listStyle: 'none',
},
ul: {
marginInline: '0.3em',
},
},
[`${componentCls}, ${componentCls}-menu-submenu`]: {
[menuCls]: {
padding: dropdownEdgeChildPadding,
listStyleType: 'none',
backgroundColor: colorBgElevated,
backgroundClip: 'padding-box',
borderRadius: token.borderRadiusLG,
outline: 'none',
boxShadow: token.boxShadowSecondary,
...genFocusStyle(token),
[`${menuCls}-item-group-title`]: {
padding: `${dropdownPaddingVertical}px ${controlPaddingHorizontal}px`,
color: token.colorTextDescription,
transition: `all ${motionDurationMid}`,
},
// ======================= Item Content =======================
[`${menuCls}-item`]: {
position: 'relative',
display: 'flex',
alignItems: 'center',
borderRadius: token.borderRadiusSM,
},
[`${menuCls}-item-icon`]: {
minWidth: fontSize,
marginInlineEnd: token.marginXS,
fontSize: token.fontSizeSM,
},
[`${menuCls}-title-content`]: {
flex: 'auto',
'> a': {
color: 'inherit',
transition: `all ${motionDurationMid}`,
'&:hover': {
color: 'inherit',
},
'&::after': {
position: 'absolute',
inset: 0,
content: '""',
},
},
},
// =========================== Item ===========================
[`${menuCls}-item, ${menuCls}-submenu-title`]: {
clear: 'both',
margin: 0,
padding: `${dropdownPaddingVertical}px ${controlPaddingHorizontal}px`,
color: token.colorText,
fontWeight: 'normal',
fontSize,
lineHeight: token.lineHeight,
cursor: 'pointer',
transition: `all ${motionDurationMid}`,
[`&:hover, &-active`]: {
backgroundColor: token.controlItemBgHover,
},
...genFocusStyle(token),
'&-selected': {
color: token.colorPrimary,
backgroundColor: token.controlItemBgActive,
'&:hover, &-active': {
backgroundColor: token.controlItemBgActiveHover,
},
},
'&-disabled': {
color: colorTextDisabled,
cursor: 'not-allowed',
'&:hover': {
color: colorTextDisabled,
backgroundColor: colorBgElevated,
cursor: 'not-allowed',
},
a: {
pointerEvents: 'none',
},
},
'&-divider': {
height: 1, // By design
margin: `${token.marginXXS}px 0`,
overflow: 'hidden',
lineHeight: 0,
backgroundColor: token.colorSplit,
},
[`${componentCls}-menu-submenu-expand-icon`]: {
position: 'absolute',
insetInlineEnd: token.paddingXS,
[`${componentCls}-menu-submenu-arrow-icon`]: {
marginInlineEnd: '0 !important',
color: token.colorTextDescription,
fontSize: fontSizeIcon,
fontStyle: 'normal',
},
},
},
[`${menuCls}-item-group-list`]: {
margin: `0 ${token.marginXS}px`,
padding: 0,
listStyle: 'none',
},
[`${menuCls}-submenu-title`]: {
paddingInlineEnd: controlPaddingHorizontal + token.fontSizeSM,
},
[`${menuCls}-submenu-vertical`]: {
position: 'relative',
},
[`${menuCls}-submenu${menuCls}-submenu-disabled ${componentCls}-menu-submenu-title`]: {
[`&, ${componentCls}-menu-submenu-arrow-icon`]: {
color: colorTextDisabled,
backgroundColor: colorBgElevated,
cursor: 'not-allowed',
},
},
// https://github.com/ant-design/ant-design/issues/19264
[`${menuCls}-submenu-selected ${componentCls}-menu-submenu-title`]: {
color: token.colorPrimary,
},
},
},
},
// Follow code may reuse in other components
[
initSlideMotion(token, 'slide-up'),
initSlideMotion(token, 'slide-down'),
initMoveMotion(token, 'move-up'),
initMoveMotion(token, 'move-down'),
initZoomMotion(token, 'zoom-big'),
],
];
};
// ============================== Export ==============================
export default genComponentStyleHook(
'Dropdown',
(token, { rootPrefixCls }) => {
const {
marginXXS,
sizePopupArrow,
controlHeight,
fontSize,
lineHeight,
paddingXXS,
componentCls,
borderRadiusOuter,
borderRadiusLG,
} = token;
const dropdownPaddingVertical = (controlHeight - fontSize * lineHeight) / 2;
const { dropdownArrowOffset } = getArrowOffset({
sizePopupArrow,
contentRadius: borderRadiusLG,
borderRadiusOuter,
});
const dropdownToken = mergeToken<DropdownToken>(token, {
menuCls: `${componentCls}-menu`,
rootPrefixCls,
dropdownArrowDistance: sizePopupArrow / 2 + marginXXS,
dropdownArrowOffset,
dropdownPaddingVertical,
dropdownEdgeChildPadding: paddingXXS,
});
return [
genBaseStyle(dropdownToken),
genButtonStyle(dropdownToken),
genStatusStyle(dropdownToken),
];
},
token => ({
zIndexPopup: token.zIndexPopupBase + 50,
}),
);

View File

@ -1,5 +0,0 @@
import '../../style/index.less';
import './index.less';
// style dependencies
import '../../button/style';

View File

@ -1,90 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@dropdown-prefix-cls: ~'@{ant-prefix}-dropdown';
.@{dropdown-prefix-cls} {
&-rtl {
direction: rtl;
}
&::before {
.@{dropdown-prefix-cls}-rtl& {
right: -7px;
left: 0;
}
}
&-menu {
&&-rtl {
direction: rtl;
text-align: right;
}
&-item-group-title {
.@{dropdown-prefix-cls}-rtl &,
.@{dropdown-prefix-cls}-menu-submenu-rtl & {
direction: rtl;
text-align: right;
}
}
&-submenu-popup {
&.@{dropdown-prefix-cls}-menu-submenu-rtl {
transform-origin: 100% 0;
}
ul,
li {
.@{dropdown-prefix-cls}-rtl & {
text-align: right;
}
}
}
&-item,
&-submenu-title {
.@{dropdown-prefix-cls}-rtl & {
text-align: right;
}
> .@{iconfont-css-prefix}:first-child,
> span > .@{iconfont-css-prefix}:first-child {
.@{dropdown-prefix-cls}-rtl & {
margin-right: 0;
margin-left: 8px;
}
}
.@{dropdown-prefix-cls}-menu-submenu-expand-icon {
.@{dropdown-prefix-cls}-rtl & {
right: auto;
left: @padding-xs;
}
.@{dropdown-prefix-cls}-menu-submenu-arrow-icon {
.@{dropdown-prefix-cls}-rtl & {
margin-left: 0 !important;
transform: scaleX(-1);
}
}
}
}
&-submenu-title {
.@{dropdown-prefix-cls}-rtl & {
padding-right: @control-padding-horizontal;
padding-left: @control-padding-horizontal + @font-size-sm;
}
}
&-submenu-vertical > & {
.@{dropdown-prefix-cls}-rtl & {
right: 100%;
left: 0;
margin-right: 4px;
margin-left: 0;
}
}
}
}

View File

@ -1,14 +0,0 @@
@import (reference) '../../style/themes/index';
@dropdown-prefix-cls: ~'@{ant-prefix}-dropdown';
.@{dropdown-prefix-cls}-menu-item {
&&-danger {
color: @error-color;
&:hover {
color: @text-color-inverse;
background-color: @error-color;
}
}
}

View File

@ -0,0 +1,25 @@
import type { DropdownToken } from '.';
import type { GenerateStyle } from '../../theme/internal';
const genStatusStyle: GenerateStyle<DropdownToken> = token => {
const { componentCls, menuCls, colorError, colorTextLightSolid } = token;
const itemCls = `${menuCls}-item`;
return {
[`${componentCls}, ${componentCls}-menu-submenu`]: {
[`${menuCls} ${itemCls}`]: {
[`&${itemCls}-danger:not(${itemCls}-disabled)`]: {
color: colorError,
'&:hover': {
color: colorTextLightSolid,
backgroundColor: colorError,
},
},
},
},
};
};
export default genStatusStyle;

View File

@ -392,7 +392,7 @@ export default defineComponent({
const lastVisibleIndex = ref(0);
const expandIcon = computed<MenuProps['expandIcon']>(() =>
props.expandIcon || slots.expandIcon
props.expandIcon || slots.expandIcon || override?.expandIcon?.value
? opt => {
let icon = props.expandIcon || slots.expandIcon;
icon = typeof icon === 'function' ? icon(opt) : icon;

View File

@ -9,6 +9,7 @@ export interface OverrideContextProps {
selectable?: ComputedRef<boolean>;
validator?: (menuProps: Pick<MenuProps, 'mode'>) => void;
onClick?: () => void;
expandIcon?: ComputedRef<any>;
}
export const OverrideContextKey: InjectionKey<OverrideContextProps> = Symbol('OverrideContextKey');
export const useInjectOverride = () => {
@ -16,12 +17,13 @@ export const useInjectOverride = () => {
};
export const useProvideOverride = (props: OverrideContextProps) => {
const { prefixCls, mode, selectable, validator, onClick } = useInjectOverride() || {};
const { prefixCls, mode, selectable, validator, onClick, expandIcon } = useInjectOverride() || {};
provide(OverrideContextKey, {
prefixCls: computed(() => (props.prefixCls?.value ?? prefixCls?.value) as string),
mode: computed(() => props.mode?.value ?? mode?.value),
selectable: computed(() => (props.selectable?.value ?? selectable?.value) as boolean),
validator: props.validator ?? validator,
onClick: props.onClick ?? onClick,
expandIcon: props.expandIcon ?? expandIcon?.value,
});
};

View File

@ -1,8 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@popconfirm-prefix-cls: ~'@{ant-prefix}-popconfirm';
.@{popconfirm-prefix-cls} {
z-index: @zindex-popoconfirm;
}

View File

@ -0,0 +1,89 @@
import type { FullToken, GenerateStyle } from '../../theme/internal';
import { genComponentStyleHook } from '../../theme/internal';
export interface ComponentToken {
zIndexPopup: number;
}
export interface PopconfirmToken extends FullToken<'Popconfirm'> {}
// =============================== Base ===============================
const genBaseStyle: GenerateStyle<PopconfirmToken> = token => {
const {
componentCls,
iconCls,
zIndexPopup,
colorText,
colorWarning,
marginXS,
fontSize,
fontWeightStrong,
lineHeight,
} = token;
return {
[componentCls]: {
zIndex: zIndexPopup,
[`${componentCls}-inner-content`]: {
color: colorText,
},
[`${componentCls}-message`]: {
position: 'relative',
marginBottom: marginXS,
color: colorText,
fontSize,
display: 'flex',
flexWrap: 'nowrap',
alignItems: 'start',
[`> ${componentCls}-message-icon ${iconCls}`]: {
color: colorWarning,
fontSize,
flex: 'none',
lineHeight: 1,
paddingTop: (Math.round(fontSize * lineHeight) - fontSize) / 2,
},
'&-title': {
flex: 'auto',
marginInlineStart: marginXS,
},
'&-title-only': {
fontWeight: fontWeightStrong,
},
},
[`${componentCls}-description`]: {
position: 'relative',
marginInlineStart: fontSize + marginXS,
marginBottom: marginXS,
color: colorText,
fontSize,
},
[`${componentCls}-buttons`]: {
textAlign: 'end',
button: {
marginInlineStart: marginXS,
},
},
},
};
};
// ============================== Export ==============================
export default genComponentStyleHook(
'Popconfirm',
token => genBaseStyle(token),
token => {
const { zIndexPopupBase } = token;
return {
zIndexPopup: zIndexPopupBase + 60,
};
},
);

View File

@ -1,8 +0,0 @@
import '../../style/index.less';
// style dependencies
// deps-lint-skip: tooltip, popover
import '../../popover/style';
import '../../button/style';
import './index.less';

View File

@ -1,258 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@popover-prefix-cls: ~'@{ant-prefix}-popover';
@popover-arrow-rotate-width: sqrt(@popover-arrow-width * @popover-arrow-width * 2);
@popover-arrow-offset-vertical: 12px;
@popover-arrow-offset-horizontal: 16px;
.@{popover-prefix-cls} {
.reset-component();
position: absolute;
top: 0;
left: 0;
z-index: @zindex-popover;
font-weight: normal;
white-space: normal;
text-align: left;
cursor: auto;
user-select: text;
&::after {
position: absolute;
background: fade(@white, 1%);
content: '';
}
&-hidden {
display: none;
}
// Offset the popover to account for the popover arrow
&-placement-top,
&-placement-topLeft,
&-placement-topRight {
padding-bottom: @popover-distance;
}
&-placement-right,
&-placement-rightTop,
&-placement-rightBottom {
padding-left: @popover-distance;
}
&-placement-bottom,
&-placement-bottomLeft,
&-placement-bottomRight {
padding-top: @popover-distance;
}
&-placement-left,
&-placement-leftTop,
&-placement-leftBottom {
padding-right: @popover-distance;
}
&-inner {
background-color: @popover-bg;
background-clip: padding-box;
border-radius: @border-radius-base;
box-shadow: @box-shadow-base;
box-shadow: ~'0 0 8px @{shadow-color} \9';
}
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
/* IE10+ */
&-inner {
box-shadow: @box-shadow-base;
}
}
&-title {
min-width: @popover-min-width;
min-height: @popover-min-height;
margin: 0; // reset heading margin
padding: 5px @popover-padding-horizontal 4px;
color: @heading-color;
font-weight: 500;
border-bottom: 1px solid @border-color-split;
}
&-inner-content {
padding: @padding-sm @popover-padding-horizontal;
color: @popover-color;
}
&-message {
position: relative;
padding: 4px 0 12px;
color: @popover-color;
font-size: @font-size-base;
> .@{iconfont-css-prefix} {
position: absolute;
top: (
4px + ((@line-height-base * @font-size-base - @font-size-base) / 2)
); // 4px for padding-top, 4px for vertical middle
color: @warning-color;
font-size: @font-size-base;
}
&-title {
padding-left: @font-size-base + 8px;
}
}
&-buttons {
margin-bottom: 4px;
text-align: right;
button:not(:first-child) {
margin-left: 8px;
}
}
// Arrows
&-arrow {
position: absolute;
display: block;
width: @popover-arrow-rotate-width;
height: @popover-arrow-rotate-width;
overflow: hidden;
background: transparent;
pointer-events: none;
&-content {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: block;
width: @popover-arrow-width;
height: @popover-arrow-width;
margin: auto;
background-color: @popover-bg;
content: '';
pointer-events: auto;
.roundedArrow(@popover-arrow-width, 5px, @popover-bg);
}
}
&-placement-top &-arrow,
&-placement-topLeft &-arrow,
&-placement-topRight &-arrow {
bottom: @popover-distance - @popover-arrow-rotate-width;
&-content {
box-shadow: 3px 3px 7px fade(@black, 7%);
transform: translateY((-@popover-arrow-rotate-width / 2)) rotate(45deg);
}
}
&-placement-top &-arrow {
left: 50%;
transform: translateX(-50%);
}
&-placement-topLeft &-arrow {
left: @popover-arrow-offset-horizontal;
}
&-placement-topRight &-arrow {
right: @popover-arrow-offset-horizontal;
}
&-placement-right &-arrow,
&-placement-rightTop &-arrow,
&-placement-rightBottom &-arrow {
left: @popover-distance - @popover-arrow-rotate-width;
&-content {
box-shadow: 3px 3px 7px fade(@black, 7%);
transform: translateX((@popover-arrow-rotate-width / 2)) rotate(135deg);
}
}
&-placement-right &-arrow {
top: 50%;
transform: translateY(-50%);
}
&-placement-rightTop &-arrow {
top: @popover-arrow-offset-vertical;
}
&-placement-rightBottom &-arrow {
bottom: @popover-arrow-offset-vertical;
}
&-placement-bottom &-arrow,
&-placement-bottomLeft &-arrow,
&-placement-bottomRight &-arrow {
top: @popover-distance - @popover-arrow-rotate-width;
&-content {
box-shadow: 2px 2px 5px fade(@black, 6%);
transform: translateY((@popover-arrow-rotate-width / 2)) rotate(-135deg);
}
}
&-placement-bottom &-arrow {
left: 50%;
transform: translateX(-50%);
}
&-placement-bottomLeft &-arrow {
left: @popover-arrow-offset-horizontal;
}
&-placement-bottomRight &-arrow {
right: @popover-arrow-offset-horizontal;
}
&-placement-left &-arrow,
&-placement-leftTop &-arrow,
&-placement-leftBottom &-arrow {
right: @popover-distance - @popover-arrow-rotate-width;
&-content {
box-shadow: 3px 3px 7px fade(@black, 7%);
transform: translateX((-@popover-arrow-rotate-width / 2)) rotate(-45deg);
}
}
&-placement-left &-arrow {
top: 50%;
transform: translateY(-50%);
}
&-placement-leftTop &-arrow {
top: @popover-arrow-offset-vertical;
}
&-placement-leftBottom &-arrow {
bottom: @popover-arrow-offset-vertical;
}
}
.generator-popover-preset-color(@i: length(@preset-colors)) when (@i > 0) {
.generator-popover-preset-color(@i - 1);
@color: extract(@preset-colors, @i);
@lightColor: '@{color}-6';
.@{popover-prefix-cls}-@{color} {
.@{popover-prefix-cls}-inner {
background-color: @@lightColor;
}
.@{popover-prefix-cls}-arrow {
&-content {
background-color: @@lightColor;
}
}
}
}
.generator-popover-preset-color();
@import './rtl';

View File

@ -0,0 +1,183 @@
import { initZoomMotion } from '../../_style/motion';
import type { FullToken, GenerateStyle, PresetColorType } from '../../theme/internal';
import { genComponentStyleHook, mergeToken, PresetColors } from '../../theme/internal';
import { resetComponent } from '../../_style';
import getArrowStyle from '../../_style/placementArrow';
export interface ComponentToken {
zIndexPopup: number;
width: number;
}
export type PopoverToken = FullToken<'Popover'> & {
popoverBg: string;
popoverColor: string;
popoverPadding: number | string;
};
const genBaseStyle: GenerateStyle<PopoverToken> = token => {
const {
componentCls,
popoverBg,
popoverColor,
width,
fontWeightStrong,
popoverPadding,
boxShadowSecondary,
colorTextHeading,
borderRadiusLG: borderRadius,
zIndexPopup,
marginXS,
colorBgElevated,
} = token;
return [
{
[componentCls]: {
...resetComponent(token),
position: 'absolute',
top: 0,
// use `left` to fix https://github.com/ant-design/ant-design/issues/39195
left: {
_skip_check_: true,
value: 0,
},
zIndex: zIndexPopup,
fontWeight: 'normal',
whiteSpace: 'normal',
textAlign: 'start',
cursor: 'auto',
userSelect: 'text',
'--antd-arrow-background-color': colorBgElevated,
'&-rtl': {
direction: 'rtl',
},
'&-hidden': {
display: 'none',
},
[`${componentCls}-content`]: {
position: 'relative',
},
[`${componentCls}-inner`]: {
backgroundColor: popoverBg,
backgroundClip: 'padding-box',
borderRadius,
boxShadow: boxShadowSecondary,
padding: popoverPadding,
},
[`${componentCls}-title`]: {
minWidth: width,
marginBottom: marginXS,
color: colorTextHeading,
fontWeight: fontWeightStrong,
},
[`${componentCls}-inner-content`]: {
color: popoverColor,
},
},
},
// Arrow Style
getArrowStyle(token, { colorBg: 'var(--antd-arrow-background-color)' }),
// Pure Render
{
[`${componentCls}-pure`]: {
position: 'relative',
maxWidth: 'none',
[`${componentCls}-content`]: {
display: 'inline-block',
},
},
},
];
};
const genColorStyle: GenerateStyle<PopoverToken> = token => {
const { componentCls } = token;
return {
[componentCls]: PresetColors.map((colorKey: keyof PresetColorType) => {
const lightColor = token[`${colorKey}-6`];
return {
[`&${componentCls}-${colorKey}`]: {
'--antd-arrow-background-color': lightColor,
[`${componentCls}-inner`]: {
backgroundColor: lightColor,
},
[`${componentCls}-arrow`]: {
background: 'transparent',
},
},
};
}),
};
};
const genWireframeStyle: GenerateStyle<PopoverToken> = token => {
const {
componentCls,
lineWidth,
lineType,
colorSplit,
paddingSM,
controlHeight,
fontSize,
lineHeight,
padding,
} = token;
const titlePaddingBlockDist = controlHeight - Math.round(fontSize * lineHeight);
const popoverTitlePaddingBlockTop = titlePaddingBlockDist / 2;
const popoverTitlePaddingBlockBottom = titlePaddingBlockDist / 2 - lineWidth;
const popoverPaddingHorizontal = padding;
return {
[componentCls]: {
[`${componentCls}-inner`]: {
padding: 0,
},
[`${componentCls}-title`]: {
margin: 0,
padding: `${popoverTitlePaddingBlockTop}px ${popoverPaddingHorizontal}px ${popoverTitlePaddingBlockBottom}px`,
borderBottom: `${lineWidth}px ${lineType} ${colorSplit}`,
},
[`${componentCls}-inner-content`]: {
padding: `${paddingSM}px ${popoverPaddingHorizontal}px`,
},
},
};
};
export default genComponentStyleHook(
'Popover',
token => {
const { colorBgElevated, colorText, wireframe } = token;
const popoverToken = mergeToken<PopoverToken>(token, {
popoverBg: colorBgElevated,
popoverColor: colorText,
popoverPadding: 12, // Fixed Value
});
return [
genBaseStyle(popoverToken),
genColorStyle(popoverToken),
wireframe && genWireframeStyle(popoverToken),
initZoomMotion(popoverToken, 'zoom-big'),
];
},
({ zIndexPopupBase }) => ({
zIndexPopup: zIndexPopupBase + 30,
width: 177,
}),
);

View File

@ -1,5 +0,0 @@
import '../../style/index.less';
import './index.less';
// style dependencies
// deps-lint-skip: tooltip

View File

@ -1,33 +0,0 @@
@import '../../style/themes/index';
@import '../../style/mixins/index';
@popover-prefix-cls: ~'@{ant-prefix}-popover';
.@{popover-prefix-cls} {
&-rtl {
direction: rtl;
text-align: right;
}
&-message {
&-title {
.@{popover-prefix-cls}-rtl & {
padding-right: @font-size-base + 8px;
padding-left: @padding-md;
}
}
}
&-buttons {
.@{popover-prefix-cls}-rtl & {
text-align: left;
}
button {
.@{popover-prefix-cls}-rtl & {
margin-right: 8px;
margin-left: 0;
}
}
}
}

View File

@ -11,11 +11,11 @@ import './pagination/style';
import './tabs/style';
import './input/style';
// import './tooltip/style';
import './popover/style';
import './popconfirm/style';
// import './popover/style';
// import './popconfirm/style';
// import './menu/style';
import './mentions/style';
import './dropdown/style';
// import './dropdown/style';
// import './divider/style';
import './card/style';
import './collapse/style';

View File

@ -12,7 +12,7 @@ import type { ComponentToken as ButtonComponentToken } from '../../button/style'
// import type { ComponentToken as CollapseComponentToken } from '../../collapse/style';
// import type { ComponentToken as DatePickerComponentToken } from '../../date-picker/style';
import type { ComponentToken as DividerComponentToken } from '../../divider/style';
// import type { ComponentToken as DropdownComponentToken } from '../../dropdown/style';
import type { ComponentToken as DropdownComponentToken } from '../../dropdown/style';
// import type { ComponentToken as DrawerComponentToken } from '../../drawer/style';
// import type { ComponentToken as EmptyComponentToken } from '../../empty/style';
// import type { ComponentToken as ImageComponentToken } from '../../image/style';
@ -24,8 +24,8 @@ import type { ComponentToken as MenuComponentToken } from '../../menu/style';
import type { ComponentToken as MessageComponentToken } from '../../message/style';
import type { ComponentToken as ModalComponentToken } from '../../modal/style';
import type { ComponentToken as NotificationComponentToken } from '../../notification/style';
// import type { ComponentToken as PopconfirmComponentToken } from '../../popconfirm/style';
// import type { ComponentToken as PopoverComponentToken } from '../../popover/style';
import type { ComponentToken as PopconfirmComponentToken } from '../../popconfirm/style';
import type { ComponentToken as PopoverComponentToken } from '../../popover/style';
// import type { ComponentToken as ProgressComponentToken } from '../../progress/style';
// import type { ComponentToken as RadioComponentToken } from '../../radio/style';
// import type { ComponentToken as RateComponentToken } from '../../rate/style';
@ -68,7 +68,7 @@ export interface ComponentTokenMap {
Descriptions?: {};
Divider?: DividerComponentToken;
// Drawer?: DrawerComponentToken;
// Dropdown?: DropdownComponentToken;
Dropdown?: DropdownComponentToken;
// Empty?: EmptyComponentToken;
// FloatButton?: FloatButtonComponentToken;
Form?: {};
@ -81,8 +81,8 @@ export interface ComponentTokenMap {
// Mentions?: MentionsComponentToken;
Notification?: NotificationComponentToken;
Pagination?: {};
// Popover?: PopoverComponentToken;
// Popconfirm?: PopconfirmComponentToken;
Popover?: PopoverComponentToken;
Popconfirm?: PopconfirmComponentToken;
// Rate?: RateComponentToken;
// Radio?: RadioComponentToken;
// Result?: ResultComponentToken;