diff --git a/.changeset/tasty-suns-behave.md b/.changeset/tasty-suns-behave.md new file mode 100644 index 00000000000..b0b7961fc54 --- /dev/null +++ b/.changeset/tasty-suns-behave.md @@ -0,0 +1,6 @@ + +--- +"@primer/react": major +"@primer/styled-react": patch +--- +Remove sx property from Button diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-colorblind-linux.png deleted file mode 100644 index 57aa4d25ef0..00000000000 Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-colorblind-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-dimmed-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-dimmed-linux.png deleted file mode 100644 index 1fd26a1e4f1..00000000000 Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-dimmed-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-high-contrast-linux.png deleted file mode 100644 index 96bb3a24e79..00000000000 Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-high-contrast-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-linux.png deleted file mode 100644 index 57aa4d25ef0..00000000000 Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-tritanopia-linux.png deleted file mode 100644 index 57aa4d25ef0..00000000000 Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-dark-tritanopia-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-colorblind-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-colorblind-linux.png deleted file mode 100644 index 1136e949782..00000000000 Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-colorblind-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-high-contrast-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-high-contrast-linux.png deleted file mode 100644 index 99f7fa8376a..00000000000 Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-high-contrast-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-linux.png deleted file mode 100644 index 1136e949782..00000000000 Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-linux.png and /dev/null differ diff --git a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-tritanopia-linux.png b/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-tritanopia-linux.png deleted file mode 100644 index 1136e949782..00000000000 Binary files a/.playwright/snapshots/components/Button.test.ts-snapshots/Button-Dev-Sx-Prop-light-tritanopia-linux.png and /dev/null differ diff --git a/e2e/components/Button.test.ts b/e2e/components/Button.test.ts index b48c0475612..22eab45b1ca 100644 --- a/e2e/components/Button.test.ts +++ b/e2e/components/Button.test.ts @@ -75,10 +75,6 @@ const stories = [ title: 'Dev Invisible Variants', id: 'components-button-dev--invisible-variants', }, - { - title: 'Dev Sx Prop', - id: 'components-button-dev--test-sx-prop', - }, { title: 'Aria Expanded Buttons', id: 'components-button-features--expanded-button', diff --git a/examples/nextjs/src/app/page.tsx b/examples/nextjs/src/app/page.tsx index 4910ebc2e78..88a1fb74ce4 100644 --- a/examples/nextjs/src/app/page.tsx +++ b/examples/nextjs/src/app/page.tsx @@ -1,6 +1,6 @@ 'use client' -import {Button, Stack, Box} from '@primer/react' +import {Button, Stack} from '@primer/react' import {useTheme} from '@primer/styled-react' import styled from 'styled-components' @@ -28,10 +28,12 @@ const ThemeUser = () => { export default function IndexPage() { return ( - - Hello world +
+ Hello world +
Hello world
diff --git a/packages/react/src/Button/Button.dev.stories.tsx b/packages/react/src/Button/Button.dev.stories.tsx index 1ec0d70bb6d..4df2b81c135 100644 --- a/packages/react/src/Button/Button.dev.stories.tsx +++ b/packages/react/src/Button/Button.dev.stories.tsx @@ -1,8 +1,6 @@ -import {SearchIcon, TriangleDownIcon, EyeIcon, IssueClosedIcon, HeartFillIcon} from '@primer/octicons-react' +import {SearchIcon, TriangleDownIcon, EyeIcon, HeartFillIcon} from '@primer/octicons-react' import {Button, IconButton} from '.' -import {default as Text} from '../Text' import {Stack} from '../Stack' -import classes from './Button.dev.stories.module.css' export default { title: 'Components/Button/Dev', @@ -33,69 +31,6 @@ export const InvisibleVariants = () => { ) } -export const TestSxProp = () => { - const count = 4 - return ( -
- - - - - - - -
- ) -} - export const DisabledButtonVariants = () => { return ( diff --git a/packages/react/src/Button/Button.docs.json b/packages/react/src/Button/Button.docs.json index c1b59a42c56..91707c4c7bd 100644 --- a/packages/react/src/Button/Button.docs.json +++ b/packages/react/src/Button/Button.docs.json @@ -151,11 +151,6 @@ "type": "'small'\n| 'medium'\n| 'large'", "defaultValue": "'medium'" }, - { - "name": "sx", - "type": "SystemStyleObject", - "deprecated": true - }, { "name": "trailingIcon", "type": "React.ComponentType", diff --git a/packages/react/src/Button/Button.tsx b/packages/react/src/Button/Button.tsx index 9019ba071a1..ad4c4968a61 100644 --- a/packages/react/src/Button/Button.tsx +++ b/packages/react/src/Button/Button.tsx @@ -2,94 +2,17 @@ import {forwardRef} from 'react' import type {ButtonProps} from './types' import {ButtonBase} from './ButtonBase' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' -import {defaultSxProp} from '../utils/defaultSxProp' -import type {BetterSystemStyleObject, CSSCustomProperties} from '../sx' - -const ButtonComponent = forwardRef(({children, sx: sxProp = defaultSxProp, ...props}, forwardedRef): JSX.Element => { - const {block, size = 'medium', leadingVisual, trailingVisual, trailingAction} = props - let sxStyles = sxProp - const style: CSSCustomProperties = {} - - if (sxProp !== null && Object.keys(sxProp).length > 0) { - sxStyles = generateCustomSxProp({block, size, leadingVisual, trailingVisual, trailingAction}, sxProp) - - // @ts-ignore sxProp can have color attribute - const {color} = sxProp - if (color) style['--button-color'] = color - } +const ButtonComponent = forwardRef(({children, ...props}, forwardedRef): JSX.Element => { return ( - + {children} ) }) as PolymorphicForwardRefComponent<'button', ButtonProps> -// This function is used to generate a custom cssSelector for the sxProp - -// The usual sx prop can like this: -// sx={{ -// [`@media (max-width: 768px)`]: { -// '& > ul': { -// backgroundColor: 'deeppink', -// }, -// '&:hover': { -// backgroundColor: 'yellow', -// }, -// }, -// '&:hover': { -// backgroundColor: 'yellow', -// }, -// '&': { -// width : 320px -// } -// }} -//* -/* What we want for Button styles is this: -sx={{ -// [`@media (max-width: 768px)`]: { -// '&[data-attribute="something"] > ul': { -// backgroundColor: 'deeppink', -// }, -// '&[data-attribute="something"]:hover': { -// backgroundColor: 'yellow', -// }, -// }, -// '&[data-attribute="something"]:hover': { -// backgroundColor: 'yellow', -// }, -// '&[data-attribute="something"]': { -// width : 320px -// } -// }} - -// We need to make sure we append the customCSSSelector to the original class selector. i.e & - > &[data-attribute="Icon"][data-size="small"] -*/ -export function generateCustomSxProp( - props: Partial>, - providedSx: BetterSystemStyleObject, -) { - // Possible data attributes: data-size, data-block, data-no-visuals - const size = `[data-size="${props.size}"]` - const block = props.block ? `[data-block="block"]` : '' - const noVisuals = props.leadingVisual || props.trailingVisual || props.trailingAction ? '' : '[data-no-visuals]' - - // this is a custom selector. We need to make sure we add the data attributes to the base css class (& -> &[data-attributename="value"]]) - const cssSelector = `&${size}${block}${noVisuals}` // &[data-size="small"][data-block="block"][data-no-visuals] - - const customSxProp: { - [key: string]: BetterSystemStyleObject - } = {} - - if (!providedSx) return customSxProp - else { - customSxProp[cssSelector] = providedSx - return customSxProp - } -} - ButtonComponent.displayName = 'Button' -ButtonComponent.__SLOT__ = Symbol('Button') +export {ButtonComponent, type ButtonProps} -export {ButtonComponent} +ButtonComponent.__SLOT__ = Symbol('Button') diff --git a/packages/react/src/Button/ButtonBase.tsx b/packages/react/src/Button/ButtonBase.tsx index d5e3ea3b689..97f3564e35c 100644 --- a/packages/react/src/Button/ButtonBase.tsx +++ b/packages/react/src/Button/ButtonBase.tsx @@ -1,12 +1,7 @@ import React, {forwardRef} from 'react' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' -import styled from 'styled-components' -import sx from '../sx' -import type {SxProp} from '../sx' import type {ButtonProps} from './types' -import {getAlignContentSize} from './styles' import {useRefObjectAsForwardedRef} from '../hooks/useRefObjectAsForwardedRef' -import {defaultSxProp} from '../utils/defaultSxProp' import {VisuallyHidden} from '../VisuallyHidden' import Spinner from '../Spinner' import CounterLabel from '../CounterLabel' @@ -17,12 +12,6 @@ import {clsx} from 'clsx' import classes from './ButtonBase.module.css' import {isElement} from 'react-is' -// TODO: remove this when we remove the `sx` prop from buttons -// Styled span component for button content that can handle sx prop -const BoxTemporaryWorkaround = styled.span` - ${sx}; -` - const renderModuleVisual = ( Visual: React.ElementType | React.ReactElement, loading: boolean, @@ -37,294 +26,168 @@ const renderModuleVisual = ( ) -const ButtonBase = forwardRef( - ({children, as: Component = 'button', sx: sxProp = defaultSxProp, ...props}, forwardedRef): JSX.Element => { - const { - leadingVisual: LeadingVisual, - trailingVisual: TrailingVisual, - trailingAction: TrailingAction, - ['aria-describedby']: ariaDescribedBy, - ['aria-labelledby']: ariaLabelledBy, - count, - icon: Icon, - id, - variant = 'default', - size = 'medium', - alignContent = 'center', - block = false, - loading, - loadingAnnouncement = 'Loading', - inactive, - onClick, - labelWrap, - className, - ...rest - } = props +const ButtonBase = forwardRef(({children, as: Component = 'button', ...props}, forwardedRef): JSX.Element => { + const { + leadingVisual: LeadingVisual, + trailingVisual: TrailingVisual, + trailingAction: TrailingAction, + ['aria-describedby']: ariaDescribedBy, + ['aria-labelledby']: ariaLabelledBy, + count, + icon: Icon, + id, + variant = 'default', + size = 'medium', + alignContent = 'center', + block = false, + loading, + loadingAnnouncement = 'Loading', + inactive, + onClick, + labelWrap, + className, + ...rest + } = props - const innerRef = React.useRef(null) - useRefObjectAsForwardedRef(forwardedRef, innerRef) + const innerRef = React.useRef(null) + useRefObjectAsForwardedRef(forwardedRef, innerRef) - const uuid = useId(id) - const loadingAnnouncementID = `${uuid}-loading-announcement` + const uuid = useId(id) + const loadingAnnouncementID = `${uuid}-loading-announcement` - if (__DEV__) { - /** - * The Linter yells because it thinks this conditionally calls an effect, - * but since this is a compile-time flag and not a runtime conditional - * this is safe, and ensures the entire effect is kept out of prod builds - * shaving precious bytes from the output, and avoiding mounting a noop effect - */ - // eslint-disable-next-line react-compiler/react-compiler - // eslint-disable-next-line react-hooks/rules-of-hooks - React.useEffect(() => { - if ( - innerRef.current && - !(innerRef.current instanceof HTMLButtonElement) && - !((innerRef.current as unknown) instanceof HTMLAnchorElement) && - !((innerRef.current as HTMLElement).tagName === 'SUMMARY') - ) { - // eslint-disable-next-line no-console - console.warn('This component should be an instanceof a semantic button or anchor') + if (__DEV__) { + /** + * The Linter yells because it thinks this conditionally calls an effect, + * but since this is a compile-time flag and not a runtime conditional + * this is safe, and ensures the entire effect is kept out of prod builds + * shaving precious bytes from the output, and avoiding mounting a noop effect + */ + // eslint-disable-next-line react-compiler/react-compiler + // eslint-disable-next-line react-hooks/rules-of-hooks + React.useEffect(() => { + if ( + innerRef.current && + !(innerRef.current instanceof HTMLButtonElement) && + !((innerRef.current as unknown) instanceof HTMLAnchorElement) && + !((innerRef.current as HTMLElement).tagName === 'SUMMARY') + ) { + // eslint-disable-next-line no-console + console.warn('This component should be an instanceof a semantic button or anchor') + } + }, [innerRef]) + } + return ( + + Boolean(descriptionID)) + .join(' ')} + // aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state. + // We only set it when the button is in a loading state because it will supersede the aria-label when the screen + // reader announces the button name. + aria-labelledby={ + loading ? [`${uuid}-label`, ariaLabelledBy].filter(labelID => Boolean(labelID)).join(' ') : ariaLabelledBy } - }, [innerRef]) - } - - if (sxProp !== defaultSxProp) { - return ( - - Boolean(descriptionID)) - .join(' ')} - // aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state. - // We only set it when the button is in a loading state because it will supersede the aria-label when the screen - // reader announces the button name. - aria-labelledby={ - loading ? [`${uuid}-label`, ariaLabelledBy].filter(labelID => Boolean(labelID)).join(' ') : ariaLabelledBy - } - id={id} - onClick={loading ? undefined : onClick} - > - {Icon ? ( - loading ? ( - - ) : isElement(Icon) ? ( - Icon - ) : ( - - ) - ) : ( - <> - - { - /* If there are no leading/trailing visuals/actions to replace with a loading spinner, - render a loading spiner in place of the button content. */ - loading && - !LeadingVisual && - !TrailingVisual && - !TrailingAction && - count === undefined && - renderModuleVisual(Spinner, loading, 'loadingSpinner', false) - } - { - /* Render a leading visual unless the button is in a loading state. - Then replace the leading visual with a loading spinner. */ - LeadingVisual && renderModuleVisual(LeadingVisual, Boolean(loading), 'leadingVisual', false) - } - {children && ( - - {children} - - )} - { - /* If there is a count, render a counter label unless there is a trailing visual. - Then render the counter label as a trailing visual. - Replace the counter label or the trailing visual with a loading spinner if: - - the button is in a loading state - - there is no leading visual to replace with a loading spinner - */ - count !== undefined && !TrailingVisual - ? renderModuleVisual( - () => ( - - {count} - - ), - Boolean(loading) && !LeadingVisual, - 'trailingVisual', - true, - ) - : TrailingVisual - ? renderModuleVisual( - TrailingVisual, - Boolean(loading) && !LeadingVisual, - 'trailingVisual', - false, - ) - : null - } - - { - /* If there is a trailing action, render it unless the button is in a loading state - and there is no leading or trailing visual to replace with a loading spinner. */ - TrailingAction && - renderModuleVisual( - TrailingAction, - Boolean(loading) && !LeadingVisual && !TrailingVisual, - 'trailingAction', - false, - ) - } - - )} - - {loading && ( - - {loadingAnnouncement} - - )} - - ) - } - return ( - - Boolean(descriptionID)) - .join(' ')} - // aria-labelledby is needed because the accessible name becomes unset when the button is in a loading state. - // We only set it when the button is in a loading state because it will supersede the aria-label when the screen - // reader announces the button name. - aria-labelledby={ - loading ? [`${uuid}-label`, ariaLabelledBy].filter(labelID => Boolean(labelID)).join(' ') : ariaLabelledBy - } - id={id} - // @ts-ignore temporary disable as we migrate to css modules, until we remove PolymorphicForwardRefComponent - onClick={loading ? undefined : onClick} - > - {Icon ? ( - loading ? ( - - ) : isElement(Icon) ? ( - Icon - ) : ( - - ) + {Icon ? ( + loading ? ( + + ) : isElement(Icon) ? ( + Icon ) : ( - <> - - { - /* If there are no leading/trailing visuals/actions to replace with a loading spinner, + + ) + ) : ( + <> + + { + /* If there are no leading/trailing visuals/actions to replace with a loading spinner, render a loading spiner in place of the button content. */ - loading && - !LeadingVisual && - !TrailingVisual && - !TrailingAction && - count === undefined && - renderModuleVisual(Spinner, loading, 'loadingSpinner', false) - } - { - /* Render a leading visual unless the button is in a loading state. + loading && + !LeadingVisual && + !TrailingVisual && + !TrailingAction && + count === undefined && + renderModuleVisual(Spinner, loading, 'loadingSpinner', false) + } + { + /* Render a leading visual unless the button is in a loading state. Then replace the leading visual with a loading spinner. */ - LeadingVisual && renderModuleVisual(LeadingVisual, Boolean(loading), 'leadingVisual', false) - } - {children && ( - - {children} - - )} - { - /* If there is a count, render a counter label unless there is a trailing visual. + LeadingVisual && renderModuleVisual(LeadingVisual, Boolean(loading), 'leadingVisual', false) + } + {children && ( + + {children} + + )} + { + /* If there is a count, render a counter label unless there is a trailing visual. Then render the counter label as a trailing visual. Replace the counter label or the trailing visual with a loading spinner if: - the button is in a loading state - there is no leading visual to replace with a loading spinner */ - count !== undefined && !TrailingVisual - ? renderModuleVisual( - () => ( - - {count} - - ), - Boolean(loading) && !LeadingVisual, - 'trailingVisual', - true, - ) - : TrailingVisual - ? renderModuleVisual(TrailingVisual, Boolean(loading) && !LeadingVisual, 'trailingVisual', false) - : null - } - - { - /* If there is a trailing action, render it unless the button is in a loading state - and there is no leading or trailing visual to replace with a loading spinner. */ - TrailingAction && - renderModuleVisual( - TrailingAction, - Boolean(loading) && !LeadingVisual && !TrailingVisual, - 'trailingAction', - false, - ) + count !== undefined && !TrailingVisual + ? renderModuleVisual( + () => ( + + {count} + + ), + Boolean(loading) && !LeadingVisual, + 'trailingVisual', + true, + ) + : TrailingVisual + ? renderModuleVisual(TrailingVisual, Boolean(loading) && !LeadingVisual, 'trailingVisual', false) + : null } - - )} - - {loading && ( - - {loadingAnnouncement} - + + { + /* If there is a trailing action, render it unless the button is in a loading state + and there is no leading or trailing visual to replace with a loading spinner. */ + TrailingAction && + renderModuleVisual( + TrailingAction, + Boolean(loading) && !LeadingVisual && !TrailingVisual, + 'trailingAction', + false, + ) + } + )} - - ) - }, -) as PolymorphicForwardRefComponent<'button' | 'a', ButtonProps> + + {loading && ( + + {loadingAnnouncement} + + )} + + ) +}) as PolymorphicForwardRefComponent<'button' | 'a', ButtonProps> export {ButtonBase} diff --git a/packages/react/src/Button/IconButton.docs.json b/packages/react/src/Button/IconButton.docs.json index aea91b2869e..8e5929fa90f 100644 --- a/packages/react/src/Button/IconButton.docs.json +++ b/packages/react/src/Button/IconButton.docs.json @@ -126,11 +126,6 @@ "required": false, "description": "If `description` is provided, we will use a Tooltip to describe the button. Then `aria-label` is used to label the button.", "defaultValue": "" - }, - { - "name": "sx", - "type": "SystemStyleObject", - "deprecated": true } ], "subcomponents": [] diff --git a/packages/react/src/Button/IconButton.tsx b/packages/react/src/Button/IconButton.tsx index 4b86ea1bd48..7c439047429 100644 --- a/packages/react/src/Button/IconButton.tsx +++ b/packages/react/src/Button/IconButton.tsx @@ -2,8 +2,6 @@ import React, {forwardRef} from 'react' import type {IconButtonProps} from './types' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' import {ButtonBase} from './ButtonBase' -import {defaultSxProp} from '../utils/defaultSxProp' -import {generateCustomSxProp} from './Button' import {TooltipContext, Tooltip} from '../TooltipV2/Tooltip' import {TooltipContext as TooltipContextV1} from '../Tooltip/Tooltip' import classes from './ButtonBase.module.css' @@ -12,7 +10,6 @@ import {clsx} from 'clsx' const IconButton = forwardRef( ( { - sx: sxProp = defaultSxProp, icon: Icon, 'aria-label': ariaLabel, description, @@ -27,14 +24,6 @@ const IconButton = forwardRef( }, forwardedRef, ): JSX.Element => { - let sxStyles = sxProp - // grap the button props that have associated data attributes in the styles - const {size = 'medium'} = props - - if (sxProp !== null && Object.keys(sxProp).length > 0) { - sxStyles = generateCustomSxProp({size}, sxProp) - } - // If the icon button is already wrapped in a tooltip, do not add one. const {tooltipId} = React.useContext(TooltipContext) // Tooltip v2 const {tooltipId: tooltipIdV1} = React.useContext(TooltipContextV1) // Tooltip v1 @@ -49,7 +38,6 @@ const IconButton = forwardRef( icon={Icon} className={clsx(className, classes.IconButton)} data-component="IconButton" - sx={sxStyles} type="button" aria-label={ariaLabel} disabled={disabled} @@ -72,7 +60,6 @@ const IconButton = forwardRef( icon={Icon} className={clsx(className, classes.IconButton)} data-component="IconButton" - sx={sxStyles} type="button" aria-keyshortcuts={keyshortcuts ?? undefined} // If description is provided, we will use the tooltip to describe the button, so we need to keep the aria-label to label the button. diff --git a/packages/react/src/Button/LinkButton.tsx b/packages/react/src/Button/LinkButton.tsx index 73cc704ffc1..bac52d497b3 100644 --- a/packages/react/src/Button/LinkButton.tsx +++ b/packages/react/src/Button/LinkButton.tsx @@ -3,7 +3,7 @@ import type {LinkButtonProps as BaseLinkButtonProps, ButtonProps} from './types' import {ButtonBase} from './ButtonBase' import type {ForwardRefComponent as PolymorphicForwardRefComponent} from '../utils/polymorphic' -export type LinkButtonProps = Omit +export type LinkButtonProps = BaseLinkButtonProps & ButtonProps const LinkButton = forwardRef(({children, as: Component = 'a', ...props}, forwardedRef): JSX.Element => { return ( diff --git a/packages/react/src/Button/__tests__/Button.types.test.tsx b/packages/react/src/Button/__tests__/Button.types.test.tsx index 50cebbc11a7..866d9b3046d 100644 --- a/packages/react/src/Button/__tests__/Button.types.test.tsx +++ b/packages/react/src/Button/__tests__/Button.types.test.tsx @@ -21,9 +21,6 @@ export function ShouldAcceptKnownButtonPropsAndDomProps() { // current target is assignable to HTMLButtonElement buttonEl.current = e.currentTarget }} - sx={{ - m: 1, - }} > Child @@ -59,7 +56,6 @@ export function iconButtonOptionalProps() { <> - ) } diff --git a/packages/react/src/Button/styles.ts b/packages/react/src/Button/styles.ts deleted file mode 100644 index dafe8a8050a..00000000000 --- a/packages/react/src/Button/styles.ts +++ /dev/null @@ -1,423 +0,0 @@ -import type {VariantType, AlignContent} from './types' -import type {Theme} from '../ThemeProvider' - -export const getVariantStyles = (variant: VariantType = 'default', theme?: Theme) => { - const style = { - default: { - color: 'btn.text', - backgroundColor: 'btn.bg', - boxShadow: `${theme?.shadows.btn.shadow}, ${theme?.shadows.btn.insetShadow}`, - '&:hover:not([disabled]):not([data-inactive])': { - backgroundColor: 'btn.hoverBg', - borderColor: `var(--button-default-borderColor-hover, ${theme?.colors.btn.hoverBorder})`, - }, - '&:active:not([disabled]):not([data-inactive])': { - backgroundColor: 'btn.activeBg', - borderColor: `var(--button-default-borderColor-active, ${theme?.colors.btn.activeBorder})`, - }, - '&:disabled': { - color: 'primer.fg.disabled', - borderColor: `var(--button-default-borderColor-disabled, ${theme?.colors.btn.border})`, - backgroundColor: `var(--button-default-bgColor-disabled, ${theme?.colors.input.disabledBg})`, - '[data-component=ButtonCounter]': { - color: 'inherit', - }, - }, - '&[aria-expanded=true]': { - backgroundColor: 'btn.activeBg', - borderColor: `var(--button-default-borderColor-active, ${theme?.colors.btn.activeBorder})`, - }, - '[data-component="leadingVisual"], [data-component="trailingVisual"], [data-component="trailingAction"]': { - color: `var(--button-color, ${theme?.colors.fg.muted})`, - }, - '[data-component=ButtonCounter]': { - backgroundColor: 'btn.counterBg', - }, - '&[data-component="IconButton"][data-no-visuals]:not(:disabled)': { - color: 'fg.muted', - }, - }, - primary: { - color: 'btn.primary.text', - backgroundColor: 'btn.primary.bg', - borderColor: 'btn.primary.border', - boxShadow: `${theme?.shadows.btn.primary.shadow}`, - '&:hover:not([disabled]):not([data-inactive])': { - color: 'btn.primary.hoverText', - backgroundColor: 'btn.primary.hoverBg', - }, - '&:focus:not([disabled])': { - boxShadow: 'inset 0 0 0 3px', - }, - '&:focus-visible:not([disabled])': { - boxShadow: 'inset 0 0 0 3px', - }, - '&:active:not([disabled]):not([data-inactive])': { - backgroundColor: 'btn.primary.selectedBg', - boxShadow: `${theme?.shadows.btn.primary.selectedShadow}`, - }, - '&:disabled': { - color: 'btn.primary.disabledText', - backgroundColor: 'btn.primary.disabledBg', - borderColor: 'btn.primary.disabledBorder', - '[data-component=ButtonCounter]': { - color: 'inherit', - }, - }, - '[data-component=ButtonCounter]': { - backgroundColor: 'btn.primary.counterBg', - color: 'btn.primary.text', - }, - '&[aria-expanded=true]': { - backgroundColor: 'btn.primary.selectedBg', - boxShadow: `${theme?.shadows.btn.primary.selectedShadow}`, - }, - }, - danger: { - color: 'btn.danger.text', - backgroundColor: 'btn.bg', - boxShadow: `${theme?.shadows.btn.shadow}`, - '&:hover:not([disabled]):not([data-inactive])': { - color: 'btn.danger.hoverText', - backgroundColor: 'btn.danger.hoverBg', - borderColor: 'btn.danger.hoverBorder', - boxShadow: `${theme?.shadows.btn.danger.hoverShadow}`, - '[data-component=ButtonCounter]': { - backgroundColor: 'btn.danger.hoverCounterBg', - color: 'btn.danger.hoverCounterFg', - }, - }, - '&:active:not([disabled]):not([data-inactive])': { - color: 'btn.danger.selectedText', - backgroundColor: 'btn.danger.selectedBg', - boxShadow: `${theme?.shadows.btn.danger.selectedShadow}`, - borderColor: 'btn.danger.selectedBorder', - }, - '&:disabled': { - color: 'btn.danger.disabledText', - backgroundColor: 'btn.danger.disabledBg', - borderColor: `var(--button-default-borderColor-disabled, ${theme?.colors.btn.border})`, - '[data-component=ButtonCounter]': { - color: 'btn.danger.disabledCounterFg', - backgroundColor: 'btn.danger.disabledCounterBg', - }, - }, - '[data-component=ButtonCounter]': { - color: 'btn.danger.counterFg', - backgroundColor: 'btn.danger.counterBg', - }, - '&[aria-expanded=true]': { - color: 'btn.danger.selectedText', - backgroundColor: 'btn.danger.selectedBg', - boxShadow: `${theme?.shadows.btn.danger.selectedShadow}`, - borderColor: 'btn.danger.selectedBorder', - }, - }, - invisible: { - color: `var(--button-invisible-fgColor-rest, ${theme?.colors.btn.text})`, - backgroundColor: 'transparent', - borderColor: 'transparent', - boxShadow: 'none', - '&:hover:not([disabled])': { - backgroundColor: 'actionListItem.default.hoverBg', - }, - '&:active:not([disabled])': { - backgroundColor: 'actionListItem.default.activeBg', - }, - '&:disabled': { - color: 'primer.fg.disabled', - backgroundColor: `var(--button-invisible-bgColor-disabled, transparent)`, - '[data-component=ButtonCounter], [data-component="leadingVisual"], [data-component="trailingAction"]': { - color: 'inherit', - }, - }, - '&[aria-expanded=true]': { - backgroundColor: 'actionListItem.default.selectedBg', - }, - '&[data-component="IconButton"][data-no-visuals]': { - color: `var(--button-invisible-iconColor-rest, ${theme?.colors.fg.muted})`, - }, - '[data-component="trailingAction"]': { - color: `var(--button-invisible-iconColor-rest, ${theme?.colors.fg.muted})`, - }, - '[data-component="leadingVisual"]': { - color: `var(--button-invisible-iconColor-rest, ${theme?.colors.fg.muted})`, - }, - '[data-component="trailingVisual"]': { - color: `var(--button-invisible-iconColor-rest, ${theme?.colors.fg.muted})`, - }, - '&[data-no-visuals]': { - color: `var(--button-invisible-fgColor-rest, ${theme?.colors.btn.text})`, - }, - '&:has([data-component="ButtonCounter"])': { - color: `var(--button-invisible-fgColor-rest, ${theme?.colors.btn.text})`, - }, - '&:disabled[data-no-visuals]': { - color: 'primer.fg.disabled', - '[data-component=ButtonCounter]': { - color: 'inherit', - }, - }, - }, - outline: { - color: 'btn.outline.text', - boxShadow: `${theme?.shadows.btn.shadow}`, - borderColor: `var(--button-default-borderColor-rest, ${theme?.colors.btn.border})`, - backgroundColor: 'btn.bg', - - '&:hover:not([disabled]):not([data-inactive])': { - color: 'btn.outline.hoverText', - backgroundColor: 'btn.outline.hoverBg', - borderColor: `${theme?.colors.btn.outline.hoverBorder}`, - boxShadow: `${theme?.shadows.btn.outline.hoverShadow}`, - '[data-component=ButtonCounter]': { - backgroundColor: 'btn.outline.hoverCounterBg', - color: 'btn.outline.hoverCounterFg', - }, - }, - '&:active:not([disabled]):not([data-inactive])': { - color: 'btn.outline.selectedText', - backgroundColor: 'btn.outline.selectedBg', - boxShadow: `${theme?.shadows.btn.outline.selectedShadow}`, - borderColor: `${theme?.colors.btn.outline.selectedBorder}`, - }, - - '&:disabled': { - color: 'btn.outline.disabledText', - backgroundColor: 'btn.outline.disabledBg', - borderColor: 'btn.border', - '[data-component=ButtonCounter]': { - backgroundColor: 'btn.outline.disabledCounterBg', - color: 'btn.outline.disabledCounterFg', - }, - }, - '[data-component=ButtonCounter]': { - backgroundColor: 'btn.outline.counterBg', - color: 'btn.outline.counterFg', - }, - '&[aria-expanded=true]': { - color: 'btn.outline.selectedText', - backgroundColor: 'btn.outline.selectedBg', - boxShadow: `${theme?.shadows.btn.outline.selectedShadow}`, - borderColor: `var(--button-default-borderColor-active, ${theme?.colors.btn.outline.selectedBorder})`, - }, - }, - link: { - color: 'var(--fgColor-link)', - display: 'inline-block', - fontSize: 'inherit', - border: 'none', - height: 'unset', - padding: '0', - minWidth: 'fit-content', - backgroundColor: 'transparent', - - '&:hover:not([disabled]):not([data-inactive])': { - textDecoration: 'underline', - }, - - '&:focus-visible:not([disabled])': { - outlineOffset: '2px', - }, - - '&:disabled': { - color: 'primer.fg.disabled', - '[data-component=ButtonCounter], [data-component="leadingVisual"], [data-component="trailingAction"]': { - color: 'inherit', - }, - }, - - '[data-component="text"]': { - whiteSpace: 'unset', - }, - }, - } - - return style[variant] -} - -export const getBaseStyles = (theme?: Theme) => ({ - borderRadius: '2', - border: '1px solid', - borderColor: `var(--button-default-borderColor-rest, ${theme?.colors.btn.border})`, - fontFamily: 'inherit', - fontWeight: 'semibold', - fontSize: '1', - cursor: 'pointer', - appearance: 'none', - userSelect: 'none', - textDecoration: 'none', - textAlign: 'center', - display: 'flex', - alignItems: 'center', - justifyContent: 'space-between', - height: '32px', - padding: '0 12px', - gap: '8px', - minWidth: 'max-content', - transition: '80ms cubic-bezier(0.65, 0, 0.35, 1)', - transitionProperty: 'color, fill, background-color, border-color', - '&[href]': { - display: 'inline-flex', - '&:hover': { - textDecoration: 'none', - }, - }, - '&:hover': { - transitionDuration: '80ms', - }, - '&:active': { - transition: 'none', - }, - '&[data-inactive]': { - cursor: 'auto', - }, - '&:disabled': { - cursor: 'not-allowed', - boxShadow: 'none', - }, - '@media (forced-colors: active)': { - '&:focus': { - // Support for Windows high contrast https://sarahmhigley.com/writing/whcm-quick-tips - outline: 'solid 1px transparent', - }, - }, - '[data-component=ButtonCounter]': { - fontSize: '0', - }, - '&[data-component=IconButton]': { - display: 'inline-grid', - padding: 'unset', - placeContent: 'center', - width: '32px', - minWidth: 'unset', - }, - '&[data-size="small"]': { - padding: '0 8px', - height: '28px', - gap: '4px', - fontSize: '0', - - '[data-component="text"]': { - lineHeight: '1.6666667', - }, - - '[data-component=ButtonCounter]': { - fontSize: '0', - }, - - '[data-component="buttonContent"] > :not(:last-child)': { - mr: '4px', - }, - - '&[data-component=IconButton]': { - width: '28px', - padding: 'unset', - }, - }, - '&[data-size="large"]': { - padding: '0 16px', - height: '40px', - gap: '8px', - - '[data-component="buttonContent"] > :not(:last-child)': { - mr: '8px', - }, - - '&[data-component=IconButton]': { - width: '40px', - padding: 'unset', - }, - }, -}) - -export const getButtonStyles = (theme?: Theme) => { - const styles = { - ...getBaseStyles(theme), - '&[data-block="block"]': { - width: '100%', - }, - '&[data-label-wrap="true"]': { - minWidth: 'fit-content', - height: 'unset', - minHeight: 'var(--control-medium-size, 2rem)', - - '[data-component="buttonContent"]': { - flex: '1 1 auto', - alignSelf: 'stretch', - paddingBlock: 'calc(var(--control-medium-paddingBlock, 0.375rem) - 2px)', - }, - - '[data-component="text"]': { - whiteSpace: 'unset', - wordBreak: 'break-word', - }, - - '&[data-size="small"]': { - height: 'unset', - minHeight: 'var(--control-small-size, 1.75rem)', - - '[data-component="buttonContent"]': { - paddingBlock: 'calc(var(--control-small-paddingBlock, 0.25rem) - 2px)', - }, - }, - - '&[data-size="large"]': { - height: 'unset', - minHeight: 'var(--control-large-size, 2.5rem)', - paddingInline: 'var(--control-large-paddingInline-spacious, 1rem)', - - '[data-component="buttonContent"]': { - paddingBlock: 'calc(var(--control-large-paddingBlock, 0.625rem) - 2px)', - }, - }, - }, - '&[data-inactive]:not([disabled])': { - backgroundColor: `var(--button-inactive-bgColor, ${theme?.colors.btn.inactive.bg})`, - borderColor: `var(--button-inactive-bgColor, ${theme?.colors.btn.inactive.bg})`, - color: `var(--button-inactive-fgColor, ${theme?.colors.btn.inactive.text})`, - }, - '&[data-inactive]:not([disabled]):focus-visible': { - boxShadow: 'none', - }, - '[data-component="leadingVisual"]': { - gridArea: 'leadingVisual', - }, - '[data-component="text"]': { - gridArea: 'text', - lineHeight: '1.4285714', - whiteSpace: 'nowrap', - }, - '[data-component="trailingVisual"]': { - gridArea: 'trailingVisual', - }, - '[data-component="trailingAction"]': { - marginRight: '-4px', - }, - '[data-component="buttonContent"]': { - flex: '1 0 auto', - display: 'grid', - gridTemplateAreas: '"leadingVisual text trailingVisual"', - gridTemplateColumns: 'min-content minmax(0, auto) min-content', - alignItems: 'center', - alignContent: 'center', - }, - '[data-component="buttonContent"] > :not(:last-child)': { - mr: '8px', - }, - '[data-component="loadingSpinner"]': { - gridArea: 'text', - marginRight: '0px !important', - placeSelf: 'center', - color: 'fg.muted', - }, - '[data-component="loadingSpinner"] + [data-component="text"]': { - visibility: 'hidden', - }, - } - return styles -} - -export const getAlignContentSize = (alignContent: AlignContent = 'center') => ({ - justifyContent: alignContent === 'center' ? 'center' : 'flex-start', -}) diff --git a/packages/react/src/Button/types.ts b/packages/react/src/Button/types.ts index cc9c183bacb..1394f10962a 100644 --- a/packages/react/src/Button/types.ts +++ b/packages/react/src/Button/types.ts @@ -1,16 +1,7 @@ import type React from 'react' -import styled from 'styled-components' -import type {SxProp} from '../sx' -import sx from '../sx' -import getGlobalFocusStyles from '../internal/utils/getGlobalFocusStyles' import type {TooltipDirection} from '../TooltipV2' import type {IconProps} from '@primer/octicons-react' -export const StyledButton = styled.button` - ${getGlobalFocusStyles('-2px')}; - ${sx}; -` - export type VariantType = 'default' | 'primary' | 'invisible' | 'danger' | 'link' export type Size = 'small' | 'medium' | 'large' @@ -56,8 +47,7 @@ export type ButtonBaseProps = { * Whether the button label should wrap to multiple lines if it is longer than the button width */ labelWrap?: boolean -} & SxProp & - React.ButtonHTMLAttributes +} & React.ButtonHTMLAttributes export type ButtonProps = { /** diff --git a/packages/react/src/Overlay/Overlay.features.stories.tsx b/packages/react/src/Overlay/Overlay.features.stories.tsx index 2966536e1b4..dba4f1d0717 100644 --- a/packages/react/src/Overlay/Overlay.features.stories.tsx +++ b/packages/react/src/Overlay/Overlay.features.stories.tsx @@ -62,7 +62,7 @@ export const DropdownOverlay = ({anchorSide, open}: Args) => { return ( <> - {isOpen || open ? ( @@ -261,7 +261,7 @@ export const MemexNestedOverlays = ({role, open}: Args) => { Duration: - + {duration} @@ -359,7 +359,13 @@ export const NestedOverlays = ({role, open}: Args) => { {state === 'loading' && } diff --git a/packages/react/src/UnderlineNav/UnderlineNav.module.css b/packages/react/src/UnderlineNav/UnderlineNav.module.css index b016174571a..d905aff3fcf 100644 --- a/packages/react/src/UnderlineNav/UnderlineNav.module.css +++ b/packages/react/src/UnderlineNav/UnderlineNav.module.css @@ -3,3 +3,20 @@ align-items: center; justify-content: space-between; } + +/* More button styles migrated from styles.ts (was moreBtnStyles) */ +.MoreButton { + margin: 0; /* reset Safari extra margin */ + border: 0; + background: transparent; + font-weight: var(--base-text-weight-normal); + box-shadow: none; + padding-top: var(--base-size-4); + padding-bottom: var(--base-size-4); + padding-left: var(--base-size-8); + padding-right: var(--base-size-8); + + & > [data-component='trailingVisual'] { + margin-left: 0; + } +} diff --git a/packages/react/src/UnderlineNav/UnderlineNav.tsx b/packages/react/src/UnderlineNav/UnderlineNav.tsx index 796d7f053da..c72f9c57577 100644 --- a/packages/react/src/UnderlineNav/UnderlineNav.tsx +++ b/packages/react/src/UnderlineNav/UnderlineNav.tsx @@ -5,7 +5,7 @@ import type {ResizeObserverEntry} from '../hooks/useResizeObserver' import {useResizeObserver} from '../hooks/useResizeObserver' import type {ChildWidthArray, ResponsiveProps, ChildSize} from './types' import VisuallyHidden from '../_VisuallyHidden' -import {moreBtnStyles, dividerStyles, menuItemStyles, baseMenuMinWidth} from './styles' +import {dividerStyles, menuItemStyles, baseMenuMinWidth} from './styles' import {UnderlineItemList, UnderlineWrapper, LoadingCounter, GAP} from '../internal/components/UnderlineTabbedInterface' import {Button} from '../Button' import {TriangleDownIcon} from '@primer/octicons-react' @@ -334,7 +334,7 @@ export const UnderlineNav = forwardRef( {!onlyMenuVisible &&
}