braid-design-system
Version:
Themeable design system for the SEEK Group
430 lines (429 loc) • 13.6 kB
JavaScript
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
import assert from "assert";
import { forwardRef, useContext } from "react";
import { negativeMargin } from "../../css/negativeMargin/negativeMargin.mjs";
import { ActionsContext } from "../Actions/ActionsContext.mjs";
import { Bleed } from "../Bleed/Bleed.mjs";
import { useColorContrast, useBackgroundLightness } from "../Box/BackgroundContext.mjs";
import { Box } from "../Box/Box.mjs";
import { useBraidTheme } from "../BraidProvider/BraidThemeContext.mjs";
import { Text } from "../Text/Text.mjs";
import { AvoidWidowIcon } from "../private/AvoidWidowIcon/AvoidWidowIcon.mjs";
import { FieldOverlay } from "../private/FieldOverlay/FieldOverlay.mjs";
import { buildDataAttributes } from "../private/buildDataAttributes.mjs";
import { focusRing, invertedBackgroundsLightMode, invertedBackgroundsDarkMode, activeAnimation, standard, small, bleedVerticallyToCapHeight, root, hoverOverlay, forceActive, activeOverlay, padToMinHeight, loadingDot } from "./Button.css.mjs";
import { virtualTouchable } from "../private/touchable/virtualTouchable.css.mjs";
const buttonVariants = [
"solid",
"ghost",
"soft",
"transparent"
];
const variants = {
solid: {
formAccent: {
textTone: void 0,
background: "formAccent",
backgroundHover: "formAccentHover",
backgroundActive: "formAccentActive",
boxShadow: void 0
},
brandAccent: {
textTone: void 0,
background: "brandAccent",
backgroundHover: "brandAccentHover",
backgroundActive: "brandAccentActive",
boxShadow: void 0
},
critical: {
textTone: void 0,
background: "critical",
backgroundHover: "criticalHover",
backgroundActive: "criticalActive",
boxShadow: void 0
},
neutral: {
textTone: void 0,
background: { light: "neutral", dark: "neutralSoft" },
backgroundHover: { light: "neutralSoftHover", dark: "neutralHover" },
backgroundActive: { light: "neutralSoftActive", dark: "neutralActive" },
boxShadow: void 0
}
},
soft: {
formAccent: {
textTone: "formAccent",
background: { light: "formAccentSoft", dark: "customDark" },
backgroundHover: "formAccentSoftHover",
backgroundActive: "formAccentSoftActive",
boxShadow: void 0
},
brandAccent: {
textTone: "brandAccent",
background: { light: "brandAccentSoft", dark: "customDark" },
backgroundHover: "brandAccentSoftHover",
backgroundActive: "brandAccentSoftActive",
boxShadow: void 0
},
critical: {
textTone: "critical",
background: { light: "criticalSoft", dark: "customDark" },
backgroundHover: "criticalSoftHover",
backgroundActive: "criticalSoftActive",
boxShadow: void 0
},
neutral: {
textTone: "neutral",
background: { light: "neutralSoft", dark: "customDark" },
backgroundHover: "neutralSoftHover",
backgroundActive: "neutralSoftActive",
boxShadow: void 0
}
},
transparent: {
formAccent: {
textTone: "formAccent",
background: void 0,
backgroundHover: "formAccentSoftHover",
backgroundActive: "formAccentSoftActive",
boxShadow: void 0
},
brandAccent: {
textTone: "brandAccent",
background: void 0,
backgroundHover: "brandAccentSoftHover",
backgroundActive: "brandAccentSoftActive",
boxShadow: void 0
},
critical: {
textTone: "critical",
background: void 0,
backgroundHover: "criticalSoftHover",
backgroundActive: "criticalSoftActive",
boxShadow: void 0
},
neutral: {
textTone: "neutral",
background: void 0,
backgroundHover: "neutralSoftHover",
backgroundActive: "neutralSoftActive",
boxShadow: void 0
}
},
ghost: {
formAccent: {
textTone: "formAccent",
background: void 0,
backgroundHover: "formAccentSoftHover",
backgroundActive: "formAccentSoftActive",
boxShadow: "borderFormAccent"
},
brandAccent: {
textTone: "brandAccent",
background: void 0,
backgroundHover: "brandAccentSoftHover",
backgroundActive: "brandAccentSoftActive",
boxShadow: "borderBrandAccent"
},
critical: {
textTone: "critical",
background: void 0,
backgroundHover: "criticalSoftHover",
backgroundActive: "criticalSoftActive",
boxShadow: "borderCritical"
},
neutral: {
textTone: "neutral",
background: void 0,
backgroundHover: "neutralSoftHover",
backgroundActive: "neutralSoftActive",
boxShadow: "borderNeutral"
}
}
};
const ButtonLoader = () => /* @__PURE__ */ jsxs(Box, { "aria-hidden": true, component: "span", display: "inlineBlock", children: [
/* @__PURE__ */ jsx(Box, { component: "span", className: loadingDot, children: "." }),
/* @__PURE__ */ jsx(Box, { component: "span", className: loadingDot, children: "." }),
/* @__PURE__ */ jsx(Box, { component: "span", className: loadingDot, children: "." })
] });
const transparentPaddingX = "small";
const buttonRadius = "standard";
const resolveToneAndVariant = ({
variant: variantProp,
tone: toneProp,
legacy
}) => {
if (legacy) {
return {
variant: variantProp ?? "solid",
tone: toneProp ?? "formAccent"
};
}
const fallbackVariant = toneProp ? "solid" : "ghost";
return {
variant: variantProp ?? fallbackVariant,
tone: toneProp ?? "neutral"
};
};
const ButtonOverlays = ({
variant: variantProp,
tone: toneProp,
forceActive: forceActive$1 = false,
radius = buttonRadius
}) => {
const { variant, tone } = resolveToneAndVariant({
variant: variantProp,
tone: toneProp,
legacy: useBraidTheme().legacy
});
const stylesForVariant = variants[variant][tone];
const colorContrast = useColorContrast();
const lightness = useBackgroundLightness();
return /* @__PURE__ */ jsxs(Fragment, { children: [
/* @__PURE__ */ jsx(
FieldOverlay,
{
borderRadius: radius,
background: stylesForVariant.backgroundHover && typeof stylesForVariant.backgroundHover !== "string" ? colorContrast(stylesForVariant.backgroundHover) : stylesForVariant.backgroundHover,
className: [
hoverOverlay,
variant !== "solid" && lightness.lightMode === "dark" ? invertedBackgroundsLightMode.hover : null,
variant !== "solid" && lightness.darkMode === "dark" ? invertedBackgroundsDarkMode.hover : null
]
}
),
/* @__PURE__ */ jsx(
FieldOverlay,
{
borderRadius: radius,
background: stylesForVariant.backgroundActive && typeof stylesForVariant.backgroundActive !== "string" ? colorContrast(stylesForVariant.backgroundActive) : stylesForVariant.backgroundActive,
className: [
forceActive$1 ? forceActive : void 0,
activeOverlay,
variant !== "solid" && lightness.lightMode === "dark" ? invertedBackgroundsLightMode.active : null,
variant !== "solid" && lightness.darkMode === "dark" ? invertedBackgroundsDarkMode.active : null
]
}
),
stylesForVariant.boxShadow ? /* @__PURE__ */ jsx(
Box,
{
component: "span",
boxShadow: stylesForVariant.boxShadow,
borderRadius: radius,
position: "absolute",
inset: 0,
pointerEvents: "none"
}
) : null
] });
};
const ButtonText = ({
children,
loading,
size: sizeProp,
icon,
iconPosition = "leading",
variant: variantProp,
tone: toneProp,
bleed
}) => {
const { variant, tone } = resolveToneAndVariant({
variant: variantProp,
tone: toneProp,
legacy: useBraidTheme().legacy
});
const lightness = useBackgroundLightness();
const actionsContext = useContext(ActionsContext);
const isLegacyTheme = useBraidTheme().legacy;
const size = sizeProp ?? (actionsContext == null ? void 0 : actionsContext.size) ?? "standard";
const stylesForVariant = variants[variant][tone];
const shouldReducePaddingX = size === "small" || variant === "transparent";
const labelPaddingXForTheme = isLegacyTheme ? "medium" : "gutter";
const labelPaddingX = shouldReducePaddingX ? transparentPaddingX : labelPaddingXForTheme;
assert(
!icon || icon.props.size === void 0 && icon.props.tone === void 0,
"Icons cannot set the 'size' or 'tone' prop when passed to a Button component"
);
return /* @__PURE__ */ jsx(
Box,
{
component: "span",
position: "relative",
display: "flex",
justifyContent: "center",
flexGrow: 1,
flexWrap: "wrap",
overflow: "hidden",
pointerEvents: "none",
paddingX: labelPaddingX,
className: padToMinHeight,
background: tone === "neutral" && variant !== "solid" ? {
lightMode: lightness.lightMode === "light" ? "customLight" : "customDark",
darkMode: lightness.darkMode === "light" ? "customLight" : "customDark"
} : void 0,
children: /* @__PURE__ */ jsxs(
Text,
{
tone: stylesForVariant.textTone,
weight: "medium",
align: "center",
size,
children: [
icon && iconPosition === "leading" ? /* @__PURE__ */ jsx(
AvoidWidowIcon,
{
iconPosition,
className: shouldReducePaddingX || bleed ? null : negativeMargin("left", "xxsmall"),
children: icon
}
) : null,
children,
loading ? /* @__PURE__ */ jsx(ButtonLoader, {}) : null,
!loading && icon && iconPosition === "trailing" ? /* @__PURE__ */ jsx(
AvoidWidowIcon,
{
iconPosition,
className: shouldReducePaddingX || bleed ? null : negativeMargin("right", "xxsmall"),
children: icon
}
) : null
]
}
)
}
);
};
const useButtonStyles = ({
variant: variantProp,
size: sizeProp,
tone: toneProp,
loading,
radius = buttonRadius,
bleed
}) => {
const { variant, tone } = resolveToneAndVariant({
variant: variantProp,
tone: toneProp,
legacy: useBraidTheme().legacy
});
const actionsContext = useContext(ActionsContext);
const size = sizeProp ?? (actionsContext == null ? void 0 : actionsContext.size) ?? "standard";
const stylesForVariant = variants[variant][tone];
const colorContrast = useColorContrast();
const lightness = useBackgroundLightness();
return {
root: {
display: "flex",
width: "full",
borderRadius: radius,
cursor: !loading ? "pointer" : void 0,
outline: "none",
className: [root, size === "small" ? virtualTouchable : void 0]
},
content: {
component: "span",
borderRadius: radius,
width: "full",
position: "relative",
display: "flex",
alignItems: "center",
justifyContent: "center",
textAlign: "center",
userSelect: "none",
background: stylesForVariant.background && typeof stylesForVariant.background !== "string" ? colorContrast(stylesForVariant.background) : stylesForVariant.background,
className: [
focusRing,
variant === "soft" && lightness.lightMode === "dark" ? invertedBackgroundsLightMode.soft : null,
variant === "soft" && lightness.darkMode === "dark" ? invertedBackgroundsDarkMode.soft : null,
activeAnimation,
size === "standard" ? standard : small,
bleed ? bleedVerticallyToCapHeight : null
]
}
};
};
const ButtonContainer = ({
children,
bleed,
variant = "solid"
}) => bleed && variant === "transparent" ? /* @__PURE__ */ jsx(Bleed, { horizontal: transparentPaddingX, children }) : /* @__PURE__ */ jsx(Fragment, { children });
const Button = forwardRef(
({
onClick,
children,
size,
tone,
icon,
iconPosition,
bleed,
variant,
loading,
type = "button",
id,
tabIndex,
onKeyUp,
onKeyDown,
"aria-haspopup": ariaHasPopup,
"aria-controls": ariaControls,
"aria-expanded": ariaExpanded,
"aria-describedby": ariaDescribedBy,
"aria-label": ariaLabel,
data,
...restProps
}, ref) => {
const { root: root2, content } = useButtonStyles({
variant,
tone,
size,
bleed,
loading
});
return /* @__PURE__ */ jsx(ButtonContainer, { bleed, variant, children: /* @__PURE__ */ jsx(
Box,
{
component: "button",
ref,
id,
type,
tabIndex,
onKeyUp,
onKeyDown,
"aria-haspopup": ariaHasPopup,
"aria-controls": ariaControls,
"aria-expanded": ariaExpanded,
"aria-describedby": ariaDescribedBy,
"aria-label": ariaLabel,
onClick,
disabled: loading,
...root2,
...buildDataAttributes({ data, validateRestProps: restProps }),
children: /* @__PURE__ */ jsxs(Box, { ...content, children: [
/* @__PURE__ */ jsx(ButtonOverlays, { variant, tone }),
/* @__PURE__ */ jsx(
ButtonText,
{
variant,
tone,
size,
loading,
icon,
iconPosition,
bleed,
children
}
)
] })
}
) });
}
);
Button.displayName = "Button";
export {
Button,
ButtonContainer,
ButtonOverlays,
ButtonText,
buttonVariants,
useButtonStyles
};