From 0c765904a63fd7bbf7d19bf3c27045b0d973d84f Mon Sep 17 00:00:00 2001 From: Yi Lei Feng Date: Mon, 16 Dec 2024 11:40:57 -0500 Subject: [PATCH 1/3] add sublist menus for horizontal ToC --- StoryRampSchema.json | 40 +++ ...000000-0000-0000-0000-000000000000_en.json | 106 ++++++- src/components/story/horizontal-menu.vue | 259 ++++++++++++++---- src/components/story/story-content.vue | 13 +- src/definitions.ts | 14 +- 5 files changed, 376 insertions(+), 56 deletions(-) diff --git a/StoryRampSchema.json b/StoryRampSchema.json index 7e56bc3c..d8ed8743 100644 --- a/StoryRampSchema.json +++ b/StoryRampSchema.json @@ -687,6 +687,46 @@ "minItems": 1 }, + "tableOfContents": { + "type": "array", + "description": "Defines a custom table of contents structure.", + "items": { + "type": "object", + "description": "A table of contents item that resides on the root level.", + "properties": { + "title": { + "type": "string", + "description": "Title description of table of contents item." + }, + "slideIndex": { + "type": "number", + "description": "Slide index linked to this table of contents item." + }, + "sublist": { + "type": "array", + "description": "List of sublist items that stem from this table of contents item.", + "items": { + "type": "object", + "description": "A table of contents child item that resides inside a sublist.", + "properties": { + "title": { + "type": "string", + "description": "Title description of table of contents item." + }, + "slideIndex": { + "type": "number", + "description": "Slide index linked to this table of contents item." + } + }, + "required": ["title", "slideIndex"] + }, + "default": [] + } + }, + "required": ["title", "slideIndex"] + } + }, + "tocOrientation": { "type": "string", "description": "Specifies the orientation of the table of contents navigation menu.", diff --git a/public/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000_en.json b/public/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000_en.json index 5213c01b..070f732b 100644 --- a/public/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000_en.json +++ b/public/00000000-0000-0000-0000-000000000000/00000000-0000-0000-0000-000000000000_en.json @@ -610,7 +610,111 @@ ] } ], - "tocOrientation": "vertical", + "tableOfContents": [ + { + "title": "Overview", + "slideIndex": 0, + "sublist": [ + { + "title": "Community Directory", + "slideIndex": 1 + }, + { + "title": "Interactive Map", + "slideIndex": 2 + }, + { + "title": "RAMP Carousel", + "slideIndex": 3 + }, + { + "title": "Dynamic Panel", + "slideIndex": 4 + }, + { + "title": "YouTube Video", + "slideIndex": 5 + }, + { + "title": "Cumulative Effects Video", + "slideIndex": 6 + } + ] + }, + { + "title": "Oil Sands Deposits", + "slideIndex": 7 + }, + { + "title": "Oil Sands Extraction", + "slideIndex": 8, + "sublist": [] + }, + { + "title": "In-situ Extraction", + "slideIndex": 9 + }, + { + "title": "Where are Facilities Located?", + "slideIndex": 10, + "sublist": [ + { + "title": "NPRI Substances Reported for Oil Sands Mining Faciltiies", + "slideIndex": 11 + }, + { + "title": "NPRI Substances Reported for Oil Sands Mining Facilities (2)", + "slideIndex": 12 + }, + { + "title": "Criteria Air Contaminant Releases From Oil Sands Mines", + "slideIndex": 13 + }, + { + "title": "Reported Mine Tailings From Oil Sands Surface Mining Facilities", + "slideIndex": 14 + }, + { + "title": "Reported Mine Tailings From Oil Sands Surface Mining Facilities (2)", + "slideIndex": 15 + }, + { + "title": "Trends in Mine Tailings Reported From Surface Mining Facilities", + "slideIndex": 16 + }, + { + "title": "Thermal in Situ Facilities", + "slideIndex": 17 + }, + { + "title": "NPRI Releases From Thermal in-situ Faciltiies", + "slideIndex": 18 + } + ] + }, + { + "title": "Highcharts Demo (╯°□°)╯︵ ┻━┻", + "slideIndex": 19, + "sublist": [ + { + "title": "Dynamic Slideshow with Hybrid Chart", + "slideIndex": 20 + } + ] + }, + { + "title": "Managing Environmental Impacts", + "slideIndex": 21, + "sublist": [ + { + "title": "Last Slide", + "slideIndex": 22 + } + ] + } + ], + "tocOrientation": "horizontal", + "returnTop": false, "stylesheets": ["00000000-0000-0000-0000-000000000000/styles/main.css"], "contextLink": "https://www.canada.ca/en/environment-climate-change/services/national-pollutant-release-inventory/tools-resources-data/exploredata.html", "contextLabel": "Explore National Pollutant Release Inventory data", diff --git a/src/components/story/horizontal-menu.vue b/src/components/story/horizontal-menu.vue index 75bb4d7c..56a9a9ae 100644 --- a/src/components/story/horizontal-menu.vue +++ b/src/components/story/horizontal-menu.vue @@ -37,58 +37,167 @@ }} -
  • - - +
  • - {{ - getTitle(slide) - }} - + + + {{ + getTitle(item) + }} + - + {{ + getTitle(item) + }} + + + + +
  • + + + + @@ -159,21 +306,23 @@ const updateActiveIdx = () => { display: flex; justify-content: center; } -.navbar ul { + +.navbar > ul { display: flex; list-style-type: none; text-align: center; justify-content: center; flex-wrap: wrap; - overflow: hidden; width: 100%; padding: 5px; margin: auto; } -.navbar ul li { + +.navbar > ul > li { float: left; width: 12%; border-radius: 8px; + position: relative; a { text-overflow: ellipsis; } @@ -196,6 +345,7 @@ const updateActiveIdx = () => { font-weight: bold; } } + .separator { position: relative; } @@ -210,4 +360,21 @@ const updateActiveIdx = () => { width: 1px; background-color: #e0e0e0; } + +.dropdown-menu { + position: absolute; + width: 100%; + background-color: rgb(241, 242, 244); + box-sizing: border-box; + + > li { + background-color: rgb(241, 242, 244); + font-weight: normal; + + &.is-active { + background-color: var(--sr-accent-colour); + font-weight: bold; + } + } +} diff --git a/src/components/story/story-content.vue b/src/components/story/story-content.vue index 6c0bd1dd..f7d089dc 100644 --- a/src/components/story/story-content.vue +++ b/src/components/story/story-content.vue @@ -7,7 +7,8 @@ class="top-menu" :active-chapter-index="activeChapterIndex" :return-to-top="config.returnTop ?? true" - :slides="tocSlides" + :slides="config.slides" + :customToc="config.tableOfContents" :plugin="!!configFileStructure || !!plugin" :lang="lang" :style="{ top: headerHeight + 'px' }" @@ -17,7 +18,8 @@ class="side-menu" :active-chapter-index="activeChapterIndex" :return-to-top="config.returnTop ?? true" - :slides="tocSlides" + :slides="config.slides" + :customToc:="config.tableOfContents" :plugin="!!configFileStructure || !!plugin" :lang="lang" v-else @@ -58,7 +60,7 @@ + + diff --git a/src/components/story/chapter-menu.vue b/src/components/story/chapter-menu.vue index b3d9f821..f9580c0c 100644 --- a/src/components/story/chapter-menu.vue +++ b/src/components/story/chapter-menu.vue @@ -29,10 +29,9 @@ - {{ $t('chapters.title') }} + {{ + $t('chapters.title') + }} @@ -143,71 +142,77 @@ }} -
  • - - +
  • - - - - {{ - slide.title - }} - + + + - + +
  • + + + +
    @@ -215,8 +220,9 @@ @@ -287,7 +321,7 @@ const updateActiveIdx = () => { display: none; /* Safari and Chrome */ } -.menu li { +.menu > li { a:hover { text-decoration: none; color: inherit; @@ -307,14 +341,43 @@ const updateActiveIdx = () => { } &.is-active { - svg { + :deep(svg) { fill: var(--sr-accent-colour); stroke: var(--sr-accent-colour); } - span { + :deep(span) { font-weight: bold; } } } + +.dropdown-menu { + > li { + font-weight: normal; + :deep(svg) { + fill: #fff !important; + stroke: #878787 !important; + } + + :deep(span) { + font-weight: normal !important; + } + + &.is-active { + :deep(span) { + font-weight: bold !important; + } + + :deep(svg) { + fill: var(--sr-accent-colour) !important; + stroke: var(--sr-accent-colour) !important; + } + } + } +} + +.rotate-180 { + transform: rotate(-180deg); +} diff --git a/src/components/story/horizontal-menu.vue b/src/components/story/horizontal-menu.vue index 56a9a9ae..69c5dfdb 100644 --- a/src/components/story/horizontal-menu.vue +++ b/src/components/story/horizontal-menu.vue @@ -46,48 +46,37 @@ :class="{ 'is-active': lastActiveIdx === item.slideIndex }" - @mouseenter="showSublist(idx)" - @mouseleave="hideSublist(idx)" > - - - {{ - getTitle(item) - }} - - - - {{ - getTitle(item) - }} - + + + @@ -153,41 +113,12 @@ separator: (!returnToTop && idx !== 0) || returnToTop }" > - - - {{ - getTitle(slide) - }} - - - - {{ - getTitle(slide) - }} - + @@ -198,9 +129,7 @@ import type { PropType } from 'vue'; import { computed, ref, watch, onMounted } from 'vue'; import { MenuItem, Slide } from '@storylines/definitions'; - -import { useI18n } from 'vue-i18n'; -const { t } = useI18n(); +import TocItem from '@storylines/components/panels/helpers/toc-item.vue'; const props = defineProps({ returnToTop: { @@ -268,21 +197,8 @@ const scrollToChapter = (id: string): void => { } }; -const getTitle = (slide: Slide | MenuItem): string => { - return slide.title !== '' ? slide.title : t('chapters.untitled'); -}; - -const getSlideId = (slideIdx: number): string => { - const slide = props.slides.find((slide, idx) => idx === slideIdx); - return slide ? `${slideIdx}-${slide.title.toLowerCase().replaceAll(' ', '-')}` : ''; -}; - -const showSublist = (index: number): void => { - sublistToggled.value[index] = true; -}; - -const hideSublist = (index: number): void => { - sublistToggled.value[index] = false; +const toggleSublist = (index: number): void => { + sublistToggled.value[index] = !sublistToggled.value[index]; }; const isSublistToggled = (index: number): boolean => { @@ -340,6 +256,7 @@ const updateActiveIdx = () => { a:visited { color: inherit; } + &.is-active { background-color: var(--sr-accent-colour); font-weight: bold; diff --git a/src/components/story/mobile-menu.vue b/src/components/story/mobile-menu.vue index 48f9f314..fc12d54f 100644 --- a/src/components/story/mobile-menu.vue +++ b/src/components/story/mobile-menu.vue @@ -37,7 +37,7 @@ @@ -191,7 +196,8 @@ diff --git a/src/components/story/mobile-menu.vue b/src/components/story/mobile-menu.vue index fc12d54f..2355111c 100644 --- a/src/components/story/mobile-menu.vue +++ b/src/components/story/mobile-menu.vue @@ -346,4 +346,8 @@ const updateActiveIdx = () => { } } } + +.rotate-180 { + transform: rotate(-180deg); +}