@explorer-1/vue 0.2.3 → 0.2.5

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.
Files changed (81) hide show
  1. package/components.d.ts +6 -3
  2. package/dist/explorer-1-vue.js +5603 -5375
  3. package/dist/explorer-1-vue.umd.cjs +12 -12
  4. package/dist/src/components/BlockCardGrid/BlockCardGrid.stories.d.ts +60 -0
  5. package/dist/src/components/BlockCardGrid/BlockCardGrid.vue.d.ts +33 -0
  6. package/dist/src/components/{BlockCard/BlockCard.stories.d.ts → BlockCardGridItem/BlockCardGridItem.stories.d.ts} +18 -4
  7. package/dist/src/components/{BlockCardGroup/BlockCardGroup.vue.d.ts → BlockCardGridItem/BlockCardGridItem.vue.d.ts} +10 -12
  8. package/dist/src/components/{BlockCard/BlockCard.vue.d.ts → BlockCardGridItem/BlockCardGridItemElement.vue.d.ts} +20 -14
  9. package/dist/src/components/BlockCircleImageCard/BlockCircleImageCard.stories.d.ts +1 -0
  10. package/dist/src/components/BlockHeading/BlockHeading.vue.d.ts +25 -2
  11. package/dist/src/components/BlockLinkCard/BlockLinkCard.stories.d.ts +1 -0
  12. package/dist/src/components/BlockLinkTile/BlockLinkTile.stories.d.ts +1 -0
  13. package/dist/src/components/BlockListCards/BlockListCards.stories.d.ts +1 -0
  14. package/dist/src/components/BlockStreamfield/BlockStreamfield.stories.d.ts +60 -0
  15. package/dist/src/components/BlockStreamfield/BlockStreamfield.vue.d.ts +2 -3
  16. package/dist/src/components/DetailHeadline/DetailHeadline.stories.d.ts +30 -1
  17. package/dist/src/components/DetailHeadline/DetailHeadline.vue.d.ts +37 -0
  18. package/dist/src/components/LayoutHelper/LayoutHelper.vue.d.ts +9 -0
  19. package/dist/src/components/NavDesktop/NavDesktop.stories.d.ts +3 -0
  20. package/dist/src/components/NavDesktop/NavDesktop.vue.d.ts +1 -0
  21. package/dist/src/components/NavDesktop/NavDesktopDropdown.vue.d.ts +1 -0
  22. package/dist/src/components/NavDesktopEdu/NavDesktopEdu.stories.d.ts +1 -0
  23. package/dist/src/components/NavDropdownToggle/NavDropdownToggle.vue.d.ts +19 -6
  24. package/dist/src/components/NavJumpMenu/NavJumpMenu.stories.d.ts +31 -0
  25. package/dist/src/components/NavMobile/NavMobile.stories.d.ts +30 -3
  26. package/dist/src/components/NavMobile/NavMobile.vue.d.ts +1 -0
  27. package/dist/src/components/NavMobile/NavMobileDropdown.vue.d.ts +9 -1
  28. package/dist/src/components/NavMobile/NavMobileEdu.stories.d.ts +3 -0
  29. package/dist/src/components/NavMobile/NavMobileSecondaryDropdown.vue.d.ts +7 -0
  30. package/dist/src/components/NavSecondary/NavSecondary.stories.d.ts +8 -0
  31. package/dist/src/components/NavSecondary/NavSecondary.vue.d.ts +23 -1
  32. package/dist/src/components/NavSecondary/NavSecondaryDropdown.vue.d.ts +33 -2
  33. package/dist/src/components/NavSecondary/NavSecondaryDropdownContent.vue.d.ts +11 -1
  34. package/dist/src/components/NavSecondary/NavSecondaryLink.vue.d.ts +21 -3
  35. package/dist/src/components/ThumbnailCarousel/ThumbnailCarousel.stories.d.ts +1 -0
  36. package/dist/src/docs/foundation/grid_layouthelpers.stories.d.ts +36 -0
  37. package/dist/src/interfaces.d.ts +13 -3
  38. package/dist/src/store/header.d.ts +2 -0
  39. package/dist/src/templates/PageNewsDetail/PageNewsDetail.stories.d.ts +8 -0
  40. package/dist/src/templates/edu/{PageEduResourceArticle/PageEduResourceArticle.stories.d.ts → PageEduExplainerArticle/PageEduExplainerArticle.stories.d.ts} +4 -2
  41. package/dist/src/templates/edu/PageEduNewsDetail/PageEduNewsDetail.stories.d.ts +1007 -6
  42. package/dist/src/templates/www/PageAsteroidWatchIndex/PageAsteroidWatchIndex.stories.d.ts +60 -0
  43. package/dist/src/templates/www/PageRoboticsDetail/PageRoboticsDetail.stories.d.ts +29 -2
  44. package/dist/src/utils/eventBus.d.ts +1 -0
  45. package/dist/src/utils/getHeadingId.d.ts +1 -0
  46. package/dist/src/utils/mixins.d.ts +1 -1
  47. package/dist/style.css +1 -1
  48. package/package.json +3 -2
  49. package/src/components/BaseAudio/BaseAudio.vue +3 -4
  50. package/src/components/BaseLink/BaseLink.vue +2 -0
  51. package/src/components/BaseTag/BaseTag.vue +4 -4
  52. package/src/components/BlockHeading/BlockHeading.vue +28 -0
  53. package/src/components/BlockStreamfield/BlockStreamfield.vue +5 -1
  54. package/src/components/DetailHeadline/DetailHeadline.stories.js +28 -2
  55. package/src/components/DetailHeadline/DetailHeadline.vue +85 -32
  56. package/src/components/NavDesktop/NavDesktopDropdown.vue +2 -4
  57. package/src/components/NavDropdownToggle/NavDropdownToggle.vue +8 -3
  58. package/src/components/NavJumpMenu/NavJumpMenu.stories.js +47 -0
  59. package/src/components/NavJumpMenu/NavJumpMenu.vue +141 -0
  60. package/src/components/NavJumpMenu/NavJumpMenuContent.vue +74 -0
  61. package/src/components/NavMobile/NavMobile.vue +2 -4
  62. package/src/components/NavMobile/NavMobileDropdown.vue +8 -4
  63. package/src/components/NavMobile/NavMobileSecondaryDropdown.vue +4 -1
  64. package/src/components/NavSecondary/NavSecondary.stories.js +8 -3
  65. package/src/components/NavSecondary/NavSecondary.vue +26 -6
  66. package/src/components/NavSecondary/NavSecondaryDropdown.vue +52 -17
  67. package/src/components/NavSecondary/NavSecondaryDropdownContent.vue +5 -1
  68. package/src/components/NavSecondary/NavSecondaryLink.vue +38 -11
  69. package/src/interfaces.ts +7 -1
  70. package/src/store/header.ts +6 -1
  71. package/src/templates/PageNewsDetail/PageNewsDetail.stories.js +1 -0
  72. package/src/templates/PageNewsDetail/PageNewsDetail.vue +1 -0
  73. package/src/templates/edu/PageEduEventDetail/PageEduEventDetail.vue +2 -2
  74. package/src/templates/edu/{PageEduResourceArticle/PageEduResourceArticle.stories.js → PageEduExplainerArticle/PageEduExplainerArticle.stories.js} +8 -6
  75. package/src/templates/edu/{PageEduResourceArticle/PageEduResourceArticle.vue → PageEduExplainerArticle/PageEduExplainerArticle.vue} +5 -2
  76. package/src/templates/edu/PageEduNewsDetail/PageEduNewsDetail.stories.js +7 -4
  77. package/src/templates/edu/PageEduNewsDetail/PageEduNewsDetail.vue +18 -2
  78. package/src/utils/eventBus.ts +3 -0
  79. package/src/utils/getHeadingId.ts +5 -0
  80. package/src/utils/mixins.ts +5 -1
  81. package/dist/src/components/BlockCardGroup/BlockCardGroup.stories.d.ts +0 -32
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@explorer-1/vue",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "private": false,
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -21,6 +21,7 @@
21
21
  "dayjs": "^1.11.11",
22
22
  "fast-qs": "^2.0.3",
23
23
  "lodash": "^4.17.21",
24
+ "mitt": "^3.0.1",
24
25
  "sass": "^1.77.4",
25
26
  "swiper": "^11.1.3",
26
27
  "tailwindcss": "^3.4.3",
@@ -28,7 +29,7 @@
28
29
  "vue": "^3.4.21",
29
30
  "vue-observe-visibility": "^1.0.0",
30
31
  "vue3-compare-image": "^1.2.5",
31
- "@explorer-1/common": "1.1.0"
32
+ "@explorer-1/common": "1.1.1"
32
33
  },
33
34
  "devDependencies": {
34
35
  "@vitejs/plugin-vue": "^5.0.4",
@@ -119,6 +119,7 @@
119
119
  </template>
120
120
  <script lang="ts">
121
121
  import { defineComponent } from 'vue'
122
+ // import { eventBus } from './../../utils/eventBus'
122
123
  import IconPlay from './../Icons/IconPlay.vue'
123
124
  import IconPause from './../Icons/IconPause.vue'
124
125
  import IconVolume from './../Icons/IconVolume.vue'
@@ -326,10 +327,8 @@ export default defineComponent({
326
327
  this.audio.addEventListener('play', this._handlePlayPause)
327
328
  this.audio.addEventListener('ended', this._handleEnded)
328
329
  }
329
- // TODO: VUE3: find solution for emitting event from slot
330
- // TODO: find a cleaner way to do this w/o using mounted or root level events
331
- // scoped slots? https://github.com/vuejs/vue/issues/4332
332
- // this.$root?.$on('play', this.pauseOthers)
330
+ // TODO: VUE3: pass uuID to pauseOthers() method
331
+ // eventBus.on('play', () => this.pauseOthers())
333
332
  },
334
333
  getAudio() {
335
334
  return this.$el.querySelectorAll('audio')[0]
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { defineComponent } from 'vue'
3
+ import { eventBus } from './../../utils/eventBus'
3
4
  import MixinAnimationCaret from './../MixinAnimationCaret/MixinAnimationCaret.vue'
4
5
 
5
6
  interface Variants {
@@ -145,6 +146,7 @@ export default defineComponent({
145
146
  clickEvent() {
146
147
  this.$root?.$emit('linkClicked')
147
148
  this.$emit('specificLinkClicked')
149
+ eventBus.emit('linkClicked')
148
150
  }
149
151
  }
150
152
  })
@@ -9,9 +9,9 @@ const variantMap: Attributes = {
9
9
  }
10
10
 
11
11
  const sizeMap: Attributes = {
12
- sm: 'text-xs uppercase border-t-2 py-1 px-2.5',
13
- md: 'text-base capitalize border-t py-1 px-3',
14
- lg: 'text-lg capitalize border-t py-1 px-3'
12
+ sm: 'text-xs border-t-2 py-1 px-2.5',
13
+ md: 'text-xs lg:text-base border-t py-1.5 px-3.5',
14
+ lg: 'text-base lg:text-lg border-t py-1.5 px-5'
15
15
  }
16
16
 
17
17
  interface BaseTagProps {
@@ -28,7 +28,7 @@ const props = withDefaults(defineProps<BaseTagProps>(), {
28
28
  <template>
29
29
  <p
30
30
  :class="`${variantMap[props.variant]} ${sizeMap[props.size]}`"
31
- class="ThemeVariantLight text-contrast-none inline-block text-white font-bold edu:font-extrabold rounded-full leading-tight m-0"
31
+ class="ThemeVariantLight text-contrast-none inline-block text-white font-bold edu:font-extrabold rounded-full leading-tight m-0 uppercase"
32
32
  >
33
33
  <slot />
34
34
  <span class="sr-only">.</span>
@@ -1,7 +1,10 @@
1
1
  <template>
2
2
  <BaseHeading
3
3
  v-if="data"
4
+ :id="getId"
4
5
  :level="data.level"
6
+ class="BlockHeading"
7
+ :class="{ 'has-anchor': generateId }"
5
8
  >
6
9
  {{ data.heading }}
7
10
  </BaseHeading>
@@ -9,6 +12,7 @@
9
12
 
10
13
  <script lang="ts">
11
14
  import { defineComponent } from 'vue'
15
+ import { getHeadingId } from '../../utils/getHeadingId'
12
16
  import BaseHeading from './../BaseHeading/BaseHeading.vue'
13
17
 
14
18
  export default defineComponent({
@@ -20,7 +24,31 @@ export default defineComponent({
20
24
  data: {
21
25
  type: Object,
22
26
  required: false
27
+ },
28
+ index: {
29
+ type: Number,
30
+ required: false,
31
+ default: undefined
32
+ },
33
+ generateId: {
34
+ type: Boolean,
35
+ default: false
36
+ }
37
+ },
38
+ computed: {
39
+ getId() {
40
+ return this.generateId ? getHeadingId(this.data?.heading, this.index) : undefined
23
41
  }
24
42
  }
25
43
  })
26
44
  </script>
45
+ <style lang="scss">
46
+ .BlockHeading {
47
+ &:target {
48
+ @apply scroll-mt-14;
49
+ @screen lg {
50
+ @apply scroll-mt-20;
51
+ }
52
+ }
53
+ }
54
+ </style>
@@ -11,7 +11,11 @@
11
11
  indent="col-3"
12
12
  class="mb-5"
13
13
  >
14
- <BlockHeading :data="block" />
14
+ <BlockHeading
15
+ :data="block"
16
+ :index="index"
17
+ generate-id
18
+ />
15
19
  </LayoutHelper>
16
20
 
17
21
  <!-- custom margin bottom that matches BlockText styles if followed by InlineImageBlock -->
@@ -1,14 +1,33 @@
1
- import DetailHeadline from './DetailHeadline.vue'
1
+ import DetailHeadline, { pillColorVariants } from './DetailHeadline.vue'
2
2
 
3
3
  export default {
4
4
  title: 'Components/Utilities/DetailHeadline',
5
5
  component: DetailHeadline,
6
- excludeStories: /.*Data$/
6
+ excludeStories: /.*Data$/,
7
+ argTypes: {
8
+ ariaLabel: {
9
+ type: 'string',
10
+ description:
11
+ "ARIA label. Recommended if your button label isn't descriptive, or if it only contains an icon."
12
+ },
13
+ pillColor: {
14
+ type: 'string',
15
+ description: 'Color of pill',
16
+ control: {
17
+ type: 'select'
18
+ },
19
+ options: pillColorVariants,
20
+ table: {
21
+ defaultValue: { summary: 'primary' }
22
+ }
23
+ }
24
+ }
7
25
  }
8
26
 
9
27
  export const DetailHeadlineData = {
10
28
  title: "NASA's Ingenuity Mars Helicopter Recharges Its Batteries in Flight",
11
29
  publicationDate: '2020-08-13',
30
+ readTime: '3 min read',
12
31
  author: {
13
32
  name: 'Jane Platt',
14
33
  organization: 'JPL'
@@ -66,3 +85,10 @@ export const NoAuthor = {
66
85
  ]
67
86
  }
68
87
  }
88
+
89
+ export const Pill = {
90
+ args: {
91
+ ...DetailHeadlineData,
92
+ pill: true
93
+ }
94
+ }
@@ -1,43 +1,63 @@
1
1
  <template>
2
- <div v-if="title || label || topics || publicationDate || author">
2
+ <div v-if="hasData">
3
3
  <div
4
- v-if="label || (topics && topics.length)"
5
- class="flex flex-wrap items-start mb-3"
4
+ v-if="hasEyebrow"
5
+ class="flex flex-wrap items-center mb-3"
6
6
  >
7
- <div
8
- v-if="topics && topics.length"
9
- class="inline"
10
- >
11
- <BaseLink
12
- variant="secondary"
13
- :to="topics[0].url"
14
- class="py-3"
15
- :use-primary-color="themeStore.theme === 'ThemeEdu'"
7
+ <template v-if="pill && pillLabel">
8
+ <BaseTag
9
+ :variant="pillColor"
10
+ size="lg"
11
+ class="mr-3"
12
+ >
13
+ {{ pillLabel }}
14
+ </BaseTag>
15
+ </template>
16
+ <template v-else>
17
+ <div
18
+ v-if="topics && topics.length"
19
+ class="inline"
16
20
  >
17
- <span :itemprop="schema ? 'articleSection' : undefined">
18
- {{ topics[0].title }}
19
- </span>
20
- </BaseLink>
21
- </div>
22
- <span
23
- v-else-if="label"
24
- class="text-subtitle py-3 edu:text-primary"
25
- >
26
- <template v-if="!labelLink">
27
- {{ label }}
28
- </template>
29
- <template v-else>
30
21
  <BaseLink
31
22
  variant="secondary"
32
- :to="labelLink"
23
+ :to="topics[0].url"
33
24
  class="py-3"
34
25
  :use-primary-color="themeStore.theme === 'ThemeEdu'"
35
26
  >
36
- {{ label }}
27
+ <span :itemprop="schema ? 'articleSection' : undefined">
28
+ {{ topics[0].title }}
29
+ </span>
37
30
  </BaseLink>
38
- </template>
31
+ </div>
32
+ <span
33
+ v-else-if="label"
34
+ class="text-subtitle py-3 edu:text-primary"
35
+ >
36
+ <template v-if="!labelLink">
37
+ {{ label }}
38
+ </template>
39
+ <template v-else>
40
+ <BaseLink
41
+ variant="secondary"
42
+ :to="labelLink"
43
+ class="py-3"
44
+ :use-primary-color="themeStore.theme === 'ThemeEdu'"
45
+ >
46
+ {{ label }}
47
+ </BaseLink>
48
+ </template>
49
+ </span>
50
+ </template>
51
+ <span
52
+ v-if="hasTag"
53
+ class="sr-only"
54
+ >.</span
55
+ >
56
+ <span
57
+ :class="`${hasTag && !pill ? 'divide-gray-mid-dark border-l ml-3 pl-3 ' : ''} my-4 text-gray-mid-dark uppercase text-sm lg:text-base leading-none`"
58
+ >
59
+ {{ readTime }}
39
60
  </span>
40
- <span class="sr-only">.</span>
41
61
  </div>
42
62
  <BaseHeading
43
63
  level="h1"
@@ -46,8 +66,8 @@
46
66
  >{{ title }}
47
67
  </BaseHeading>
48
68
  <div
49
- v-if="authors?.length || publicationDate"
50
- class="lg:text-base text-gray-mid-dark divide-gray-mid-dark px-3 mt-5 -ml-3 text-sm leading-normal"
69
+ v-if="hasByline"
70
+ class="lg:text-base text-gray-mid-dark divide-gray-mid-dark px-3 mt-5 -ml-3 text-sm leading-none"
51
71
  >
52
72
  <span
53
73
  v-if="authors?.length"
@@ -102,12 +122,16 @@ import { useThemeStore } from '../../store/theme'
102
122
  import type { Topic, AuthorObject } from './../../interfaces'
103
123
  import BaseLink from './../BaseLink/BaseLink.vue'
104
124
  import BaseHeading from './../BaseHeading/BaseHeading.vue'
125
+ import BaseTag from '../BaseTag/BaseTag.vue'
126
+
127
+ export const pillColorVariants = ['primary', 'secondary', 'action']
105
128
 
106
129
  export default defineComponent({
107
130
  name: 'DetailHeadline',
108
131
  components: {
109
132
  BaseLink,
110
- BaseHeading
133
+ BaseHeading,
134
+ BaseTag
111
135
  },
112
136
  props: {
113
137
  title: {
@@ -130,6 +154,11 @@ export default defineComponent({
130
154
  required: false,
131
155
  default: undefined
132
156
  },
157
+ readTime: {
158
+ type: String,
159
+ required: false,
160
+ default: undefined
161
+ },
133
162
  topics: {
134
163
  type: Array as PropType<Topic[]>,
135
164
  required: false,
@@ -146,6 +175,15 @@ export default defineComponent({
146
175
  required: false,
147
176
  default: undefined
148
177
  },
178
+ pill: {
179
+ type: Boolean,
180
+ default: false
181
+ },
182
+ pillColor: {
183
+ type: String,
184
+ default: 'primary',
185
+ validator: (prop: string): boolean => pillColorVariants.includes(prop)
186
+ },
149
187
  schema: {
150
188
  type: Boolean,
151
189
  default: false
@@ -153,6 +191,18 @@ export default defineComponent({
153
191
  },
154
192
  computed: {
155
193
  ...mapStores(useThemeStore),
194
+ hasTag(): boolean {
195
+ return this.topics?.length || this.label ? true : false
196
+ },
197
+ hasEyebrow(): boolean {
198
+ return this.hasTag || this.readTime ? true : false
199
+ },
200
+ hasByline(): boolean {
201
+ return this.authors?.length || this.publicationDate ? true : false
202
+ },
203
+ hasData(): boolean {
204
+ return this.title || this.hasEyebrow || this.hasByline ? true : false
205
+ },
156
206
  pubDatetime(): string | undefined {
157
207
  const currentTime = this.publicationTime || '00:00:00'
158
208
  const returnDate = new Date(this.publicationDate + ' ' + currentTime)
@@ -171,6 +221,9 @@ export default defineComponent({
171
221
  authors = [this.author] as AuthorObject[]
172
222
  }
173
223
  return authors
224
+ },
225
+ pillLabel(): string | undefined {
226
+ return this.label ? this.label : this.topics?.length ? this.topics[0].title : undefined
174
227
  }
175
228
  }
176
229
  })
@@ -37,6 +37,7 @@
37
37
 
38
38
  <script lang="ts">
39
39
  import { defineComponent } from 'vue'
40
+ import { eventBus } from './../../utils/eventBus'
40
41
  import { mapStores } from 'pinia'
41
42
  import { useHeaderStore } from './../../store/header'
42
43
  import NavDropdownToggle from './../NavDropdownToggle/NavDropdownToggle.vue'
@@ -80,10 +81,7 @@ export default defineComponent({
80
81
  }
81
82
  },
82
83
  mounted() {
83
- // TODO: VUE3: find solution for emitting event from slot
84
- // TODO: find a cleaner way to do this w/o using mounted or root level events
85
- // scoped slots? https://github.com/vuejs/vue/issues/4332
86
- // this.$root?.$on('linkClicked', this.closeDropdown)
84
+ eventBus.on('linkClicked', () => this.closeDropdown())
87
85
  },
88
86
  methods: {
89
87
  toggleDropdown() {
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <button
3
3
  class="NavDropdownToggle group cursor-pointer"
4
- :class="{ '-active': isActivePath }"
4
+ :class="{ '-active': isActivePath, '-invert': invert }"
5
5
  :aria-expanded="ariaExpanded"
6
6
  @click="clickEvent()"
7
7
  @keydown.esc="escEvent()"
@@ -26,13 +26,18 @@ export default defineComponent({
26
26
  props: {
27
27
  path: {
28
28
  type: String,
29
- required: false
29
+ default: undefined
30
30
  },
31
31
  ariaExpanded: {
32
32
  type: Boolean,
33
- required: false
33
+ default: false
34
+ },
35
+ invert: {
36
+ type: Boolean,
37
+ default: false
34
38
  }
35
39
  },
40
+ emits: ['closeDropdown', 'toggleClicked'],
36
41
  computed: {
37
42
  isActivePath() {
38
43
  if (this.path) {
@@ -0,0 +1,47 @@
1
+ import NavJumpMenu from './NavJumpMenu.vue'
2
+
3
+ export default {
4
+ title: 'Navigation/Jump Menu',
5
+ component: NavJumpMenu,
6
+ tags: ['!autodocs'],
7
+ excludeStories: /.*Data$/
8
+ }
9
+
10
+ const JumpLinksData = [
11
+ {
12
+ title: 'Heading title',
13
+ path: '#heading_title'
14
+ },
15
+ {
16
+ title: 'Heading title',
17
+ path: '#heading_title'
18
+ },
19
+ {
20
+ title: 'Heading title',
21
+ path: '#heading_title'
22
+ },
23
+ {
24
+ title: 'Heading title',
25
+ path: '#heading_title'
26
+ },
27
+ {
28
+ title: 'Heading title',
29
+ path: '#heading_title'
30
+ }
31
+ ]
32
+ export const BaseStory = {
33
+ name: 'Jump Menu',
34
+ args: {
35
+ title: 'Page Title',
36
+ jumpLinks: JumpLinksData,
37
+ invert: true
38
+ }
39
+ }
40
+
41
+ export const Light = {
42
+ args: {
43
+ title: 'Page Title',
44
+ jumpLinks: JumpLinksData,
45
+ invert: false
46
+ }
47
+ }
@@ -0,0 +1,141 @@
1
+ <template>
2
+ <NavSecondary
3
+ v-if="enabled"
4
+ id="JumpMenuTop"
5
+ ref="NavJumpMenuRef"
6
+ class="NavJumpMenu -hide-until-threshold"
7
+ :invert="invert"
8
+ >
9
+ <template v-for="(item, index) in theBreadcrumbs">
10
+ <template v-if="item.children && item.children.length > 0">
11
+ <NavSecondaryDropdown
12
+ :key="index"
13
+ :item="item"
14
+ :index="index"
15
+ :is-last="theBreadcrumbs && index === theBreadcrumbs.length - 1"
16
+ :invert="invert"
17
+ >
18
+ <NavJumpMenuContent
19
+ :key="index"
20
+ :item="item"
21
+ />
22
+ </NavSecondaryDropdown>
23
+ </template>
24
+ <template v-else>
25
+ <NavSecondaryLink
26
+ :key="index"
27
+ :item="item"
28
+ :index="index"
29
+ :invert="invert"
30
+ />
31
+ </template>
32
+ </template>
33
+ </NavSecondary>
34
+ </template>
35
+ <script setup lang="ts">
36
+ import { computed, defineExpose, ref, onMounted, watch } from 'vue'
37
+ import { mixinUpdateSecondary } from './../../utils/mixins'
38
+ import { useRoute } from 'vue-router'
39
+ import NavSecondary from './../NavSecondary/NavSecondary.vue'
40
+ import NavSecondaryDropdown from './../NavSecondary/NavSecondaryDropdown.vue'
41
+ import NavSecondaryLink from './../NavSecondary/NavSecondaryLink.vue'
42
+ import NavJumpMenuContent from './../NavJumpMenu/NavJumpMenuContent.vue'
43
+ import type { BlockData, BreadcrumbPathObject } from './../../interfaces'
44
+ import { getHeadingId } from '../../utils/getHeadingId'
45
+
46
+ interface NavJumpMenuProps {
47
+ title?: string
48
+ jumpLinks?: BreadcrumbPathObject[]
49
+ blocks?: BlockData[]
50
+ headingLevel?: string
51
+ invert?: boolean
52
+ enabled?: boolean
53
+ }
54
+
55
+ const props = withDefaults(defineProps<NavJumpMenuProps>(), {
56
+ title: undefined,
57
+ jumpLinks: undefined,
58
+ blocks: undefined,
59
+ headingLevel: 'h2',
60
+ enabled: true,
61
+ invert: true,
62
+ hidden: false
63
+ })
64
+
65
+ const NavJumpMenuRef = ref()
66
+
67
+ const theJumpLinks = computed(() => {
68
+ if (props.jumpLinks) {
69
+ return props.jumpLinks
70
+ } else if (props.blocks) {
71
+ const indexedBlocks = props.blocks.map((b, index) => {
72
+ return {
73
+ ...b,
74
+ index: index
75
+ }
76
+ })
77
+ const filteredBlocks = indexedBlocks.filter((b) => {
78
+ return b.blockType === 'HeadingBlock' && b.level === props.headingLevel
79
+ })
80
+ // map to the correct data shape
81
+ const links: BreadcrumbPathObject[] = filteredBlocks.map((l) => {
82
+ return {
83
+ // @ts-expect-error using parameter that was added to BlockData
84
+ path: '#' + getHeadingId(l.heading, l.index),
85
+ title: l.heading
86
+ } as BreadcrumbPathObject
87
+ })
88
+ return links
89
+ }
90
+ return []
91
+ })
92
+
93
+ const theBreadcrumbs = computed(() => {
94
+ let breadcrumb = undefined
95
+ const rootItem = props.title
96
+ ? {
97
+ title: props.title,
98
+ path: '#JumpMenuTop'
99
+ }
100
+ : {
101
+ title: 'Back to top',
102
+ path: '#JumpMenuTop'
103
+ }
104
+ const jumpMenu: BreadcrumbPathObject = {
105
+ title: 'Jump to…',
106
+ path: '#',
107
+ children: theJumpLinks.value as BreadcrumbPathObject[]
108
+ }
109
+ if (theJumpLinks.value) {
110
+ breadcrumb = [rootItem, jumpMenu]
111
+ }
112
+ return breadcrumb as BreadcrumbPathObject[] | undefined
113
+ })
114
+ defineExpose({
115
+ NavJumpMenuRef
116
+ })
117
+ onMounted(() => {
118
+ mixinUpdateSecondary(theBreadcrumbs.value, true)
119
+ })
120
+ const route = useRoute()
121
+
122
+ // repopulate the store with the jump links since the store is cleared on route changes
123
+ watch(
124
+ route,
125
+ () => {
126
+ mixinUpdateSecondary(theBreadcrumbs.value, true)
127
+ }
128
+ // { flush: 'pre', immediate: true, deep: true }
129
+ )
130
+ </script>
131
+ <style lang="scss">
132
+ .NavJumpMenu {
133
+ &.-hide-until-threshold {
134
+ @apply opacity-0 h-0 transition-none overflow-visible;
135
+ &.-is-sticky,
136
+ &.-is-sticky-offset {
137
+ @apply opacity-100 transition-opacity;
138
+ }
139
+ }
140
+ }
141
+ </style>
@@ -0,0 +1,74 @@
1
+ <template>
2
+ <ul class="NavJumpMenuContent bg-white">
3
+ <li
4
+ v-for="(child, index) in item.children"
5
+ :key="index"
6
+ class="text-base border-b border-gray-light"
7
+ >
8
+ <BaseLink
9
+ :href="child.path"
10
+ variant="none"
11
+ :link-class="linkClass(child)"
12
+ >
13
+ <span>
14
+ {{ child.title }}
15
+ </span>
16
+ </BaseLink>
17
+ </li>
18
+ </ul>
19
+ </template>
20
+
21
+ <script lang="ts">
22
+ import { defineComponent } from 'vue'
23
+ import BaseLink from './../BaseLink/BaseLink.vue'
24
+
25
+ interface NavItemObject {
26
+ path: String
27
+ title: String
28
+ children: [Object]
29
+ }
30
+ export default defineComponent({
31
+ name: 'NavJumpMenuContent',
32
+ components: {
33
+ BaseLink
34
+ },
35
+ props: {
36
+ // the tertiary nav item object that includes path, title, and children
37
+ item: {
38
+ type: Object,
39
+ required: true
40
+ }
41
+ },
42
+ methods: {
43
+ linkClass(item?: NavItemObject) {
44
+ // default
45
+ let computedClass = 'py-2 lg:py-4'
46
+ if (!item) {
47
+ // if first (aka Overview)
48
+ computedClass = 'py-2 lg:pt-4 lg:pb-4'
49
+ } else if (item.children && item.children.length > 0) {
50
+ // if has children
51
+ computedClass = 'pt-2 pb-1 lg:pt-4 lg:pb-2'
52
+ }
53
+ return computedClass
54
+ }
55
+ }
56
+ })
57
+ </script>
58
+ <style lang="scss">
59
+ .NavJumpMenuContent {
60
+ a {
61
+ @apply block pl-18 pr-6 lg:pl-6 text-gray-dark;
62
+
63
+ > span {
64
+ @apply border-b border-transparent pb-2px;
65
+ }
66
+
67
+ &:hover {
68
+ > span {
69
+ @apply border-gray-dark text-gray-dark;
70
+ }
71
+ }
72
+ }
73
+ }
74
+ </style>