diff --git a/packages/unity-bootstrap-theme/src/scss/extends/_buttons.scss b/packages/unity-bootstrap-theme/src/scss/extends/_buttons.scss
index 97cb1a53f7..e0734e3fe4 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 {
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 06b74bdf11..f341ca67f5 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,34 @@ import React from "react";
export default {
title: "Atoms/Buttons/Examples",
parameters: { controls: { disable: true } },
+ decorators: [
+ (Story, { parameters }) => {
+ if (parameters?.disableDeprecatedNotice) {
+ return ;
+ }
+ return (
+
+
+
Deprecated
+
+ Please see new button styles in unity-react-core{" "}
+
+ UDSButton
+ {" "}
+ storybook instead.
+
+
+
+
+ );
+ },
+ ],
};
export const ButtonColorsComponent = () => (
@@ -210,6 +238,10 @@ export const ButtonTagsComponent = () => (
);
+ButtonTagsComponent.parameters = {
+ disableDeprecatedNotice: true,
+};
+
export const IconOnlyButtonsColorAndSizesComponent = () => (
@@ -257,6 +289,11 @@ export const IconOnlyButtonsColorAndSizesComponent = () => (
);
+
+IconOnlyButtonsColorAndSizesComponent.parameters = {
+ disableDeprecatedNotice: true,
+};
+
export const IconOnlyButtonsColorCombinationsComponent = () => (
@@ -343,6 +380,9 @@ export const IconOnlyButtonsColorCombinationsComponent = () => (
);
+IconOnlyButtonsColorCombinationsComponent.parameters = {
+ disableDeprecatedNotice: true,
+};
export const PrevAndNextButtonsComponent = () => (
@@ -398,59 +438,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 ecb2d26e23..4e7ffeb7e1 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",
@@ -113,4 +141,7 @@ 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 67f15609df..0000000000
--- 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();
- });
-});
diff --git a/packages/unity-react-core/src/components/Button/Button.jsx b/packages/unity-react-core/src/components/Button/Button.jsx
index ad044ff7dd..89409aaa23 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 3db46af012..f6169e565b 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 0000000000..4563032b91
--- /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 0000000000..0430069ca1
--- /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 0000000000..0e18312b04
--- /dev/null
+++ b/packages/unity-react-core/src/components/UDSButton/init.js
@@ -0,0 +1 @@
+export { UDSButton } from "./UDSButton";