@cypress-design/vue-docmenu 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,14 @@
1
1
  $ yarn build:module && yarn build:types
2
2
  $ yarn vite build
3
3
  $ /home/runner/work/cypress-design/cypress-design/node_modules/.bin/vite build
4
- vite v4.3.9 building for production...
4
+ The CJS build of Vite's Node API is deprecated. See https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.
5
+ vite v5.0.4 building for production...
5
6
  transforming...
6
- ✓ 8 modules transformed.
7
+ ✓ 10 modules transformed.
7
8
  rendering chunks...
8
9
  computing gzip size...
9
- dist/index.es.mjs 6.04 kB │ gzip: 2.03 kB │ map: 4.72 kB
10
- dist/index.umd.js 5.04 kB │ gzip: 1.78 kB │ map: 4.53 kB
11
- ✓ built in 4.51s
10
+ dist/index.es.mjs 9.07 kB │ gzip: 2.73 kB │ map: 12.03 kB
11
+ dist/index.umd.js 7.15 kB │ gzip: 2.40 kB │ map: 11.67 kB
12
+ ✓ built in 3.08s
12
13
  $ yarn vue-tsc --project ./tsconfig.build.json
13
14
  $ /home/runner/work/cypress-design/cypress-design/node_modules/.bin/vue-tsc --project ./tsconfig.build.json
package/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # @cypress-design/vue-docmenu
2
2
 
3
+ ## 0.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#353](https://github.com/cypress-io/cypress-design/pull/353) [`b382b9e`](https://github.com/cypress-io/cypress-design/commit/b382b9ecc99e3ab6288eee406831199edb661c19) Thanks [@elevatebart](https://github.com/elevatebart)! - feat: Docgen wrapping active links
8
+
9
+ - Updated dependencies [[`b382b9e`](https://github.com/cypress-io/cypress-design/commit/b382b9ecc99e3ab6288eee406831199edb661c19)]:
10
+ - @cypress-design/constants-docmenu@0.1.2
11
+
12
+ ## 0.4.0
13
+
14
+ ### Minor Changes
15
+
16
+ - [#350](https://github.com/cypress-io/cypress-design/pull/350) [`abc414d`](https://github.com/cypress-io/cypress-design/commit/abc414da149eb6fe0cfa61497a0aa8630d8d8d6e) Thanks [@elevatebart](https://github.com/elevatebart)! - feat: allow usage of a custom link component
17
+
18
+ ### Patch Changes
19
+
20
+ - [#347](https://github.com/cypress-io/cypress-design/pull/347) [`de67952`](https://github.com/cypress-io/cypress-design/commit/de679522ea2269854ec8ac8de925fcf930ce96e4) Thanks [@elevatebart](https://github.com/elevatebart)! - fix: docmenu docusaurus style items structure
21
+
22
+ - Updated dependencies [[`de67952`](https://github.com/cypress-io/cypress-design/commit/de679522ea2269854ec8ac8de925fcf930ce96e4)]:
23
+ - @cypress-design/constants-docmenu@0.1.1
24
+
3
25
  ## 0.3.0
4
26
 
5
27
  ### Minor Changes
package/DocMenu.vue CHANGED
@@ -1,26 +1,55 @@
1
1
  <script lang="ts" setup>
2
+ import { ref, type DefineComponent } from 'vue'
2
3
  import { NavGroup, NavItemLink } from '@cypress-design/constants-docmenu'
3
- import DocGroup from './_DocGroup.vue'
4
- import DocLink from './_DocLink.vue'
4
+ import DocGroupElements from './_DocGroupElements.vue'
5
5
 
6
6
  withDefaults(
7
7
  defineProps<{
8
8
  items: (NavItemLink | NavGroup)[]
9
+ activePath?: string
9
10
  collapsible?: boolean
11
+ linkComponent?: DefineComponent | 'a'
10
12
  }>(),
11
13
  {
14
+ linkComponent: 'a',
12
15
  collapsible: true,
13
- }
16
+ activePath: undefined,
17
+ },
14
18
  )
19
+
20
+ const $container = ref<HTMLDivElement>()
21
+
22
+ const activeTop = ref(0)
23
+ const activeHeight = ref(36)
24
+ const showMarker = ref(false)
25
+
26
+ function updateActiveMarkerPosition({ top, height } = { height: 0, top: 0 }) {
27
+ const containerTop = $container.value?.getBoundingClientRect().top || 0
28
+ activeTop.value = top - containerTop
29
+ activeHeight.value = height
30
+ showMarker.value = true
31
+ }
15
32
  </script>
16
33
 
17
34
  <template>
18
- <ul class="overflow-y-hidden list-none p-0">
19
- <template v-for="item in items">
20
- <li v-if="'items' in item" class="relative list-none p-0">
21
- <DocGroup :group="item" :depth="0" :collapsible="collapsible" />
22
- </li>
23
- <DocLink v-else :item="item" :collapsible="collapsible" />
24
- </template>
25
- </ul>
35
+ <div ref="$container" class="relative">
36
+ <div
37
+ v-if="showMarker && collapsible"
38
+ data-cy="doc-menu-active-marker"
39
+ class="absolute h-[36px] w-[4px] z-50 rounded-full bg-indigo-500 transition-all duration-300 ml-[6.5px] mt-[4px]"
40
+ :style="{
41
+ top: `${activeTop + 4}px`,
42
+ height: `${activeHeight - 8}px`,
43
+ }"
44
+ />
45
+ <DocGroupElements
46
+ :items="items"
47
+ :active-path="activePath"
48
+ :collapsible="collapsible"
49
+ :link-component="linkComponent"
50
+ :depth="-1"
51
+ @update-active-position="updateActiveMarkerPosition"
52
+ @hide-marker="showMarker = false"
53
+ />
54
+ </div>
26
55
  </template>
package/DocMenuVue.cy.tsx CHANGED
@@ -1,12 +1,50 @@
1
1
  /// <reference types="cypress" />
2
+ import { type DefineComponent, defineComponent } from 'vue'
2
3
  import { mount } from 'cypress/vue'
3
4
  import assertions from '../assertions'
4
5
  import type { NavGroup, NavItemLink } from '../constants'
5
- import DocMenuStory from './DocMenu.rootstory'
6
+ import DocMenu from './DocMenu.vue'
7
+
8
+ const CustomLinkVue: DefineComponent<{ href: StringConstructor }> =
9
+ defineComponent({
10
+ props: {
11
+ href: String,
12
+ },
13
+ setup(props, { slots }) {
14
+ return () => (
15
+ <div>
16
+ {slots.default?.()} + href: {props.href}
17
+ </div>
18
+ )
19
+ },
20
+ })
6
21
 
7
22
  describe('<DocMenu/>', () => {
8
- function mountStory(items: (NavItemLink | NavGroup)[] = []) {
9
- mount(() => <DocMenuStory items={items} />)
23
+ it('renders', () => {
24
+ mount(() => <DocMenu items={[{ href: '/foo', label: 'Foo' }]} />)
25
+ cy.get('ul').should('exist')
26
+ })
27
+
28
+ it('renders the custom link components', () => {
29
+ mount(() => (
30
+ <DocMenu
31
+ items={[
32
+ {
33
+ label: 'Baaaaaaz',
34
+ items: [{ label: 'Bar', items: [{ href: '/foo', label: 'Foo' }] }],
35
+ },
36
+ ]}
37
+ linkComponent={CustomLinkVue}
38
+ />
39
+ ))
40
+
41
+ cy.findByText('Foo + href: /foo', { selector: 'div' }).should('be.visible')
42
+ })
43
+
44
+ function mountStory(items: (NavItemLink | NavGroup)[] = [], activePath = '') {
45
+ mount(() => {
46
+ return <DocMenu items={items} activePath={activePath} />
47
+ })
10
48
  }
11
49
  assertions(mountStory)
12
50
  })
package/ReadMe.md CHANGED
@@ -20,57 +20,56 @@ import DocMenu from '@cypress-design/vue-docmenu'
20
20
 
21
21
  ```vue live
22
22
  <DocMenu
23
+ activePath="#group-item-3"
23
24
  :items="[
24
25
  {
25
- text: 'Get Started',
26
- href: '#',
26
+ label: 'Get Started',
27
+ href: '#get-started',
27
28
  },
28
29
  {
29
- text: 'Overview',
30
+ label: 'Overview',
30
31
  items: [
31
32
  {
32
- text: 'Overview Item 1',
33
- href: '#',
33
+ label: 'Overview Item 1',
34
+ href: '#overview-item-1',
34
35
  },
35
36
  {
36
- text: 'Overview Item 2',
37
- href: '#',
38
- active: true,
37
+ label: 'Overview Item 2',
38
+ href: '#overview-item-2',
39
39
  },
40
40
  ],
41
41
  },
42
42
  {
43
- text: 'Getting Started',
43
+ label: 'Getting Started',
44
44
  items: [
45
45
  {
46
- text: 'Item 1',
47
- href: '#',
46
+ label: 'Item 1',
47
+ href: '#item-1',
48
48
  },
49
49
  {
50
- text: 'Group',
50
+ label: 'Group',
51
51
  items: [
52
52
  {
53
- text: 'Item 1',
54
- href: '#',
53
+ label: 'Item 1',
54
+ href: '#group-item-1',
55
55
  },
56
56
  {
57
- text: 'Item 2',
58
- href: '#',
57
+ label: 'Item 2',
58
+ href: '#group-item-2',
59
59
  },
60
60
  {
61
- text: 'Item 3',
62
- href: '#',
61
+ label: 'Item 3',
62
+ href: '#group-item-3',
63
63
  },
64
64
  {
65
- text: 'Item 4',
66
- href: '#',
65
+ label: 'Item 4',
66
+ href: '#group-item-4',
67
67
  },
68
68
  ],
69
69
  },
70
70
  {
71
- text: 'Item 5',
71
+ label: 'Item 5',
72
72
  href: '#',
73
- active: true,
74
73
  },
75
74
  ],
76
75
  },
package/_DocGroup.vue CHANGED
@@ -1,63 +1,72 @@
1
1
  <script lang="ts" setup>
2
- import { computed, Ref, ref } from 'vue'
2
+ import { computed, type DefineComponent, ref, watch, nextTick } from 'vue'
3
3
  import { IconChevronDownSmall } from '@cypress-design/vue-icon'
4
4
  import { NavGroup, classes } from '@cypress-design/constants-docmenu'
5
- import DocLink from './_DocLink.vue'
6
- import DocGroup from './_DocGroup.vue'
5
+ import DocGroupElements from './_DocGroupElements.vue'
7
6
 
8
7
  const props = withDefaults(
9
8
  defineProps<{
10
9
  group: NavGroup
10
+ activePath?: string
11
11
  collapsible: boolean
12
+ linkComponent: DefineComponent | 'a'
12
13
  depth?: number
13
14
  }>(),
14
15
  {
15
16
  depth: 0,
16
- }
17
+ },
17
18
  )
18
19
 
19
- const open = ref(props.depth === 0)
20
+ const open = ref(props.group.collapsed !== true)
21
+ const $groupElements = ref<typeof DocGroupElements>()
20
22
 
21
- const $groups = ref<{ height: number }[]>([])
23
+ const Head = computed(() =>
24
+ props.collapsible ? 'button' : props.group.href ? 'a' : 'div',
25
+ )
22
26
 
23
- const height = computed(() => {
24
- return $groups.value && open.value
25
- ? $groups.value.reduce(
26
- (acc, { height: h }) => acc + h,
27
- props.group.items.length
28
- )
29
- : 0
30
- })
27
+ const emit = defineEmits<{
28
+ (event: 'updateActivePosition', opts?: { top: number; height: number }): void
29
+ (event: 'hideMarker'): void
30
+ (event: 'updateMarkerPosition'): void
31
+ }>()
31
32
 
32
- defineExpose<{
33
- height: Ref<number>
34
- }>({
35
- height,
36
- })
33
+ function hasActiveItemRecursively(items = props.group.items): boolean {
34
+ return items.some((item) => {
35
+ if ('items' in item) {
36
+ return hasActiveItemRecursively(item.items)
37
+ }
38
+ return item.href === props.activePath
39
+ })
40
+ }
37
41
 
38
- const activeMarkerTop = computed(() => {
39
- const activeIndex = props.group.items.findIndex(
40
- (item) => 'href' in item && item.active
41
- )
42
+ function reTriggerSetActiveGroup() {
43
+ $groupElements.value?.reTriggerSetActiveGroup()
44
+ }
42
45
 
43
- // how many groups are before the active element?
44
- let numberOfGroups = props.group.items.filter(
45
- (item, index) => !('href' in item) && index < activeIndex
46
- ).length
46
+ const active = computed(() => props.group.href === props.activePath)
47
47
 
48
- // if there is any open group before the active element
49
- // compensate for the height
50
- const groupHeight = $groups.value?.reduce((acc, group) => {
51
- if (numberOfGroups < -1) return acc
52
- numberOfGroups--
53
- return acc + group.height
54
- }, 0)
55
- return (activeIndex + groupHeight) * 44
48
+ watch(active, (active) => {
49
+ if (active) {
50
+ emit('hideMarker')
51
+ }
56
52
  })
57
53
 
58
- const Head = computed(() =>
59
- props.collapsible ? 'button' : props.group.href ? 'a' : 'div'
60
- )
54
+ watch(open, async (open) => {
55
+ await nextTick()
56
+ if (hasActiveItemRecursively()) {
57
+ if (open) {
58
+ reTriggerSetActiveGroup()
59
+ } else {
60
+ emit('hideMarker')
61
+ }
62
+ } else {
63
+ emit('updateMarkerPosition')
64
+ }
65
+ })
66
+
67
+ defineExpose({
68
+ reTriggerSetActiveGroup,
69
+ })
61
70
  </script>
62
71
 
63
72
  <template>
@@ -68,14 +77,16 @@ const Head = computed(() =>
68
77
  {
69
78
  [classes.topButton]: depth === 0,
70
79
  [classes.leafButton]: depth,
71
- 'text-indigo-500': props.group.active,
80
+ 'text-indigo-500': active,
72
81
  },
73
82
  ]"
74
- :href="props.group.href"
83
+ :href="group.href"
75
84
  @click="
76
85
  () => {
77
86
  if (collapsible) {
78
87
  open = !open
88
+ } else {
89
+ emit('updateActivePosition')
79
90
  }
80
91
  }
81
92
  "
@@ -91,38 +102,18 @@ const Head = computed(() =>
91
102
  'ml-[16px]': depth,
92
103
  }"
93
104
  />
94
- {{ group.text }}
105
+ {{ group.label }}
95
106
  </component>
96
- <div
97
- v-if="
98
- props.collapsible &&
99
- depth >= 0 &&
100
- open &&
101
- group.items.some((item) => 'href' in item && item.active)
102
- "
103
- class="absolute h-[36px] w-[4px] z-10 rounded-full bg-indigo-500 transition-all duration-300 ml-[6px] mt-[48px]"
104
- :style="{
105
- top: `${activeMarkerTop}px`,
106
- left: `-${depth === 0 ? 0 : depth * 7.5 + 1}px`,
107
- }"
108
- />
109
- <ul
107
+ <DocGroupElements
108
+ ref="$groupElements"
110
109
  v-show="open"
111
- class="list-none p-0 ml-[7.5px]"
112
- :class="{
113
- 'border-l border-gray-100': depth === 0 && props.collapsible,
114
- }"
115
- >
116
- <template v-for="item in group.items">
117
- <li class="relative list-none p-0" v-if="item && 'items' in item">
118
- <DocGroup
119
- ref="$groups"
120
- :group="item"
121
- :depth="depth + 1"
122
- :collapsible="props.collapsible"
123
- />
124
- </li>
125
- <DocLink v-else :item="item" :depth="depth" :collapsible="collapsible" />
126
- </template>
127
- </ul>
110
+ :depth="depth"
111
+ :items="group.items"
112
+ :active-path="activePath"
113
+ :collapsible="collapsible"
114
+ :link-component="linkComponent"
115
+ @hide-marker="emit('hideMarker')"
116
+ @update-marker-position="emit('updateMarkerPosition')"
117
+ @update-active-position="(opts) => emit('updateActivePosition', opts)"
118
+ />
128
119
  </template>
@@ -0,0 +1,97 @@
1
+ <script lang="ts" setup>
2
+ import { DefineComponent, ref } from 'vue'
3
+ import { NavGroup, NavItemLink } from '@cypress-design/constants-docmenu'
4
+ import DocGroup from './_DocGroup.vue'
5
+ import DocLink from './_DocLink.vue'
6
+
7
+ const props = defineProps<{
8
+ items: (NavGroup | NavItemLink)[]
9
+ activePath?: string
10
+ collapsible: boolean
11
+ linkComponent: DefineComponent | 'a'
12
+ depth: number
13
+ }>()
14
+
15
+ const emit = defineEmits<{
16
+ (event: 'updateActivePosition', opts?: { top: number; height: number }): void
17
+ (event: 'hideMarker'): void
18
+ (event: 'updateMarkerPosition'): void
19
+ }>()
20
+
21
+ const $items = ref<(typeof DocLink)[]>([])
22
+ const $groups = ref<(typeof DocGroup)[]>([])
23
+
24
+ function hasActiveItemRecursively(items = props.items): boolean {
25
+ return items.some((item) => {
26
+ if ('items' in item) {
27
+ return hasActiveItemRecursively(item.items)
28
+ }
29
+ return item.href === props.activePath
30
+ })
31
+ }
32
+
33
+ function reTriggerSetActiveGroup() {
34
+ $items.value.forEach((item) => {
35
+ item.setActiveMarkerPosition()
36
+ })
37
+ $groups.value.forEach((group) => {
38
+ group.reTriggerSetActiveGroup()
39
+ })
40
+ }
41
+
42
+ defineExpose({
43
+ reTriggerSetActiveGroup,
44
+ })
45
+ </script>
46
+
47
+ <template>
48
+ <ul
49
+ class="list-none p-0"
50
+ :class="{
51
+ 'border-l border-gray-100': depth === 0 && collapsible,
52
+ 'ml-[8px]': depth >= 0,
53
+ }"
54
+ >
55
+ <template v-for="item in items">
56
+ <li class="relative list-none p-0" v-if="item && 'items' in item">
57
+ <DocGroup
58
+ ref="$groups"
59
+ :active-path="activePath"
60
+ :group="item"
61
+ :depth="depth + 1"
62
+ :collapsible="collapsible"
63
+ :link-component="linkComponent"
64
+ @update-active-position="
65
+ (opts) => {
66
+ emit('updateActivePosition', opts)
67
+ }
68
+ "
69
+ @update-marker-position="
70
+ () => {
71
+ if (hasActiveItemRecursively()) {
72
+ reTriggerSetActiveGroup()
73
+ } else {
74
+ emit('updateMarkerPosition')
75
+ }
76
+ }
77
+ "
78
+ />
79
+ </li>
80
+ <DocLink
81
+ v-else
82
+ ref="$items"
83
+ :item="item"
84
+ :active="item.href === activePath"
85
+ :depth="depth"
86
+ :collapsible="collapsible"
87
+ :link-component="linkComponent"
88
+ @update:active="
89
+ (_, opts) => {
90
+ if (!opts) return
91
+ emit('updateActivePosition', opts)
92
+ }
93
+ "
94
+ />
95
+ </template>
96
+ </ul>
97
+ </template>
package/_DocLink.vue CHANGED
@@ -1,29 +1,74 @@
1
1
  <script lang="ts" setup>
2
+ import { watch, type DefineComponent, ref, onMounted } from 'vue'
2
3
  import type { NavItemLink } from '@cypress-design/constants-docmenu'
3
4
 
4
- withDefaults(
5
+ const props = withDefaults(
5
6
  defineProps<{
6
7
  item: NavItemLink
7
- depth?: number
8
+ active: boolean
8
9
  collapsible: boolean
10
+ depth?: number
11
+ linkComponent: DefineComponent | 'a'
9
12
  }>(),
10
13
  {
11
14
  depth: -1,
12
- }
15
+ },
13
16
  )
17
+
18
+ const emit = defineEmits<{
19
+ (
20
+ event: 'update:active',
21
+ active: boolean,
22
+ opts?: { top: number; height: number },
23
+ ): void
24
+ }>()
25
+
26
+ const $container = ref<HTMLLIElement>()
27
+
28
+ function setActiveMarkerPosition() {
29
+ if (props.active) {
30
+ const { top = 0, height = 36 } =
31
+ $container.value?.getBoundingClientRect() ?? {}
32
+
33
+ emit('update:active', props.active, {
34
+ top,
35
+ height,
36
+ })
37
+ }
38
+ }
39
+
40
+ onMounted(() => {
41
+ watch(
42
+ () => props.active,
43
+ (active) => {
44
+ if (active) {
45
+ setActiveMarkerPosition()
46
+ } else {
47
+ emit('update:active', active)
48
+ }
49
+ },
50
+ { immediate: true },
51
+ )
52
+ })
53
+
54
+ defineExpose({
55
+ setActiveMarkerPosition,
56
+ })
14
57
  </script>
15
58
 
16
59
  <template>
17
60
  <li
61
+ ref="$container"
18
62
  class="list-none p-0"
19
63
  :class="{
20
64
  'pl-[16px]': depth >= 0,
21
65
  }"
22
66
  >
23
- <a
67
+ <component
68
+ :is="linkComponent"
24
69
  class="group relative block w-full pl-[24px]"
25
70
  :class="{
26
- 'text-indigo-500': item.active,
71
+ 'text-indigo-500': active,
27
72
  'py-[8px] text-[16px] leading-[24px]': depth < 0,
28
73
  'leading-[20px] text-[14px] py-[12px]': depth >= 0,
29
74
  }"
@@ -33,13 +78,13 @@ withDefaults(
33
78
  v-if="depth >= 0"
34
79
  class="absolute h-[80%] top-[10%] w-[4px] z-10 rounded-full hidden"
35
80
  :class="{
36
- 'group-hover:block bg-gray-300': !item.active && collapsible,
81
+ 'group-hover:block bg-gray-300': !active && collapsible,
37
82
  }"
38
83
  :style="{
39
84
  left: `-${18.5 + depth * 7.5}px`,
40
85
  }"
41
86
  />
42
- {{ item.text }}
43
- </a>
87
+ {{ item.label }}
88
+ </component>
44
89
  </li>
45
90
  </template>