UNPKG

braid-design-system

Version:
430 lines (429 loc) • 13.6 kB
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 };