From a0918e4fd74fb26d75ed071591b6eeae984a0c10 Mon Sep 17 00:00:00 2001 From: david ornelas Date: Mon, 22 Dec 2025 15:03:32 -0700 Subject: [PATCH 1/4] feat(unity-bootstrap-theme): add new button styles --- .../src/scss/extends/_buttons.scss | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/packages/unity-bootstrap-theme/src/scss/extends/_buttons.scss b/packages/unity-bootstrap-theme/src/scss/extends/_buttons.scss index 97cb1a53f..e0734e3fe 100644 --- a/packages/unity-bootstrap-theme/src/scss/extends/_buttons.scss +++ b/packages/unity-bootstrap-theme/src/scss/extends/_buttons.scss @@ -123,6 +123,131 @@ } } +// Button sizes +.btn-small { + font-size: 0.75rem; + padding: 0.25rem 0.5rem; +} +.btn-medium { + font-size: 0.875rem; + padding: 0.5rem 1rem; +} +.btn-large { + font-size: 1rem; + padding: 0.75rem 1.5rem; +} + +// Button color variants map +$button-colors: ( + 'gray': ($asu-gray-4, $asu-gray-1), + 'gold': ($gold, $dark), + 'dark': ($dark, $white), + 'maroon': ($maroon, $white) +); + +// Mixin for borderless button variants +@mixin button-borderless-variant($color, $hover-color) { + color: $color; + background: transparent; + border: 2px solid transparent; + + &:hover, + &:active, + &.active { + background-color: $color; + color: $hover-color; + } + + &:focus, + &:focus-visible { + color: $color; + background: transparent; + + &:hover { + background-color: $color; + color: $hover-color; + } + + &:active { + background-color: $color; + color: $hover-color; + } + } + + &:disabled, + &.disabled { + color: $asu-gray-4; + background: transparent; + opacity: 0.5; + + &:hover, + &:focus, + &:active, + &.active { + background: transparent; + color: $asu-gray-4; + } + } +} + +// Generate borderless button variants +@each $name, $colors in $button-colors { + .btn-borderless-#{$name} { + @include button-borderless-variant(nth($colors, 1), nth($colors, 2)); + } +} + +// Generate outlined button variants using Bootstrap's outline mixin +@each $name, $colors in $button-colors { + .btn-outline-#{$name} { + @include button-outline-variant(nth($colors, 1), nth($colors, 2)); + border-width: 2px; + + &:active, + &.active { + background-color: nth($colors, 1); + border-color: nth($colors, 1); + color: nth($colors, 2); + } + + &:focus, + &:focus-visible { + color: nth($colors, 1); + background: transparent; + border-color: transparent; + + &:hover { + background-color: nth($colors, 1); + border-color: nth($colors, 1); + color: nth($colors, 2); + } + + &:active { + background-color: nth($colors, 1); + border-color: nth($colors, 1); + color: nth($colors, 2); + } + } + } +} + +// Generate filled button variants using Bootstrap's button mixin +@each $name, $colors in $button-colors { + .btn-filled-#{$name} { + @include button-variant(nth($colors, 1), nth($colors, 1)); + border-width: 2px; + + &:focus, + &:focus-visible { + color: nth($colors, 2); + + &:active { + color: nth($colors, 2); + } + } + } + } + a { From 309ae92a85851fd5c5740d08ba52335835fcc9a6 Mon Sep 17 00:00:00 2001 From: david ornelas Date: Tue, 30 Dec 2025 10:09:10 -0700 Subject: [PATCH 2/4] feat(unity-react-core): add new button styles --- .../src/components/Button/Button.jsx | 1 + .../src/components/Button/Button.stories.jsx | 14 ++ .../src/components/UDSButton/UDSButton.jsx | 126 ++++++++++++++++++ .../UDSButton/UDSButton.stories.jsx | 83 ++++++++++++ .../src/components/UDSButton/init.js | 1 + 5 files changed, 225 insertions(+) create mode 100644 packages/unity-react-core/src/components/UDSButton/UDSButton.jsx create mode 100644 packages/unity-react-core/src/components/UDSButton/UDSButton.stories.jsx create mode 100644 packages/unity-react-core/src/components/UDSButton/init.js diff --git a/packages/unity-react-core/src/components/Button/Button.jsx b/packages/unity-react-core/src/components/Button/Button.jsx index ad044ff7d..89409aaa2 100644 --- a/packages/unity-react-core/src/components/Button/Button.jsx +++ b/packages/unity-react-core/src/components/Button/Button.jsx @@ -22,6 +22,7 @@ const gaDefaultObject = { /** * @param {ButtonProps} props * @returns {JSX.Element} + * @deprecated Use UDSButton instead */ export const Button = ({ label = "", diff --git a/packages/unity-react-core/src/components/Button/Button.stories.jsx b/packages/unity-react-core/src/components/Button/Button.stories.jsx index 3db46af01..f6169e565 100644 --- a/packages/unity-react-core/src/components/Button/Button.stories.jsx +++ b/packages/unity-react-core/src/components/Button/Button.stories.jsx @@ -23,6 +23,20 @@ View component examples and source code below. }, }, }, + decorators: [ + (Story, {parameters}) => { + if (parameters?.disableDeprecatedNotice) { + return ; + } + return
+
+

Deprecated

+

Please see new button styles in UDSButton component.

+
+ +
+ }, + ] }; const handleClick = () => { diff --git a/packages/unity-react-core/src/components/UDSButton/UDSButton.jsx b/packages/unity-react-core/src/components/UDSButton/UDSButton.jsx new file mode 100644 index 000000000..4563032b9 --- /dev/null +++ b/packages/unity-react-core/src/components/UDSButton/UDSButton.jsx @@ -0,0 +1,126 @@ +// @ts-check +import classNames from "classnames"; +import PropTypes from "prop-types"; +import React from "react"; + +import { gaDataType } from "../../core/models/shared-prop-types"; +import { GaEventWrapper } from "../GaEventWrapper/GaEventWrapper"; + +const gaDefaultObject = { + name: "onclick", + event: "link", + action: "click", + type: "internal link", + region: "main content", +}; + +/** + * @typedef {Object} UDSButtonProps + * @property {string} [label] - Button label text + * @property {import('../GaEventWrapper/GaEventWrapper').GaEventType} [gaData] - Google Analytics event data + * @property {"gold"|"maroon"|"gray"|"dark"} [color] - Button color variant + * @property {boolean} [disabled] - Disable the button + * @property {React.ElementType|string} [element] - Component to override default button element + * @property {string} [href] - Link target url; will cause button to be rendered as `` link + * @property {string[]} [icon] - React Font Awesome icon prefix and name string + * @property {React.Ref} [innerRef] - Reference to the DOM element + * @property {Function} [onClick] - Event handler function + * @property {"large"|"medium"|"small"} [size] - Button size + * @property {"borderless"|"outline"|"filled"} [variant] - Button style variant + * @property {string[]} [classes] - Classes to add to button + * @property {"_blank"|"_self"|"_top"|"_parent"} [target] - Link target type + */ + +/** + * UDS Button component with multiple variants (borderless, outline, filled) + * + * Unified button component supporting three visual styles: + * - Borderless: Text-only button that fills with color on hover + * - Outline: Button with border and transparent background that fills on hover + * - Filled: Solid background button + * + * @param {UDSButtonProps} props - Component props + * @returns {JSX.Element} + */ +export const UDSButton = ({ + label = "", + gaData, + color = "gold", + disabled, + element = "button", + href, + icon, + innerRef, + onClick, + size = "large", + variant = "filled", + classes, + target = "_self", + ...props +}) => { + const variantClasses = { + borderless: "borderless", + outline: "outline", + filled: "filled", + }; + + const btnClasses = classNames("btn", variantClasses[variant], { + [`btn-${variantClasses[variant]}-${color}`]: true, + [`btn-md`]: size === "medium", + [`btn-sm`]: size === "small", + [`disabled`]: disabled, + }); + + let Tag = element; + if (href && element === "button") { + Tag = "a"; + } + + return ( + + + {icon && } + {label} + + + ); +}; + +UDSButton.propTypes = { + label: PropTypes.string, + gaData: gaDataType, + color: PropTypes.oneOf(["gold", "maroon", "gray", "dark"]), + disabled: PropTypes.bool, + element: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, + PropTypes.shape({ $$typeof: PropTypes.symbol, render: PropTypes.func }), + ]), + href: PropTypes.string, + icon: PropTypes.arrayOf(PropTypes.string), + innerRef: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.func, + PropTypes.string, + ]), + onClick: PropTypes.func, + size: PropTypes.oneOf(["large", "medium", "small"]), + variant: PropTypes.oneOf(["borderless", "outline", "filled"]), + classes: PropTypes.arrayOf(PropTypes.string), + target: PropTypes.oneOf(["_blank", "_self", "_top", "_parent"]), +}; diff --git a/packages/unity-react-core/src/components/UDSButton/UDSButton.stories.jsx b/packages/unity-react-core/src/components/UDSButton/UDSButton.stories.jsx new file mode 100644 index 000000000..0430069ca --- /dev/null +++ b/packages/unity-react-core/src/components/UDSButton/UDSButton.stories.jsx @@ -0,0 +1,83 @@ +/* eslint react/jsx-props-no-spreading: "off", no-alert: "off" */ +import React from "react"; + +import { UDSButton } from "./UDSButton"; + +export default { + title: "Components/UDSButton", + component: UDSButton, + parameters: { + docs: { + description: { + component: `The UDSButton component provides three button variants in one unified component: +- **Borderless**: Text-only button that fills with color on hover +- **Outline**: Button with border and transparent background that fills on hover +- **Filled**: Solid background button + +All styles come from the Unity Bootstrap Theme SCSS.`, + }, + }, + }, + args: { + label: "UDS Button", + color: "gold", + size: "large", + variant: "filled", + disabled: false, + }, + argTypes: { + variant: { + control: "select", + options: ["borderless", "outline", "filled"], + description: "Button style variant", + }, + color: { + control: "select", + options: ["gold", "maroon", "gray", "dark"], + description: "Button color", + }, + size: { + control: "select", + options: ["large", "medium", "small"], + description: "Button size", + }, + disabled: { + control: "boolean", + description: "Disable the button", + }, + label: { + control: "text", + description: "Button text", + }, + }, +}; + +const handleClick = () => { + alert("Button clicked!"); +}; + +export const Default = args => ( + +); + +export const AsLink = args => ( + +); +AsLink.args = { + label: "Button Link", +}; + +export const AllVariants = () => ( +
+
+
+ + + +
+
+
+); +AllVariants.parameters = { + controls: { disable: true }, +}; diff --git a/packages/unity-react-core/src/components/UDSButton/init.js b/packages/unity-react-core/src/components/UDSButton/init.js new file mode 100644 index 000000000..0e18312b0 --- /dev/null +++ b/packages/unity-react-core/src/components/UDSButton/init.js @@ -0,0 +1 @@ +export { UDSButton } from "./UDSButton"; From 495ce5ef1dd8e69deb5260fe69adc1c3b8cbfba2 Mon Sep 17 00:00:00 2001 From: david ornelas Date: Tue, 30 Dec 2025 10:09:34 -0700 Subject: [PATCH 3/4] chore(unity-bootstrap-theme): deprecate old button styles and add deprecated notice --- .../atoms/buttons/buttons.examples.stories.js | 83 ++++++----------- .../buttons/buttons.templates.stories.js | 16 +++- .../stories/atoms/buttons/buttons.test.js | 89 ------------------- 3 files changed, 43 insertions(+), 145 deletions(-) delete mode 100644 packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.test.js diff --git a/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.examples.stories.js b/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.examples.stories.js index 06b74bdf1..8fcd7e9d7 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.examples.stories.js @@ -3,6 +3,20 @@ import React from "react"; export default { title: "Atoms/Buttons/Examples", parameters: { controls: { disable: true } }, + decorators: [ + (Story, {parameters}) => { + if (parameters?.disableDeprecatedNotice) { + return ; + } + return
+ }, + ], }; export const ButtonColorsComponent = () => ( @@ -210,6 +224,10 @@ export const ButtonTagsComponent = () => ( ); +ButtonTagsComponent.parameters = { + disableDeprecatedNotice: true, +}; + export const IconOnlyButtonsColorAndSizesComponent = () => (
@@ -257,6 +275,11 @@ export const IconOnlyButtonsColorAndSizesComponent = () => (
); + +IconOnlyButtonsColorAndSizesComponent.parameters = { + disableDeprecatedNotice: true, +}; + export const IconOnlyButtonsColorCombinationsComponent = () => (
@@ -343,6 +366,9 @@ export const IconOnlyButtonsColorCombinationsComponent = () => (
); +IconOnlyButtonsColorCombinationsComponent.parameters = { + disableDeprecatedNotice: true, +}; export const PrevAndNextButtonsComponent = () => (
@@ -398,59 +424,6 @@ export const PrevAndNextButtonsComponent = () => (
); - -const demoButtonStyle = { - display: "flex", - justifyContent: "center", - paddingTop: "10px", +PrevAndNextButtonsComponent.parameters = { + disableDeprecatedNotice: true, }; - -const TestButtons = () => ( -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-); diff --git a/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.templates.stories.js b/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.templates.stories.js index ecb2d26e2..f5357cae8 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.templates.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.templates.stories.js @@ -34,6 +34,20 @@ export default { control: { type: "boolean" }, }, }, + decorators: [ + (Story, {parameters}) => { + if (parameters?.disableDeprecatedNotice) { + return ; + } + return
+
+

Deprecated

+

Please see new button styles in unity-react-core UDSButton storybook instead.

+
+ +
+ }, + ] }; export const BasicButton = args => { @@ -113,4 +127,4 @@ ButtonTag.argTypes = { size: { table: { disable: true } }, disabled: { table: { disable: true } }, }; -ButtonTag.parameters = { controls: { disable: true } }; +ButtonTag.parameters = { controls: { disable: true }, disableDeprecatedNotice: true }; diff --git a/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.test.js b/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.test.js deleted file mode 100644 index 67f15609d..000000000 --- a/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.test.js +++ /dev/null @@ -1,89 +0,0 @@ -describe("Buttons", () => { - it("examples visually look correct", async () => { - // APIs from jest-puppeteer - await page.goto( - "http://localhost:9009/iframe.html?id=buttons--standard-button-tags" - ); - const image = await page.screenshot(); - - // API from jest-image-snapshot - expect(image).toMatchImageSnapshot(); - }); - - it("button tags visually look correct", async () => { - // APIs from jest-puppeteer - await page.goto( - "http://localhost:9009/iframe.html?id=buttons--button-color-examples" - ); - const image = await page.screenshot(); - - // API from jest-image-snapshot - expect(image).toMatchImageSnapshot(); - }); - - it("outline buttons visually look correct", async () => { - // APIs from jest-puppeteer - await page.goto( - "http://localhost:9009/iframe.html?id=buttons--button-outlines" - ); - const image = await page.screenshot(); - - // API from jest-image-snapshot - expect(image).toMatchImageSnapshot(); - }); - - it("button sizes visually look correct", async () => { - // APIs from jest-puppeteer - await page.goto( - "http://localhost:9009/iframe.html?id=buttons--button-sizes" - ); - const image = await page.screenshot(); - - // API from jest-image-snapshot - expect(image).toMatchImageSnapshot(); - }); - - it("active button state visually looks correct", async () => { - // APIs from jest-puppeteer - await page.goto( - "http://localhost:9009/iframe.html?id=buttons--active-state" - ); - const image = await page.screenshot(); - - // API from jest-image-snapshot - expect(image).toMatchImageSnapshot(); - }); - - it("disabled button state visually looks correct", async () => { - // APIs from jest-puppeteer - await page.goto( - "http://localhost:9009/iframe.html?id=buttons--disabled-state" - ); - const image = await page.screenshot(); - - // API from jest-image-snapshot - expect(image).toMatchImageSnapshot(); - }); - - it("button plugin for toggle state visually looks correct", async () => { - // APIs from jest-puppeteer - await page.goto( - "http://localhost:9009/iframe.html?id=buttons--button-toggle-state" - ); - const image = await page.screenshot(); - - // API from jest-image-snapshot - expect(image).toMatchImageSnapshot(); - }); - - it("checkbox and radio buttons visually look correct", async () => { - // APIs from jest-puppeteer - await page.goto( - "http://localhost:9009/iframe.html?id=buttons--checkbox-and-radio-buttons" - ); - const image = await page.screenshot(); - - // API from jest-image-snapshot - expect(image).toMatchImageSnapshot(); - }); -}); From b44c5c6e4a4ed925a61786a7ea299c97f47e8121 Mon Sep 17 00:00:00 2001 From: david ornelas Date: Tue, 30 Dec 2025 10:24:29 -0700 Subject: [PATCH 4/4] chore(unity-bootstrap-theme): linting --- .../atoms/buttons/buttons.examples.stories.js | 28 ++++++++--- .../buttons/buttons.templates.stories.js | 49 +++++++++++++------ 2 files changed, 54 insertions(+), 23 deletions(-) diff --git a/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.examples.stories.js b/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.examples.stories.js index 8fcd7e9d7..f341ca67f 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.examples.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.examples.stories.js @@ -4,17 +4,31 @@ export default { title: "Atoms/Buttons/Examples", parameters: { controls: { disable: true } }, decorators: [ - (Story, {parameters}) => { + (Story, { parameters }) => { if (parameters?.disableDeprecatedNotice) { return ; } - return
-
-

Deprecated

-

Please see new button styles in unity-react-core UDSButton storybook instead.

+ return ( +
+
+

Deprecated

+

+ Please see new button styles in unity-react-core{" "} + + UDSButton + {" "} + storybook instead. +

+
+
- -
+ ); }, ], }; diff --git a/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.templates.stories.js b/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.templates.stories.js index f5357cae8..4e7ffeb7e 100644 --- a/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.templates.stories.js +++ b/packages/unity-bootstrap-theme/stories/atoms/buttons/buttons.templates.stories.js @@ -3,7 +3,35 @@ import { defaultDecorator } from "@asu/shared"; export default { title: "Atoms/Buttons/Templates", - decorators: [defaultDecorator], + decorators: [ + defaultDecorator, + (Story, { parameters }) => { + if (parameters?.disableDeprecatedNotice) { + return ; + } + return ( +
+
+

Deprecated

+

+ Please see new button styles in unity-react-core{" "} + + UDSButton + {" "} + storybook instead. +

+
+ +
+ ); + }, + ], args: { color: "btn-gold", size: "Large", @@ -34,20 +62,6 @@ export default { control: { type: "boolean" }, }, }, - decorators: [ - (Story, {parameters}) => { - if (parameters?.disableDeprecatedNotice) { - return ; - } - return
-
-

Deprecated

-

Please see new button styles in unity-react-core UDSButton storybook instead.

-
- -
- }, - ] }; export const BasicButton = args => { @@ -127,4 +141,7 @@ ButtonTag.argTypes = { size: { table: { disable: true } }, disabled: { table: { disable: true } }, }; -ButtonTag.parameters = { controls: { disable: true }, disableDeprecatedNotice: true }; +ButtonTag.parameters = { + controls: { disable: true }, + disableDeprecatedNotice: true, +};
+
+

Deprecated

+

Please see new button styles in unity-react-core UDSButton storybook instead.

+
+ +