Skip to content

Commit f644b48

Browse files
feedback; allow label passthrough
1 parent 8e5cf83 commit f644b48

6 files changed

Lines changed: 83 additions & 49 deletions

File tree

pkg/rancher-components/src/components/Accordion/Accordion.vue

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<script lang="ts">
2-
import { defineComponent } from 'vue';
2+
import {
3+
computed, defineComponent, getCurrentInstance, nextTick, ref
4+
} from 'vue';
35
import { mapGetters } from 'vuex';
46
import { useInSummary } from '@shell/components/TableOfContents/composables';
57
@@ -23,14 +25,35 @@ export default defineComponent({
2325
}
2426
},
2527
26-
setup() {
27-
const { summary } = useInSummary();
28+
setup(props) {
29+
const instance = getCurrentInstance();
30+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
31+
const t = (instance?.proxy as any)?.$store?.getters?.['i18n/t'] as ((key: string) => string) | undefined;
32+
const label = computed(() => props.titleKey && typeof t === 'function' ? t(props.titleKey) : props.title);
2833
29-
return { summary };
34+
const isOpen = ref(props.openInitially);
35+
36+
const scrollTo = () => {
37+
isOpen.value = true;
38+
39+
nextTick(() => {
40+
const el = getCurrentInstance()?.proxy?.$el as HTMLElement | undefined;
41+
42+
el?.scrollIntoView(true);
43+
});
44+
};
45+
46+
const { summary } = useInSummary({ scrollTo, label });
47+
48+
return {
49+
summary,
50+
isOpen,
51+
scrollTo,
52+
};
3053
},
3154
3255
data() {
33-
return { isOpen: this.openInitially };
56+
return {};
3457
},
3558
3659
computed: {
@@ -45,16 +68,6 @@ export default defineComponent({
4568
toggle() {
4669
this.isOpen = !this.isOpen;
4770
},
48-
49-
scrollTo() {
50-
this.isOpen = true;
51-
52-
this.$nextTick(() => {
53-
const el = this.$el as HTMLElement;
54-
55-
el.scrollIntoView(true);
56-
});
57-
}
5871
},
5972
});
6073
</script>

pkg/rancher-components/src/components/RcSection/RcSection.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ provide(RC_SECTION_BG_KEY, resolvedBackground);
5959
6060
const expanded = defineModel<boolean>('expanded', { default: true });
6161
62-
// Register this section in form summary/table-of-contents context (if provided)
63-
const { summary } = useInSummary();
64-
6562
// Expose summary, name, and a display label on the component public instance so
6663
// TOC discovery can access component
6764
const displayTitle = computed(() => props.title);
6865
6966
const name = 'RcSection';
7067
68+
// Register this section in form summary/table-of-contents context (if provided)
69+
const { summary } = useInSummary({ label: displayTitle });
70+
7171
defineExpose({
7272
summary, displayTitle, name
7373
});

shell/components/CruResource.vue

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -320,29 +320,29 @@ export default {
320320
stringify,
321321
322322
computeTocContainerHeight() {
323-
const root = this.$el;
323+
// const root = this.$el;
324324
325-
if (!root) {
326-
this.tocContainerHeight = 0;
325+
// if (!root) {
326+
// this.tocContainerHeight = 0;
327327
328-
return 0;
329-
}
328+
// return 0;
329+
// }
330330
331-
const tocEl = root.querySelector('.cru__toc');
332-
const footerEl = root.querySelector('.cru__footer');
331+
// const tocEl = root.querySelector('.cru__toc');
332+
// const footerEl = root.querySelector('.cru__footer');
333333
334-
if (!tocEl || !footerEl) {
335-
this.tocContainerHeight = 0;
334+
// if (!tocEl || !footerEl) {
335+
// this.tocContainerHeight = 0;
336336
337-
return 0;
338-
}
337+
// return 0;
338+
// }
339339
340-
const tocTop = tocEl.getBoundingClientRect().top;
341-
const footerTop = footerEl.getBoundingClientRect().top;
342-
const gapLgValue = getComputedStyle(root).getPropertyValue('--gap-lg').trim();
343-
const gapLg = Number.parseFloat(gapLgValue) || 0;
340+
// const tocTop = tocEl.getBoundingClientRect().top;
341+
// const footerTop = footerEl.getBoundingClientRect().top;
342+
// const gapLgValue = getComputedStyle(root).getPropertyValue('--gap-lg').trim();
343+
// const gapLg = Number.parseFloat(gapLgValue) || 0;
344344
345-
this.tocContainerHeight = Math.max(0, Math.round((footerTop - tocTop) - gapLg));
345+
// this.tocContainerHeight = Math.max(0, Math.round((footerTop - tocTop) - gapLg));
346346
},
347347
348348
confirmCancel(isCancelNotBack = true) {

shell/components/Tabbed/Tab.vue

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script>
22
import { useTabCountWatcher } from '@shell/components/form/ResourceTabs/composable';
33
import { useInSummary } from '@shell/components/TableOfContents/composables';
4+
import { computed, getCurrentInstance, inject } from 'vue';
45
56
export default {
67
name: 'Tab',
@@ -67,8 +68,19 @@ export default {
6768
},
6869
6970
setup(props) {
71+
const select = inject('select');
72+
const instance = getCurrentInstance();
73+
const t = instance?.proxy?.$store?.getters?.['i18n/t'];
74+
const label = computed(() => {
75+
if (props.labelKey && typeof t === 'function') {
76+
return t(props.labelKey);
77+
}
78+
79+
return props.label ?? props.name;
80+
});
7081
const { count, isCountVisible } = useTabCountWatcher();
71-
const { summary } = useInSummary();
82+
// when a Tab is scrolled to, call its Tabbed's 'select' method to ensure the Tab is active
83+
const { summary } = useInSummary({ scrollTo: () => select(props.name), label });
7284
7385
return {
7486
inferredCount: count, isInferredCountVisible: isCountVisible, summary
@@ -127,12 +139,6 @@ export default {
127139
}
128140
},
129141
130-
methods: {
131-
scrollTo() {
132-
this.select(this.name);
133-
}
134-
},
135-
136142
watch: {
137143
active(neu) {
138144
if (neu) {

shell/components/Tabbed/index.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import findIndex from 'lodash/findIndex';
77
import { ExtensionPoint, TabLocation } from '@shell/core/types';
88
import { getApplicableExtensionEnhancements } from '@shell/core/plugin-helpers';
99
import Tab from '@shell/components/Tabbed/Tab';
10-
import { ref } from 'vue';
10+
import { computed, ref } from 'vue';
1111
import { useIsInResourceDetailDrawer } from '@shell/components/Drawer/ResourceDetailDrawer/composables';
1212
import { useIsInResourceDetailPage } from '@shell/composables/resourceDetail';
1313
import { useIsInResourceCreatePage, useIsInResourceEditPage } from '@shell/composables/cruResource';
@@ -170,12 +170,12 @@ export default {
170170
},
171171
},
172172
173-
setup() {
173+
setup(props) {
174174
const isInResourceDetailDrawer = ref(useIsInResourceDetailDrawer());
175175
const isInResourceDetailPage = ref(useIsInResourceDetailPage());
176176
const isInResourceEditPage = ref(useIsInResourceEditPage());
177177
const isInResourceCreatePage = ref(useIsInResourceCreatePage());
178-
const { summary } = useInSummary();
178+
const { summary } = useInSummary({ label: computed(() => props.title ?? '') });
179179
180180
return {
181181
isInResourceDetailDrawer, isInResourceDetailPage, isInResourceEditPage, isInResourceCreatePage, summary

shell/components/TableOfContents/composables.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,16 @@ export function useFormSummary() {
239239
* When the component is mounted (including after v-if re-reveals it), it re-registers
240240
* into the correct scoped context. Components using inFormSummary will register themselves
241241
* with the nearest ancestor containing useFormSummary
242+
*
243+
* @param options.scrollTo - Optional scroll handler. When provided, the ToC system calls
244+
* this function instead of the default `element.scrollIntoView` behaviour when the user
245+
* navigates to this component via the Table of Contents. Use this to perform any
246+
* additional work before scrolling (e.g. expanding an accordion, revealing a tab).
247+
* @param options.label - Optional label for this component's ToC entry. Accepts a plain
248+
* string or a `ComputedRef<string>`. When provided it takes precedence over the
249+
* automatic label derived from `displayTitle`, `title`, `labelKey`, etc.
242250
*/
243-
export function useInSummary() {
251+
export function useInSummary(options?: { scrollTo?: () => void; label?: ComputedRef<string> | string }) {
244252
const {
245253
registerComponent = () => {},
246254
unRegisterComponent = () => {},
@@ -251,13 +259,14 @@ export function useInSummary() {
251259
const t = instance?.proxy?.$store?.getters?.['i18n/t'] as ((key: string) => string) | undefined;
252260
const component = ref<SummaryComponent | null>(null);
253261

254-
// can be overwritten by a component method also named "scrollTo"
255-
const scrollTo = () => {
262+
// Use the caller-supplied scrollTo if provided; otherwise fall back to a plain scrollIntoView.
263+
const scrollTo = options?.scrollTo ?? (() => {
256264
const el = instance?.proxy?.$el as ElementWithSummaryID | null | undefined;
257265

258266
(el as HTMLElement | undefined)?.scrollIntoView(true);
259-
};
267+
});
260268

269+
// options.label will take precedence if defined
261270
const getComponentLabel = () => {
262271
const currentComponent = component.value;
263272

@@ -287,7 +296,13 @@ export function useInSummary() {
287296
const summaryID = randomStr();
288297
const summary: SummaryInfo = { id: summaryID, scrollTo };
289298

290-
summary.label = computed(getComponentLabel);
299+
if (options?.label !== undefined) {
300+
// Caller supplied an explicit label — wrap a plain string in a computed so the
301+
// type is always ComputedRef<string>, and skip the component-field heuristics.
302+
summary.label = typeof options.label === 'string' ? computed(() => options.label as string) : options.label;
303+
} else {
304+
summary.label = computed(getComponentLabel);
305+
}
291306

292307
watch(summary.label, (label) => {
293308
const updated = updateComponentLabel(summaryID, label);

0 commit comments

Comments
 (0)