Skip to content

Commit

Permalink
feat: new directive v-tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
zzxming committed Aug 1, 2024
1 parent 9205acf commit 68857f5
Show file tree
Hide file tree
Showing 15 changed files with 2,107 additions and 2,044 deletions.
4 changes: 4 additions & 0 deletions docs/.vitepress/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export default defineConfig({
{ text: 'PullRefresh', link: '/component/pull-refresh' },
],
},
{
text: '指令',
items: [{ text: 'vToolTip', link: '/component/v-tooltip' }],
},
],
},
},
Expand Down
13 changes: 13 additions & 0 deletions docs/component/v-tooltip.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# vToolTip

## 基础用法

:::demo v-tooltip/base

:::

## 提示位置

:::demo v-tooltip/position

:::
5 changes: 5 additions & 0 deletions docs/demos/v-tooltip/base.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<template>
<button v-tooltip="false">
hover
</button>
</template>
16 changes: 16 additions & 0 deletions docs/demos/v-tooltip/position.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<template>
<div style="position: relative;height: 300px; ">
<button v-tooltip:top="'top tooltip'" style="border: 1px solid; position: absolute; top: 0; left: 50%; transform: translate(-50%, 0);">
hover
</button>
<button v-tooltip:right="'right tooltip'" style="border: 1px solid; position: absolute; top: 50%; right: 0; transform: translate(0, -50%);">
hover
</button>
<button v-tooltip:bottom="'bottom tooltip'" style="border: 1px solid; position: absolute; bottom: 0; left: 50%; transform: translate(-50%, 0);">
hover
</button>
<button v-tooltip:left="'left tooltip'" style="border: 1px solid; position: absolute; top:50%; left: 0; transform: translate(0, -50%);">
hover
</button>
</div>
</template>
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
"peerDependencies": {
"vue": "^3.3.11"
},
"dependencies": {
"lodash-unified": "^1.0.3"
},
"devDependencies": {
"@cdx-component/build-utils": "workspace:^",
"@cdx-component/components": "workspace:^",
Expand Down
4 changes: 4 additions & 0 deletions packages/cdx-component/directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { Plugin } from 'vue';
import { CdxVTooltip } from '../directives/tooltip';

export default [CdxVTooltip] as Plugin[]; ;
1 change: 1 addition & 0 deletions packages/cdx-component/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import makeInstall from './makeInstall';

export * from '../components';
export * from '../directives';
export * from '../utils';
export * from '../constants';
export * from '../hooks';
Expand Down
7 changes: 5 additions & 2 deletions packages/cdx-component/makeInstall.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import type { App, Plugin } from '@vue/runtime-core';
import { INSTALLED_KEY } from '../constants';
import Components from './component';
import Directives from './directive';

export const makeInstaller = (components: Plugin[] = []) => {
const install = (app: App) => {
if (app[INSTALLED_KEY]) return;

app[INSTALLED_KEY] = true;
for (const c of components) app.use(c);
for (const c of components) {
app.use(c);
}
};

return {
install,
};
};

export default makeInstaller(Components);
export default makeInstaller([...Components, ...Directives]);
1 change: 1 addition & 0 deletions packages/cdx-component/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const resolveComponent = (name: string) => {
const resolveDirective = async (name: string) => {
const directiveMap: Record<string, any> = {
Loading: {},
Tooltip: {},
};
if (!directiveMap[name]) return;
const partialName = name.toLowerCase();
Expand Down
1 change: 1 addition & 0 deletions packages/directives/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './tooltip';
8 changes: 8 additions & 0 deletions packages/directives/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@cdx-component/directives",
"version": "0.0.0",
"main": "index.ts",
"peerDependencies": {
"vue": "^3.3.11"
}
}
151 changes: 151 additions & 0 deletions packages/directives/tooltip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import { withInstallDirective } from '@cdx-component/utils';
import type { DirectiveBinding, ObjectDirective } from 'vue';
import { useBem, useZIndex } from '@cdx-component/hooks';
import { throttle } from 'lodash-unified';

const tooltipKey = Symbol('tooltip');
const distance = 4;
type ToolTipElement = HTMLElement & { [tooltipKey]: HTMLElement };
type ToolTipOptions = string;

const { nextZIndex } = useZIndex();
const [, bem] = useBem('tooltip');

const tooltipWrapper = document.createElement('div');
tooltipWrapper.className = bem.be('wrapper');
document.body.appendChild(tooltipWrapper);

const directions = ['top', 'right', 'bottom', 'left'] as const;
const getValidDirection = (val: any): typeof directions[number] => {
if (!directions.includes(val)) {
return 'top';
}
return val;
};
const isVisibleInWindow = ({ top, left, width, height }: { top: number; left: number; width: number; height: number }) => {
const { innerWidth, innerHeight, scrollX, scrollY } = window;

const isLeftWithinWindow = left > 0 && left + width < innerWidth + scrollX;
const isTopWithinWindow = top > 0 && top + height < innerHeight + scrollY;

return isLeftWithinWindow && isTopWithinWindow;
};

const updateTooltipPosition = (el: ToolTipElement) => {
const tooltipContent = el[tooltipKey];
if (!tooltipContent) return;
let arrow = tooltipContent.querySelector(`.${bem.be('arrow')}`) as HTMLElement;
if (!arrow) {
arrow = document.createElement('div');
arrow.className = bem.be('arrow');
tooltipContent.appendChild(arrow);
}

const direction = getValidDirection(el.dataset.direction || '');
const arrowRect = arrow.getBoundingClientRect();
const elRect = el.getBoundingClientRect();
const contentRect = tooltipContent.getBoundingClientRect();

let top = window.scrollY + elRect.top;
let left = window.scrollX + elRect.left;

const extraPositionMap = {
top: {
top: -contentRect.height - arrowRect.height - distance,
left: elRect.width / 2 - contentRect.width / 2,
arrow: {
top: `${contentRect.height - arrowRect.height / 2}px`,
left: `${contentRect.width / 2 - arrowRect.width / 2}px`,
},
},
right: {
top: elRect.height / 2 - contentRect.height / 2,
left: elRect.width + arrowRect.width / 2 + distance,
arrow: {
top: `${contentRect.height / 2 - arrowRect.height / 2}px`,
left: `${-arrowRect.width / 2}px`,
},
},
bottom: {
top: contentRect.height + arrowRect.height + distance,
left: elRect.width / 2 - contentRect.width / 2,
arrow: {
top: `${-arrowRect.height / 2}px`,
left: `${contentRect.width / 2 - arrowRect.width / 2}px`,
},
},
left: {
top: elRect.height / 2 - contentRect.height / 2,
left: -contentRect.width - arrowRect.width / 2 - distance,
arrow: {
top: `${contentRect.height / 2 - arrowRect.height / 2}px`,
left: `${contentRect.width - arrowRect.width / 2}px`,
},
},
} as const;
let extra = extraPositionMap[direction];

if (!isVisibleInWindow({ top: top + extra.top, left: left + extra.left, width: contentRect.width, height: contentRect.height })) {
const reverseDirection = {
top: 'bottom',
bottom: 'top',
left: 'right',
right: 'left',
} as const;
extra = extraPositionMap[reverseDirection[direction]];
}

top += extra.top;
left += extra.left;
Object.assign(arrow.style, extra.arrow);
Object.assign(tooltipContent.style, {
zIndex: String(nextZIndex()),
top: `${top}px`,
left: `${left}px`,
});
};
const updateTooltipValue = (el: ToolTipElement, binding: DirectiveBinding<ToolTipOptions>) => {
const tooltipContent = el[tooltipKey];
let tooltipText = tooltipContent.querySelector('span');
if (!tooltipText) {
tooltipText = document.createElement('span');
tooltipContent.insertBefore(tooltipText, tooltipContent.firstChild);
}
el.dataset.direction = getValidDirection(binding.arg || '');
};
const createTooltip = (el: ToolTipElement, binding: DirectiveBinding<ToolTipOptions>) => {
const tooltipContent = document.createElement('div');
tooltipContent.className = `${bem.b()} ${bem.bm('hidden')}`;
const tooltipText = document.createElement('span');
tooltipText.textContent = binding.value;

const arrow = document.createElement('div');
arrow.className = bem.be('arrow');

tooltipContent.appendChild(tooltipText);
tooltipContent.appendChild(arrow);
tooltipWrapper.appendChild(tooltipContent);
el[tooltipKey] = tooltipContent;

updateTooltipValue(el, binding);
updateTooltipPosition(el);
return tooltipContent;
};

export const vTooltipDirective: ObjectDirective<ToolTipElement> = {
mounted(el, binding) {
const tooltipContent = createTooltip(el, binding);
el.addEventListener('mouseenter', () => {
tooltipContent.classList.remove(bem.bm('hidden'));
updateTooltipPosition(el);
});
el.addEventListener('mouseleave', throttle(() => {
tooltipContent.classList.add(bem.bm('hidden'));
}, 300));
},
updated(el, binding) {
updateTooltipValue(el, binding);
},
};

export const CdxVTooltip = withInstallDirective(vTooltipDirective, 'tooltip');
1 change: 1 addition & 0 deletions packages/theme/src/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
@import './collapse-transition.less';
@import './collapse-item.less';
@import './pull-refresh.less';
@import './tooltip.less';
17 changes: 17 additions & 0 deletions packages/theme/src/tooltip.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
@import './shared/variables.less';

.@{namespace}-tooltip {
background-color: .getCssVar(text-color-primary) [];
color: .getCssVar(bg-color-base) [];
@apply absolute px-3 py-1 rounded text-xs;
&--hidden {
@apply hidden;
}
&__arrow {
@apply absolute -z-10;
&::before {
background-color: .getCssVar(text-color-primary) [];
@apply block content-[''] w-2 h-2 rotate-45;
}
}
}
Loading

0 comments on commit 68857f5

Please sign in to comment.