First pass at adding the new surfaces

Signed-off-by: Charles de Dreuille <charles.dedreuille@gmail.com>
This commit is contained in:
Charles de Dreuille
2025-12-22 22:53:50 +01:00
parent 027ef66a7c
commit 0edcb325c7
8 changed files with 203 additions and 23 deletions
@@ -22,4 +22,20 @@
font-weight: var(--bui-font-weight-regular);
color: var(--bui-fg-primary);
}
.bui-Box[data-surface='0'] {
background-color: var(--bui-bg-surface-0);
}
.bui-Box[data-surface='1'] {
background-color: var(--bui-bg-surface-1);
}
.bui-Box[data-surface='2'] {
background-color: var(--bui-bg-surface-2);
}
.bui-Box[data-surface='3'] {
background-color: var(--bui-bg-surface-3);
}
}
@@ -18,6 +18,7 @@ import preview from '../../../../../.storybook/preview';
import { ReactNode } from 'react';
import { Box } from './Box';
import { Flex } from '../Flex';
import { Button } from '../Button';
const meta = preview.meta({
title: 'Backstage UI/Box',
@@ -344,3 +345,50 @@ export const Display = meta.story({
</Flex>
),
});
export const Surfaces = meta.story({
args: { px: '6', py: '4' },
render: args => (
<Flex align="center">
<Box {...args}>Default</Box>
<Box surface="0" {...args}>
Surface 0
</Box>
<Box surface="1" {...args}>
Surface 1
</Box>
<Box surface="2" {...args}>
Surface 2
</Box>
<Box surface="3" {...args}>
Surface 3
</Box>
<Box surface={{ initial: '0', sm: '1' }} {...args}>
Responsive Surface
</Box>
</Flex>
),
});
export const SurfacesNested = meta.story({
args: { px: '6', py: '4' },
render: args => (
<Flex direction="column">
<Box style={{ maxWidth: '600px' }} mb="4">
In this test, we are nesting boxes and buttons on different surfaces to
ensure that the correct surface is applied to each element. If a Button
is placed on a surface that doesn't have the surface prop set, it will
inherit the surface from the parent.
</Box>
<Box {...args} surface="1">
<Button>Button</Button>
<Box {...args} surface="2" mt="4">
<Button>Button</Button>
<Box {...args} mt="4">
<Button>Button</Button>
</Box>
</Box>
</Box>
</Flex>
),
});
+10 -6
View File
@@ -20,15 +20,14 @@ import clsx from 'clsx';
import { useStyles } from '../../hooks/useStyles';
import styles from './Box.module.css';
import { BoxDefinition } from './definition';
import { SurfaceProvider } from '../../hooks/useSurface';
/** @public */
export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
const { classNames, utilityClasses, style, cleanedProps } = useStyles(
BoxDefinition,
props,
);
const { classNames, dataAttributes, utilityClasses, style, cleanedProps } =
useStyles(BoxDefinition, props);
const { as = 'div', children, className, ...rest } = cleanedProps;
const { as = 'div', children, className, surface, ...rest } = cleanedProps;
return createElement(
as,
@@ -41,9 +40,14 @@ export const Box = forwardRef<HTMLDivElement, BoxProps>((props, ref) => {
className,
),
style,
...dataAttributes,
...rest,
},
children,
surface ? (
<SurfaceProvider surface={surface}>{children}</SurfaceProvider>
) : (
children
),
);
});
@@ -48,4 +48,7 @@ export const BoxDefinition = {
'minHeight',
'maxHeight',
],
dataAttributes: {
surface: ['0', '1', '2', '3'] as const,
},
} as const satisfies ComponentDefinition;
+2 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import type { SpaceProps, Responsive } from '../../types';
import type { SpaceProps, Responsive, Surface } from '../../types';
/** @public */
export interface BoxProps extends SpaceProps {
@@ -32,4 +32,5 @@ export interface BoxProps extends SpaceProps {
children?: React.ReactNode;
className?: string;
style?: React.CSSProperties;
surface?: Responsive<Surface>;
}
+58 -16
View File
@@ -80,24 +80,45 @@
--bui-gray-7: #757575;
--bui-gray-8: #595959;
/* Background Colors */
/* Surfaces colors */
--bui-bg-surface-0: var(--bui-gray-1);
--bui-bg-surface-1: var(--bui-white);
--bui-bg-surface-2: var(--bui-gray-1);
--bui-bg-surface-3: var(--bui-gray-2);
/* Solid background colors */
--bui-bg-solid: #1f5493;
--bui-bg-solid-hover: #163a66;
--bui-bg-solid-pressed: #0f2b4e;
--bui-bg-solid-disabled: #ebebeb;
--bui-bg-tint: transparent;
--bui-bg-tint-hover: rgba(31, 84, 147, 0.4);
--bui-bg-tint-pressed: rgba(31, 84, 147, 0.6);
--bui-bg-tint-disabled: #ebebeb;
/* Neutral background colors */
--bui-bg-neutral-on-surface-0: oklch(0% 0 0 / 6%);
--bui-bg-neutral-on-surface-0-hover: oklch(0% 0 0 / 12%);
--bui-bg-neutral-on-surface-0-pressed: oklch(0% 0 0 / 16%);
--bui-bg-neutral-on-surface-0-disabled: oklch(0% 0 0 / 6%);
--bui-bg-neutral-on-surface-1: oklch(0% 0 0 / 6%);
--bui-bg-neutral-on-surface-1-hover: oklch(0% 0 0 / 12%);
--bui-bg-neutral-on-surface-1-pressed: oklch(0% 0 0 / 16%);
--bui-bg-neutral-on-surface-1-disabled: oklch(0% 0 0 / 6%);
--bui-bg-neutral-on-surface-2: oklch(0% 0 0 / 6%);
--bui-bg-neutral-on-surface-2-hover: oklch(0% 0 0 / 12%);
--bui-bg-neutral-on-surface-2-pressed: oklch(0% 0 0 / 16%);
--bui-bg-neutral-on-surface-2-disabled: oklch(0% 0 0 / 6%);
--bui-bg-neutral-on-surface-3: oklch(0% 0 0 / 6%);
--bui-bg-neutral-on-surface-3-hover: oklch(0% 0 0 / 12%);
--bui-bg-neutral-on-surface-3-pressed: oklch(0% 0 0 / 16%);
--bui-bg-neutral-on-surface-3-disabled: oklch(0% 0 0 / 6%);
/* Status background colors */
--bui-bg-danger: #feebe7;
--bui-bg-warning: #fff2b2;
--bui-bg-success: #e6f6eb;
/* Foreground Colors */
/* Foreground colors */
--bui-fg-primary: var(--bui-black);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-link: #1f5493;
@@ -111,7 +132,7 @@
--bui-fg-warning: #e36d05;
--bui-fg-success: #1db954;
/* Border Colors */
/* Border colors */
--bui-border: rgba(0, 0, 0, 0.1);
--bui-border-hover: rgba(0, 0, 0, 0.2);
--bui-border-pressed: rgba(0, 0, 0, 0.4);
@@ -120,7 +141,7 @@
--bui-border-warning: #e36d05;
--bui-border-success: #53db83;
/* Special Colors */
/* Special colors */
--bui-ring: #1f5493;
--bui-scrollbar: #a0a0a03b;
--bui-scrollbar-thumb: #a0a0a0;
@@ -140,24 +161,45 @@
--bui-gray-7: #9e9e9e;
--bui-gray-8: #b4b4b4;
/* Background Colors */
/* Surfaces colors */
--bui-bg-surface-0: var(--bui-black);
--bui-bg-surface-1: var(--bui-gray-1);
--bui-bg-surface-2: var(--bui-gray-2);
--bui-bg-surface-3: var(--bui-gray-3);
/* Solid background colors */
--bui-bg-solid: #9cc9ff;
--bui-bg-solid-hover: #83b9fd;
--bui-bg-solid-pressed: #83b9fd;
--bui-bg-solid-disabled: #222222;
--bui-bg-tint: transparent;
--bui-bg-tint-hover: rgba(156, 201, 255, 0.12);
--bui-bg-tint-pressed: rgba(156, 201, 255, 0.16);
--bui-bg-tint-disabled: transparent;
/* Neutral background colors */
--bui-bg-neutral-on-surface-0: oklch(100% 0 0 / 10%);
--bui-bg-neutral-on-surface-0-hover: oklch(100% 0 0 / 14%);
--bui-bg-neutral-on-surface-0-pressed: oklch(100% 0 0 / 20%);
--bui-bg-neutral-on-surface-0-disabled: oklch(100% 0 0 / 10%);
--bui-bg-neutral-on-surface-1: oklch(100% 0 0 / 6%);
--bui-bg-neutral-on-surface-1-hover: oklch(100% 0 0 / 10%);
--bui-bg-neutral-on-surface-1-pressed: oklch(100% 0 0 / 16%);
--bui-bg-neutral-on-surface-1-disabled: oklch(100% 0 0 / 6%);
--bui-bg-neutral-on-surface-2: oklch(100% 0 0 / 8%);
--bui-bg-neutral-on-surface-2-hover: oklch(100% 0 0 / 12%);
--bui-bg-neutral-on-surface-2-pressed: oklch(100% 0 0 / 20%);
--bui-bg-neutral-on-surface-2-disabled: oklch(100% 0 0 / 8%);
--bui-bg-neutral-on-surface-3: oklch(100% 0 0 / 8%);
--bui-bg-neutral-on-surface-3-hover: oklch(100% 0 0 / 12%);
--bui-bg-neutral-on-surface-3-pressed: oklch(100% 0 0 / 20%);
--bui-bg-neutral-on-surface-3-disabled: oklch(100% 0 0 / 8%);
/* Status background colors */
--bui-bg-danger: #3b1219;
--bui-bg-warning: #302008;
--bui-bg-success: #132d21;
/* Foreground Colors */
/* Foreground colors */
--bui-fg-primary: var(--bui-white);
--bui-fg-secondary: var(--bui-gray-7);
--bui-fg-link: #9cc9ff;
@@ -171,7 +213,7 @@
--bui-fg-warning: #e36d05;
--bui-fg-success: #1db954;
/* Border Colors */
/* Border colors */
--bui-border: rgba(255, 255, 255, 0.12);
--bui-border-hover: rgba(255, 255, 255, 0.4);
--bui-border-pressed: rgba(255, 255, 255, 0.5);
@@ -180,7 +222,7 @@
--bui-border-warning: #e36d05;
--bui-border-success: #53db83;
/* Special Colors */
/* Special colors */
--bui-ring: #1f5493;
--bui-scrollbar: #3636363a;
--bui-scrollbar-thumb: #575757;
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright 2025 The Backstage Authors
*
* 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.
*/
import { createContext, useContext, ReactNode } from 'react';
import { Surface, Responsive } from '../types';
/** @public */
export interface SurfaceContextValue {
surface: Responsive<Surface> | undefined;
}
/** @public */
export interface SurfaceProviderProps {
surface: Responsive<Surface>;
children: ReactNode;
}
const SurfaceContext = createContext<SurfaceContextValue>({
surface: undefined,
});
/**
* Provider component that establishes the surface context for child components.
* This allows components to adapt their styling based on their background surface.
*
* @public
*/
export const SurfaceProvider = ({
surface,
children,
}: SurfaceProviderProps) => {
return (
<SurfaceContext.Provider value={{ surface }}>
{children}
</SurfaceContext.Provider>
);
};
/**
* Hook to access the current surface context.
* Returns the current surface level, or undefined if no provider is present.
*
* @public
*/
export const useSurface = (): SurfaceContextValue => {
return useContext(SurfaceContext);
};
+6
View File
@@ -173,3 +173,9 @@ export interface ComponentDefinition {
dataAttributes?: DataAttributesMap;
utilityProps?: string[];
}
/**
* Surface type
* @public
*/
export type Surface = '0' | '1' | '2' | '3';