preview.vue 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185
  1. <script setup lang="ts">
  2. import { Col, Row, Typography, theme, Tag, Flex } from 'ant-design-vue';
  3. import { computed, onBeforeUnmount, ref, useTemplateRef, watch } from 'vue';
  4. import { XProvider } from 'ant-design-x-vue'
  5. import { useData } from 'vitepress'
  6. const { semantics, height} = defineProps<{
  7. semantics: Array<{
  8. name: string;
  9. desc: string;
  10. version?: string;
  11. }>;
  12. height?: number;
  13. }>()
  14. const MARK_BORDER_SIZE = 2
  15. const { token } = theme.useToken();
  16. const { isDark } = useData()
  17. const algorithm = computed(() => isDark.value ? theme.darkAlgorithm : theme.defaultAlgorithm)
  18. // ======================= Semantic =======================
  19. const getMarkClassName = (semanticKey: string) => `semantic-mark-${semanticKey}`
  20. const semanticClassNames = computed(() => {
  21. const classNames: Record<string, string> = {};
  22. semantics.forEach((semantic) => {
  23. classNames[semantic.name] = getMarkClassName(semantic.name);
  24. });
  25. return classNames;
  26. });
  27. // ======================== Hover =========================
  28. const containerRef = useTemplateRef('container');
  29. let timer: ReturnType<typeof setTimeout> | null = null;
  30. const positionMotion = ref(false)
  31. const hoverSemantic = ref<string | null>(null)
  32. const markPos = ref<[number, number, number, number]>([0,0,0,0])
  33. watch(hoverSemantic, () => {
  34. if(timer) {
  35. clearTimeout(timer);
  36. }
  37. if (hoverSemantic.value) {
  38. const targetClassName = getMarkClassName(hoverSemantic.value);
  39. const targetElement = containerRef.value?.querySelector<HTMLElement>(`.${targetClassName}`);
  40. const containerRect = containerRef.value?.getBoundingClientRect();
  41. const targetRect = targetElement?.getBoundingClientRect();
  42. markPos.value = [
  43. (targetRect?.left || 0) - (containerRect?.left || 0),
  44. (targetRect?.top || 0) - (containerRect?.top || 0),
  45. targetRect?.width || 0,
  46. targetRect?.height || 0,
  47. ]
  48. timer = setTimeout(() => {
  49. positionMotion.value = true
  50. }, 10);
  51. } else {
  52. timer = setTimeout(() => {
  53. positionMotion.value = false
  54. }, 500);
  55. }
  56. })
  57. onBeforeUnmount(() => {
  58. if (timer) {
  59. clearTimeout(timer);
  60. timer = null;
  61. }
  62. })
  63. </script>
  64. <template>
  65. <div class="semantic-container" ref="container">
  66. <Row :style="{minHeight: height}">
  67. <Col :span="18" class="col-wrap">
  68. <XProvider :theme="{
  69. algorithm
  70. }">
  71. <slot :classNames="semanticClassNames" />
  72. </XProvider>
  73. </Col>
  74. <Col :span="6">
  75. <ul class="list-wrap">
  76. <li
  77. v-for="semantic in semantics"
  78. :key="semantic.name"
  79. class="list-item"
  80. @mouseenter="hoverSemantic = semantic.name"
  81. @mouseleave="hoverSemantic = null"
  82. >
  83. <Flex vertical gap="small">
  84. <Flex gap="small" align="center">
  85. <Typography.Title :level="5" :style="{ margin: 0 }">
  86. {{ semantic.name }}
  87. </Typography.Title>
  88. <Tag v-if="semantic.version" color="blue">{{ semantic.version }}</Tag>
  89. </Flex>
  90. <Typography.Paragraph :style="{margin: 0, fontSize: token.fontSizeSM + 'px' }">
  91. {{ semantic.desc }}
  92. </Typography.Paragraph>
  93. </Flex>
  94. </li>
  95. </ul>
  96. </Col>
  97. </Row>
  98. <div
  99. :class="`marker ${hoverSemantic ? 'marker-active' : 'marker-not-active'} ${positionMotion ? 'marker-motion' : 'marker-not-motion'}`"
  100. />
  101. </div>
  102. </template>
  103. <style scoped lang="scss">
  104. .semantic-container {
  105. border: 1px solid var(--vp-border-color);
  106. border-radius: 8px;
  107. position: relative;
  108. overflow: hidden;
  109. .col-wrap {
  110. border-right: 1px solid v-bind('token.colorBorderSecondary');
  111. display: flex;
  112. justify-content: center;
  113. align-items: center;
  114. padding: v-bind('token.paddingMD + "px"');
  115. overflow: hidden;
  116. }
  117. .list-wrap {
  118. display: flex;
  119. flex-direction: column;
  120. list-style: none;
  121. margin: 0;
  122. padding: 0;
  123. overflow: hidden;
  124. }
  125. .list-item {
  126. margin-top: 0;
  127. cursor: pointer;
  128. padding: v-bind('token.paddingSM + "px"');
  129. transition: background-color v-bind('token.motionDurationFast') ease;
  130. &:hover {
  131. background-color: v-bind('token.controlItemBgHover');
  132. }
  133. &:not(:first-of-type) {
  134. border-top: 1px solid v-bind('token.colorBorderSecondary');
  135. }
  136. }
  137. .marker {
  138. position: absolute;
  139. border: v-bind('MARK_BORDER_SIZE + "px"') solid v-bind('token.colorWarning');
  140. box-sizing: border-box;
  141. z-index: 999999;
  142. box-shadow: 0 0 0 1px #fff;
  143. pointer-events: none;
  144. inset-inline-start: v-bind('markPos[0] - MARK_BORDER_SIZE + "px"');
  145. top: v-bind('markPos[1] - MARK_BORDER_SIZE + "px"');
  146. width: v-bind('markPos[2] + MARK_BORDER_SIZE * 2 + "px"');
  147. height: v-bind('markPos[3] + MARK_BORDER_SIZE * 2 + "px"');
  148. };
  149. .marker-active {
  150. opacity: 1;
  151. }
  152. .marker-not-active {
  153. opacity: 0;
  154. }
  155. .marker-motion {
  156. transition:
  157. opacity v-bind('token.motionDurationSlow') ease,
  158. all v-bind('token.motionDurationSlow') ease;
  159. }
  160. .marker-not-motion {
  161. transition: opacity v-bind('token.motionDurationSlow') ease;
  162. }
  163. }
  164. </style>