diff --git a/.changeset/blankslate-size-scale.md b/.changeset/blankslate-size-scale.md new file mode 100644 index 00000000000..27f011a27d5 --- /dev/null +++ b/.changeset/blankslate-size-scale.md @@ -0,0 +1,5 @@ +--- +'@primer/react': minor +--- + +Blankslate: Add compact-default sizing and default-sized primary actions behind the default-off `primer_react_blankslate_compact_default` feature flag. The `small` size remains supported. diff --git a/packages/react/src/Blankslate/Blankslate.module.css b/packages/react/src/Blankslate/Blankslate.module.css index 8604a1dcb12..106e492f366 100644 --- a/packages/react/src/Blankslate/Blankslate.module.css +++ b/packages/react/src/Blankslate/Blankslate.module.css @@ -137,3 +137,52 @@ } } } + +.Blankslate:where([data-compact-default][data-size='medium']) { + --blankslate-heading-text: var(--text-title-shorthand-small); + --blankslate-heading-margin-block: 0 var(--base-size-4); + --blankslate-description-text: var(--text-body-shorthand-medium); + --blankslate-description-margin-block: 0; + --blankslate-padding: var(--base-size-16); + --blankslate-action-margin-block-end: var(--base-size-12); + --blankslate-visual-size: var(--base-size-24); + + &:where([data-spacious]) { + --blankslate-padding: var(--base-size-44) var(--base-size-28); + } +} + +.Blankslate:where([data-compact-default][data-size='large']) { + --blankslate-heading-text: var(--text-title-shorthand-medium); + --blankslate-heading-margin-block: 0 var(--base-size-4); + --blankslate-description-text: var(--text-body-shorthand-large); + --blankslate-description-margin-block: 0; + --blankslate-padding: var(--base-size-32); + --blankslate-action-margin-block-end: var(--base-size-16); + + &:where([data-spacious]) { + --blankslate-padding: var(--base-size-80) var(--base-size-40); + } +} + +.Blankslate:where([data-compact-default]) { + & .Action { + margin-block-start: var(--base-size-16); + } + + &[data-size='medium'] .Action:last-of-type { + margin-block-end: var(--base-size-12); + } + + &[data-size='large'] .Action:last-of-type { + margin-block-end: var(--base-size-16); + } + + & .Visual { + margin-block-end: var(--base-size-8); + } +} + +.Blankslate:where([data-compact-default][data-size='large']) .Visual { + max-width: none; +} diff --git a/packages/react/src/Blankslate/Blankslate.test.tsx b/packages/react/src/Blankslate/Blankslate.test.tsx index 912d0e02daa..38b3e51ffca 100644 --- a/packages/react/src/Blankslate/Blankslate.test.tsx +++ b/packages/react/src/Blankslate/Blankslate.test.tsx @@ -2,6 +2,7 @@ import {describe, expect, it, vi} from 'vitest' import {render, screen} from '@testing-library/react' import userEvent from '@testing-library/user-event' import {Blankslate} from '../Blankslate' +import {FeatureFlags} from '../FeatureFlags' import {implementsClassName} from '../utils/testing' import classes from './Blankslate.module.css' @@ -23,11 +24,12 @@ function getCSSStyleRules(): Array { }) } -function getSizePadding(size: 'small' | 'medium' | 'large') { +function getSizePadding(size: 'small' | 'medium' | 'large', compactDefault = false) { const rule = getCSSStyleRules().find(cssRule => { return ( cssRule.selectorText.includes(`.${classes.Blankslate}`) && cssRule.selectorText.includes(`data-size="${size}"`) && + cssRule.selectorText.includes('data-compact-default') === compactDefault && !cssRule.selectorText.includes('data-spacious') ) }) @@ -59,6 +61,39 @@ describe('Blankslate', () => { expect(getSizePadding('small')).toMatch(/^var\(--base-size-16/) }) + it('keeps the existing size scale when the compact default feature is disabled', () => { + const {container} = render(Test content) + const blankslate = container.querySelector('[data-component="Blankslate"]') + + expect(blankslate).toHaveAttribute('data-size', 'small') + expect(blankslate).not.toHaveAttribute('data-compact-default') + }) + + it('uses the compact default size scale when the feature is enabled', () => { + const {container} = render( + + Test content + , + ) + const blankslate = container.querySelector('[data-component="Blankslate"]') + + expect(blankslate).toHaveAttribute('data-size', 'medium') + expect(blankslate).toHaveAttribute('data-compact-default', '') + expect(getSizePadding('medium', true)).toMatch(/^var\(--base-size-16/) + expect(getSizePadding('large', true)).toMatch(/^var\(--base-size-32/) + }) + + it('maps the legacy small size to the compact default size when the feature is enabled', () => { + const {container} = render( + + Test content + , + ) + const blankslate = container.querySelector('[data-component="Blankslate"]') + + expect(blankslate).toHaveAttribute('data-size', 'medium') + }) + it('renders data-component attributes', () => { const {container} = render( @@ -146,6 +181,28 @@ describe('Blankslate', () => { expect(screen.getByRole('button', {name: 'Primary action'})).toBeInTheDocument() }) + it('uses the default button size when the compact default feature is enabled', () => { + render( + + + Primary action + + , + ) + + expect(screen.getByRole('button', {name: 'Primary action'})).toHaveAttribute('data-size', 'medium') + }) + + it('keeps the small button size when the compact default feature is disabled', () => { + render( + + Primary action + , + ) + + expect(screen.getByRole('button', {name: 'Primary action'})).toHaveAttribute('data-size', 'small') + }) + it('should handle click events on the button', async () => { const user = userEvent.setup() const onClick = vi.fn() diff --git a/packages/react/src/Blankslate/Blankslate.tsx b/packages/react/src/Blankslate/Blankslate.tsx index fa785e9991b..21908142cb9 100644 --- a/packages/react/src/Blankslate/Blankslate.tsx +++ b/packages/react/src/Blankslate/Blankslate.tsx @@ -2,6 +2,7 @@ import {clsx} from 'clsx' import {useMemo} from 'react' import type React from 'react' import {Button} from '../Button' +import {useFeatureFlag} from '../FeatureFlags' import Link from '../Link' import {Provider, useBlankslate} from './BlankslateContext' import classes from './Blankslate.module.css' @@ -34,11 +35,13 @@ type BlankslateProps = React.HTMLAttributes & { } function Blankslate({border, children, narrow, spacious, className, size = 'medium', ...rest}: BlankslateProps) { + const compactDefault = useFeatureFlag('primer_react_blankslate_compact_default') const value = useMemo(() => { return { size, + compactDefault, } - }, [size]) + }, [size, compactDefault]) return ( @@ -49,7 +52,8 @@ function Blankslate({border, children, narrow, spacious, className, size = 'medi data-border={border ? '' : undefined} data-narrow={narrow ? '' : undefined} data-spacious={spacious ? '' : undefined} - data-size={size} + data-compact-default={compactDefault ? '' : undefined} + data-size={compactDefault && size === 'small' ? 'medium' : size} > {children} @@ -108,7 +112,7 @@ type BlankslatePrimaryActionProps = }> function PrimaryAction({children, href, ...props}: BlankslatePrimaryActionProps) { - const {size} = useBlankslate() + const {size, compactDefault} = useBlankslate() return (
diff --git a/packages/react/src/Blankslate/BlankslateContext.ts b/packages/react/src/Blankslate/BlankslateContext.ts index 59bed1202bc..0c8afaa5890 100644 --- a/packages/react/src/Blankslate/BlankslateContext.ts +++ b/packages/react/src/Blankslate/BlankslateContext.ts @@ -2,6 +2,7 @@ import {createContext, useContext} from 'react' type BlankslateContextType = { size: 'small' | 'medium' | 'large' + compactDefault: boolean } const BlankslateContext = createContext(null) diff --git a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts index 0862870621f..726bcd42906 100644 --- a/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts +++ b/packages/react/src/FeatureFlags/DefaultFeatureFlags.ts @@ -11,4 +11,5 @@ export const DefaultFeatureFlags = FeatureFlagScope.create({ primer_react_timeline_list_semantics: false, primer_react_merged_forwarded_refs: false, primer_react_button_loading_disabled_styles: false, + primer_react_blankslate_compact_default: false, })