Skip to content

Commit

Permalink
feat: APPS-2700 Create Component FTVA Breadcrumb Variant (#548)
Browse files Browse the repository at this point in the history
* feat: implement logic for multi-level breadcrumb nesting"

* task: reorganize, rename scss files

* fix: linting

* feat: implement logic to expand truncated links

* update: add story for nesting behavior

* task: update lock file

* fix: styling adjustments

* revert code to initial link expansion logic

* wip: breadcrumb responsive logic

* update the design tokens

* fix: styling, mobile range

* fix: breadcrumb caret order

* style: change caret dir on mobile

* fix: update logic for rendering carets

* fix: linting

* refactor, add useRoute

* fix: linting

* fix: remove console statement

* feat: Revert specific commits to undo recent changes (#557)

Revert specific commits to undo recent changes

* chore(release): set `package.json` to 2.39.0-alpha.105 [skip ci]

* stop releases while merging ([698c834](698c834))

* Revert specific commits to undo recent changes ([#557](#557)) ([ab94529](ab94529))

* add nav-search import to base scss files

* fix: add back props, template logic for hardcoded breadcrumbs

* chore: refactor navbreadcrumb template for Tinu review (#559)

chore: refactor navbreadcrumb template

Co-authored-by: Jess Divers <[email protected]>

* task: alphabetize scss imports

* task: correct merge overwrite of yml files

* Update publish-to-npm.yml indentation

* Update CHANGELOG.md with code from main

* Update pnpm-lock.yaml with code from main

---------

Co-authored-by: tinuola <[email protected]>
Co-authored-by: jendiamond <[email protected]>
Co-authored-by: Parinita Mulak <[email protected]>
Co-authored-by: semantic-release-bot <[email protected]>
Co-authored-by: Jess <[email protected]>
Co-authored-by: Jess Divers <[email protected]>
  • Loading branch information
7 people authored Jul 16, 2024
1 parent 45af3e0 commit 8c2b9d3
Show file tree
Hide file tree
Showing 12 changed files with 336 additions and 83 deletions.
216 changes: 144 additions & 72 deletions src/lib-components/NavBreadcrumb.vue
Original file line number Diff line number Diff line change
@@ -1,93 +1,165 @@
<script>
// SVGs
import SvgIconCaretLeft from 'ucla-library-design-tokens/assets/svgs/icon-caret-left.svg'
<script setup>
import { computed, ref } from 'vue'
import { useRoute } from 'vue-router'
import SvgIconCaretRight from 'ucla-library-design-tokens/assets/svgs/icon-caret-right.svg'
import { useTheme } from '@/composables/useTheme'
import { useGlobalStore } from '@/stores/GlobalStore'
// COMPONENTS
import SmartLink from '@/lib-components/SmartLink.vue'
export default {
name: 'NavBreadcrumb',
components: {
SvgIconCaretLeft,
SmartLink
const { to, parentTitle, title } = defineProps({
to: {
type: String,
default: '',
},
props: {
to: {
type: String,
default: '',
},
parentTitle: {
type: String,
default: '',
},
title: {
type: String,
default: '',
},
parentTitle: {
type: String,
default: '',
},
}
</script>
title: {
type: String,
default: '',
},
})
<template>
<div class="nav-breadcrumb subtitle">
<SmartLink :to="to" class="parent-page-url" v-text="parentTitle" />
<SvgIconCaretLeft aria-hidden="true" />
<span class="current-page-title" v-text="title" />
</div>
</template>
const route = useRoute()
<style lang="scss" scoped>
.nav-breadcrumb {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: center;
max-width: $container-xl-full-width + px;
margin: var(--space-m) auto;
padding: 0 var(--unit-gutter);
.parent-page-url {
@include step-1;
color: var(--color-primary-blue-03);
flex-shrink: 0;
}
const isExpanded = ref(null)
.svg__icon-caret-left {
flex-shrink: 0;
}
const globalStore = useGlobalStore()
.current-page-title {
@include step-0;
color: var(--color-black);
@include truncate(1);
}
const isMobile = computed(() => {
return globalStore.winWidth <= 1200
})
// Split URI path; then remove empty string at start of the array
const parsedBreadcrumbs = computed(() => {
const pagePathArray = route.path.split('/').slice(1)
return pagePathArray
})
const parsedBreadcrumbLinks = computed(() => {
const breadcrumbsList = parsedBreadcrumbs.value
@media #{$extra-large} {
padding: 0;
const arrLength = breadcrumbsList.length
if (isMobile.value) {
const mobileBreadcrumb
= createBreadcrumbLinks(breadcrumbsList).slice(-2)
return mobileBreadcrumb.splice(0, 1)
}
else if (!isMobile.value) {
if (arrLength > 4 && !isExpanded.value) {
setLinkExpansion()
@media #{$small} {
padding-left: calc(var(--unit-gutter) - 8px);
const truncatedBreadcrumbsList = breadcrumbsList.toSpliced(
1,
arrLength - 3,
'...'
)
.current-page-title {
display: none;
return createBreadcrumbLinks(truncatedBreadcrumbsList)
}
}
.svg__icon-caret-left {
order: 1;
}
return createBreadcrumbLinks(breadcrumbsList)
})
.parent-page-url {
order: 2;
}
// METHODS
function createBreadcrumbLinks(arr) {
const breadCrumbObjects = []
// if props are present, we are using the legacy single breadcrumb
if (to && parentTitle && title) {
breadCrumbObjects.push({
to,
title: parentTitle,
isLastItem: false,
isTruncatedGroup: false
})
breadCrumbObjects.push({
to: '',
title,
isLastItem: true,
isTruncatedGroup: false
})
return breadCrumbObjects
}
// otherwise format based on route
arr.forEach((item, index) => {
const linkLength = item.length
const linkIndex = route.path.indexOf(item)
const linkTo = route.path.substring(0, linkLength + linkIndex)
const linkTitle = item.replaceAll('-', ' ')
let isLastItem
index === arr.length - 1 ? (isLastItem = true) : (isLastItem = false)
let isTruncatedGroup
isExpanded.value === false && index === 1
? (isTruncatedGroup = true)
: (isTruncatedGroup = false)
breadCrumbObjects.push({
to: linkTo,
title: linkTitle,
isTruncatedGroup,
isLastItem,
})
})
return breadCrumbObjects
}
// Hovers
@media #{$has-hover} {
.parent-page-url:hover {
@include link-hover;
}
function setLinkExpansion() {
isExpanded.value = false
}
function toggleLinksExpansion() {
isExpanded.value = !isExpanded.value
}
// THEME
const theme = useTheme()
const parsedClasses = computed(() => {
return ['nav-breadcrumb', 'subtitle', theme?.value || '']
})
</script>
<template>
<div :class="parsedClasses">
<span
v-for="linkObj in parsedBreadcrumbLinks"
:key="linkObj.title"
class="breadcrumb-wrapper"
>
<SmartLink
v-if="!linkObj.isLastItem && !linkObj.isTruncatedGroup"
:to="linkObj.to"
class="parent-page-url"
v-text="linkObj.title"
/>
<!-- Collapsed group should not link -->
<button
v-else-if="!linkObj.isLastItem && linkObj.isTruncatedGroup"
class="parent-page-url collapsed-url"
tabindex="0"
@click="toggleLinksExpansion()"
v-text="linkObj.title"
/>
<SvgIconCaretRight v-if="!linkObj.isLastItem" aria-hidden="true" />
<span
v-if="linkObj.isLastItem"
class="current-page-title"
v-text="linkObj.title"
/>
</span>
</div>
</template>
<style lang="scss" scoped>
@import "@/styles/themes.scss";
</style>
89 changes: 86 additions & 3 deletions src/stories/NavBreadcrumb.stories.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,98 @@
import { computed, onBeforeUnmount, onMounted } from 'vue'
import router from '@/router'
import NavBreadcrumb from '@/lib-components/NavBreadcrumb'
import { useGlobalStore } from '@/stores/GlobalStore'

// Storybook default settings
export default {
title: 'NAV / Breadcrumb',
component: NavBreadcrumb,
decorators: [
() => ({
router,
template: '<story />',
}),
],
}

// Variations of stories below
export function Default() {
function Template(args) {
router.push(args.to)
return {
setup() {
onMounted(() => {
const globalStore = useGlobalStore()

const updateWinWidth = () => {
globalStore.winWidth = window.innerWidth
}

// Set initial winWidth
updateWinWidth()

window.addEventListener('resize', updateWinWidth)

// Clean up
onBeforeUnmount(() => {
window.removeEventListener('resize', updateWinWidth)
})
})

return { args }
},
components: { NavBreadcrumb },
template: '<nav-breadcrumb v-bind="args" />',
}
}

export const Default = Template.bind({})
Default.args = {
to: '/about/news',
title: 'jane doe',
parentTitle: 'parent',
}

export const MultipleNesting = Template.bind({})
MultipleNesting.args = {
to: '/explore-collections/watch-and-listen-online/senator-john-f.-kennedy-gives-press-conference-in-los-angeles',
}

export const MultipleNestingCollapsed = Template.bind({})
MultipleNestingCollapsed.args = {
to: '/explore-collections/watch-and-listen-online/ktla-collection/national-and-local-politics/ktla-news-demo-article',
}

function TemplateFTVA(args) {
router.push(args.to)
return {
provide() {
return {
theme: computed(() => 'ftva'),
}
},
setup() {
onMounted(() => {
const globalStore = useGlobalStore()

const updateWinWidth = () => {
globalStore.winWidth = window.innerWidth
}

updateWinWidth()

window.addEventListener('resize', updateWinWidth)

onBeforeUnmount(() => {
window.removeEventListener('resize', updateWinWidth)
})
})
return { args }
},
components: { NavBreadcrumb },
template: '<nav-breadcrumb title="jane-doe" to="/about/news" parent-title="parent"/>',
template: '<nav-breadcrumb v-bind="args" />',
}
}

export const FTVA = TemplateFTVA.bind({})
FTVA.args = {
to: '/watch-and-listen-online/senator-john-f.-kennedy-gives-press-conference-in-los-angeles',
}
9 changes: 5 additions & 4 deletions src/styles/_base.scss
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// default styles correspond to old library-website-nuxt styles formerly in components

@import "default/_blockremovesearchfilter";
@import "default/_divider-way-finder";
@import "default/_blockeventdetail";
@import "default/_blocktag";
@import "default/_block-event-detail";
@import "default/_block-remove-search-filter";
@import "default/_block-tag";
@import "default/_button-link";
@import "default/_card-meta";
@import "default/_divider-way-finder";
@import "default/_nav-breadcrumb";
@import "default/_nav-search";
@import "default/_new-lightbox";
9 changes: 5 additions & 4 deletions src/styles/_ftva-theme.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
// https://github.com/UCLALibrary/design-tokens/blob/main/scss/app.scss
@import "ucla-library-design-tokens/scss/_tokens-ftva";

@import "ftva/_block-event-detail";
@import "ftva/_block-info";
@import "ftva/_blocktag";
@import "ftva/_blockremovesearchfilter";
@import "ftva/_divider-way-finder";
@import "ftva/_blockeventdetail";
@import "ftva/_block-remove-search-filter";
@import "ftva/_block-tag";
@import "ftva/_button-link";
@import "ftva/_card-meta";
@import "ftva/_divider-way-finder";
@import "ftva/_nav-breadcrumb";
@import "ftva/_nav-search";
@import "ftva/_new-lightbox";
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit 8c2b9d3

Please sign in to comment.