Skip to content

Commit e219501

Browse files
committed
Added basic Storybook setup
1 parent ef57282 commit e219501

File tree

17 files changed

+4515
-2534
lines changed

17 files changed

+4515
-2534
lines changed

.github/workflows/chromatic.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Chromatic Visual Testing Workflow
2+
# Documentation: https://www.chromatic.com/docs/github-actions/
3+
4+
name: "Chromatic"
5+
6+
on: push
7+
8+
jobs:
9+
chromatic:
10+
name: Run Chromatic
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout code
14+
uses: actions/checkout@v5
15+
with:
16+
fetch-depth: 0
17+
18+
- name: Setup Node.js
19+
uses: actions/setup-node@v4
20+
with:
21+
node-version: 20
22+
cache: 'npm'
23+
24+
- name: Install dependencies
25+
run: npm ci
26+
27+
- name: Run Chromatic
28+
uses: chromaui/action@latest
29+
with:
30+
# Make sure to configure a `CHROMATIC_PROJECT_TOKEN` repository secret
31+
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
32+
# Automatically accept all changes on main branch
33+
autoAcceptChanges: "master"
34+
# Exit with zero code even when changes are detected
35+
exitZeroOnChanges: true
36+
# Skip builds for dependabot branches
37+
skip: "dependabot/**"

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ dist
1515
# cache
1616
.cache
1717

18+
# storybook artifacts
19+
*storybook.log
20+
storybook-static
21+
1822
npm-debug.log*
1923
yarn-debug.log*
2024
yarn-error.log*

.storybook/context-providers.tsx

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import React, { createContext, useContext, ReactNode, useMemo } from 'react';
2+
import { fn } from 'storybook/test';
3+
4+
// Types
5+
export interface ChromeConfig {
6+
environment: string;
7+
[key: string]: any;
8+
}
9+
10+
export interface FeatureFlagsConfig {
11+
[flagName: string]: boolean;
12+
}
13+
14+
// Chrome Context
15+
const ChromeContext = createContext<ChromeConfig>({
16+
environment: 'prod'
17+
});
18+
19+
export const ChromeProvider: React.FC<{ value: ChromeConfig; children: ReactNode }> = ({ value, children }) => (
20+
<ChromeContext.Provider value={value}>{children}</ChromeContext.Provider>
21+
);
22+
23+
// Feature Flags Context
24+
const FeatureFlagsContext = createContext<FeatureFlagsConfig>({});
25+
26+
export const FeatureFlagsProvider: React.FC<{ value: FeatureFlagsConfig; children: ReactNode }> = ({ value, children }) => (
27+
<FeatureFlagsContext.Provider value={value}>{children}</FeatureFlagsContext.Provider>
28+
);
29+
30+
// Chrome spy for testing - IS the spy function
31+
export const chromeAppNavClickSpy = fn();
32+
33+
// Mock Hook Implementations (only for Storybook)
34+
export const useChrome = () => {
35+
const chromeConfig = useContext(ChromeContext);
36+
37+
return useMemo(() => ({
38+
getEnvironment: () => chromeConfig.environment,
39+
getEnvironmentDetails: () => ({
40+
environment: chromeConfig.environment,
41+
sso: 'https://sso.redhat.com',
42+
portal: 'https://console.redhat.com'
43+
}),
44+
isProd: () => chromeConfig.environment === 'prod',
45+
isBeta: () => chromeConfig.environment !== 'prod',
46+
appNavClick: chromeAppNavClickSpy,
47+
appObjectId: () => undefined,
48+
appAction: () => undefined,
49+
updateDocumentTitle: (title: string) => {
50+
// Mock document title update for Storybook
51+
if (typeof document !== 'undefined') {
52+
document.title = title;
53+
}
54+
},
55+
auth: chromeConfig.auth || {
56+
getUser: () => Promise.resolve({
57+
identity: {
58+
user: {
59+
username: 'test-user',
60+
email: 'test@redhat.com',
61+
is_org_admin: true,
62+
is_internal: false
63+
}
64+
}
65+
}),
66+
getToken: () => Promise.resolve('mock-jwt-token-12345')
67+
},
68+
getBundle: () => 'settings',
69+
getApp: () => 'user-preferences',
70+
getUserPermissions: () => Promise.resolve([
71+
{
72+
permission: 'user-preferences:*:*',
73+
resourceDefinitions: []
74+
},
75+
]),
76+
...chromeConfig
77+
}), [chromeConfig]);
78+
};
79+
80+
export const useFlag = (flagName: string): boolean => {
81+
const flags = useContext(FeatureFlagsContext);
82+
return flags[flagName] || false;
83+
};
84+
85+
// Re-export for Unleash mock compatibility
86+
export const FlagProvider = FeatureFlagsProvider;
87+
export const useFlagsStatus = () => ({ flagsReady: true, flagsError: false });
88+
export const useUnleashContext = () => ({});
89+
export const IFlagProvider = FeatureFlagsProvider;
90+
91+
// Export contexts for direct use if needed
92+
export { ChromeContext, FeatureFlagsContext };

.storybook/hooks/unleash.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Re-export Unleash mocks from context-providers for webpack alias
2+
export {
3+
useFlag,
4+
FlagProvider,
5+
useFlagsStatus,
6+
useUnleashContext,
7+
IFlagProvider,
8+
} from '../context-providers';

.storybook/hooks/useChrome.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// Re-export useChrome from context-providers for webpack alias
2+
export { useChrome } from '../context-providers';

.storybook/main.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import type { StorybookConfig } from '@storybook/react-webpack5';
2+
import path from 'path';
3+
4+
const config: StorybookConfig = {
5+
stories: [
6+
"../src/docs/*.mdx",
7+
"../src/**/*.mdx",
8+
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"
9+
],
10+
addons: [
11+
"@storybook/addon-webpack5-compiler-swc",
12+
"@storybook/addon-docs",
13+
"msw-storybook-addon",
14+
],
15+
framework: {
16+
name: "@storybook/react-webpack5",
17+
options: {}
18+
},
19+
docs: {
20+
defaultName: 'Documentation',
21+
},
22+
staticDirs: ['../static'],
23+
webpackFinal: async (config) => {
24+
// Mock hooks for Storybook - replace real implementations with our context-aware versions
25+
config.resolve = {
26+
...config.resolve,
27+
alias: {
28+
...config.resolve?.alias,
29+
'@redhat-cloud-services/frontend-components/useChrome': path.resolve(process.cwd(), '.storybook/hooks/useChrome'),
30+
'@unleash/proxy-client-react': path.resolve(process.cwd(), '.storybook/hooks/unleash'),
31+
},
32+
};
33+
34+
// Add SCSS support
35+
config.module = config.module || {};
36+
config.module.rules = config.module.rules || [];
37+
38+
// Add SCSS rule
39+
config.module.rules.push({
40+
test: /\.s[ac]ss$/i,
41+
use: [
42+
'style-loader',
43+
'css-loader',
44+
'sass-loader',
45+
],
46+
});
47+
48+
return config;
49+
},
50+
typescript: {
51+
check: false,
52+
reactDocgen: 'react-docgen-typescript',
53+
reactDocgenTypescriptOptions: {
54+
shouldExtractLiteralValuesFromEnum: true,
55+
propFilter: (prop) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
56+
},
57+
},
58+
};
59+
60+
export default config;

.storybook/preview.tsx

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import type { Preview } from '@storybook/react-webpack5';
2+
import '@patternfly/react-core/dist/styles/base.css';
3+
import '@patternfly/patternfly/patternfly-addons.css';
4+
import './storybook.css';
5+
import React, { Fragment } from 'react';
6+
import { Provider } from 'react-redux';
7+
import { MemoryRouter } from 'react-router-dom';
8+
import NotificationsProvider from '@redhat-cloud-services/frontend-components-notifications/NotificationsProvider';
9+
import registry from '../src/Utilities/store';
10+
import { ChromeProvider, FeatureFlagsProvider, type ChromeConfig, type FeatureFlagsConfig } from './context-providers';
11+
import { initialize, mswLoader } from 'msw-storybook-addon';
12+
13+
// Mock insights global for Storybook
14+
declare global {
15+
var insights: {
16+
chrome: {
17+
getEnvironment: () => string;
18+
};
19+
};
20+
}
21+
22+
// Mock global insights object for libraries that access it directly
23+
const mockInsightsChrome = {
24+
getEnvironment: () => 'prod',
25+
getUserPermissions: () => Promise.resolve([
26+
{ permission: 'user-preferences:*:*', resourceDefinitions: [] },
27+
]),
28+
auth: {
29+
getUser: () => Promise.resolve({
30+
identity: {
31+
user: {
32+
username: 'test-user',
33+
email: 'test@redhat.com',
34+
is_org_admin: true,
35+
is_internal: false,
36+
},
37+
},
38+
}),
39+
getToken: () => Promise.resolve('mock-jwt-token-12345'),
40+
},
41+
};
42+
43+
if (typeof global !== 'undefined') {
44+
(global as any).insights = { chrome: mockInsightsChrome };
45+
} else if (typeof window !== 'undefined') {
46+
(window as any).insights = { chrome: mockInsightsChrome };
47+
}
48+
49+
const preview: Preview = {
50+
beforeAll: async () => {
51+
initialize({ onUnhandledRequest: 'warn' });
52+
},
53+
loaders: [mswLoader],
54+
parameters: {
55+
options: {
56+
storySort: {
57+
method: 'alphabetical',
58+
order: ['Documentation', 'Components', '*'],
59+
},
60+
},
61+
layout: 'fullscreen',
62+
chromatic: { delay: 300 },
63+
actions: { argTypesRegex: '^on.*' },
64+
controls: {
65+
matchers: {
66+
color: /(background|color)$/i,
67+
date: /Date$/i,
68+
},
69+
},
70+
// Default configurations for all stories (can be overridden per story)
71+
chrome: {
72+
environment: 'prod',
73+
},
74+
featureFlags: {},
75+
},
76+
decorators: [
77+
(Story, { parameters, args }) => {
78+
// Merge chrome config from parameters with any arg overrides
79+
const chromeConfig: ChromeConfig = {
80+
environment: 'prod',
81+
...parameters.chrome,
82+
...(args.environment !== undefined && { environment: args.environment }),
83+
};
84+
85+
const featureFlags: FeatureFlagsConfig = {
86+
...parameters.featureFlags,
87+
};
88+
89+
return (
90+
<Provider store={registry.getStore()}>
91+
<ChromeProvider value={chromeConfig}>
92+
<FeatureFlagsProvider value={featureFlags}>
93+
<MemoryRouter>
94+
<Fragment>
95+
<NotificationsProvider>
96+
<Story />
97+
</NotificationsProvider>
98+
</Fragment>
99+
</MemoryRouter>
100+
</FeatureFlagsProvider>
101+
</ChromeProvider>
102+
</Provider>
103+
);
104+
},
105+
],
106+
};
107+
108+
export default preview;

.storybook/storybook.css

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Custom CSS for Storybook documentation and stories
3+
*/
4+
5+
/* Storybook documentation styling */
6+
.sbdocs-wrapper {
7+
padding: var(--pf-v5-global--spacer--lg);
8+
}
9+
10+
.sbdocs-content {
11+
max-width: 1200px;
12+
}
13+
14+
/* Code blocks in MDX */
15+
.sbdocs pre {
16+
background-color: var(--pf-v5-global--BackgroundColor--dark-100);
17+
border-radius: var(--pf-v5-global--BorderRadius--sm);
18+
padding: var(--pf-v5-global--spacer--md);
19+
}
20+
21+
/* Tables in MDX */
22+
.sbdocs table {
23+
border-collapse: collapse;
24+
width: 100%;
25+
}
26+
27+
.sbdocs table th,
28+
.sbdocs table td {
29+
border: 1px solid var(--pf-v5-global--BorderColor--100);
30+
padding: var(--pf-v5-global--spacer--sm);
31+
text-align: left;
32+
}
33+
34+
.sbdocs table th {
35+
background-color: var(--pf-v5-global--BackgroundColor--200);
36+
font-weight: var(--pf-v5-global--FontWeight--bold);
37+
}

0 commit comments

Comments
 (0)