diff --git a/devui/tooltip/index.ts b/devui/tooltip/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..b5c96f4db064987a842fb0ad212550f6f8538db9 --- /dev/null +++ b/devui/tooltip/index.ts @@ -0,0 +1,16 @@ +import type { App } from 'vue' +import Tooltip from './src/tooltip' + +Tooltip.install = function(app: App) { + app.component(Tooltip.name, Tooltip) +} + +export { Tooltip } + +export default { + title: 'Tooltip提示', + category: '反馈', + install(app: App): void { + app.use(Tooltip as any) + } +} diff --git a/devui/tooltip/src/tooltip-types.ts b/devui/tooltip/src/tooltip-types.ts new file mode 100644 index 0000000000000000000000000000000000000000..545d337fabfca2f2c9b78efa0d6ee20ff5029807 --- /dev/null +++ b/devui/tooltip/src/tooltip-types.ts @@ -0,0 +1,27 @@ +import type { ExtractPropTypes } from 'vue' + +export type TTooltip = 'top' | 'right' | 'bottom' | 'left'; + +export const tooltipProps = { + position: { + type: String, + default: 'top' + }, + showAnimation: { + type: Boolean, + default: true + }, + content: { + type: String + }, + mouseLeaveDelay: { + type: String, + default: '150' + }, + mouseEnterDelay: { + type: String, + default: '100' + } +} as const + +export type TooltipProps = ExtractPropTypes diff --git a/devui/tooltip/src/tooltip.scss b/devui/tooltip/src/tooltip.scss new file mode 100644 index 0000000000000000000000000000000000000000..0a799650311d673b25c1ea3b0f4ee32f15382397 --- /dev/null +++ b/devui/tooltip/src/tooltip.scss @@ -0,0 +1,22 @@ +.devui-tooltip { + box-sizing: border-box; + .tooltip { + box-sizing: border-box; + position: absolute; + width: fit-content; + transition: all 0.5s; + .arrow { + width: 0; + height: 0; + position: absolute; + } + .tooltipcontent { + box-sizing: border-box; + padding: 10px; + margin-left: 10px; + width: fit-content; + background-color: rgb(51, 51, 51); + color: #fff; + } + } +} diff --git a/devui/tooltip/src/tooltip.tsx b/devui/tooltip/src/tooltip.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dd5cb3a0ead484ff63901fbd93c444a9bb9dfee5 --- /dev/null +++ b/devui/tooltip/src/tooltip.tsx @@ -0,0 +1,161 @@ +import { defineComponent, reactive, ref, watch, onMounted, getCurrentInstance, onBeforeUnmount, renderSlot, useSlots} from 'vue' +import { tooltipProps } from './tooltip-types' +import EventListener from '../utils/event-listener' +import './tooltip.scss' + +export default defineComponent({ + name: 'DTooltip', + props: tooltipProps, + setup(props, ctx){ + let position = reactive({ + left: 0, + top: 0 + }) + let show = ref(false) + + // slotElement元素的ref + let slotElement = ref(null) + // tooltip元素的引用 + let tooltip = ref(null) + // arrow元素的引用 + let arrow = ref(null) + // tooltipcontent的引用 + let tooltipcontent = ref(null) + + let enterEvent + let leaveEvent + + const arrowStyle = (attr, value)=>{ + arrow.value.style[attr] = value + } + + // 延迟显示 + const delayShowTrue = function (fn, delay=props.mouseEnterDelay){ + let start + if (parseInt(delay)>=0){ + return function (){ + if (start){ + clearTimeout(start) + } + start = setTimeout(fn, parseInt(delay)) + } + } else{ + console.error('the value of delay is bigger than 0 and the type of delay must be string!') + return + } + } + // 延迟消失 + const delayShowFalse = function (fn, delay=props.mouseLeaveDelay){ + if (show.value && parseInt(delay) >= 0){ + setTimeout(fn, parseInt(delay)) + } + } + + onMounted(()=>{ + // 组件初始化不渲染tooltip + if (!show.value){ + tooltip.value.style.opacity = '0' + } + + // 注册鼠标引入事件 + /*enterEvent = EventListener.listen(slotElement.value.children[0], 'mouseenter', function (){ + show.value = true + })*/ + enterEvent = EventListener.listen(slotElement.value.children[0], 'mouseenter', delayShowTrue(function (){ + show.value = true + }, props.mouseEnterDelay)) + + // 注册鼠标移除事件 + leaveEvent = EventListener.listen(slotElement.value.children[0], 'mouseleave', function (){ + if (show.value){ + setTimeout(function (){ + show.value = false + }, props.mouseLeaveDelay) + } + }) + }) + + watch(show, function (newValue, oldValue){ + if (newValue){ + // 鼠标悬浮为true,显示提示框 + tooltip.value.style.opacity = '1' + tooltip.value.style.zIndex = '999' + arrow.value.style.border = '10px solid transparent' + // 具体的判定规则 + switch (props.position){ + case 'top': + // 设置 tooltip 内容的样式 + position.left = slotElement.value.children[0].offsetLeft - tooltip.value.offsetWidth / 2 + slotElement.value.children[0].offsetWidth / 2 + position.top = slotElement.value.children[0].offsetTop - 10 - tooltipcontent.value.offsetHeight + // 设置箭头的样式 + arrowStyle('borderTop', '10px solid cornflowerblue') + arrow.value.style.top = `${tooltipcontent.value.offsetHeight}px` + arrow.value.style.left = `${tooltipcontent.value.offsetWidth/2 - 5}px` + break + + case 'right': + // 设置tooltip 内容的样式 + position.left = slotElement.value.children[0].offsetLeft + slotElement.value.children[0].offsetWidth + position.top = slotElement.value.children[0].offsetTop + slotElement.value.children[0].offsetHeight/2 - tooltipcontent.value.offsetHeight/2 + // 设置箭头的样式 + arrowStyle('borderRight', '10px solid cornflowerblue') + arrow.value.style.top = `${tooltipcontent.value.offsetHeight/2 - 10}px` + arrow.value.style.left = '-10px' + break + + case 'bottom': + // 设置tooltip的样式 + position.top = slotElement.value.children[0].offsetHeight + slotElement.value.children[0].offsetTop + 10 + position.left = slotElement.value.children[0].offsetLeft + slotElement.value.children[0].offsetWidth/2 - tooltipcontent.value.offsetWidth/2 + // 设置arrow.value的样式 + arrowStyle('borderBottom', '10px solid cornflowerblue') + arrow.value.style.top = '-20px' + arrow.value.style.left = `${tooltipcontent.value.offsetWidth/2 - 10}px` + break + + case 'left': + position.top = slotElement.value.children[0].offsetTop + slotElement.value.children[0].offsetHeight/2 - tooltipcontent.value.offsetHeight/2 + position.left = slotElement.value.children[0].offsetLeft - 20 - tooltipcontent.value.offsetWidth + // 设置arrow.value的样式 + arrowStyle('borderLeft', '10px solid cornflowerblue') + arrow.value.style.left = `${tooltipcontent.value.offsetWidth + 10}px` + arrow.value.style.top = `${tooltipcontent.value.offsetHeight/2 - 10}px` + break + + default: + console.error('The attribute position value is wrong, the value is one of top、right、left、bottom') + break + } + tooltip.value.style.top = position.top + 'px' + tooltip.value.style.left = position.left + 'px' + } else { + position.top = 0 + position.left = 0 + // 鼠标移走为false,隐藏提示框 + tooltip.value.style.opacity = '0' + } + }) + + onBeforeUnmount (()=>{ + enterEvent.remove() + leaveEvent.remove() + }) + + return ()=>{ + const defaultSlot = renderSlot(useSlots(), 'default') + return ( +
+
+ {defaultSlot} +
+
+
+
+ {props.content} +
+
+
+ ) + } + } +}) \ No newline at end of file diff --git a/devui/tooltip/utils/event-listener.js b/devui/tooltip/utils/event-listener.js new file mode 100644 index 0000000000000000000000000000000000000000..73f6f68c535deb2a2001bca79a2c0916460fd1f2 --- /dev/null +++ b/devui/tooltip/utils/event-listener.js @@ -0,0 +1,21 @@ +const EventListener = { + listen: function (target, eventType, callback) { + if (target.addEventListener){ + target.addEventListener(eventType, callback, false); + return { + remove (){ + target.removeEventListener(target, callback, false); + } + } + } else { + target.attchEvent(eventType, callback); + return { + remove (){ + target.detachEvent(eventType, callback); + } + }; + } + } +}; + +export default EventListener; \ No newline at end of file diff --git a/sites/components/tooltip/index.md b/sites/components/tooltip/index.md new file mode 100644 index 0000000000000000000000000000000000000000..489df085278157c752b51adf6e793d3c5746ec58 --- /dev/null +++ b/sites/components/tooltip/index.md @@ -0,0 +1,100 @@ +# Tooltip 提示 + +文字提示组件。 + +### 何时使用 + +用户鼠标移动到文字上,需要进一步的提示时使用。 + +### 基本用法 + +:::demo我们可以通过控制属性`position`来控制tooltip的显示位置,`position`取值有4个,分别是`top`、`right`、`bottom`、`left`。通过属性`content`控制tooltip提示框的内容。 + + + + + + + + + +```vue + +``` + +```css +.example { + height: 50px; + width: 60px; + background: cornflowerblue; + margin-top: 30px; +} +``` +::: + +### 延时触发 + +鼠标移入的时长超过 [mouseEnterDelay] 毫秒之后才会触发,以防止用户无意划过导致的闪现,默认值是150毫秒;鼠标移出之后,再经过[mouseLeaveDelay]毫秒后,toolTip组件才会隐藏,默认值是100毫秒。 + +:::demo 通过`mouseEnterDelay`属性来控制tooltip提示框的`延迟显示`(默认是100ms),`mouseLeaveDelay`属性来控制tooltip提示框的`延迟消失`(默认是150ms) +
MouseEnter delay 500ms
+ +
MouseLeave delay 1000ms
+```vue + +``` + +```css +.customCss { + width: fit-content; + height: 30px; + padding: 10px; + display: flex; + justify-content: center; + align-items: center; + color: #fff; + background: cornflowerblue; +} +.customCss-leave { + width: fit-content; + height: 30px; + padding: 10px; + display: flex; + justify-content: center; + align-items: center; + color: #252b3a; + background: #fff; +} +``` +::: + +### Tooltip Api + +| 参数 | 类型 | 默认 | 说明 | 跳转 Demo | 全局配置项 | +| :---------: | :------: | :-------: | :----------------------- | --------------------------------- | --------- | +| content | `string|DOMString` | -- | 必选,tooltip显示内容 | [基本用法](#基本用法) || +| position | `PositionType | PositionType[]` | ['top', 'right', 'bottom', 'left'] | 可选,tooltip显示位置 | [基本用法](#基本用法) || +| showAnimation | `boolean` | true | 可选,是否显示划出动画 | |✔| +| mouseEnterDelay | `number` | 150 | 可选,鼠标移入后延时多少才显示Tooltip,单位是ms | [基本用法](#延时触发) || +| mouseLeaveDelay | `number` | 100 | 可选,鼠标移出后延时多少才隐藏Tooltip,单位是ms | [基本用法](#延时触发) ||