Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
b51be91
vnode wip
mantis-toboggan-md Dec 16, 2025
0d4e6db
working but flat
mantis-toboggan-md Dec 18, 2025
bb1387d
children
mantis-toboggan-md Jan 5, 2026
4b77a4c
use subtree when possible
mantis-toboggan-md Jan 5, 2026
baf97eb
make list and scroll to accordion
mantis-toboggan-md Jan 5, 2026
5ab4894
open accordion when scrolling to
mantis-toboggan-md Jan 5, 2026
62dcece
functional and stylish
mantis-toboggan-md Jan 8, 2026
cf22f1c
fix some css vars
mantis-toboggan-md Jan 15, 2026
369a921
composable
mantis-toboggan-md Jan 15, 2026
561232f
show toc only when accordions found on page
mantis-toboggan-md Jan 15, 2026
9c0fb68
clean up
mantis-toboggan-md Jan 15, 2026
585c30b
track accordions in composable
mantis-toboggan-md Jan 16, 2026
ce13f2b
re-add add accordion test buttons
mantis-toboggan-md Jan 16, 2026
cfb0665
scroll bar
mantis-toboggan-md Feb 24, 2026
f8581d7
wip
mantis-toboggan-md Mar 11, 2026
1bf9723
remove dependency on component css classes
mantis-toboggan-md Mar 13, 2026
307abba
scroll to not-accordion not quite
mantis-toboggan-md Mar 13, 2026
c4887a0
composable has desired functionality, needs typing
mantis-toboggan-md Mar 13, 2026
be25df3
add typing
mantis-toboggan-md Mar 13, 2026
9329d6e
Revert "add typing"
mantis-toboggan-md Mar 13, 2026
668af5f
add types, comments
mantis-toboggan-md Mar 16, 2026
05f1602
cp feedback; style
mantis-toboggan-md Mar 17, 2026
93a1494
add to rcsection; revert to using provide/inject; style fixes
mantis-toboggan-md Mar 20, 2026
1f47b7c
e2e test
mantis-toboggan-md Mar 27, 2026
f1dd38b
comment out failing tests to build
mantis-toboggan-md Mar 27, 2026
852118a
omment out tests
mantis-toboggan-md Mar 27, 2026
2e62135
.
mantis-toboggan-md Mar 27, 2026
cc3b6cb
move toc back to shell for testing
mantis-toboggan-md Mar 27, 2026
38c750e
lotta console logs
mantis-toboggan-md Mar 27, 2026
8c89665
add summary id attribute to html element
mantis-toboggan-md Mar 27, 2026
71bc685
start adding to tabbed
mantis-toboggan-md Mar 30, 2026
c2cf17d
add tabs to table of contents
mantis-toboggan-md Mar 31, 2026
7c24d74
update summary when labels change
mantis-toboggan-md Mar 31, 2026
b4a9342
word wrap long titles
mantis-toboggan-md Mar 31, 2026
f801b59
clean up ts
mantis-toboggan-md Mar 31, 2026
ed7ef43
toc need to grow when scroll
mantis-toboggan-md Mar 31, 2026
d1c52a8
toc resize but a little jittery
mantis-toboggan-md Apr 2, 2026
921d32a
fix jittery toc resize using a transition; add a breakpoint to hide t…
mantis-toboggan-md Apr 2, 2026
e831585
ux feedback
mantis-toboggan-md Apr 3, 2026
25f244c
minor performance enhancement on label calc
mantis-toboggan-md Apr 3, 2026
618a175
remove console logs
mantis-toboggan-md Apr 3, 2026
289c406
re-enable failing tests
mantis-toboggan-md Apr 9, 2026
88de97f
update auditpolicy snapshot following cruresource prop addition
mantis-toboggan-md Apr 9, 2026
9d134a7
hack away ts error?
mantis-toboggan-md Apr 9, 2026
ed0d0e3
revert unrelated pod detail change
mantis-toboggan-md Apr 9, 2026
1e331eb
revert tsconfig change
mantis-toboggan-md Apr 9, 2026
7c3d98b
feedback; allow label passthrough
mantis-toboggan-md Apr 30, 2026
65eee4e
add comment to computeTocContainerHeight
mantis-toboggan-md Apr 30, 2026
01bb494
fix test
mantis-toboggan-md May 12, 2026
c73c8d4
remove console log
mantis-toboggan-md May 12, 2026
0857065
remove test-breaking cruresource css change
mantis-toboggan-md May 12, 2026
03a2b34
add comment explaning 0-wait debounce
mantis-toboggan-md May 12, 2026
6df8d34
pr feedback, minus getCurrentInstance use in useInSummary
mantis-toboggan-md May 14, 2026
ed4317b
pr feedback
mantis-toboggan-md May 14, 2026
d431e55
lint
mantis-toboggan-md May 14, 2026
b98a53e
fix ts errors; fix tabbed not registering with ToC
mantis-toboggan-md May 14, 2026
06157b1
pr feedback
mantis-toboggan-md May 20, 2026
1c83aaf
fix String->string
mantis-toboggan-md May 20, 2026
3ec7cdc
fix user test
mantis-toboggan-md May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cypress/e2e/po/components/fixed-banner.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@ export default class FixedBannerPo extends ComponentPo {
color() {
return this.self().find('.banner').first().should('have.css', 'color');
}

close() {
return this.self().find('[data-testid="banner-close"]').first().click();
}
}
30 changes: 29 additions & 1 deletion cypress/e2e/tests/pages/manager/cluster-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,35 @@ describe('Cluster Manager', { testIsolation: 'off', tags: ['@manager', '@adminUs
clusterList.list().providerSubType(importGenericName).should('contain.text', 'K3s');
});

it('creation page should include a table of contents containing an entry for each Accordion on the page', () => {
cy.intercept('GET', `${ USERS_BASE_URL }?*`).as('getUsers');

clusterList.goTo();
clusterList.checkIsCurrentPage();
clusterList.importCluster();

importClusterPage.waitForPage('mode=import');
importClusterPage.selectGeneric(0);
importClusterPage.waitForPage('mode=import&type=import&rkeType=rke2');
cy.wait('@getUsers');
// verify that the table of contents is shown and contains the same number of entries as there are accordions on the page
cy.get('[data-testid="accordion-header"]')
.its('length')
.then((accordionHeaderCount) => {
cy.get('[data-testid^="toc-list-item-"]')
.should('have.length', accordionHeaderCount);
});

// verify that clicking an accordion label in the table of contents scrolls the page to the associated accordion and opens it
cy.get('[data-testid="toc-list-item-3"] button').click();

cy.window().its('scrollY').should('be.greaterThan', 0);

cy.get('[data-testid="registries-accordion"]')
.find('[data-testid="accordion-body"]')
.should('be.visible');
});

it('can edit imported cluster and see changes afterwards', () => {
cy.getClusterIdByName(importGenericName).then((clusterId) => {
const editImportedClusterPage = new ClusterManagerEditImportedPagePo(undefined, 'fleet-default', clusterId);
Expand Down Expand Up @@ -853,7 +882,6 @@ describe('Cluster Manager as standard user', { testIsolation: 'off', tags: ['@ma
});
});
});

describe('Visual Testing', { tags: ['@percy', '@manager', '@adminUser'] }, () => {
before(() => {
cy.login();
Expand Down
6 changes: 5 additions & 1 deletion cypress/e2e/tests/pages/users-and-auth/users.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -533,7 +533,7 @@ describe('Users', { tags: ['@usersAndAuths', '@adminUser'] }, () => {
cy.login();
});

it('User creation should complete after admin user fails to create for Standard user with Manage Users Role', () => {
it('User creation should throw an error if creating a user with a higher permission set, the error should be removable, and creation should succeed once permissions are appropriately lowered', () => {
// create standard user
usersPo.goTo();
usersPo.waitForPage();
Expand Down Expand Up @@ -579,6 +579,10 @@ describe('Users', { tags: ['@usersAndAuths', '@adminUser'] }, () => {
banner.checkExists();
banner.text().should('eq', 'You cannot assign Global Permissions that are higher than your own. Please verify the permissions you are attempting to assign.');

// close the error banner before updating the Global Permissions
banner.close();
banner.checkNotExists();

// update the Global Permissions
userCreate.selectCheckbox('User-Base').set();
Copy link
Copy Markdown
Member Author

@mantis-toboggan-md mantis-toboggan-md May 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this test is failing because this checkbox is hidden by the intentionally-introduced error banner. I can't discern any visual or behavioral differences in this area of the UI with this PR; I do not think this test failure is indicative of an actual unintended change observable by users.

Watching test runs, it appears that on the master branch, cypress is able to click a checkbox that is not visible:

Image

Looks like this in this PR:

Image

I think it is a fluke that it passed before and correct for the test to fail if trying to interact with an element that is hidden by another element...?

I updated the test to close the error banner before clicking the checkbox. In doing so I noticed the banner was not close-able and had to fix the CruResource props in the users page. I can move this work into a separate PR that blocks this PR if this is too much scope creep

userCreate.saveAndWaitForRequests('POST', '/v3/globalrolebindings');
Expand Down
2 changes: 2 additions & 0 deletions pkg/eks/components/CruEKS.vue
Original file line number Diff line number Diff line change
Expand Up @@ -662,6 +662,7 @@ export default defineComponent({
:done-route="doneRoute"
:errors="fvUnreportedValidationErrors"
:validation-passed="fvFormIsValid"
:show-toc="hasCredential"
@error="e=>errors=e"
@finish="save"
@cancel="done"
Expand Down Expand Up @@ -724,6 +725,7 @@ export default defineComponent({
<template v-else>
<div><h3>{{ t('eks.nodeGroups.title') }}</h3></div>
<Tabbed
:title="t('eks.nodeGroups.title')"
class="mb-20"
:side-tabs="true"
:show-tabs-add-remove="mode !== VIEW"
Expand Down
40 changes: 34 additions & 6 deletions pkg/imported/components/CruImported.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { IMPORTED_CLUSTER_VERSION_MANAGEMENT } from '@shell/config/labels-annota
import cloneDeep from 'lodash/cloneDeep';
import { VERSION_MANAGEMENT_DEFAULT } from '@pkg/imported/util/shared.ts';
import SchedulingCustomization from '@shell/components/form/SchedulingCustomization';
import { randomStr } from '@shell/utils/string';

const HARVESTER_HIDE_KEY = 'cm-harvester-import';
const defaultCluster = {
Expand Down Expand Up @@ -142,6 +143,7 @@ export default defineComponent({
}
],
AGENT_CONFIGURATION_TYPES,
testAccs: [] // TODO nb remove
Comment thread
mantis-toboggan-md marked this conversation as resolved.
};
},

Expand Down Expand Up @@ -264,6 +266,8 @@ export default defineComponent({
},

methods: {
// TODO nb remove
randomStr,
Comment thread
mantis-toboggan-md marked this conversation as resolved.

onMembershipUpdate(update) {
this.membershipUpdate = update;
Expand Down Expand Up @@ -423,6 +427,7 @@ export default defineComponent({
:done-route="doneRoute"
:errors="fvUnreportedValidationErrors"
:validation-passed="fvFormIsValid"
:show-toc="true"
@error="e=>errors=e"
@finish="save"
>
Expand Down Expand Up @@ -550,6 +555,26 @@ export default defineComponent({
@scheduling-customization-changed="setSchedulingCustomization"
/>
</Accordion>
<!-- //TODO nb remove -->
<button
class="btn role-primary"
@click="testAccs.push({})"
>
add accordion
</button><button
class="btn role-secondary ml-5"
@click="testAccs.pop()"
>
remove accordion
</button>
<Accordion
v-for="(acc, i) in testAccs"
:key="i"
class="mb-20 accordion"
:title="i.toString()"
>
{{ i }}
</Accordion>
<Accordion
class="mb-20 accordion"
Comment thread
mantis-toboggan-md marked this conversation as resolved.
title-key="imported.accordions.labels"
Expand Down Expand Up @@ -615,6 +640,15 @@ export default defineComponent({
title-key="imported.accordions.advanced"
:open-initially="false"
>
<!-- //TODO nb remove -->
<Accordion
v-if="!isRKE1"
class="mb-20 accordion"
title="test accordion has a pretty long title to test long titles"
:open-initially="false"
>
test
</Accordion>
Comment thread
mantis-toboggan-md marked this conversation as resolved.
<h3>
{{ t('imported.agentEnv.header') }}
</h3>
Expand All @@ -634,9 +668,3 @@ export default defineComponent({
</div>
</CruResource>
</template>

<style lang="scss" scoped>
.accordion {
border-radius: 16px;
}
</style>
62 changes: 53 additions & 9 deletions pkg/rancher-components/src/components/Accordion/Accordion.vue
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
<script lang="ts">
import { defineComponent } from 'vue';
import { mapGetters } from 'vuex';
import {
computed, defineComponent, nextTick, ref, useTemplateRef
} from 'vue';
import { mapGetters, useStore } from 'vuex';
import { useInSummary } from '@shell/components/TableOfContents/composables';
import { useI18n } from '@shell/composables/useI18n';

export default defineComponent({
name: 'Accordion',

props: {
title: {
type: String,
Expand All @@ -20,22 +26,59 @@ export default defineComponent({
}
},

setup(props) {
const store = useStore();
const { t } = useI18n(store);
const label = computed(() => props.titleKey && typeof t === 'function' ? t(props.titleKey) : props.title);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of scope for this change - I think we should consider deprecating the titleKey pattern. Callers should be able to provide a valid title without Rancher Components requiring a dependency on the store and the i18n implementation in order to perform a key lookup. This current approach tightly couples components to the Dashboard implementation.

FYI @codyrancher


const isOpen = ref(props.openInitially);
const accordionSummarizedContainer = useTemplateRef<HTMLElement>('accordion-summarized-container');

const scrollTo = () => {
isOpen.value = true;
nextTick(() => {
accordionSummarizedContainer.value?.scrollIntoView();
});
};

const { summary } = useInSummary({
scrollTo,
label,
elementRef: accordionSummarizedContainer,
});

return {
summary,
isOpen,
scrollTo,
};
},

data() {
return { isOpen: this.openInitially };
return {};
},

computed: { ...mapGetters({ t: 'i18n/t' }) },
computed: {
...mapGetters({ t: 'i18n/t' }),

displayTitle() {
return this.titleKey ? this.t(this.titleKey) : this.title;
},
},

methods: {
toggle() {
this.isOpen = !this.isOpen;
}
}
},
},
});
</script>

<template>
<div class="accordion-container">
<div
ref="accordion-summarized-container"
class="accordion-container"
>
<div
class="accordion-header"
data-testid="accordion-header"
Expand All @@ -51,7 +94,7 @@ export default defineComponent({
data-testid="accordion-title-slot-content"
class="mb-0"
>
{{ titleKey ? t(titleKey) : title }}
{{ displayTitle }}
</h2>
</slot>
</div>
Expand All @@ -67,7 +110,8 @@ export default defineComponent({

<style lang="scss" scoped>
.accordion-container {
border: 1px solid var(--border)
border: 1px solid var(--border);
border-radius: var(--border-radius);
}
.accordion-header {
padding: 16px 16px 16px 11px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface Option {
}

export default defineComponent({
name: 'RadioGroup',
components: { RadioButton },
props: {
/**
Expand Down
28 changes: 26 additions & 2 deletions pkg/rancher-components/src/components/RcSection/RcSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@
* <p>Section content here</p>
* </RcSection>
*/
import { computed, inject, provide, type Ref } from 'vue';
import {
computed, inject, provide, useTemplateRef, type Ref
} from 'vue';
import RcButton from '@components/RcButton/RcButton.vue';
import RcIcon from '@components/RcIcon/RcIcon.vue';
import { useInSummary } from '@shell/components/TableOfContents/composables';
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see one of two options here depending on intention:

  1. If we want to expose ToC to all consumers of Rancher Components I have a feeling we should the entire implementation into Rancher Components.
  2. We only want to expose ToC to shell, in that case I think we should wrap RcSection in a shell component such as TableOfContentsSection which uses RcSection and ToC composables to make the new component.

Any thoughts on this @rak-phillip ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with both of your positions. I think that the question that we need answered if is "do we consider ToC to be a core building block?"; if we do, then we need to sync with @oboc-sts to ensure that the toc is detailed in the design system. We'll also need to ensure that the ToC is properly documented in the storybook.

Based on what I'm seeing, I think the answer is yes - we might want this implemented in Rancher Components.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In an effort to try and unblock this pr since it's definitely the reviewing that has held this one up. @oboc-sts, is the ToC component something we're planning to use in extensions or outside of rancher?

If so, can we put the component it in the design system?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per slack discussion it sounds like we dont want extension developers/people outside our team relying on this feature just yet.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just saw the message. Given that I'm okay to approve. I think we may want to move where the composable is used but I don't think we need to hold this up anymore for that.

import type { RcSectionProps, SectionBackground } from './types';

const RC_SECTION_BG_KEY = 'rc-section-background';
Expand All @@ -58,6 +61,24 @@ provide(RC_SECTION_BG_KEY, resolvedBackground);

const expanded = defineModel<boolean>('expanded', { default: true });

// Expose summary, name, and a display label on the component public instance so
// TOC discovery can access component
const displayTitle = computed(() => props.title);

const name = 'RcSection';

// Register this section in form summary/table-of-contents context (if provided)
const sectionRef = useTemplateRef<HTMLElement>('rc-section-summarized-container');
const { summary } = useInSummary({
label: displayTitle,
scrollTo: () => sectionRef.value?.scrollIntoView(true),
elementRef: sectionRef,
});

defineExpose({
summary, displayTitle, name
});

const hasHeader = computed(() => {
return props.mode === 'with-header';
});
Expand All @@ -84,7 +105,10 @@ function toggle() {
</script>

<template>
<div :class="sectionClass">
<div
ref="rc-section-summarized-container"
:class="sectionClass"
>
<div
v-if="hasHeader"
class="section-header"
Expand Down
4 changes: 4 additions & 0 deletions shell/assets/styles/global/_layout.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
grid-area: main;
overflow: auto;

@media (prefers-reduced-motion: no-preference) {
scroll-behavior: smooth;
}

.outlet {
display: flex;
flex-direction: column;
Expand Down
2 changes: 2 additions & 0 deletions shell/assets/translations/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2841,6 +2841,8 @@ cruResource:
reviewYaml: "Keep editing YAML"
previewYaml: Edit as YAML
showYaml: View as YAML
tableOfContents:
jumpTo: Jump to...

providers:
hosted:
Expand Down
Loading
Loading