Skip to content

Commit c53be55

Browse files
committed
Refactor: Show icon in breadcrumb + fix styles - refs BT#23134
1 parent 68912b3 commit c53be55

4 files changed

Lines changed: 79 additions & 54 deletions

File tree

assets/css/app.scss

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -964,19 +964,6 @@ img.course-tool__icon {
964964
.form-horizontal .control-label{ text-align:left; }
965965
textarea.form-control{ min-height:140px; }
966966
}
967-
.app-breadcrumb { font-size: 0.8125rem; }
968-
.app-breadcrumb .p-breadcrumb,
969-
.app-breadcrumb .p-breadcrumb-list li > a,
970-
.app-breadcrumb .p-breadcrumb-list li > span,
971-
.app-breadcrumb .p-breadcrumb .p-menuitem-text {
972-
font-size: inherit !important;
973-
line-height: 1.25rem;
974-
}
975-
.app-breadcrumb .p-breadcrumb-list li > a,
976-
.app-breadcrumb .p-breadcrumb-list li:last-child > span {
977-
font-weight: 600;
978-
}
979-
.app-breadcrumb .p-breadcrumb-separator { padding-inline: .25rem; }
980967

981968
// Glossary auto-highlighted term
982969
.glossary-term {

assets/css/scss/layout/_breadcrumb.scss

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.app-breadcrumb {
2-
@apply bg-white mb-3 md:px-6 px-4 text-tiny font-semibold leading-4 space-y-1;
2+
@apply bg-white mb-3 text-tiny font-semibold leading-4 space-y-1;
33

44
.p-breadcrumb-item-link {
55
@apply text-tiny font-semibold;
@@ -8,13 +8,17 @@
88
&__session-title {
99
@apply text-tiny font-semibold text-gray-50 uppercase;
1010
}
11+
12+
.mdi {
13+
@apply text-tiny leading-4;
14+
}
1115
}
1216

1317
.p-breadcrumb {
1418
@apply overflow-x-auto bg-white;
1519

1620
&-list {
17-
@apply p-0 m-0 flex gap-1 text-gray-90 list-none items-center flex-nowrap;
21+
@apply p-0 m-0 flex gap-2 text-gray-90 list-none items-center flex-nowrap;
1822

1923
li {
2024
.p-menuitem-link {
@@ -41,6 +45,10 @@
4145
@apply hidden;
4246
}
4347

48+
&-item {
49+
@apply flex items-center gap-1;
50+
}
51+
4452
&-item-link {
4553
@apply no-underline flex items-center text-black gap-0 outline-none outline-0
4654
focus-visible:outline-none focus-visible:drop-shadow-none

assets/vue/components/Breadcrumb.vue

Lines changed: 68 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
<template>
22
<div
3-
v-if="itemList.length > 0"
3+
v-if="items.length > 0"
44
class="app-breadcrumb"
55
>
6-
<Breadcrumb :model="itemList">
6+
<Breadcrumb
7+
:model="items"
8+
:home="home"
9+
>
710
<template #item="{ item, props }">
11+
<BaseIcon
12+
v-if="item.icon"
13+
:icon="item.icon"
14+
size="small"
15+
/>
816
<BaseAppLink
9-
v-if="(item.route || item.url) && item !== itemList[itemList.length - 1]"
17+
v-if="(item.route || item.url) && item !== items[items.length - 1]"
1018
:to="item.route"
1119
:url="item.url"
1220
v-bind="props.action"
@@ -17,7 +25,8 @@
1725
<span
1826
v-else
1927
v-text="stripHtml(item.label)"
20-
></span>
28+
v-bind="props.action"
29+
/>
2130
</template>
2231

2332
<template #separator> /</template>
@@ -38,6 +47,7 @@ import Breadcrumb from "primevue/breadcrumb"
3847
import { useCidReqStore } from "../store/cidReq"
3948
import { storeToRefs } from "pinia"
4049
import { useStore } from "vuex"
50+
import BaseIcon from "./basecomponents/BaseIcon.vue"
4151
4252
const legacyItems = ref([])
4353
@@ -62,7 +72,26 @@ const specialRouteNames = [
6272
"MessageCreate",
6373
]
6474
65-
const itemList = ref([])
75+
const calculatedList = ref([])
76+
77+
const home = computed(() => {
78+
if (calculatedList.value.length) {
79+
return {
80+
...calculatedList.value[0],
81+
icon: "compass",
82+
}
83+
}
84+
85+
return undefined
86+
})
87+
88+
const items = computed(() => {
89+
if (!calculatedList.value.length) {
90+
return []
91+
}
92+
93+
return calculatedList.value.slice(1)
94+
})
6695
6796
/**
6897
* Group breadcrumb support (no API calls)
@@ -87,7 +116,7 @@ const formatToolName = (name) => {
87116
}
88117
89118
const addToolWithResourceBreadcrumb = (toolName, listRouteName, detailRouteName) => {
90-
itemList.value.push({
119+
calculatedList.value.push({
91120
label: t(formatToolName(toolName)),
92121
route: {
93122
name: listRouteName,
@@ -104,7 +133,7 @@ const addToolWithResourceBreadcrumb = (toolName, listRouteName, detailRouteName)
104133
const resourceLabel = resourceNode.value.title
105134
const idParam = cleanIdParam(route.params.id)
106135
107-
itemList.value.push({
136+
calculatedList.value.push({
108137
label: resourceLabel,
109138
route: idParam ? { name: detailRouteName, params: { id: idParam }, query: route.query } : undefined,
110139
})
@@ -114,7 +143,7 @@ const addToolWithResourceBreadcrumb = (toolName, listRouteName, detailRouteName)
114143
const label = currentMatched?.meta?.breadcrumb || formatToolName(route.name)
115144
116145
if (route.name !== detailRouteName) {
117-
itemList.value.push({
146+
calculatedList.value.push({
118147
label: t(label),
119148
route: { name: route.name, params: route.params, query: route.query },
120149
})
@@ -125,10 +154,10 @@ function addRemainingMatchedBreadcrumbs() {
125154
route.matched.slice(1).forEach((r) => {
126155
const label = r.meta?.breadcrumb || formatToolName(r.name)
127156
const alreadyHasResource =
128-
resourceNode.value?.title && itemList.value.some((item) => item.label === resourceNode.value.title)
157+
resourceNode.value?.title && calculatedList.value.some((item) => item.label === resourceNode.value.title)
129158
130159
if (!alreadyHasResource) {
131-
itemList.value.push({
160+
calculatedList.value.push({
132161
label: t(label),
133162
route: {
134163
name: r.name,
@@ -177,7 +206,7 @@ function addDocumentBreadcrumb() {
177206
current = current.parent
178207
}
179208
const first = folderTrail.shift()
180-
itemList.value.push({
209+
calculatedList.value.push({
181210
label: t("Documents"),
182211
route: {
183212
name: "DocumentsList",
@@ -186,7 +215,7 @@ function addDocumentBreadcrumb() {
186215
},
187216
})
188217
folderTrail.forEach((folder) => {
189-
itemList.value.push({
218+
calculatedList.value.push({
190219
label: folder.label,
191220
route: { name: "DocumentsList", params: { node: folder.nodeId }, query: route.query },
192221
})
@@ -196,9 +225,9 @@ function addDocumentBreadcrumb() {
196225
const label = currentMatched?.meta?.breadcrumb
197226
if (label !== "") {
198227
const finalLabel = label || formatToolName(currentMatched?.name)
199-
const alreadyShown = itemList.value.some((item) => item.label === finalLabel)
228+
const alreadyShown = calculatedList.value.some((item) => item.label === finalLabel)
200229
if (!alreadyShown) {
201-
itemList.value.push({
230+
calculatedList.value.push({
202231
label: t(finalLabel),
203232
route: { name: currentMatched.name, params: route.params, query: route.query },
204233
})
@@ -220,13 +249,13 @@ function addGroupBreadcrumbIfNeeded() {
220249
const label = `${labelBase} ${padded}`
221250
222251
// Avoid duplicates (e.g. if some legacy breadcrumb already includes it)
223-
const alreadyExists = itemList.value.some((it) => stripHtml(it.label) === stripHtml(label))
252+
const alreadyExists = calculatedList.value.some((it) => stripHtml(it.label) === stripHtml(label))
224253
if (alreadyExists) return
225254
226255
// Legacy group space URL, works even if there is no Vue route for group space
227256
const url = buildGroupSpaceUrl(currentGid)
228257
229-
itemList.value.push({
258+
calculatedList.value.push({
230259
label,
231260
url: url || undefined,
232261
})
@@ -296,22 +325,22 @@ function resolveSettingsSectionLabel(nsRaw) {
296325
// Watch route changes to dynamically rebuild the breadcrumb trail
297326
watchEffect(() => {
298327
if ("/" === route.fullPath) return
299-
itemList.value = []
300-
// special-case accessurl routes
328+
calculatedList.value = []
329+
// special-case accessurl routes
301330
if (/^\/resources\/accessurl\/[^\/]+\/delete(?:\/|$)/.test(route.path)) {
302-
itemList.value = []
331+
calculatedList.value = []
303332
304-
itemList.value.push({
333+
calculatedList.value.push({
305334
label: t("Administration"),
306335
url: "/main/admin/index.php",
307336
})
308337
309-
itemList.value.push({
338+
calculatedList.value.push({
310339
label: t("Multiple access URL / Branding"),
311340
url: "/main/admin/access_urls.php",
312341
})
313342
314-
itemList.value.push({ label: t("Delete access") })
343+
calculatedList.value.push({ label: t("Delete access") })
315344
316345
return
317346
}
@@ -320,18 +349,18 @@ watchEffect(() => {
320349
321350
// Static route categories (must use "route" or "url" for our slot)
322351
if (route.name?.includes("Page")) {
323-
itemList.value.push({ label: t("Pages"), route: { path: "/resources/pages" } })
352+
calculatedList.value.push({ label: t("Pages"), route: { path: "/resources/pages" } })
324353
}
325354
if (route.name?.includes("Message")) {
326-
itemList.value.push({ label: t("Messages"), route: { path: "/resources/messages" } })
355+
calculatedList.value.push({ label: t("Messages"), route: { path: "/resources/messages" } })
327356
}
328357
329358
// Do not build breadcrumb for top-level routes
330359
if (specialRouteNames.includes(route.name)) return
331360
332361
// Add course or session link
333362
if (course.value) {
334-
itemList.value.push({
363+
calculatedList.value.push({
335364
label: t(session.value ? "My sessions" : "My courses"),
336365
route: { name: session.value ? "MySessions" : "MyCourses" },
337366
})
@@ -345,15 +374,15 @@ watchEffect(() => {
345374
let newUrl = (item.url || "").toString()
346375
if (newUrl.indexOf("main/") > 0) newUrl = "/" + newUrl.substring(mainPath)
347376
if (newUrl === "/") newUrl = "#"
348-
itemList.value.push({ label: item.name, url: newUrl || undefined })
377+
calculatedList.value.push({ label: item.name, url: newUrl || undefined })
349378
})
350379
legacyItems.value = []
351380
return
352381
}
353382
354383
// Standard: add course title crumb
355384
if (course.value && route.name !== "CourseHome") {
356-
itemList.value.push({
385+
calculatedList.value.push({
357386
label: course.value.title,
358387
route: { name: "CourseHome", params: { id: course.value.id }, query: route.query },
359388
})
@@ -399,17 +428,17 @@ watchEffect(() => {
399428
const gidVal = Number(route.query?.gid || 0)
400429
toolLabel = gidVal > 0 ? "Group agenda" : cid > 0 ? "Agenda" : "Personal agenda"
401430
}
402-
itemList.value.push({
431+
calculatedList.value.push({
403432
label: t(toolLabel),
404433
route: { name: toolBase.name, params: route.params, query: route.query },
405434
})
406435
407436
const label = currentMatched.meta?.breadcrumb
408437
if (label !== "") {
409438
const finalLabel = label || formatToolName(currentMatched.name)
410-
const alreadyShown = itemList.value.some((item) => item.label === finalLabel)
439+
const alreadyShown = calculatedList.value.some((item) => item.label === finalLabel)
411440
if (!alreadyShown) {
412-
itemList.value.push({
441+
calculatedList.value.push({
413442
label: t(finalLabel),
414443
route: { name: currentMatched.name, params: route.params, query: route.query },
415444
})
@@ -441,7 +470,7 @@ function buildManualBreadcrumbIfNeeded() {
441470
let newUrl = (item.url || "").toString()
442471
if (newUrl.indexOf("main/") > 0) newUrl = "/" + newUrl.substring(mainPath)
443472
if (newUrl === "/") newUrl = "#"
444-
itemList.value.push({ label: item.name, url: newUrl || undefined })
473+
calculatedList.value.push({ label: item.name, url: newUrl || undefined })
445474
})
446475
legacyItems.value = []
447476
return true
@@ -464,16 +493,16 @@ function buildManualBreadcrumbIfNeeded() {
464493
if (isAdminSettings) {
465494
const ns = pathSegments[2] || route.params?.namespace || route.query?.namespace || ""
466495
const adminLabel = t("Admin")
467-
itemList.value.push({
496+
calculatedList.value.push({
468497
label: adminLabel,
469498
route: { name: overrides.admin, params: route.params, query: route.query },
470499
})
471-
itemList.value.push({
500+
calculatedList.value.push({
472501
label: t("Settings"),
473502
route: { path: "/admin/settings" },
474503
})
475504
const section = resolveSettingsSectionLabel(ns)
476-
itemList.value.push({ label: section })
505+
calculatedList.value.push({ label: section })
477506
return true
478507
}
479508
@@ -488,15 +517,15 @@ function buildManualBreadcrumbIfNeeded() {
488517
const label = t(segment.charAt(0).toUpperCase() + segment.slice(1))
489518
const override = overrides[segment]
490519
if (override === null) {
491-
itemList.value.push({ label })
520+
calculatedList.value.push({ label })
492521
} else if (override) {
493-
itemList.value.push({
522+
calculatedList.value.push({
494523
label,
495524
route: { name: override, params: route.params, query: route.query },
496525
})
497526
} else {
498527
const partialPath = "/" + pathSegments.slice(0, index + 1).join("/")
499-
itemList.value.push({
528+
calculatedList.value.push({
500529
label,
501530
route: { path: partialPath },
502531
})
@@ -567,7 +596,7 @@ function buildSkillBreadcrumbIfNeeded() {
567596
568597
const rootUrl = origin || (isSocial ? "/social" : "/admin")
569598
570-
itemList.value.push({
599+
calculatedList.value.push({
571600
label: rootLabel,
572601
url: rootUrl,
573602
})
@@ -576,7 +605,7 @@ function buildSkillBreadcrumbIfNeeded() {
576605
const lastLabel =
577606
route.name === "SkillWheel" ? translateOrFallback("Skills wheel", "Skills wheel") : formatToolName(route.name)
578607
579-
itemList.value.push({ label: lastLabel })
608+
calculatedList.value.push({ label: lastLabel })
580609
581610
return true
582611
}

assets/vue/components/basecomponents/ChamiloIcons.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,4 +159,5 @@ export const chamiloIconToClass = {
159159
"promotion": "mdi mdi-school-outline",
160160
"help": "mdi mdi-face-agent",
161161
"robot": "mdi mdi-robot",
162+
"compass": "mdi mdi-compass-outline",
162163
}

0 commit comments

Comments
 (0)