Skip to content

Commit 151c767

Browse files
Jonathan de FlaugerguesJonathan de Flaugergues
authored andcommitted
feat(VBreadcrumbs): add collapseFrom props and expose collapse function
1 parent 9780a9b commit 151c767

File tree

4 files changed

+105
-41
lines changed

4 files changed

+105
-41
lines changed

packages/api-generator/src/locale/en/VBreadcrumbs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
{
22
"props": {
33
"collapseInMenu": "When true, overflowing breadcrumb items are collapsed into a contextual menu rather than a static ellipsis.",
4+
"collapseFrom": "Determines the number of leading breadcrumb items that remain visible before collapsing the middle items.",
45
"divider": "Specifies the dividing character between items.",
56
"ellipsis": "Text displayed when breadcrumb items collapsed.",
67
"icons": "Specifies that the dividers between items are [v-icon](/components/icons)s.",
78
"justifyCenter": "Align the breadcrumbs center.",
89
"justifyEnd": "Align the breadcrumbs at the end.",
910
"large": "Increase the font-size of the breadcrumb item text to 16px (14px default).",
10-
"totalVisible": "Determines how many breadcrumb items can be shown before middle items are collapsed."
11+
"totalVisible": "Determines how many breadcrumb items can be shown before other items are collapsed."
1112
},
1213
"slots": {
1314
"divider": "The slot used for dividers.",

packages/docs/src/examples/v-breadcrumbs/prop-total-visible.vue

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
<template>
22
<v-container max-width="600">
33
<v-breadcrumbs
4+
ref="breadcrumbsRef"
5+
:collapse-from="collapseFrom"
46
:items="items"
57
:total-visible="limit"
68
></v-breadcrumbs>
79

810
<v-breadcrumbs
11+
:collapse-from="collapseFrom"
912
:items="items"
1013
:total-visible="limit"
1114
collapse-in-menu
@@ -14,13 +17,17 @@
1417
<div class="border-t pa-6">
1518
<v-slider v-model="count" label="Items count:" max="10" step="1" thumb-label="always"></v-slider>
1619
<v-slider v-model="limit" label="Collapse when exceeding:" max="10" step="1" thumb-label="always"></v-slider>
20+
<v-slider v-model="collapseFrom" label="Collapse from:" max="10" step="1" thumb-label="always"></v-slider>
21+
<v-btn @click="breadcrumbsRef.collapse()">Re-Collapse</v-btn>
1722
</div>
1823
</v-container>
1924
</template>
2025

2126
<script setup>
2227
import { shallowRef, toRef } from 'vue'
2328
29+
const breadcrumbsRef = useTemplateRef('breadcrumbsRef')
30+
const collapseFrom = shallowRef(0)
2431
const count = shallowRef(6)
2532
const limit = shallowRef(3)
2633

packages/docs/src/pages/en/components/breadcrumbs.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ Breadcrumbs separator can be set using `divider` property.
6262
#### Total visible
6363

6464
You can use the `total-visible` prop to limit the visible items. Items that exceed limit are displayed after clicking on "..." item. The default behavior can be changed with `collapse-in-menu` to display the collapsed items inside a dropdown menu.
65+
You can also use the `collapse-from` prop to control how many leading items remain visible before collapsing items.
6566

6667
<ExamplesExample file="v-breadcrumbs/prop-total-visible" />
6768

packages/vuetify/src/components/VBreadcrumbs/VBreadcrumbs.tsx

Lines changed: 95 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ export const makeVBreadcrumbsProps = propsFactory({
3939
activeColor: String,
4040
bgColor: String,
4141
collapseInMenu: Boolean,
42+
collapseFrom: {
43+
type: Number,
44+
default: 0,
45+
},
4246
color: String,
4347
disabled: Boolean,
4448
divider: {
@@ -61,7 +65,10 @@ export const makeVBreadcrumbsProps = propsFactory({
6165
menuProps: {
6266
type: Object as PropType<VMenu['$props']>,
6367
},
64-
totalVisible: Number,
68+
totalVisible: {
69+
type: Number,
70+
default: Number.MAX_VALUE,
71+
},
6572

6673
...makeComponentProps(),
6774
...makeDensityProps(),
@@ -86,7 +93,7 @@ export const VBreadcrumbs = genericComponent<new <T extends BreadcrumbItem>(
8693

8794
props: makeVBreadcrumbsProps(),
8895

89-
setup (props, { slots }) {
96+
setup (props, { slots, expose }) {
9097
const { backgroundColorClasses, backgroundColorStyles } = useBackgroundColor(() => props.bgColor)
9198
const { densityClasses } = useDensity(props)
9299
const { roundedClasses } = useRounded(props)
@@ -103,12 +110,28 @@ export const VBreadcrumbs = genericComponent<new <T extends BreadcrumbItem>(
103110
},
104111
})
105112

106-
const items = computed(() => props.items.map(item => {
107-
return typeof item === 'string' ? { item: { title: item }, raw: item } : { item, raw: item }
108-
}))
109-
const ellipsisEnabled = toRef(() => props.totalVisible ? items.value.length > props.totalVisible : false)
113+
const items = computed(() => props.items.map(item =>
114+
typeof item === 'string' ? { item: { title: item }, raw: item } : { item, raw: item }
115+
))
116+
const ellipsisEnabled = toRef(() => items.value.length > props.totalVisible)
110117
const hasEllipsis = ref(ellipsisEnabled.value)
118+
const collapseStartIndex = toRef(() => Math.min(props.collapseFrom, props.totalVisible))
119+
const collapseEndIndex = toRef(() => collapseStartIndex.value + items.value.length - props.totalVisible)
120+
const visibleItemsStart = toRef(() =>
121+
collapseStartIndex.value < items.value.length ? items.value.slice(0, collapseStartIndex.value) : items.value
122+
)
123+
const collapsedItems = toRef(() =>
124+
items.value.slice(collapseStartIndex.value, collapseEndIndex.value)
125+
)
126+
const visibleItemsEnd = toRef(() => {
127+
const sliceIndex = props.totalVisible - collapseStartIndex.value
128+
129+
return sliceIndex <= 0 ? [] : items.value.slice(-sliceIndex)
130+
})
111131

132+
const collapse = () => {
133+
hasEllipsis.value = ellipsisEnabled.value
134+
}
112135
const onClickEllipsis = () => {
113136
hasEllipsis.value = false
114137
}
@@ -135,7 +158,7 @@ export const VBreadcrumbs = genericComponent<new <T extends BreadcrumbItem>(
135158
props.style,
136159
]}
137160
>
138-
<ol>
161+
<ol role="list">
139162
{ hasPrepend && (
140163
<li key="prepend" class="v-breadcrumbs__prepend">
141164
{ !slots.prepend ? (
@@ -188,41 +211,66 @@ export const VBreadcrumbs = genericComponent<new <T extends BreadcrumbItem>(
188211
{ hasEllipsis.value && (
189212
<>
190213
{ (() => {
191-
const { item } = items.value[0]
192-
return (
193-
<>
194-
{ slots.item?.({ item, index: 0 }) ?? (
195-
<VBreadcrumbsItem
196-
disabled={ false }
197-
{ ...(typeof item === 'string' ? { title: item } : item) }
198-
/>
199-
)}
200-
</>
201-
)
214+
return visibleItemsStart.value.map(({ item }, i) => {
215+
const isLast = i === visibleItemsStart.value.length - 1
216+
217+
return (
218+
<>
219+
{ slots.item?.({ item, index: i }) ?? (
220+
<VBreadcrumbsItem
221+
key={ i }
222+
disabled={ false }
223+
{ ...(typeof item === 'string' ? { title: item } : item) }
224+
/>
225+
)}
226+
227+
{ !isLast && (
228+
<VBreadcrumbsDivider />
229+
)}
230+
</>
231+
)
232+
})
202233
})()}
203234

204-
<VBreadcrumbsDivider />
235+
{ collapseStartIndex.value > 0 && <VBreadcrumbsDivider /> }
205236

206237
<VBreadcrumbsItem
207238
tabindex="0"
208239
onClick={ props.collapseInMenu ? noop : onClickEllipsis }
240+
onKeydown={ (e: KeyboardEvent) => {
241+
if (!['Enter', ' '].includes(e.key)) return
242+
e.preventDefault()
243+
props.collapseInMenu ? e.currentTarget.click() : onClickEllipsis()
244+
}}
209245
class="v-breadcrumbs-item--ellipsis"
246+
role="button"
247+
aria-haspopup={ props.collapseInMenu ? 'menu' : undefined }
248+
aria-expanded={ !hasEllipsis.value }
249+
aria-label="show more breadcrumb items"
210250
>
211251
{ props.ellipsis }
252+
212253
{ props.collapseInMenu ? (
213-
<VMenu
214-
activator="parent"
215-
{ ...props.menuProps }
216-
>
254+
<VMenu activator="parent" { ...props.menuProps } role="menu" aria-label="hidden breadcrumb items">
217255
{{
218256
default: () => (
219257
<VList { ...props.listProps }>
220-
{ items.value.slice(1, items.value.length - 1).map(({ item }, index) => {
258+
{ collapsedItems.value.map(({ item }, index) => {
259+
const isLastCollapsedItem = index === collapsedItems.value.length - 1
260+
const isCurrentPage = !visibleItemsEnd.value.length && isLastCollapsedItem
221261
if (slots['list-item']) {
222262
return slots['list-item']({ item, index })
223263
}
224264
return (
225-
<VListItem key={ index } value={ index } href={ 'href' in item ? item.href : undefined }>
265+
<VListItem
266+
key={ index }
267+
value={ index }
268+
active={ isCurrentPage }
269+
aria-current={ isCurrentPage ? 'page' : undefined }
270+
disabled={ isCurrentPage }
271+
href={ 'href' in item ? item.href : undefined }
272+
role="menuitem"
273+
>
226274
<VListItemTitle>{ item.title }</VListItemTitle>
227275
</VListItem>
228276
)
@@ -234,22 +282,29 @@ export const VBreadcrumbs = genericComponent<new <T extends BreadcrumbItem>(
234282
) : null }
235283
</VBreadcrumbsItem>
236284

237-
<VBreadcrumbsDivider />
285+
{ visibleItemsEnd.value.length > 0 && <VBreadcrumbsDivider /> }
238286

239287
{ (() => {
240-
const lastIndex = items.value.length - 1
241-
const { item } = items.value[lastIndex]
242-
return (
243-
<>
244-
{ slots.item?.({ item, index: lastIndex }) ?? (
245-
<VBreadcrumbsItem
246-
disabled
247-
active
248-
{ ...(typeof item === 'string' ? { title: item } : item) }
249-
/>
250-
)}
251-
</>
252-
)
288+
return visibleItemsEnd.value.map(({ item }, i) => {
289+
const isLast = i === visibleItemsEnd.value.length - 1
290+
291+
return (
292+
<>
293+
{ slots.item?.({ item, index: i }) ?? (
294+
<VBreadcrumbsItem
295+
key={ i }
296+
disabled={ i === items.value.length - 1 }
297+
active={ i === items.value.length - 1 }
298+
{ ...(typeof item === 'string' ? { title: item } : item) }
299+
/>
300+
)}
301+
302+
{ !isLast && (
303+
<VBreadcrumbsDivider key={ `divider-last-${i}` } />
304+
)}
305+
</>
306+
)
307+
})
253308
})()}
254309
</>
255310
)}
@@ -259,7 +314,7 @@ export const VBreadcrumbs = genericComponent<new <T extends BreadcrumbItem>(
259314
</props.tag>
260315
)
261316
})
262-
317+
expose({ collapse })
263318
return {}
264319
},
265320
})

0 commit comments

Comments
 (0)