From 3b6bbf43e2a67aeeddfb5c0f99615ba96d52d303 Mon Sep 17 00:00:00 2001 From: Thaddaeus Chua Date: Tue, 20 Jan 2026 13:02:20 +0800 Subject: [PATCH 1/5] Add watcher to populate selectedTags on first load --- .../src/cardstack/CardStack.vue | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/vue-components/src/cardstack/CardStack.vue b/packages/vue-components/src/cardstack/CardStack.vue index a962d27f0f..7e52977b00 100644 --- a/packages/vue-components/src/cardstack/CardStack.vue +++ b/packages/vue-components/src/cardstack/CardStack.vue @@ -62,6 +62,17 @@ export default { }, computed: { }, + watch: { + 'cardStackRef.tagMapping': { + handler(newMapping) { + // Initialise the selectedTags with all tag names when loading for the first time + if (this.selectedTags.length === 0 && newMapping.length > 0) { + this.selectedTags = newMapping.map(key => key[0]); + } + }, + immediate: true, + }, + }, provide() { return { cardStackRef: this.cardStackRef, @@ -100,7 +111,13 @@ export default { } if (this.selectedTags.length === 0) { - this.showAllTags(); + // this.showAllTags(); //reminder to remove later + // This ensures that all cards are disabled when no tags are selected + this.cardStackRef.children.forEach((child) => { + if (!child.$props.disabled) { + child.$data.disableTag = true; + } + }); } else { this.cardStackRef.children.forEach((child) => { if (child.$props.disabled) return; From 4502021819d48432677ab177809cc0119167c891 Mon Sep 17 00:00:00 2001 From: Thaddaeus Chua Date: Tue, 20 Jan 2026 14:27:33 +0800 Subject: [PATCH 2/5] Add toggle all tag indicator --- .../src/cardstack/CardStack.vue | 39 +++++++++++++++---- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/packages/vue-components/src/cardstack/CardStack.vue b/packages/vue-components/src/cardstack/CardStack.vue index 7e52977b00..ca6b441831 100644 --- a/packages/vue-components/src/cardstack/CardStack.vue +++ b/packages/vue-components/src/cardstack/CardStack.vue @@ -12,6 +12,17 @@ /> + + Select All  + + +     + + { - if (!child.$props.disabled) { - child.$data.disableTag = true; - } - }); + // this.cardStackRef.children.forEach((child) => { + // if (!child.$props.disabled) { + // child.$data.disableTag = true; + // } + // }); + this.showAllTags(false); } else { this.cardStackRef.children.forEach((child) => { if (child.$props.disabled) return; @@ -128,16 +143,26 @@ export default { }); } }, - showAllTags() { + showAllTags(showTag) { this.cardStackRef.children.forEach((child) => { if (child.$props.disabled) return; - child.$data.disableTag = false; + child.$data.disableTag = !showTag; }); }, computeShowTag(tagName) { return this.selectedTags.includes(tagName); }, + toggleAllTags() { + const allTags = this.cardStackRef.tagMapping.map(key => key[0]); + if (this.selectedTags.length === allTags.length) { + this.selectedTags = []; + this.showAllTags(false); + } else { + this.selectedTags = allTags; + this.showAllTags(true); + } + }, }, data() { return { From 79cbd4c89e84f1cb6052576df1efe46194e689f3 Mon Sep 17 00:00:00 2001 From: Thaddaeus Chua Date: Tue, 20 Jan 2026 14:53:57 +0800 Subject: [PATCH 3/5] Add test cases --- .../src/__tests__/CardStack.spec.js | 62 +++++++++++++++++++ .../__snapshots__/CardStack.spec.js.snap | 16 ++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/packages/vue-components/src/__tests__/CardStack.spec.js b/packages/vue-components/src/__tests__/CardStack.spec.js index 8c04b1486f..b41eed8d86 100644 --- a/packages/vue-components/src/__tests__/CardStack.spec.js +++ b/packages/vue-components/src/__tests__/CardStack.spec.js @@ -91,4 +91,66 @@ describe('CardStack', () => { await wrapper.vm.$nextTick(); expect(wrapper.element).toMatchSnapshot(); }); + + test('should have all tags checked by default on load', async () => { + const wrapper = mount(CardStack, { + slots: { default: CARDS_CUSTOMISATION }, + global: DEFAULT_GLOBAL_MOUNT_OPTIONS, + }); + await wrapper.vm.$nextTick(); + const allTags = wrapper.vm.cardStackRef.tagMapping.map(key => key[0]); + expect(wrapper.vm.selectedTags).toEqual(expect.arrayContaining(allTags)); + expect(wrapper.vm.allSelected).toBe(true); + }); + + test('toggleAllTags should unselect everything and then select everything', async () => { + const wrapper = mount(CardStack, { + slots: { default: CARDS_CUSTOMISATION }, + global: DEFAULT_GLOBAL_MOUNT_OPTIONS, + }); + await wrapper.vm.$nextTick(); + + // selected all initially + expect(wrapper.vm.allSelected).toBe(true); + + // deselect everything + const selectAllBadge = wrapper.find('.bg-dark.tag-badge'); + await selectAllBadge.trigger('click'); + expect(wrapper.vm.selectedTags.length).toBe(0); + expect(wrapper.vm.allSelected).toBe(false); + + // all cards should be hidden + const cards = wrapper.findAllComponents(Card); + cards.forEach((card) => { + if (card.props('tag') === 'Short') { + expect(card.vm.disableTag).toBe(true); + } + }); + + // select all again -> everything should be selected back + await selectAllBadge.trigger('click'); + expect(wrapper.vm.allSelected).toBe(true); + expect(wrapper.vm.selectedTags.length).toBeGreaterThan(0); + }); + + test('Select All checkbox should sync with individual tag clicks', async () => { + const wrapper = mount(CardStack, { + slots: { default: CARDS_CUSTOMISATION }, + global: DEFAULT_GLOBAL_MOUNT_OPTIONS, + }); + await wrapper.vm.$nextTick(); + + // uncheck first tag + const firstTagBadge = wrapper.findAll('.tag-badge').at(1); + await firstTagBadge.trigger('click'); + + // select all should no longer be checked + expect(wrapper.vm.allSelected).toBe(false); + const selectAllIndicator = wrapper.find('.bg-dark.tag-badge .tag-indicator'); + expect(selectAllIndicator.text()).not.toContain('✓'); + + // Check first tag -> select all should be checked again + await firstTagBadge.trigger('click'); + expect(wrapper.vm.allSelected).toBe(true); + }); }); diff --git a/packages/vue-components/src/__tests__/__snapshots__/CardStack.spec.js.snap b/packages/vue-components/src/__tests__/__snapshots__/CardStack.spec.js.snap index 7524c4a9b6..e29180be3f 100644 --- a/packages/vue-components/src/__tests__/__snapshots__/CardStack.spec.js.snap +++ b/packages/vue-components/src/__tests__/__snapshots__/CardStack.spec.js.snap @@ -16,6 +16,7 @@ exports[`CardStack markdown in header, content 1`] = ` type="text" /> + @@ -96,6 +97,18 @@ exports[`CardStack should not hide cards when no filter is provided 1`] = ` type="text" /> + + Select All  + + + ✓ + + + -     + ✓ @@ -218,6 +231,7 @@ exports[`CardStack should not hide cards when no filter is provided 2`] = ` type="text" /> + From c9fde7b8f6d191dfabecefc92cb730cac0bbb013 Mon Sep 17 00:00:00 2001 From: Thaddaeus Chua Date: Tue, 20 Jan 2026 14:58:26 +0800 Subject: [PATCH 4/5] Remove old redundant code comments --- packages/vue-components/src/cardstack/CardStack.vue | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/vue-components/src/cardstack/CardStack.vue b/packages/vue-components/src/cardstack/CardStack.vue index ca6b441831..f03647ff8e 100644 --- a/packages/vue-components/src/cardstack/CardStack.vue +++ b/packages/vue-components/src/cardstack/CardStack.vue @@ -125,13 +125,6 @@ export default { } if (this.selectedTags.length === 0) { - // this.showAllTags(); //reminder to remove later - // This ensures that all cards are disabled when no tags are selected - // this.cardStackRef.children.forEach((child) => { - // if (!child.$props.disabled) { - // child.$data.disableTag = true; - // } - // }); this.showAllTags(false); } else { this.cardStackRef.children.forEach((child) => { From c8ec29c8118a74b6e84127db0d6c14a4477ed630 Mon Sep 17 00:00:00 2001 From: Thaddaeus Chua Date: Thu, 22 Jan 2026 18:12:20 +0800 Subject: [PATCH 5/5] Fix disable documentation error --- docs/userGuide/syntax/cardstacks.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/userGuide/syntax/cardstacks.md b/docs/userGuide/syntax/cardstacks.md index 7c5107c050..2916dfb371 100644 --- a/docs/userGuide/syntax/cardstacks.md +++ b/docs/userGuide/syntax/cardstacks.md @@ -149,7 +149,7 @@ Name | Type | Default | Description tag | `String` | `null` | Tags of each card component.
Each unique tag should be seperated by a `,`.
Tags are added to the search field. header | `String` | `null` | Header of each card component.
Supports the use of inline markdown elements. keywords | `String` | `null` | Keywords of each card component.
Each unique keyword should be seperated by a `,`.
Keywords are added to the search field. -disable | `Boolean` | `false` | Disable card.
This removes visibility of the card and makes it unsearchable. +disabled | `Boolean` | `false` | Disable card.
This removes visibility of the card and makes it unsearchable.