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
80 changes: 80 additions & 0 deletions packages/joint-react/.storybook/decorators/with-showcase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { Decorator } from '@storybook/react-vite';
import type { ReactNode } from 'react';
import { Showcase } from '../../stories/ui/showcase';
import { Badge } from '../../stories/ui/badge';
import type { CodeFile } from '../../stories/ui/code-block';

/**
* Per-story frame configuration, set via `parameters.showcase`. All fields are
* optional; the frame derives sensible defaults from the story's title/name.
* Set `parameters.showcase = false` to opt a story out of the frame entirely.
*/
interface ShowcaseParameters {
/** One-line description. Deep docs live in joint-docs, keep this to a sentence. */
readonly description?: ReactNode;
/** Auto-injected source, always via a `?raw` import — never a hand-written string. */
readonly code?: string;
/** Multiple named `?raw` snippets, rendered as tabs. */
readonly files?: readonly CodeFile[];
/** Filename label for a single `code` snippet. */
readonly filename?: string;
/** Link to the matching API reference page. */
readonly apiUrl?: string;
/** Override the auto-derived title. */
readonly title?: string;
/** Canvas height in px. Default 420. */
readonly canvasHeight?: number | string;
/** Render the diagram surface without the dot grid. */
readonly plainCanvas?: boolean;
/** Skip the canvas surface — for full-app demos that own their background. */
readonly bare?: boolean;
}

const EYEBROWS: Record<string, string> = {
Examples: 'Example',
Demos: 'Demo',
Components: 'Component',
Tutorials: 'Tutorial',
Utils: 'Utility',
Hooks: 'Hook',
};

function eyebrowFor(group: string): string {
return EYEBROWS[group] ?? group.replace(/s$/, '');
}

/**
* Wraps every story in the unified {@link Showcase} frame. Title and category
* eyebrow are derived from the Storybook `title`; the source snippet and
* description come from `parameters.showcase`.
*/
export const withShowcase: Decorator = (Story, context) => {
const showcase = context.parameters?.showcase as ShowcaseParameters | false | undefined;
if (showcase === false) {
return <Story />;
}

const segments = context.title.split('/');
const group = segments[0] ?? '';
const base = segments.at(-1) ?? context.title;
const storyName = context.name;
const isVariant = storyName !== 'Default' && storyName !== base;

return (
<Showcase
title={showcase?.title ?? base}
eyebrow={eyebrowFor(group)}
badge={isVariant ? <Badge>{storyName}</Badge> : undefined}
description={showcase?.description}
apiUrl={showcase?.apiUrl}
code={showcase?.code}
files={showcase?.files}
filename={showcase?.filename}
canvasHeight={showcase?.canvasHeight}
plainCanvas={showcase?.plainCanvas}
bare={showcase?.bare}
>
<Story />
</Showcase>
);
};
169 changes: 0 additions & 169 deletions packages/joint-react/.storybook/decorators/with-simple-data.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions packages/joint-react/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ const __dirname = path.dirname(__filename);

configureSort({
storyOrder: {
tutorials: null,
examples: null,
demos: null,
components: null,
hooks: null,
utils: null,
'**': { default: null },
},
});
Expand Down
18 changes: 18 additions & 0 deletions packages/joint-react/.storybook/preview.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,25 @@
@import 'tailwindcss';
@import '../stories/ui/tokens.css';
@import '../stories/ui/demo.css';
@import '../src/css/styles.css' layer(components);

/* Custom variants for the Tailwind theme demo */
@custom-variant forest (&:where(.forest, .forest *));
@custom-variant ocean (&:where(.ocean, .ocean *));
@custom-variant sunset (&:where(.sunset, .sunset *));

/* Storybook canvas root — the deep app backdrop behind every Showcase frame. */
.sb-show-main,
body {
background-color: var(--color-app);
color: var(--color-ink);
font-family: var(--font-display);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}

/* A single, very subtle brand glow anchored top-center. Not a glowing-accent
cliché — it just keeps the flat dark backdrop from feeling dead. */
.sb-show-main {
background-image: radial-gradient(120% 60% at 50% -10%, oklch(0.5 0.14 20 / 0.06), transparent 60%);
}
27 changes: 8 additions & 19 deletions packages/joint-react/.storybook/preview.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,22 @@
import type { Preview } from '@storybook/react-vite';
import { theme } from './theme';
import { withStrictMode } from './decorators/with-strict-mode';
import { withShowcase } from './decorators/with-showcase';
import './preview.css';

export const decorators = [withStrictMode];
// Showcase is the outermost frame; StrictMode wraps the live story inside it.
export const decorators = [withShowcase, withStrictMode];

const preview: Preview = {
parameters: {
layout: 'padded',
// The Showcase frame owns all spacing and the app background.
layout: 'fullscreen',
docs: {
theme,
},
backgrounds: {
options: {
dark: { name: 'Dark', value: theme.appBg },
},
},
parameters: {
options: {
// @ts-expect-error its storybook multilevel sort
storySort: (a, b) => globalThis['storybook-multilevel-sort:storySort'](a, b),
},
options: {
// @ts-expect-error injected by storybook-multilevel-sort
storySort: (a, b) => globalThis['storybook-multilevel-sort:storySort'](a, b),
},
controls: {
matchers: {
Expand All @@ -30,14 +26,7 @@ const preview: Preview = {
},
},

tags: ['autodocs'],
decorators,

initialGlobals: {
backgrounds: {
value: 'dark',
},
},
};

export default preview;
Loading
Loading