import { Meta, StoryObj } from '@storybook/react-webpack5';
import { Bank, Defrost } from '@transferwise/icons';
import Body from '../body';
import Button from '../button';
import Header from '../header';
import Link from '../link';
import IconButton from '../iconButton';
import StatusIcon from '../statusIcon';
import AvatarView from '../avatarView';
import SentimentSurface from './SentimentSurface';
import type { Sentiment, Emphasis } from './SentimentSurface.types';
const withComponentGrid = (Story: () => JSX.Element) => (
);
/**
* SentimentSurface is a polymorphic container component that exposes and, optionally, applies
* contextual colour tokens as CSS custom properties, based on sentiment types (`negative`,
* `warning`, `neutral`, `success`, `proposition`).
*/
const meta: Meta = {
component: SentimentSurface,
title: 'Foundations/SentimentSurface',
tags: ['early-access'],
argTypes: {
sentiment: {
control: 'select',
options: ['negative', 'warning', 'neutral', 'success', 'proposition'],
description: 'The sentiment type that determines the colour scheme',
table: {
type: { summary: 'Sentiment' },
},
},
emphasis: {
control: 'inline-radio',
options: ['base', 'elevated'],
description: 'The emphasis level affecting background and text contrast',
table: {
defaultValue: { summary: '"base"' },
},
},
hasBaseStyles: {
control: 'boolean',
description:
'If true, sets the `background-color` and `color` on the container. Otherwise, only exposes the tokens as CSS custom properties without rendering.',
table: {
defaultValue: { summary: 'true' },
},
},
as: {
control: 'text',
description: 'The underlying HTML element to render.',
table: {
defaultValue: {
summary: '"div"',
},
type: { summary: 'string' },
},
},
className: {
table: {
type: { summary: 'string' },
},
control: 'text',
},
id: {
table: {
type: { summary: 'string' },
},
control: 'text',
},
'data-testid': {
control: 'text',
table: {
type: { summary: 'string' },
},
},
children: {
table: {
type: { summary: 'ReactNode' },
},
control: false,
},
},
args: {
sentiment: 'neutral',
emphasis: 'base',
hasBaseStyles: undefined,
as: undefined,
className: undefined,
id: undefined,
'data-testid': undefined,
children: 'This is a sentiment surface',
},
};
export default meta;
type Story = StoryObj;
/**
* The Playground story allows you to experiment with all the props of the SentimentSurface component.
* Use the controls below to customise the sentiment type, emphasis level, and content.
*/
export const Playground: Story = {
args: {
sentiment: 'negative',
emphasis: 'base',
children: (
This is a custom div demonstrating a sentiment surface. It exposes CSS custom properties for
the selected sentiment type and emphasis level and, optionally, applies background and text
colours.
);
/**
* Each sentiment type has two emphasis levels – `base` and `elevated` – to provide different levels of visual prominence.
*
* **When to use:**
*
* - `Base`: For inline notifications, subtle callouts, or when sentiment needs to be communicated without drawing too much attention
* - `Elevated`: For prominent banners, critical alerts, or when the sentiment should be immediately noticeable
*
*/
export const EmphasisLevels: Story = {
render: (args) => (
<>
Negative - Base emphasis
Negative - Elevated emphasis
Success - Base emphasis
Success - Elevated emphasis
>
),
parameters: {
controls: { disable: true },
},
decorators: [withComponentGrid],
};
/**
* By default, `SentimentSurface` exposes the tokens as CSS custom properties and also applies
* the base styles for `background-color` and `color`. However, you can choose to expose said
* properties without rendering them, by setting the `hasBaseStyles` prop to `false`.
* This is useful when you want to create custom components that adapt to the sentiment context
* without applying default styles.
*/
export const RenderingBaseStyles: Story = {
render: (args) => (
<>
This example exposes and applies the tokens.
This example only exposes the tokens without applying them.
Which means you have to apply them yourself.
>
),
parameters: {
controls: { disable: true },
docs: {
source: {
// Storybook strips out props that are set to false, so we need to hack it back in :/
transform: async (source: string) => {
if (source.includes('hasBaseStyles')) {
return source;
}
return source.replace(
/(<\/SentimentSurface>\s+ {
return (
Success base sentiment surface
Neutral elevated sentiment surface
Warning base sentiment surface
);
},
};
/**
* Some DS components (namely `Button`, `Link`, `StatusIcon`, `IconButton`) automatically adapt to the sentiment context when used within a `SentimentSurface`.
*
* > ⚠️ You should not mix sentiments within the same surface. For example, avoid using a `StatusIcon` with `sentiment="negative"` inside a `SentimentSurface` with `sentiment="success"`.
*/
export const SentimentAwareComponents: Story = {
name: 'Sentiment-aware Components',
parameters: {
controls: { disable: true },
docs: {
canvas: {
sourceState: 'hidden',
},
},
},
render: function Render(args) {
const BODY = [
This text and its inline links should use sentiment-matched colour.
,
,
,
,
,
,
];
const STATUS_SUCCESS = ;
const STATUS_NEGATIVE = ;
const AVATAR_SUCCESS = (
);
const AVATAR_NEGATIVE = (
);
return (
<>
);
}
export const CustomComponents: Story = {
tags: ['!autodocs', '!dev'],
render: (args) => (
<>
This component works outside a SentimentSurfaceThis component adapts to the context of the SentimentSurface
>
),
parameters: {
docs: {
source: {
code: `
// CSS
.custom-card {
color: var(
--color-sentiment-content-primary,
var(--color-content-primary)
);
border: 1px solid var(
--color-sentiment-interactive-secondary,
var(--color-border-neutral)
);
gap: var(--size-16);
}
// JSX
function CustomCard({ children }) {
return (
{children}
);
}
function View() {
return (
<>
This card works outside a SentimentSurfaceThis card adapts to the warning sentiment
>
);
}
`,
},
},
},
};
/**
* The component exposes token groups for all 5 sentiment types (`negative`, `warning`, `neutral`, `proposition` and `success`) each of them with two emphasis levels (`base` and `elevated`).
*/
export const ExposedTokens: Story = {
render: () => (
{sentiments.map((sentiment) =>
emphasisLevels.map((emphasis) => (