From 05cd2eb374ec6e9f3ab19e15fe678246b5191693 Mon Sep 17 00:00:00 2001 From: Michael Webber Date: Tue, 14 Jul 2026 09:28:42 -0700 Subject: [PATCH 01/74] fix(unity-react-core): fixed success font and title --- .../form-fields.examples.stories.js | 96 +++++++++---------- .../src/components/Checkbox/Checkbox.tsx | 4 +- .../src/components/Radio/Radio.tsx | 2 +- .../src/components/Select/Select.tsx | 4 +- .../src/components/TextInput/TextInput.tsx | 4 +- .../src/components/Textarea/Textarea.tsx | 4 +- 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/packages/unity-bootstrap-theme/stories/atoms/form-fields/form-fields.examples.stories.js b/packages/unity-bootstrap-theme/stories/atoms/form-fields/form-fields.examples.stories.js index 6a95682cd3..7fa1b1256b 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/form-fields/form-fields.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/form-fields/form-fields.examples.stories.js @@ -118,7 +118,7 @@ export const TextInputs = () => ( data-ga-input-section="text label" /> - + Success message @@ -224,7 +224,7 @@ export const TextInputsVariousBackgrounds = () => ( value="Input text" /> - + Success message @@ -315,7 +315,7 @@ export const TextInputsVariousBackgrounds = () => ( value="Input text" /> - + Success message @@ -406,7 +406,7 @@ export const TextInputsVariousBackgrounds = () => ( value="Input text" /> - + Success message @@ -497,7 +497,7 @@ export const TextInputsVariousBackgrounds = () => ( value="Input text" /> - + Success message @@ -618,7 +618,7 @@ export const Textareas = () => ( Agreeable content was entered. - + Success message @@ -709,7 +709,7 @@ export const TextareasMultipleBackgrounds = () => ( id="successTextareaHelpWhite" className="form-textarea valid-feedback" > - + Success message @@ -798,7 +798,7 @@ export const TextareasMultipleBackgrounds = () => ( id="successTextareaHelpGray1" className="form-textarea valid-feedback" > - + Success message @@ -887,7 +887,7 @@ export const TextareasMultipleBackgrounds = () => ( id="successTextareaHelpGray2" className="form-textarea valid-feedback" > - + Success message @@ -976,7 +976,7 @@ export const TextareasMultipleBackgrounds = () => ( id="successTextareaHelpGray7" className="form-textarea valid-feedback" > - + Success message @@ -1054,7 +1054,7 @@ export const Checkboxes = () => ( I accept - + Success message @@ -1145,7 +1145,7 @@ export const Checkboxes = () => (
A Group of Valid Checkboxes - + Success message
@@ -1447,7 +1447,7 @@ export const Radios = () => (
A Group of Valid Radios - + Success message
@@ -1561,7 +1561,7 @@ export const CheckboxesAndRadiosWhiteBackground = () => ( I accept - + Success message
@@ -1628,7 +1628,7 @@ export const CheckboxesAndRadiosWhiteBackground = () => (
A Group of Valid Checkboxes - + Success message
@@ -1845,7 +1845,7 @@ export const CheckboxesAndRadiosWhiteBackground = () => (
A Group of Valid Radios - + Success message
@@ -1936,7 +1936,7 @@ export const CheckboxesAndRadiosGray1Background = () => ( I accept - + Success message
@@ -2003,7 +2003,7 @@ export const CheckboxesAndRadiosGray1Background = () => (
A Group of Valid Checkboxes - + Success message
@@ -2220,7 +2220,7 @@ export const CheckboxesAndRadiosGray1Background = () => (
A Group of Valid Radios - + Success message
@@ -2311,7 +2311,7 @@ export const CheckboxesAndRadiosGray2Background = () => ( I accept - + Success message
@@ -2378,7 +2378,7 @@ export const CheckboxesAndRadiosGray2Background = () => (
A Group of Valid Checkboxes - + Success message
@@ -2595,7 +2595,7 @@ export const CheckboxesAndRadiosGray2Background = () => (
A Group of Valid Radios - + Success message
@@ -2686,7 +2686,7 @@ export const CheckboxesAndRadiosGray7Background = () => ( I accept - + Success message
@@ -2753,7 +2753,7 @@ export const CheckboxesAndRadiosGray7Background = () => (
A Group of Valid Checkboxes - + Success message
@@ -2970,7 +2970,7 @@ export const CheckboxesAndRadiosGray7Background = () => (
A Group of Valid Radios - + Success message
@@ -3074,7 +3074,7 @@ export const Selects = () => ( - + Success message
@@ -3388,7 +3388,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat value="Input text" /> - + Success message
@@ -3449,7 +3449,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat id="successTextareaHelp" className="form-textarea valid-feedback" > - + Success message
@@ -3489,7 +3489,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat I accept (required field) - + Success message
@@ -3523,7 +3523,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat
A Group of Valid Checkboxes - + Success message
@@ -3709,7 +3709,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat
A Group of Valid Radios - + Success message
@@ -3868,7 +3868,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm value="Input text" /> - + Success message
@@ -3925,7 +3925,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm id="successTextareaHelp" className="form-textarea valid-feedback" > - + Success message
@@ -3965,7 +3965,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm I accept (required) - + Success message
@@ -3999,7 +3999,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm
A Group of Valid Checkboxes - + Success message
@@ -4185,7 +4185,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm
A Group of Valid Radios - + Success message
@@ -4258,7 +4258,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm - + Success message
@@ -4373,7 +4373,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat value="Input text" /> - + Success message
@@ -4434,7 +4434,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat id="successTextareaHelp" className="form-textarea valid-feedback" > - + Success message
@@ -4474,7 +4474,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat I accept (required field) - + Success message @@ -4511,7 +4511,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat
A Group of Valid Checkboxes - + Success message
@@ -4697,7 +4697,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat
A Group of Valid Radios - + Success message
@@ -4861,7 +4861,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm value="Input text" /> - + Success message
@@ -4918,7 +4918,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm id="successTextareaHelp" className="form-textarea valid-feedback" > - + Success message
@@ -4958,7 +4958,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm I accept (required) - + Success message @@ -4995,7 +4995,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm
A Group of Valid Checkboxes - + Success message
@@ -5181,7 +5181,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm
A Group of Valid Radios - + Success message
@@ -5254,7 +5254,7 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm - + Success message
diff --git a/packages/unity-react-core/src/components/Checkbox/Checkbox.tsx b/packages/unity-react-core/src/components/Checkbox/Checkbox.tsx index eb0a8034a4..aa48d906d5 100644 --- a/packages/unity-react-core/src/components/Checkbox/Checkbox.tsx +++ b/packages/unity-react-core/src/components/Checkbox/Checkbox.tsx @@ -70,7 +70,7 @@ export const Checkboxes = () => ( I accept - Success + Success message
@@ -158,7 +158,7 @@ export const Checkboxes = () => (
A Group of Valid Checkboxes - Success + Success message
diff --git a/packages/unity-react-core/src/components/Radio/Radio.tsx b/packages/unity-react-core/src/components/Radio/Radio.tsx index fa56880a68..f3ddc66212 100644 --- a/packages/unity-react-core/src/components/Radio/Radio.tsx +++ b/packages/unity-react-core/src/components/Radio/Radio.tsx @@ -170,7 +170,7 @@ export const Radios = () => (
A Group of Valid Radios - Success + Success message
diff --git a/packages/unity-react-core/src/components/Select/Select.tsx b/packages/unity-react-core/src/components/Select/Select.tsx index c848205b66..79acab753b 100644 --- a/packages/unity-react-core/src/components/Select/Select.tsx +++ b/packages/unity-react-core/src/components/Select/Select.tsx @@ -45,7 +45,7 @@ const defaultGaEvent = { export const propDefaults: SelectProps = { id: undefined, - label: undefined, + label: "", displaySize: "md", required: false, disabled: false, @@ -112,7 +112,7 @@ export const Select: React.FC = ({ {valid && ( - + {validMessage} )} diff --git a/packages/unity-react-core/src/components/TextInput/TextInput.tsx b/packages/unity-react-core/src/components/TextInput/TextInput.tsx index d9096fc5f5..e78bc2e179 100644 --- a/packages/unity-react-core/src/components/TextInput/TextInput.tsx +++ b/packages/unity-react-core/src/components/TextInput/TextInput.tsx @@ -30,7 +30,7 @@ const defaultGaEvent = { export const propDefaults: TextInputProps = { id: undefined, - label: undefined, + label: "", placeholder: undefined, displaySize: "md", required: false, @@ -85,7 +85,7 @@ export const TextInput: React.FC = ({ {valid && ( - + {validMessage} )} diff --git a/packages/unity-react-core/src/components/Textarea/Textarea.tsx b/packages/unity-react-core/src/components/Textarea/Textarea.tsx index 871813e3c0..2e35b39cf5 100644 --- a/packages/unity-react-core/src/components/Textarea/Textarea.tsx +++ b/packages/unity-react-core/src/components/Textarea/Textarea.tsx @@ -31,7 +31,7 @@ const defaultGaEvent = { export const propDefaults: TextareaProps = { id: undefined, - label: undefined, + label: "", placeholder: undefined, displaySize: "md", required: false, @@ -85,7 +85,7 @@ export const Textarea: React.FC = ({ {valid && ( - + {validMessage} )} From e0e6a2d8cddf7ea21fd7376d340e9d25f1453885 Mon Sep 17 00:00:00 2001 From: Michael Webber Date: Fri, 17 Jul 2026 10:11:51 -0700 Subject: [PATCH 02/74] refactor(unity-react-core): removed unused import --- packages/unity-react-core/src/components/Checkbox/Checkbox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/unity-react-core/src/components/Checkbox/Checkbox.tsx b/packages/unity-react-core/src/components/Checkbox/Checkbox.tsx index aa48d906d5..9e250ffa91 100644 --- a/packages/unity-react-core/src/components/Checkbox/Checkbox.tsx +++ b/packages/unity-react-core/src/components/Checkbox/Checkbox.tsx @@ -1,5 +1,5 @@ /* eslint-disable react/jsx-props-no-spreading */ -import React from "react"; +// import React from "react"; export const Checkboxes = () => (
From fc9d817248b006232a19ce0194f20b4c1d5a7822 Mon Sep 17 00:00:00 2001 From: Michael Webber Date: Mon, 20 Jul 2026 16:09:55 -0700 Subject: [PATCH 03/74] fix(unity-bootstrap-theme): fixed linting error --- .../form-fields.examples.stories.js | 240 ++++++++++++++---- 1 file changed, 192 insertions(+), 48 deletions(-) diff --git a/packages/unity-bootstrap-theme/stories/atoms/form-fields/form-fields.examples.stories.js b/packages/unity-bootstrap-theme/stories/atoms/form-fields/form-fields.examples.stories.js index 7fa1b1256b..5d94cc8806 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/form-fields/form-fields.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/form-fields/form-fields.examples.stories.js @@ -118,7 +118,10 @@ export const TextInputs = () => ( data-ga-input-section="text label" /> - + + Success message
@@ -224,7 +227,10 @@ export const TextInputsVariousBackgrounds = () => ( value="Input text" /> - + + Success message
@@ -315,7 +321,10 @@ export const TextInputsVariousBackgrounds = () => ( value="Input text" /> - + + Success message @@ -406,7 +415,10 @@ export const TextInputsVariousBackgrounds = () => ( value="Input text" /> - + + Success message @@ -497,7 +509,10 @@ export const TextInputsVariousBackgrounds = () => ( value="Input text" /> - + + Success message @@ -618,7 +633,10 @@ export const Textareas = () => ( Agreeable content was entered. - + + Success message @@ -709,7 +727,10 @@ export const TextareasMultipleBackgrounds = () => ( id="successTextareaHelpWhite" className="form-textarea valid-feedback" > - + + Success message @@ -798,7 +819,10 @@ export const TextareasMultipleBackgrounds = () => ( id="successTextareaHelpGray1" className="form-textarea valid-feedback" > - + + Success message @@ -887,7 +911,10 @@ export const TextareasMultipleBackgrounds = () => ( id="successTextareaHelpGray2" className="form-textarea valid-feedback" > - + + Success message @@ -976,7 +1003,10 @@ export const TextareasMultipleBackgrounds = () => ( id="successTextareaHelpGray7" className="form-textarea valid-feedback" > - + + Success message @@ -1054,7 +1084,10 @@ export const Checkboxes = () => ( I accept - + + Success message @@ -1145,7 +1178,10 @@ export const Checkboxes = () => (
A Group of Valid Checkboxes - + + Success message
@@ -1447,7 +1483,10 @@ export const Radios = () => (
A Group of Valid Radios - + + Success message
@@ -1561,7 +1600,10 @@ export const CheckboxesAndRadiosWhiteBackground = () => ( I accept - + + Success message
@@ -1628,7 +1670,10 @@ export const CheckboxesAndRadiosWhiteBackground = () => (
A Group of Valid Checkboxes - + + Success message
@@ -1845,7 +1890,10 @@ export const CheckboxesAndRadiosWhiteBackground = () => (
A Group of Valid Radios - + + Success message
@@ -1936,7 +1984,10 @@ export const CheckboxesAndRadiosGray1Background = () => ( I accept - + + Success message
@@ -2003,7 +2054,10 @@ export const CheckboxesAndRadiosGray1Background = () => (
A Group of Valid Checkboxes - + + Success message
@@ -2220,7 +2274,10 @@ export const CheckboxesAndRadiosGray1Background = () => (
A Group of Valid Radios - + + Success message
@@ -2311,7 +2368,10 @@ export const CheckboxesAndRadiosGray2Background = () => ( I accept - + + Success message
@@ -2378,7 +2438,10 @@ export const CheckboxesAndRadiosGray2Background = () => (
A Group of Valid Checkboxes - + + Success message
@@ -2595,7 +2658,10 @@ export const CheckboxesAndRadiosGray2Background = () => (
A Group of Valid Radios - + + Success message
@@ -2686,7 +2752,10 @@ export const CheckboxesAndRadiosGray7Background = () => ( I accept - + + Success message
@@ -2753,7 +2822,10 @@ export const CheckboxesAndRadiosGray7Background = () => (
A Group of Valid Checkboxes - + + Success message
@@ -2970,7 +3042,10 @@ export const CheckboxesAndRadiosGray7Background = () => (
A Group of Valid Radios - + + Success message
@@ -3074,7 +3149,10 @@ export const Selects = () => ( - + + Success message
@@ -3388,7 +3466,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat value="Input text" /> - + + Success message
@@ -3449,7 +3530,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat id="successTextareaHelp" className="form-textarea valid-feedback" > - + + Success message
@@ -3489,7 +3573,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat I accept (required field) - + + Success message
@@ -3523,7 +3610,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat
A Group of Valid Checkboxes - + + Success message
@@ -3709,7 +3799,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat
A Group of Valid Radios - + + Success message
@@ -3868,7 +3961,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm value="Input text" /> - + + Success message
@@ -3925,7 +4021,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm id="successTextareaHelp" className="form-textarea valid-feedback" > - + + Success message
@@ -3965,7 +4064,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm I accept (required) - + + Success message
@@ -3999,7 +4101,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm
A Group of Valid Checkboxes - + + Success message
@@ -4185,7 +4290,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm
A Group of Valid Radios - + + Success message
@@ -4258,7 +4366,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm - + + Success message
@@ -4373,7 +4484,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat value="Input text" /> - + + Success message
@@ -4434,7 +4548,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat id="successTextareaHelp" className="form-textarea valid-feedback" > - + + Success message
@@ -4474,7 +4591,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat I accept (required field) - + + Success message @@ -4511,7 +4631,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat
A Group of Valid Checkboxes - + + Success message
@@ -4697,7 +4820,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidNotYetValidat
A Group of Valid Radios - + + Success message
@@ -4861,7 +4987,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm value="Input text" /> - + + Success message
@@ -4918,7 +5047,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm id="successTextareaHelp" className="form-textarea valid-feedback" > - + + Success message
@@ -4958,7 +5090,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm I accept (required) - + + Success message @@ -4995,7 +5130,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm
A Group of Valid Checkboxes - + + Success message
@@ -5181,7 +5319,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm
A Group of Valid Radios - + + Success message
@@ -5254,7 +5395,10 @@ export const KitchenSinkFormClientSideValidationWithInvalidAndValidValidatedForm - + + Success message
From 73b17727c8ed75408d8f449d3a5941c0f8c190cc Mon Sep 17 00:00:00 2001 From: Juan Pablo Mitriatti Date: Tue, 16 Jun 2026 14:03:37 -0300 Subject: [PATCH 04/74] feat(component-events): expose configurable noFeedText prop Add an optional noFeedText prop to CardsGridEvents and CardsListEvents, propagated through BaseFeed to FeedContainerProvider in place of the hardcoded "No events to show." string. Defaults to the original text so existing consumers are unaffected. - Add noFeedText to propTypes and BaseFeed propTypes - Add Storybook stories and unit tests for the override behavior - Bump package to 3.2.0 and update changelog --- packages/component-events/CHANGELOG.md | 7 ++++ packages/component-events/package.json | 2 +- .../src/components/CardsGridEvents/index.jsx | 10 ++++- .../CardsGridEvents/index.stories.jsx | 17 +++++++++ .../components/CardsGridEvents/index.test.jsx | 37 +++++++++++++++++++ .../src/components/CardsListEvents/index.jsx | 10 ++++- .../CardsListEvents/index.stories.jsx | 17 +++++++++ .../components/CardsListEvents/index.test.jsx | 37 +++++++++++++++++++ .../src/core/components/BaseFeed/index.jsx | 12 +++++- .../src/core/models/propTypes.js | 1 + .../src/core/utils/story-utils.js | 19 +++++++++- 11 files changed, 160 insertions(+), 9 deletions(-) diff --git a/packages/component-events/CHANGELOG.md b/packages/component-events/CHANGELOG.md index 1ea0223e13..3a05eb048c 100644 --- a/packages/component-events/CHANGELOG.md +++ b/packages/component-events/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@asu/component-events-v3.2.0](https://github.com/asu/asu-unity-stack/compare/@asu/component-events-v3.1.0...@asu/component-events-v3.2.0) (2026-06-16) + + +### Features + +* **component-events:** expose configurable `noFeedText` prop on `CardsGridEvents` and `CardsListEvents` (defaults to "No events to show." for backward compatibility) + # [@asu/component-events-v3.1.0](https://github.com/asu/asu-unity-stack/compare/@asu/component-events-v3.0.1...@asu/component-events-v3.1.0) (2026-04-03) diff --git a/packages/component-events/package.json b/packages/component-events/package.json index 8b7600ad61..3402038070 100644 --- a/packages/component-events/package.json +++ b/packages/component-events/package.json @@ -1,6 +1,6 @@ { "name": "@asu/component-events", - "version": "3.1.0", + "version": "3.2.0", "description": "ASU Events component", "main": "./dist/asuEvents.cjs.js", "browser": "./dist/asuEvents.umd.js", diff --git a/packages/component-events/src/components/CardsGridEvents/index.jsx b/packages/component-events/src/components/CardsGridEvents/index.jsx index 5ddff92e4c..ce3b3269ea 100644 --- a/packages/component-events/src/components/CardsGridEvents/index.jsx +++ b/packages/component-events/src/components/CardsGridEvents/index.jsx @@ -15,7 +15,13 @@ import { GridView } from "./GridView"; * @param {FeedType} props * @returns {JSX.Element} */ -const CardsGridEvents = ({ header, ctaButton, dataSource, maxItems }) => { +const CardsGridEvents = ({ + header, + ctaButton, + dataSource, + maxItems, + noFeedText = "No events to show.", +}) => { useEffect(() => { if (typeof window !== "undefined") { trackReactComponent({ @@ -33,7 +39,7 @@ const CardsGridEvents = ({ header, ctaButton, dataSource, maxItems }) => { }, []); return ( // Calling the high order component that fetch the data - + ); diff --git a/packages/component-events/src/components/CardsGridEvents/index.stories.jsx b/packages/component-events/src/components/CardsGridEvents/index.stories.jsx index ec930b008b..6c57519dda 100644 --- a/packages/component-events/src/components/CardsGridEvents/index.stories.jsx +++ b/packages/component-events/src/components/CardsGridEvents/index.stories.jsx @@ -75,3 +75,20 @@ WithNoHeader.args = { url: "/api/mocks/feeds-json", }, }; + +/** + * Demonstrates a custom "no results" message. The empty data source forces the + * feed to render the `noFeedText` instead of cards. + * @type {{ args: FeedType, parameters: object}} + */ +export const WithCustomNoFeedText = Template.bind({}); +WithCustomNoFeedText.args = { + header: { color: "dark", text: "Events Cards" }, + dataSource: { + url: "/api/mocks/empty-feeds-json", + }, + noFeedText: "There are no upcoming events at this time.", +}; +WithCustomNoFeedText.parameters = { + msw: { handlers: createMockHandlers({ empty: true }) }, +}; diff --git a/packages/component-events/src/components/CardsGridEvents/index.test.jsx b/packages/component-events/src/components/CardsGridEvents/index.test.jsx index 7b7790410f..2971c1fb98 100644 --- a/packages/component-events/src/components/CardsGridEvents/index.test.jsx +++ b/packages/component-events/src/components/CardsGridEvents/index.test.jsx @@ -1,6 +1,7 @@ // @ts-check import { render, cleanup, act } from "@testing-library/react"; import React from "react"; +import { vi } from "vitest"; import { CardsGridEvents } from "./index"; @@ -63,6 +64,42 @@ describe("#Cards Grid Events", () => { }); }); + describe("#No feed text", () => { + // Force an empty feed so the "no results" message is rendered + const emptyFeedArgs = { ...defaultArgs, dataSource: { url: "/empty" } }; + + beforeEach(() => { + vi.mocked(fetch).mockImplementation( + async () => + new Response(JSON.stringify({ nodes: [] }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }) + ); + }); + + afterEach(() => { + vi.mocked(fetch).mockReset(); + cleanup(); + }); + + it("should render the default no-feed text when noFeedText is not provided", async () => { + await renderCardsGridEvents(emptyFeedArgs); + expect( + await component.findByText("No events to show.") + ).toBeInTheDocument(); + }); + + it("should render a custom no-feed text when noFeedText is provided", async () => { + const customText = "Nothing happening right now."; + await renderCardsGridEvents({ ...emptyFeedArgs, noFeedText: customText }); + expect(await component.findByText(customText)).toBeInTheDocument(); + expect( + component.queryByText("No events to show.") + ).not.toBeInTheDocument(); + }); + }); + describe("#With No Header", () => { const noHeaderArgs = { ...defaultArgs, header: undefined }; beforeEach(async () => { diff --git a/packages/component-events/src/components/CardsListEvents/index.jsx b/packages/component-events/src/components/CardsListEvents/index.jsx index e1ca97f272..4af4e19772 100644 --- a/packages/component-events/src/components/CardsListEvents/index.jsx +++ b/packages/component-events/src/components/CardsListEvents/index.jsx @@ -15,7 +15,13 @@ import { ListView } from "./ListView"; * @param {FeedType} props * @returns {JSX.Element} */ -const CardsListEvents = ({ header, ctaButton, dataSource, maxItems }) => { +const CardsListEvents = ({ + header, + ctaButton, + dataSource, + maxItems, + noFeedText = "No events to show.", +}) => { useEffect(() => { if (typeof window !== "undefined") { trackReactComponent({ @@ -34,7 +40,7 @@ const CardsListEvents = ({ header, ctaButton, dataSource, maxItems }) => { return ( // Calling the high order component that fetch the data - + ); diff --git a/packages/component-events/src/components/CardsListEvents/index.stories.jsx b/packages/component-events/src/components/CardsListEvents/index.stories.jsx index 9e3ee25607..4739a8709b 100644 --- a/packages/component-events/src/components/CardsListEvents/index.stories.jsx +++ b/packages/component-events/src/components/CardsListEvents/index.stories.jsx @@ -78,3 +78,20 @@ WithNoHeader.args = { url: "/api/mocks/feeds-json", }, }; + +/** + * Demonstrates a custom "no results" message. The empty data source forces the + * feed to render the `noFeedText` instead of cards. + * @type {{ args: FeedType, parameters: object}} + */ +export const WithCustomNoFeedText = Template.bind({}); +WithCustomNoFeedText.args = { + header: { color: "dark", text: "Events List" }, + dataSource: { + url: "/api/mocks/empty-feeds-json", + }, + noFeedText: "There are no upcoming events at this time.", +}; +WithCustomNoFeedText.parameters = { + msw: { handlers: createMockHandlers({ empty: true }) }, +}; diff --git a/packages/component-events/src/components/CardsListEvents/index.test.jsx b/packages/component-events/src/components/CardsListEvents/index.test.jsx index 73d9f7bbd1..11e0ae0163 100644 --- a/packages/component-events/src/components/CardsListEvents/index.test.jsx +++ b/packages/component-events/src/components/CardsListEvents/index.test.jsx @@ -1,6 +1,7 @@ // @ts-check import { render, cleanup, act } from "@testing-library/react"; import React from "react"; +import { vi } from "vitest"; import { CardsListEvents } from "./index"; @@ -63,6 +64,42 @@ describe("#Cards List Events", () => { }); }); + describe("#No feed text", () => { + // Force an empty feed so the "no results" message is rendered + const emptyFeedArgs = { ...defaultArgs, dataSource: { url: "/empty" } }; + + beforeEach(() => { + vi.mocked(fetch).mockImplementation( + async () => + new Response(JSON.stringify({ nodes: [] }), { + status: 200, + headers: { "Content-Type": "application/json" }, + }) + ); + }); + + afterEach(() => { + vi.mocked(fetch).mockReset(); + cleanup(); + }); + + it("should render the default no-feed text when noFeedText is not provided", async () => { + await renderCardsListEvents(emptyFeedArgs); + expect( + await component.findByText("No events to show.") + ).toBeInTheDocument(); + }); + + it("should render a custom no-feed text when noFeedText is provided", async () => { + const customText = "Nothing happening right now."; + await renderCardsListEvents({ ...emptyFeedArgs, noFeedText: customText }); + expect(await component.findByText(customText)).toBeInTheDocument(); + expect( + component.queryByText("No events to show.") + ).not.toBeInTheDocument(); + }); + }); + describe("#With No Header", () => { const noHeaderArgs = { ...defaultArgs, header: undefined }; beforeEach(async () => { diff --git a/packages/component-events/src/core/components/BaseFeed/index.jsx b/packages/component-events/src/core/components/BaseFeed/index.jsx index 8a6d06f881..aa3763159f 100644 --- a/packages/component-events/src/core/components/BaseFeed/index.jsx +++ b/packages/component-events/src/core/components/BaseFeed/index.jsx @@ -14,7 +14,14 @@ import { defaultProps } from "../../constants/default-props"; import { filterData } from "../../services/dataManager"; import { transformData } from "../../transformers/data.transfromer"; -const BaseFeed = ({ children, header, ctaButton, dataSource, maxItems }) => ( +const BaseFeed = ({ + children, + header, + ctaButton, + dataSource, + maxItems, + noFeedText = "No events to show.", +}) => ( // Calling the unity-react-core component to fetch the data, transform it and filter it // We provide in the renderBody the view specified before in the parent component, recieved as "children" in this component. // We provide in the renderHeader the unity-react-core header, if it is desired to be shown @@ -36,7 +43,7 @@ const BaseFeed = ({ children, header, ctaButton, dataSource, maxItems }) => ( dataFilter={filterData} dataSource={dataSource} defaultProps={defaultProps} - noFeedText="No events to show." + noFeedText={noFeedText} maxItems={maxItems} /> ); @@ -46,6 +53,7 @@ BaseFeed.propTypes = { ctaButton: feedCtaButtonShape, dataSource: feedDataSourceShape, maxItems: PropTypes.number, + noFeedText: PropTypes.string, children: PropTypes.element, }; diff --git a/packages/component-events/src/core/models/propTypes.js b/packages/component-events/src/core/models/propTypes.js index 26cbb51e9a..c2f682304b 100644 --- a/packages/component-events/src/core/models/propTypes.js +++ b/packages/component-events/src/core/models/propTypes.js @@ -22,6 +22,7 @@ const eventsPropTypes = { ctaButton: layoutCTAPropTypes, dataSource: dataSourcePropTypes, maxItems: PropTypes.number, + noFeedText: PropTypes.string, }; export { diff --git a/packages/component-events/src/core/utils/story-utils.js b/packages/component-events/src/core/utils/story-utils.js index 74d6a33e00..d3db73b705 100644 --- a/packages/component-events/src/core/utils/story-utils.js +++ b/packages/component-events/src/core/utils/story-utils.js @@ -3,13 +3,28 @@ import { http, HttpResponse } from "msw"; import * as feeds from "../../../__mocks__/api/feeds.json"; -function createMockHandlers() { - return [ +/** + * @param {{ empty?: boolean }} [options] - When `empty` is true an additional + * handler is registered that returns a feed with no nodes, useful to showcase + * the configurable `noFeedText`. + */ +function createMockHandlers(options = {}) { + const handlers = [ http.get("/api/mocks/feeds-json", () => { // @ts-ignore return HttpResponse.json(feeds.default || feeds); }), ]; + + if (options.empty) { + handlers.push( + http.get("/api/mocks/empty-feeds-json", () => + HttpResponse.json({ nodes: [] }) + ) + ); + } + + return handlers; } export { createMockHandlers }; From 15e0d1453aea5e0e136ca6f40b9eda2a5e453da6 Mon Sep 17 00:00:00 2001 From: Juan Pablo Mitriatti Date: Wed, 8 Jul 2026 13:21:01 -0300 Subject: [PATCH 05/74] chore(component-events): drop manual version bump and changelog entry semantic-release generates these in the pipeline --- packages/component-events/CHANGELOG.md | 7 ------- packages/component-events/package.json | 2 +- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/packages/component-events/CHANGELOG.md b/packages/component-events/CHANGELOG.md index 3a05eb048c..1ea0223e13 100644 --- a/packages/component-events/CHANGELOG.md +++ b/packages/component-events/CHANGELOG.md @@ -1,10 +1,3 @@ -# [@asu/component-events-v3.2.0](https://github.com/asu/asu-unity-stack/compare/@asu/component-events-v3.1.0...@asu/component-events-v3.2.0) (2026-06-16) - - -### Features - -* **component-events:** expose configurable `noFeedText` prop on `CardsGridEvents` and `CardsListEvents` (defaults to "No events to show." for backward compatibility) - # [@asu/component-events-v3.1.0](https://github.com/asu/asu-unity-stack/compare/@asu/component-events-v3.0.1...@asu/component-events-v3.1.0) (2026-04-03) diff --git a/packages/component-events/package.json b/packages/component-events/package.json index 3402038070..8b7600ad61 100644 --- a/packages/component-events/package.json +++ b/packages/component-events/package.json @@ -1,6 +1,6 @@ { "name": "@asu/component-events", - "version": "3.2.0", + "version": "3.1.0", "description": "ASU Events component", "main": "./dist/asuEvents.cjs.js", "browser": "./dist/asuEvents.umd.js", From 45ff937c35d402868a2a8629ac832296191703d0 Mon Sep 17 00:00:00 2001 From: Juan Pablo Mitriatti Date: Wed, 8 Jul 2026 13:21:02 -0300 Subject: [PATCH 06/74] fix(component-events): fall back to default noFeedText when blank An empty or whitespace-only noFeedText now renders the default "No events to show." instead of an empty message --- .../src/components/CardsGridEvents/index.test.jsx | 7 +++++++ .../src/components/CardsListEvents/index.test.jsx | 7 +++++++ .../src/core/components/BaseFeed/index.jsx | 6 ++++-- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/component-events/src/components/CardsGridEvents/index.test.jsx b/packages/component-events/src/components/CardsGridEvents/index.test.jsx index 2971c1fb98..216f227aa5 100644 --- a/packages/component-events/src/components/CardsGridEvents/index.test.jsx +++ b/packages/component-events/src/components/CardsGridEvents/index.test.jsx @@ -98,6 +98,13 @@ describe("#Cards Grid Events", () => { component.queryByText("No events to show.") ).not.toBeInTheDocument(); }); + + it("should fall back to the default no-feed text when noFeedText is empty", async () => { + await renderCardsGridEvents({ ...emptyFeedArgs, noFeedText: "" }); + expect( + await component.findByText("No events to show.") + ).toBeInTheDocument(); + }); }); describe("#With No Header", () => { diff --git a/packages/component-events/src/components/CardsListEvents/index.test.jsx b/packages/component-events/src/components/CardsListEvents/index.test.jsx index 11e0ae0163..86a5ad5ffd 100644 --- a/packages/component-events/src/components/CardsListEvents/index.test.jsx +++ b/packages/component-events/src/components/CardsListEvents/index.test.jsx @@ -98,6 +98,13 @@ describe("#Cards List Events", () => { component.queryByText("No events to show.") ).not.toBeInTheDocument(); }); + + it("should fall back to the default no-feed text when noFeedText is empty", async () => { + await renderCardsListEvents({ ...emptyFeedArgs, noFeedText: "" }); + expect( + await component.findByText("No events to show.") + ).toBeInTheDocument(); + }); }); describe("#With No Header", () => { diff --git a/packages/component-events/src/core/components/BaseFeed/index.jsx b/packages/component-events/src/core/components/BaseFeed/index.jsx index aa3763159f..3141661b92 100644 --- a/packages/component-events/src/core/components/BaseFeed/index.jsx +++ b/packages/component-events/src/core/components/BaseFeed/index.jsx @@ -14,13 +14,15 @@ import { defaultProps } from "../../constants/default-props"; import { filterData } from "../../services/dataManager"; import { transformData } from "../../transformers/data.transfromer"; +const DEFAULT_NO_FEED_TEXT = "No events to show."; + const BaseFeed = ({ children, header, ctaButton, dataSource, maxItems, - noFeedText = "No events to show.", + noFeedText = DEFAULT_NO_FEED_TEXT, }) => ( // Calling the unity-react-core component to fetch the data, transform it and filter it // We provide in the renderBody the view specified before in the parent component, recieved as "children" in this component. @@ -43,7 +45,7 @@ const BaseFeed = ({ dataFilter={filterData} dataSource={dataSource} defaultProps={defaultProps} - noFeedText={noFeedText} + noFeedText={noFeedText.trim() ? noFeedText : DEFAULT_NO_FEED_TEXT} maxItems={maxItems} /> ); From 533a8cbca392210fd7eb82fab0b19086121a3348 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 14 Jul 2026 22:38:16 +0000 Subject: [PATCH 07/74] chore(release): 3.2.0 [skip ci] # [@asu/component-events-v3.2.0](https://github.com/asu/asu-unity-stack/compare/@asu/component-events-v3.1.0...@asu/component-events-v3.2.0) (2026-07-14) ### Bug Fixes * **component-events:** fall back to default noFeedText when blank ([e85d4da](https://github.com/asu/asu-unity-stack/commit/e85d4dacd9d7ee6138805fd505ceabca737f3aeb)) ### Features * **component-events:** expose configurable noFeedText prop ([9cac3e9](https://github.com/asu/asu-unity-stack/commit/9cac3e912e1b0bc4c573f8e72378b1d147b0bce8)) --- packages/component-events/CHANGELOG.md | 12 ++++++++++++ packages/component-events/package.json | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/component-events/CHANGELOG.md b/packages/component-events/CHANGELOG.md index 1ea0223e13..0b32c53944 100644 --- a/packages/component-events/CHANGELOG.md +++ b/packages/component-events/CHANGELOG.md @@ -1,3 +1,15 @@ +# [@asu/component-events-v3.2.0](https://github.com/asu/asu-unity-stack/compare/@asu/component-events-v3.1.0...@asu/component-events-v3.2.0) (2026-07-14) + + +### Bug Fixes + +* **component-events:** fall back to default noFeedText when blank ([e85d4da](https://github.com/asu/asu-unity-stack/commit/e85d4dacd9d7ee6138805fd505ceabca737f3aeb)) + + +### Features + +* **component-events:** expose configurable noFeedText prop ([9cac3e9](https://github.com/asu/asu-unity-stack/commit/9cac3e912e1b0bc4c573f8e72378b1d147b0bce8)) + # [@asu/component-events-v3.1.0](https://github.com/asu/asu-unity-stack/compare/@asu/component-events-v3.0.1...@asu/component-events-v3.1.0) (2026-04-03) diff --git a/packages/component-events/package.json b/packages/component-events/package.json index 8b7600ad61..3402038070 100644 --- a/packages/component-events/package.json +++ b/packages/component-events/package.json @@ -1,6 +1,6 @@ { "name": "@asu/component-events", - "version": "3.1.0", + "version": "3.2.0", "description": "ASU Events component", "main": "./dist/asuEvents.cjs.js", "browser": "./dist/asuEvents.umd.js", From bc5ce60355fb671f3940e24acacf609372d5393a Mon Sep 17 00:00:00 2001 From: Juan Pablo Mitriatti Date: Fri, 10 Jul 2026 13:46:55 -0300 Subject: [PATCH 08/74] fix(unity-bootstrap-theme): keep hero CTA inside the hero box below md breakpoint --- .../src/scss/extends/_heroes.scss | 43 ++++++++++++------- 1 file changed, 27 insertions(+), 16 deletions(-) diff --git a/packages/unity-bootstrap-theme/src/scss/extends/_heroes.scss b/packages/unity-bootstrap-theme/src/scss/extends/_heroes.scss index c29965c6a4..bdc5d0aa4a 100644 --- a/packages/unity-bootstrap-theme/src/scss/extends/_heroes.scss +++ b/packages/unity-bootstrap-theme/src/scss/extends/_heroes.scss @@ -213,7 +213,7 @@ div[class^="uds-hero"] { div.uds-hero-sm { min-height: $uds-size-spacing-32; - grid-template-rows: 1fr auto auto 3rem; + grid-template-rows: 1fr auto auto auto 3rem; h1, a.btn, @@ -251,16 +251,12 @@ div.uds-hero-sm { min-height: $uds-size-spacing-32; } } - - > a.btn { - top: 15rem; - } } div.uds-hero-md { min-height: 21rem; // by design, the hero-md has no content on mobile, change grid template rows and hide .content - grid-template-rows: 1fr auto auto 3rem; + grid-template-rows: 1fr auto auto auto 3rem; .content { display: none; @@ -270,18 +266,12 @@ div.uds-hero-md { .btn-row { flex-wrap: wrap; align-self: flex-start; - top: 20rem; } .hero-overlay { min-height: 21rem; } } - - // TODO: Remove, fallback for buttons without btn-row - > a.btn { - top: 20rem; - } } div.uds-hero-lg { @@ -307,7 +297,6 @@ div.uds-hero-lg { .btn-row { align-self: flex-start; - top: 31rem; } .hero-overlay { @@ -319,10 +308,30 @@ div.uds-hero-lg { &.uds-video-hero .video-hero-controls { display: none; } +} - // TODO: Remove, fallback for buttons without btn-row - > a.btn { - top: 31rem; +// WS2-2950: below the md breakpoint, hero CTAs flow in their own auto grid row +// instead of hanging at a fixed absolute offset that overflows the hero box. +// Scoped to media-breakpoint-down(md) so tablet/desktop styles are untouched. +@include media-breakpoint-down(md) { + div.uds-hero-md.has-btn-row .btn-row, + div.uds-hero-lg.has-btn-row .btn-row { + grid-row: 4; + position: relative; + top: auto; + margin-top: 1rem; + } + + div.uds-hero-sm, + div.uds-hero-md, + div.uds-hero-lg { + // TODO: Remove, fallback for buttons without btn-row + > a.btn { + position: relative; + top: auto; + grid-row: 4; + margin-top: 1rem; + } } } @@ -384,6 +393,8 @@ div.uds-hero-lg { position: relative; top: auto; grid-row: 4; + margin-top: 1rem; + margin-bottom: 1rem; } &.has-btn-row { From cbb437de2cf2b35f02e235994acea0fb11233792 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 14 Jul 2026 23:17:54 +0000 Subject: [PATCH 09/74] chore(release): 1.39.6 [skip ci] # [@asu/unity-bootstrap-theme-v1.39.6](https://github.com/ASU/asu-unity-stack/compare/@asu/unity-bootstrap-theme-v1.39.5...@asu/unity-bootstrap-theme-v1.39.6) (2026-07-14) ### Bug Fixes * **unity-bootstrap-theme:** keep hero CTA inside the hero box below md breakpoint ([0057cd4](https://github.com/ASU/asu-unity-stack/commit/0057cd44f9919319ce6d07033d0b4f05950609bb)) --- packages/unity-bootstrap-theme/CHANGELOG.md | 7 +++++++ packages/unity-bootstrap-theme/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/unity-bootstrap-theme/CHANGELOG.md b/packages/unity-bootstrap-theme/CHANGELOG.md index da442db104..46fbc183c8 100644 --- a/packages/unity-bootstrap-theme/CHANGELOG.md +++ b/packages/unity-bootstrap-theme/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@asu/unity-bootstrap-theme-v1.39.6](https://github.com/ASU/asu-unity-stack/compare/@asu/unity-bootstrap-theme-v1.39.5...@asu/unity-bootstrap-theme-v1.39.6) (2026-07-14) + + +### Bug Fixes + +* **unity-bootstrap-theme:** keep hero CTA inside the hero box below md breakpoint ([0057cd4](https://github.com/ASU/asu-unity-stack/commit/0057cd44f9919319ce6d07033d0b4f05950609bb)) + # [@asu/unity-bootstrap-theme-v1.39.5](https://github.com/ASU/asu-unity-stack/compare/@asu/unity-bootstrap-theme-v1.39.4...@asu/unity-bootstrap-theme-v1.39.5) (2026-07-07) diff --git a/packages/unity-bootstrap-theme/package.json b/packages/unity-bootstrap-theme/package.json index f950524716..707f29bb1c 100644 --- a/packages/unity-bootstrap-theme/package.json +++ b/packages/unity-bootstrap-theme/package.json @@ -1,6 +1,6 @@ { "name": "@asu/unity-bootstrap-theme", - "version": "1.39.5", + "version": "1.39.6", "description": "Please see @asu/unity-react-core for up-to-date stories and examples.\nBase UI theme for ASU Web Standards 2.0 developed with Bootstrap 5", "homepage": "https://github.com/ASU/asu-unity-stack#readme", "license": "MIT", From 778e23346f36cacdeb58cb53a39e77b8804ff962 Mon Sep 17 00:00:00 2001 From: Juan Pablo Mitriatti Date: Sun, 12 Jul 2026 20:52:21 -0300 Subject: [PATCH 10/74] fix(unity-bootstrap-theme): add bottom spacing to hero-sm title when no CTA is present On md+ viewports, grid row 4 of uds-hero-sm collapses to 0 when the hero has no CTA button, leaving the h1 flush against the bottom edge. Carry the bottom spacing on the h1 instead, and reset it to 0 when a CTA is present (via .has-btn-row or the :has(> a.btn) fallback). --- .../src/scss/extends/_heroes.scss | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/unity-bootstrap-theme/src/scss/extends/_heroes.scss b/packages/unity-bootstrap-theme/src/scss/extends/_heroes.scss index bdc5d0aa4a..693028ab65 100644 --- a/packages/unity-bootstrap-theme/src/scss/extends/_heroes.scss +++ b/packages/unity-bootstrap-theme/src/scss/extends/_heroes.scss @@ -389,6 +389,17 @@ div.uds-hero-lg { div.uds-hero-sm { grid-template-rows: 0px 1fr auto auto; + // Row 4 collapses to 0 when the hero has no CTA, leaving the h1 flush + // against the bottom edge — carry the bottom spacing on the h1 instead. + h1 { + margin-bottom: $uds-size-spacing-4; + } + + &.has-btn-row h1, + &:has(> a.btn) h1 { + margin-bottom: 0; + } + > a.btn { position: relative; top: auto; From 41da0737acf2ca4596551bd5f37ae19e3b9b5ef1 Mon Sep 17 00:00:00 2001 From: semantic-release-bot Date: Tue, 14 Jul 2026 23:27:47 +0000 Subject: [PATCH 11/74] chore(release): 1.39.7 [skip ci] # [@asu/unity-bootstrap-theme-v1.39.7](https://github.com/ASU/asu-unity-stack/compare/@asu/unity-bootstrap-theme-v1.39.6...@asu/unity-bootstrap-theme-v1.39.7) (2026-07-14) ### Bug Fixes * **unity-bootstrap-theme:** add bottom spacing to hero-sm title when no CTA is present ([51ebf35](https://github.com/ASU/asu-unity-stack/commit/51ebf358d8e487b02b247e95c7542829b0fd7ff0)) --- packages/unity-bootstrap-theme/CHANGELOG.md | 7 +++++++ packages/unity-bootstrap-theme/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/unity-bootstrap-theme/CHANGELOG.md b/packages/unity-bootstrap-theme/CHANGELOG.md index 46fbc183c8..4ea9440db1 100644 --- a/packages/unity-bootstrap-theme/CHANGELOG.md +++ b/packages/unity-bootstrap-theme/CHANGELOG.md @@ -1,3 +1,10 @@ +# [@asu/unity-bootstrap-theme-v1.39.7](https://github.com/ASU/asu-unity-stack/compare/@asu/unity-bootstrap-theme-v1.39.6...@asu/unity-bootstrap-theme-v1.39.7) (2026-07-14) + + +### Bug Fixes + +* **unity-bootstrap-theme:** add bottom spacing to hero-sm title when no CTA is present ([51ebf35](https://github.com/ASU/asu-unity-stack/commit/51ebf358d8e487b02b247e95c7542829b0fd7ff0)) + # [@asu/unity-bootstrap-theme-v1.39.6](https://github.com/ASU/asu-unity-stack/compare/@asu/unity-bootstrap-theme-v1.39.5...@asu/unity-bootstrap-theme-v1.39.6) (2026-07-14) diff --git a/packages/unity-bootstrap-theme/package.json b/packages/unity-bootstrap-theme/package.json index 707f29bb1c..637bfce168 100644 --- a/packages/unity-bootstrap-theme/package.json +++ b/packages/unity-bootstrap-theme/package.json @@ -1,6 +1,6 @@ { "name": "@asu/unity-bootstrap-theme", - "version": "1.39.6", + "version": "1.39.7", "description": "Please see @asu/unity-react-core for up-to-date stories and examples.\nBase UI theme for ASU Web Standards 2.0 developed with Bootstrap 5", "homepage": "https://github.com/ASU/asu-unity-stack#readme", "license": "MIT", From 2fe46d6ef68aae8a1e72b5df59eb5689fce8700f Mon Sep 17 00:00:00 2001 From: david ornelas Date: Fri, 19 Dec 2025 14:14:51 -0700 Subject: [PATCH 12/74] docs(unity-react-core): add todos for tabbed panels --- .../TABBED_PANELS_REDESIGN_TODO.md | 411 ++++++++++++++++++ 1 file changed, 411 insertions(+) create mode 100644 packages/unity-react-core/src/components/TabbedPanels/TABBED_PANELS_REDESIGN_TODO.md diff --git a/packages/unity-react-core/src/components/TabbedPanels/TABBED_PANELS_REDESIGN_TODO.md b/packages/unity-react-core/src/components/TabbedPanels/TABBED_PANELS_REDESIGN_TODO.md new file mode 100644 index 0000000000..6869b1f4aa --- /dev/null +++ b/packages/unity-react-core/src/components/TabbedPanels/TABBED_PANELS_REDESIGN_TODO.md @@ -0,0 +1,411 @@ +# Tabbed Panels V3 Redesign - Implementation TODO + +## Overview +This document outlines the tasks needed to update the TabbedPanels component to match the new Figma design specifications. The redesign introduces a "More" dropdown for overflow tabs, updated styling, and improved responsive behavior. + +**Figma Link**: https://www.figma.com/design/fCGmbJDE7gn87tqezmv815/241121-Tabbed-panel-updates?node-id=11-884&p=f&m=dev + +**Related Ticket**: UDS-1664 + +--- + +## Key Design Changes + +### 1. Tab Display and Overflow Management +- **OLD**: Horizontal scrolling with arrow navigation buttons +- **NEW**: Tabs display inline until they overflow, then remaining tabs move into a "More" dropdown + +### 2. Visual Specifications +- **Tab height**: 41px (including 8px bottom border for active tab) +- **Tab padding**: 0.5rem horizontal, 0.5rem top +- **Active tab border**: 8px solid maroon (#8c1d40) on bottom +- **Tab spacing**: 8px gap between tabs +- **Max width**: 690px (7 columns) for content area +- **Min width**: 282px (3 columns) for mobile + +### 3. More Dropdown Behavior +- Appears as the last tab when tabs overflow available horizontal space +- Label: "More" with chevron-down icon +- On click/active: Chevron rotates 180° (pointing up) +- Dropdown opens below with white background and gray border +- Lists remaining tabs that don't fit in horizontal space +- Dropdown width: 180px (2 columns) +- Dropdown padding: 2rem +- Tab limit: Maximum 9 tabs recommended + +--- + +## Implementation Tasks + +### Phase 1: Component Architecture Updates + +#### TODO 1.1: Add Overflow Detection Logic +**File**: `TabbedPanels.jsx` + +- [ ] Add a new state variable `overflowTabs` to track which tabs don't fit +- [ ] Add a new state variable `visibleTabs` to track which tabs are displayed inline +- [ ] Create a `useEffect` hook that runs on mount and window resize +- [ ] In this effect, measure the available width of the tab container +- [ ] Calculate the cumulative width of each tab as you iterate through them +- [ ] When cumulative width + "More" button width exceeds container width, mark remaining tabs as overflow +- [ ] Update both `visibleTabs` and `overflowTabs` state arrays + +**Hint**: Use `getBoundingClientRect()` to measure element widths. Remember to account for the "More" dropdown width (approximately 83px) in your calculations. + +#### TODO 1.2: Create More Dropdown Component +**File**: Create new file `components/MoreDropdown.jsx` + +- [ ] Create a new component called `MoreDropdown` +- [ ] Add props: `overflowTabs` (array), `activeTabID` (string), `selectTab` (function), `gaData` (object) +- [ ] Add local state `isOpen` (boolean) to track dropdown open/closed state +- [ ] Render a button with: + - Text: "More" + - Icon: FontAwesome chevron-down (use className `fas fa-chevron-down`) + - Rotate icon 180° when `isOpen` is true + - Add onClick handler to toggle `isOpen` +- [ ] When `isOpen` is true, render a dropdown menu positioned absolutely below the button +- [ ] The dropdown should contain a list of the tabs from `overflowTabs` array +- [ ] Each dropdown item should be clickable and call `selectTab` when clicked +- [ ] Wrap in `GaEventWrapper` for analytics tracking +- [ ] Add click-outside detection to close dropdown (use `useEffect` with document click listener) + +**Styling Notes**: +- Button should have 0.5rem padding top, 0.25rem padding horizontal +- Active state should show 8px maroon border on bottom +- Dropdown should have white background, gray border (#d0d0d0) +- Dropdown should be 180px wide with 2rem padding +- Position dropdown using `position: absolute; top: 41px; right: 0;` + +#### TODO 1.3: Update TabHeader Export +**File**: `components/index.js` + +- [ ] Export the new `MoreDropdown` component +- [ ] Ensure all existing exports remain unchanged + +--- + +### Phase 2: Update Main TabbedPanels Component + +#### TODO 2.1: Integrate Overflow Detection +**File**: `TabbedPanels.jsx` + +- [ ] Import the new `MoreDropdown` component +- [ ] Add ref for measuring tab widths: `const tabRefs = useRef({})` +- [ ] Create a function `calculateOverflow()` that determines which tabs overflow +- [ ] Call `calculateOverflow()` in a `useEffect` that depends on `children` and window resize +- [ ] Store results in state variables created in TODO 1.1 + +#### TODO 2.2: Update Tab Rendering Logic +**File**: `TabbedPanels.jsx` (in the return statement) + +- [ ] Filter `childrenArray` to only map over `visibleTabs` for inline display +- [ ] After the map of visible tabs, conditionally render the `MoreDropdown` component +- [ ] Only render `MoreDropdown` if `overflowTabs.length > 0` +- [ ] Pass the necessary props to `MoreDropdown` (overflowTabs, activeTabID, selectTab function, gaData) + +#### TODO 2.3: Remove/Update NavControls +**File**: `TabbedPanels.jsx` + +- [ ] Remove the `` component from the render (no longer needed with dropdown approach) +- [ ] Remove related state variables: `scrollLeft`, `scrollableWidth` +- [ ] Remove the `slideNav` function (no longer needed) +- [ ] Remove scroll-related event listeners and useEffect hooks +- [ ] Keep the horizontal scrolling capability on the tab container for mobile/smaller screens + +**Note**: Some scroll behavior may still be useful for very narrow viewports where even a few tabs don't fit. Consider keeping the smooth scroll on the container but removing the arrow buttons. + +--- + +### Phase 3: Update Styles + +#### TODO 3.1: Update SCSS Variables +**File**: `packages/unity-bootstrap-theme/src/scss/variables/_tabbed-panels.scss` + +- [ ] Verify the following variables exist (add if missing): + - Tab height: 41px + - Tab padding: 0.5rem horizontal, 0.5rem top + - Active border width: 8px + - Tab gap/spacing: 8px + - Font size: 16px (should use body text variable) + - Dropdown width: 180px + - Dropdown padding: 2rem + +#### TODO 3.2: Update Main Tabbed Panels Styles +**File**: `packages/unity-bootstrap-theme/src/scss/extends/_tabbed-panels.scss` + +- [ ] Update `.nav-link` styles: + - Height should be 41px total (including padding and border) + - Padding: 0.5rem top, 0.5rem left/right, 0 bottom + - Margin: 0 0.5rem 0 0 (0.5rem right margin for gap) + - Font-size: 16px + - Font-weight: 400 (regular, not bold) + - Line-height: 24px +- [ ] Update `.nav-link.active` styles: + - Border-bottom: 8px solid maroon + - Padding adjustment to account for 8px border +- [ ] Remove styles related to scroll controls (`.scroll-control-prev`, `.scroll-control-next`) +- [ ] Remove gradient backgrounds used for scroll buttons + +#### TODO 3.3: Add More Dropdown Styles +**File**: `packages/unity-bootstrap-theme/src/scss/extends/_tabbed-panels.scss` + +- [ ] Add new class `.more-dropdown-button`: + - Padding: 0.5rem top, 0.25rem left/right + - Display: flex + - Align-items: center + - Gap: 8px (for spacing between text and icon) + - Border-bottom: 8px solid transparent (for inactive state) + - Background: #fafafa (gray-7) +- [ ] Add class `.more-dropdown-button.active`: + - Border-bottom: 8px solid maroon (same as active tab) +- [ ] Add class `.more-dropdown-icon`: + - Transition: transform 0.3s ease +- [ ] Add class `.more-dropdown-icon.open`: + - Transform: rotate(180deg) +- [ ] Add class `.more-dropdown-menu`: + - Position: absolute + - Top: 41px + - Right: 0 + - Width: 180px + - Background: white + - Border: 1px solid #d0d0d0 + - Padding: 2rem + - Display: flex + - Flex-direction: column + - Gap: 16px + - Z-index: 1000 + - Box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) (optional for depth) +- [ ] Add class `.more-dropdown-item`: + - Font-size: 16px + - Line-height: 24px + - Color: #191919 + - Text-decoration: none + - Cursor: pointer + - Padding: 0 (items should not have extra padding) +- [ ] Add hover state `.more-dropdown-item:hover`: + - Color: maroon + - Text-decoration: underline + +#### TODO 3.4: Update Dark Theme Styles +**File**: `packages/unity-bootstrap-theme/src/scss/extends/_tabbed-panels.scss` + +- [ ] Update `.uds-tabbed-panels-dark` section to match new specifications +- [ ] Active tabs should use gold (#ffc627) instead of maroon for border +- [ ] Ensure More dropdown works on dark backgrounds (may need different dropdown background) + +--- + +### Phase 4: Component Props and PropTypes + +#### TODO 4.1: Update PropTypes +**File**: `TabbedPanels.jsx` + +- [ ] Review and ensure all PropTypes are accurate +- [ ] Add PropTypes for any new state variables if passed as props +- [ ] Update documentation comments if needed + +#### TODO 4.2: Update Storybook Stories +**File**: `TabbedPanels.stories.jsx` + +- [ ] Create a new story called "With Overflow Tabs" that includes 10+ tabs to demonstrate the More dropdown +- [ ] Create a story showing responsive behavior at different widths +- [ ] Update existing stories to ensure they still work correctly +- [ ] Add story demonstrating dark background variant with overflow + +--- + +### Phase 5: Accessibility + +#### TODO 5.1: Add Keyboard Navigation for Dropdown +**File**: `components/MoreDropdown.jsx` + +- [ ] Add keyboard handler to open dropdown with Enter or Space key +- [ ] Add Escape key handler to close dropdown +- [ ] Add Tab key handling to navigate through dropdown items +- [ ] Ensure focus management works correctly (focus should move to dropdown when opened) + +#### TODO 5.2: Add ARIA Attributes +**File**: `components/MoreDropdown.jsx` + +- [ ] Add `aria-expanded` attribute to More button (true when open, false when closed) +- [ ] Add `aria-haspopup="true"` to More button +- [ ] Add `aria-label` to More button: "More tabs" +- [ ] Add `role="menu"` to dropdown container +- [ ] Add `role="menuitem"` to each dropdown item +- [ ] Add `aria-controls` linking button to dropdown menu ID + +#### TODO 5.3: Update Existing Tab ARIA +**File**: `components/TabHeader.jsx` + +- [ ] Verify `aria-selected` is still correctly applied +- [ ] Verify `role="tab"` is present +- [ ] Ensure keyboard navigation (left/right arrows) still works for visible tabs +- [ ] Test that tab order makes sense with new More dropdown + +--- + +### Phase 6: Testing and Edge Cases + +#### TODO 6.1: Manual Testing Checklist + +- [ ] Test with 3 tabs (should all be visible, no More dropdown) +- [ ] Test with 9 tabs (some should be in dropdown) +- [ ] Test with 15+ tabs (ensure dropdown scrolls if needed) +- [ ] Test responsive behavior from 282px to 1200px width +- [ ] Test tab switching when active tab is in dropdown (should still work) +- [ ] Test dark background variant +- [ ] Test keyboard navigation (Tab, Enter, Escape, Arrow keys) +- [ ] Test with screen reader (test aria attributes work correctly) +- [ ] Test that clicking outside dropdown closes it +- [ ] Test that clicking a tab in dropdown closes dropdown and switches tabs + +#### TODO 6.2: Browser Testing + +- [ ] Test in Chrome +- [ ] Test in Firefox +- [ ] Test in Safari +- [ ] Test in Edge +- [ ] Test on mobile devices (iOS Safari, Android Chrome) + +#### TODO 6.3: Update Unit Tests +**File**: `TabbedPanels.test.jsx` + +- [ ] Add test for overflow detection logic +- [ ] Add test that More dropdown appears when tabs overflow +- [ ] Add test that clicking More dropdown opens/closes it +- [ ] Add test that selecting tab from dropdown works correctly +- [ ] Add test for keyboard navigation in dropdown +- [ ] Update any existing tests that may be affected by changes +- [ ] Ensure all existing tests still pass + +--- + +### Phase 7: Documentation + +#### TODO 7.1: Update Component Documentation +**File**: `TabbedPanels.jsx` (JSDoc comments) + +- [ ] Update main component description to mention More dropdown behavior +- [ ] Document the maximum recommended tabs (9) +- [ ] Add notes about responsive behavior +- [ ] Document any new props + +#### TODO 7.2: Update README (if exists) +**File**: Look for `README.md` in TabbedPanels folder + +- [ ] Update usage examples +- [ ] Add screenshots or animated GIFs showing the More dropdown +- [ ] Document the new behavior +- [ ] Add troubleshooting section if needed + +--- + +## Implementation Tips + +### For Overflow Detection +```javascript +// Example approach for measuring tab widths +const calculateOverflow = () => { + const container = headerTabs.current; + if (!container) return; + + const containerWidth = container.offsetWidth; + const moreButtonWidth = 83; // Approximate width of More button + let currentWidth = 0; + const visible = []; + const overflow = []; + + childrenArray.forEach((child, index) => { + const tabElement = headerTabItems.current[child.props.id]; + if (tabElement) { + const tabWidth = tabElement.offsetWidth + 8; // 8px for margin + if (currentWidth + tabWidth + moreButtonWidth < containerWidth) { + visible.push(child); + currentWidth += tabWidth; + } else { + overflow.push(child); + } + } + }); + + setVisibleTabs(visible); + setOverflowTabs(overflow); +}; +``` + +### For Click Outside Detection +```javascript +// In MoreDropdown component +useEffect(() => { + const handleClickOutside = (event) => { + if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { + setIsOpen(false); + } + }; + + if (isOpen) { + document.addEventListener('mousedown', handleClickOutside); + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; +}, [isOpen]); +``` + +### For Icon Rotation +```scss +// In your SCSS file +.more-dropdown-icon { + transition: transform 0.3s ease; + + &.open { + transform: rotate(180deg); + } +} +``` + +--- + +## Questions or Blockers? + +If you encounter any of the following, reach out to your tech lead: +1. Difficulty measuring tab widths accurately +2. Issues with positioning the dropdown correctly across different screen sizes +3. Complex accessibility requirements that aren't clear +4. Breaking changes to existing functionality that weren't anticipated +5. Performance issues with the overflow calculation on resize + +--- + +## Definition of Done + +- [ ] All TODO items marked as complete +- [ ] Component matches Figma design pixel-perfect (within reason) +- [ ] All tests passing +- [ ] Accessibility audit passed (keyboard nav, screen reader, ARIA) +- [ ] Documentation updated +- [ ] Code reviewed and approved +- [ ] No console errors or warnings +- [ ] Tested in all required browsers +- [ ] Storybook stories updated and working + +--- + +## Related Files + +### Files to Modify: +- `packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.jsx` +- `packages/unity-react-core/src/components/TabbedPanels/components/TabHeader.jsx` +- `packages/unity-react-core/src/components/TabbedPanels/components/index.js` +- `packages/unity-bootstrap-theme/src/scss/extends/_tabbed-panels.scss` +- `packages/unity-bootstrap-theme/src/scss/variables/_tabbed-panels.scss` +- `packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.stories.jsx` +- `packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.test.jsx` + +### Files to Create: +- `packages/unity-react-core/src/components/TabbedPanels/components/MoreDropdown.jsx` + +### Files to Remove/Deprecate: +- `packages/unity-react-core/src/components/TabbedPanels/components/NavControls.jsx` (may be deprecated) +- `packages/unity-react-core/src/components/TabbedPanels/components/NavControls.styles.js` (may be deprecated) From 49c79f4b8e450be938431fdc21d48a8833ae0858 Mon Sep 17 00:00:00 2001 From: Ojas Atkar Date: Mon, 22 Dec 2025 13:50:41 -0500 Subject: [PATCH 13/74] fix(unity-react-core): toDo 1.1 --- .../components/TabbedPanels/TabbedPanels.jsx | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.jsx b/packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.jsx index abf43081d5..343513582c 100644 --- a/packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.jsx +++ b/packages/unity-react-core/src/components/TabbedPanels/TabbedPanels.jsx @@ -75,6 +75,45 @@ const TabbedPanels = ({ const [scrollLeft, setScrollLeft] = useState(0); const [scrollableWidth, setScrollableWidth] = useState(); + // ----------------------------- + // TODO 1.1 + // ----------------------------- + const [overflowTabs, setOverflowTabs] = useState([]); + const [visibleTabs, setVisibleTabs] = useState(childrenArray.map((c) => c.props.id)); + + useEffect(() => { + const More_Width = 83; + const Tab_Gap = 8; + + const container = headerTabs.current; + const tabIDs = childrenArray.map((c) => c.props.id); + const widths = tabIDs.map((id) => { + const DOMelement = headerTabItems.current?.[id]; + return DOMelement ? DOMelement.getBoundingClientRect().width : 80; + }); + + const remainingWidth = container.clientWidth; + let spaceUsed = 0; + for (let i = 0; i < tabIDs.length; i++) { + const curWidth = widths[i]; + + if (curWidth + spaceUsed + More_Width > remainingWidth) { + for (let j = i; j < tabIDs.length; j++) { + overflowTabs.push(tabIDs[j]); + } + break; + } + + visibleTabs.push(tabIDs[i]); + spaceUsed += curWidth + Tab_Gap; + } + + setVisibleTabs(visibleTabs); + setOverflowTabs(overflowTabs); + setScrollableWidth(container.scrollWidth - container.clientWidth); + + }); + const handleResize = useCallback(() => { setScrollableWidth( headerTabs.current?.scrollWidth - headerTabs.current?.offsetWidth From e61aadb27e7588cddfd215124e915a1fa319b228 Mon Sep 17 00:00:00 2001 From: Ojas Atkar Date: Fri, 2 Jan 2026 17:39:14 -0500 Subject: [PATCH 14/74] fix(unity-react-core): to do 1.2 --- .../TabbedPanels/components/MoreDropdown.jsx | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 packages/unity-react-core/src/components/TabbedPanels/components/MoreDropdown.jsx diff --git a/packages/unity-react-core/src/components/TabbedPanels/components/MoreDropdown.jsx b/packages/unity-react-core/src/components/TabbedPanels/components/MoreDropdown.jsx new file mode 100644 index 0000000000..1e45e5d6a1 --- /dev/null +++ b/packages/unity-react-core/src/components/TabbedPanels/components/MoreDropdown.jsx @@ -0,0 +1,189 @@ +import PropTypes from "prop-types"; +import React,{useEffect, useRef, useState} from "react"; + +import { GaEventWrapper } from "../../GaEventWrapper/GaEventWrapper"; +import { root } from "postcss"; +import { title } from "process"; + + // ----------------------------- + // TODO 1.2 + // ----------------------------- + + const MoreDropdown = ({ overflowTabs, activeTabID, selectTab, gaData }) => { + const [isOpen, setIsOpen] = useState(false); + const rootRef = useRef(null); + const items = overflowTabs.map((str) => { + if (typeof str === "string") { + return { + id: str, + title: str, + }; + } + else { + return { + id: str.id, + title: str.title || str.id + }; + } + } + ); + + const toggle = (event) => { + if (event) { + event.preventDefault(); + } + setIsOpen((isCurOpen) => !isCurOpen); + }; + + useEffect(() => { + function onDocClick(event) { + const clickedInside = rootRef.current.contains(event.target); + + if (clickedInside) { + return; + } + else { + setIsOpen(false); + } + } + + document.addEventListener("mousedown", onDocClick); + + return () => { + document.removeEventListener("mousedown", onDocClick); + }; + }, []); + + useEffect(() => { + const onKey = (event) => { + if (event.key === "Escape") setIsOpen(false); + }; + document.addEventListener("keydown", onKey); + return () => document.removeEventListener("keydown", onKey); + }, []); + + const onItemClick = (event, id, title) => { + event.preventDefault(); + setIsOpen(false); + if (typeof selectTab === "function") { + selectTab(event, id, title); + } + }; + + if (items.length === 0) { + return null; + } + + return ( +