-
-
Notifications
You must be signed in to change notification settings - Fork 76
/
Copy pathuseBreadcrumbItems.ts
203 lines (196 loc) · 6.35 KB
/
useBreadcrumbItems.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
import type { NuxtLinkProps } from 'nuxt/app'
import type { MaybeRefOrGetter } from 'vue'
import type { RouteMeta } from 'vue-router'
import {
computed,
createSitePathResolver,
defineBreadcrumb,
toValue,
useI18n,
useRouter,
useSchemaOrg,
useSiteConfig,
} from '#imports'
import { defu } from 'defu'
import { fixSlashes } from 'site-config-stack/urls'
import { withoutTrailingSlash } from 'ufo'
import { pathBreadcrumbSegments } from '../../pure/breadcrumbs'
interface NuxtUIBreadcrumbItem extends NuxtLinkProps {
label: string
labelClass?: string
icon?: string
iconClass?: string
as?: string
type?: string
disabled?: boolean
active?: boolean
exact?: boolean
exactQuery?: boolean
exactMatch?: boolean
inactiveClass?: string
[key: string]: any
}
export interface BreadcrumbProps {
/**
* Generate the breadcrumbs based on a different path than the current route.
*/
path?: MaybeRefOrGetter<string>
/**
* The id of the breadcrumb list. It's recommended to provide a unique
* id when adding multiple breadcrumb lists to the same page.
*/
id?: string
/**
* Append additional breadcrumb items to the end of the list. This is applied
* after the `overrides` option.
*/
append?: BreadcrumbItemProps[]
/**
* Prepend additional breadcrumb items to the start of the list. This is applied
* after the `overrides` option.
*/
prepend?: BreadcrumbItemProps[]
/**
* Override any of the breadcrumb items based on the index.
*/
overrides?: (BreadcrumbItemProps | false | undefined)[]
/**
* Should the schema.org breadcrumb be generated.
* @default true
*/
schemaOrg?: boolean
/**
* The Aria Label for the breadcrumbs.
* You shouldn't need to change this.
*
* @default 'Breadcrumbs'
*/
ariaLabel?: string
/**
* Should the current breadcrumb item be shown.
*
* @default false
*/
hideCurrent?: MaybeRefOrGetter<boolean>
/**
* Should the root breadcrumb be shown.
*/
hideRoot?: MaybeRefOrGetter<boolean>
}
export interface BreadcrumbItemProps extends NuxtUIBreadcrumbItem {
/** Whether the breadcrumb item represents the aria-current. */
current?: boolean
/**
* The type of current location the breadcrumb item represents, if `isCurrent` is true.
* @default 'page'
*/
ariaCurrent?: 'page' | 'step' | 'location' | 'date' | 'time' | boolean | 'true' | 'false'
to?: string
ariaLabel?: string
separator?: boolean | string
class?: (string | string[] | undefined)[] | string
/**
* @internal
*/
_props?: {
first: boolean
last: boolean
}
}
function withoutQuery(path: string) {
return path.split('?')[0]
}
function titleCase(s: string) {
return s
.replaceAll('-', ' ')
.replace(/\w\S*/g, w => w.charAt(0).toUpperCase() + w.substr(1).toLowerCase())
}
export function useBreadcrumbItems(options: BreadcrumbProps = {}) {
const router = useRouter()
const i18n = useI18n()
const siteResolver = createSitePathResolver({
canonical: true,
absolute: true,
})
const siteConfig = useSiteConfig()
const items = computed(() => {
let rootNode = '/'
if (i18n) {
if (i18n.strategy === 'prefix' || (i18n.strategy !== 'no_prefix' && toValue(i18n.defaultLocale) !== toValue(i18n.locale)))
rootNode = `/${toValue(i18n.locale)}`
}
const current = withoutQuery(withoutTrailingSlash(toValue(options.path || router.currentRoute.value?.path) || rootNode))
// apply overrides
const overrides = options.overrides || []
const segments = pathBreadcrumbSegments(current, rootNode)
.map((path, index) => {
let item = <BreadcrumbItemProps> {
to: path,
}
if (typeof overrides[index] !== 'undefined') {
if (overrides[index] === false)
return false
item = defu(overrides[index] as any as BreadcrumbItemProps, item)
}
return item
})
// apply prepends and appends
if (options.prepend)
segments.unshift(...options.prepend)
if (options.append)
segments.push(...options.append)
return (segments.filter(Boolean) as BreadcrumbItemProps[])
.map((item) => {
const route = router.resolve(item.to)?.matched?.[0] || router.currentRoute.value // fallback to current route
const routeMeta = (route?.meta || {}) as RouteMeta & { title?: string, breadcrumbLabel: string }
const routeName = route ? String(route.name || route.path) : (item.to === '/' ? 'index' : 'unknown')
let [name] = routeName.split('___')
if (name === 'unknown' || name.includes('/'))
name = (item.to || '').split('/').pop() || '' // fallback to last path segment
// merge with the route meta
if (routeMeta.breadcrumb) {
item = {
...item,
...routeMeta.breadcrumb,
}
}
// allow opt-out of label normalise with `false` value
// @ts-expect-error untyped
item.label = item.label || routeMeta.breadcrumbTitle || routeMeta.title
if (typeof item.label === 'undefined') {
item.label = i18n.t(`breadcrumb.items.${name}.label`, name === 'index' ? 'Home' : titleCase(name), { missingWarn: false })
}
if (typeof item.ariaLabel === 'undefined') {
item.ariaLabel = i18n.t(`breadcrumb.items.${name}.ariaLabel`, item.label, { missingWarn: false }) || item.label
}
// mark the current based on the options
item.current = item.current || item.to === current
if (toValue(options.hideCurrent) && item.current)
return false
return item
})
.map((m) => {
if (m && m.to) {
m.to = fixSlashes(siteConfig.trailingSlash, m.to)
if (m.to === rootNode && toValue(options.hideRoot))
return false
}
return m
})
.filter(Boolean) as BreadcrumbItemProps[]
})
const schemaOrgEnabled = typeof options.schemaOrg === 'undefined' ? true : options.schemaOrg
// TODO can probably drop this schemaOrgEnabled flag as we mock the function
if ((import.meta.dev || import.meta.server) && schemaOrgEnabled) {
useSchemaOrg([
defineBreadcrumb({
id: `#${options.id || 'breadcrumb'}`,
itemListElement: computed(() => items.value.map(item => ({
name: item.label || item.ariaLabel,
item: item.to ? siteResolver(item.to) : undefined,
}))),
}),
])
}
return items
}