Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions examples/bpk-component-layout/box-examples.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@
*/

import {
BACKGROUND_COLORS,
BpkBox,
BpkSpacing,
} from '../../packages/bpk-component-layout';
import { TEXT_COLORS } from '../../packages/bpk-component-text';

import Wrapper from './layout-wrapper';

import type { BpkBoxBackgroundColor } from '../../packages/bpk-component-layout';

import STYLES from './examples.module.scss';

/**
Expand Down Expand Up @@ -183,6 +187,111 @@ export const GridExample = () => (
</Wrapper>
);

type ColorSwatch = { label: string; value: BpkBoxBackgroundColor };

const SURFACE_SWATCHES: ColorSwatch[] = [
{ label: 'surfaceDefault', value: BACKGROUND_COLORS.surfaceDefault },
{ label: 'surfaceElevated', value: BACKGROUND_COLORS.surfaceElevated },
{ label: 'surfaceLowContrast', value: BACKGROUND_COLORS.surfaceLowContrast },
{ label: 'surfaceSubtle', value: BACKGROUND_COLORS.surfaceSubtle },
{ label: 'surfaceTint', value: BACKGROUND_COLORS.surfaceTint },
{ label: 'surfaceHighlight', value: BACKGROUND_COLORS.surfaceHighlight },
{ label: 'surfaceHero', value: BACKGROUND_COLORS.surfaceHero },
{ label: 'surfaceContrast', value: BACKGROUND_COLORS.surfaceContrast },
];

const STATUS_FILL_SWATCHES: ColorSwatch[] = [
{ label: 'statusSuccessFill', value: BACKGROUND_COLORS.statusSuccessFill },
{ label: 'statusDangerFill', value: BACKGROUND_COLORS.statusDangerFill },
{ label: 'statusWarningFill', value: BACKGROUND_COLORS.statusWarningFill },
];

const CANVAS_SWATCHES: ColorSwatch[] = [
{ label: 'canvas', value: BACKGROUND_COLORS.canvas },
{ label: 'canvasContrast', value: BACKGROUND_COLORS.canvasContrast },
];

const SwatchGrid = ({ columns, swatches }: { columns: number; swatches: ColorSwatch[] }) => (
<BpkBox padding={BpkSpacing.MD}>
<BpkBox
display="grid"
gridTemplateColumns={`repeat(${columns}, minmax(0, 1fr))`}
gap={BpkSpacing.SM}
>
{swatches.map(({ label, value }) => (
<BpkBox
key={label}
backgroundColor={value}
padding={BpkSpacing.MD}
minHeight="5rem"
>
<span className={STYLES['bpk-layout-examples__outline']}>{label}</span>
</BpkBox>
))}
</BpkBox>
</BpkBox>
);

/**
* Surface background color swatches – shows all surface tokens available for backgroundColor.
*
* @returns {JSX.Element} A grid of surface color swatches.
*/
export const SurfaceBackgroundColorExample = () => (
<Wrapper>
<SwatchGrid columns={4} swatches={SURFACE_SWATCHES} />
</Wrapper>
);

/**
* Status fill background color swatches – shows status fill tokens for backgroundColor.
*
* @returns {JSX.Element} A grid of status fill color swatches.
*/
export const StatusFillBackgroundColorExample = () => (
<Wrapper>
<SwatchGrid columns={3} swatches={STATUS_FILL_SWATCHES} />
</Wrapper>
);

/**
* Canvas background color swatches – shows canvas tokens for backgroundColor.
*
* @returns {JSX.Element} A grid of canvas color swatches.
*/
export const CanvasBackgroundColorExample = () => (
<Wrapper>
<SwatchGrid columns={2} swatches={CANVAS_SWATCHES} />
</Wrapper>
);

const TEXT_COLOR_SWATCHES = Object.entries(TEXT_COLORS).map(([label, value]) => ({
label,
value,
}));

/**
* Text color example – shows all TEXT_COLORS tokens available for the color prop.
*
* @returns {JSX.Element} A grid of text color swatches.
*/
export const ColorExample = () => (
<Wrapper>
<BpkBox
display="grid"
gridTemplateColumns="repeat(3, minmax(0, 1fr))"
gap={BpkSpacing.SM}
padding={BpkSpacing.MD}
>
{TEXT_COLOR_SWATCHES.map(({ label, value }) => (
<BpkBox key={label} color={value} padding={BpkSpacing.SM}>
<span className={STYLES['bpk-layout-examples__outline']}>{label}</span>
</BpkBox>
))}
</BpkBox>
</Wrapper>
);

/**
* Mixed visual regression example – used for Percy/visual tests.
*
Expand Down
8 changes: 8 additions & 0 deletions examples/bpk-component-layout/box.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import {
PositionExample,
FlexExample,
GridExample,
SurfaceBackgroundColorExample,
StatusFillBackgroundColorExample,
CanvasBackgroundColorExample,
ColorExample,
} from './box-examples';

export default {
Expand Down Expand Up @@ -63,5 +67,9 @@ export const Responsive = () => <ResponsiveExample />;
export const Position = () => <PositionExample />;
export const FlexViaBox = () => <FlexExample />;
export const GridViaBox = () => <GridExample />;
export const SurfaceBackgroundColor = () => <SurfaceBackgroundColorExample />;
export const StatusFillBackgroundColor = () => <StatusFillBackgroundColorExample />;
export const CanvasBackgroundColor = () => <CanvasBackgroundColorExample />;
export const Color = () => <ColorExample />;


17 changes: 7 additions & 10 deletions examples/bpk-component-layout/examples.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
.bpk-layout-examples {
&__frame {
padding: tokens.bpk-spacing-base();
background-color: tokens.$bpk-canvas-contrast-day;
border-radius: tokens.$bpk-border-radius-md;
background-color: tokens.$bpk-canvas-contrast-day;

// Subtle grid to make layout boundaries easier to see.
background-size: 8px 8px;
background-size: tokens.bpk-spacing-sm() tokens.bpk-spacing-sm();

// Outline the root element of each example (not the inner content).
// This avoids needing border props on layout primitives.
Expand All @@ -41,26 +41,23 @@
// Use this on regular DOM elements (not layout primitives) to avoid exposing
// border props on the layout surface.
&__item {
border-radius: tokens.$bpk-border-radius-sm;
outline: tokens.$bpk-border-size-sm solid tokens.$bpk-line-day;
outline-offset: 0;
border-radius: tokens.$bpk-border-radius-sm;
background-color: tokens.$bpk-canvas-day;
box-sizing: border-box;
}

&__outline {
@include typography.bpk-body-default;

display: block;
width: 100%;
padding: tokens.bpk-spacing-sm();

color: tokens.$bpk-text-primary-day;
background-color: tokens.$bpk-surface-highlight-day;

border-radius: tokens.$bpk-border-radius-sm;
outline: tokens.$bpk-border-size-sm dashed tokens.$bpk-line-day;
outline-offset: 0;
border-radius: tokens.$bpk-border-radius-sm;
background-color: tokens.$bpk-surface-highlight-day;
box-sizing: border-box;

@include typography.bpk-body-default;
}
}
3 changes: 3 additions & 0 deletions packages/bpk-component-layout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export type {
} from './src/types';
export type { BpkStackSpecificProps } from './src/types';

export { BACKGROUND_COLORS } from './src/backgroundColors';
export type { BpkBoxBackgroundColor } from './src/backgroundColors';

// Export token types and utilities
export type {
BpkSpacingToken,
Expand Down
39 changes: 39 additions & 0 deletions packages/bpk-component-layout/src/BpkBox-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import '@testing-library/jest-dom';

import { BpkBox } from './BpkBox';
import { BpkProvider } from './BpkProvider';
import { BACKGROUND_COLORS } from './backgroundColors';
import { BpkSpacing } from './tokens';

describe('BpkBox', () => {
Expand Down Expand Up @@ -65,6 +66,44 @@ describe('BpkBox', () => {
// but we at least assert that the element rendered successfully.
});

describe('backgroundColor', () => {
it('renders with a surface color token', () => {
const { container } = render(
Comment on lines +69 to +71
<BpkProvider>
<BpkBox backgroundColor={BACKGROUND_COLORS.surfaceDefault}>Content</BpkBox>
</BpkProvider>,
);
expect(container.querySelector('div')).toBeInTheDocument();
});

it('renders with a status fill color token', () => {
const { container } = render(
<BpkProvider>
<BpkBox backgroundColor={BACKGROUND_COLORS.statusSuccessFill}>Content</BpkBox>
</BpkProvider>,
);
expect(container.querySelector('div')).toBeInTheDocument();
});

it('renders with a canvas color token', () => {
const { container } = render(
<BpkProvider>
<BpkBox backgroundColor={BACKGROUND_COLORS.canvas}>Content</BpkBox>
</BpkProvider>,
);
expect(container.querySelector('div')).toBeInTheDocument();
});

it('renders without backgroundColor when prop is omitted', () => {
const { container } = render(
<BpkProvider>
<BpkBox>No background</BpkBox>
</BpkProvider>,
);
expect(container.querySelector('div')).toBeInTheDocument();
});
});

it('supports basic interaction props: onClick, onFocus, onBlur', () => {
const handleClick = jest.fn();
const handleFocus = jest.fn();
Expand Down
69 changes: 69 additions & 0 deletions packages/bpk-component-layout/src/BpkBox.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Backpack - Skyscanner's Design System
*
* Copyright 2016 Skyscanner Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

// backgroundColor keys must stay in sync with backgroundColors.ts
// color keys must stay in sync with TEXT_COLORS in bpk-component-text
Comment on lines +19 to +20

@use '../../bpk-mixins/tokens';
@use '../../bpk-mixins/surfaces';

$text-colors: (
'text-disabled': tokens.$bpk-text-disabled-day,
'text-disabled-on-dark': tokens.$bpk-text-disabled-on-dark-day,
'text-error': tokens.$bpk-text-error-day,
'text-hero': tokens.$bpk-text-hero-day,
'text-link': tokens.$bpk-text-link-day,
'text-on-dark': tokens.$bpk-text-on-dark-day,
'text-on-light': tokens.$bpk-text-on-light-day,
'text-primary': tokens.$bpk-text-primary-day,
'text-primary-inverse': tokens.$bpk-text-primary-inverse-day,
'text-secondary': tokens.$bpk-text-secondary-day,
'text-success': tokens.$bpk-text-success-day,
);

.bpk-box {
@include surfaces.bpk-surface-bg-colors;

@each $color-name, $color-value in $text-colors {
// Double-class selector matches BpkText pattern, needed to beat Chakra emotion specificity.
// stylelint-disable-next-line selector-class-pattern
&.bpk-box--#{$color-name} {
color: $color-value;
}
}

&--status-success-fill {
background-color: tokens.$bpk-status-success-fill-day;
}

&--status-danger-fill {
background-color: tokens.$bpk-status-danger-fill-day;
}

&--status-warning-fill {
background-color: tokens.$bpk-status-warning-fill-day;
}

&--canvas {
background-color: tokens.$bpk-canvas-day;
}

&--canvas-contrast {
background-color: tokens.$bpk-canvas-contrast-day;
}
}
39 changes: 30 additions & 9 deletions packages/bpk-component-layout/src/BpkBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,42 @@
* limitations under the License.
*/

import { forwardRef } from 'react';

import { Box } from '@chakra-ui/react';

import { getDataComponentAttribute } from '../../bpk-react-utils';
import { cssModules, getDataComponentAttribute } from '../../bpk-react-utils';

import { processBpkComponentProps } from './tokenUtils';

import type { BpkBoxProps } from './types';

export const BpkBox = ({ children, ...props }: BpkBoxProps) => {
const processedProps = processBpkComponentProps(props, { component: 'BpkBox' });
return (
<Box {...getDataComponentAttribute('Box')} {...processedProps}>
{children}
</Box>
);
};
import STYLES from './BpkBox.module.scss';

const getClassName = cssModules(STYLES);

export const BpkBox = forwardRef<HTMLElement, BpkBoxProps>(
({ backgroundColor, children, color, ...props }, ref) => {
const processedProps = processBpkComponentProps(props, { component: 'BpkBox' });
const combinedClass = getClassName(
backgroundColor ? `bpk-box--${backgroundColor}` : '',
// 'bpk-box' base class is required alongside the modifier to form a two-class
// compound selector (.bpk-box.bpk-box--text-*), matching BpkText's pattern and
// beating Chakra emotion's default color specificity.
color ? 'bpk-box' : '',
color ? `bpk-box--${color}` : '',
) || undefined;

return (
// className is allowed here: combinedClass is an internal SCSS module class, not a consumer override.
// eslint-disable-next-line @skyscanner/rules/forbid-component-props
<Box ref={ref} className={combinedClass} {...getDataComponentAttribute('Box')} {...processedProps}>
{children}
</Box>
);
},
);

BpkBox.displayName = 'BpkBox';

export type { BpkBoxProps };
Loading
Loading