Skip to content

feat(repo): Storybook #6347

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
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
685 changes: 347 additions & 338 deletions eslint.config.mjs

Large diffs are not rendered by default.

12 changes: 12 additions & 0 deletions packages/storybook/.storybook/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
:root {
--background: #f5f5f5;
}

.dark {
--background: #0a0a0a;
}

body {
font-family: 'ui-sans-serif', sans-serif;
background-color: var(--background);
}
30 changes: 30 additions & 0 deletions packages/storybook/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
import type { StorybookConfig } from '@storybook/react-vite';

const require = createRequire(import.meta.url);

const config: StorybookConfig = {
stories: ['../stories/**/*.stories.@(js|jsx|mjs|ts|tsx|mdx)'],
addons: [getAbsolutePath('@storybook/addon-docs'), getAbsolutePath('@storybook/addon-themes')],

framework: {
name: getAbsolutePath('@storybook/react-vite'),
options: {},
},

typescript: {
check: false,
reactDocgen: 'react-docgen-typescript',
reactDocgenTypescriptOptions: {
shouldExtractLiteralValuesFromEnum: true,
propFilter: (prop: any) => (prop.parent ? !/node_modules/.test(prop.parent.fileName) : true),
},
},
};

export default config;

function getAbsolutePath(value: string): any {
return dirname(require.resolve(join(value, 'package.json')));
}
247 changes: 247 additions & 0 deletions packages/storybook/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
import './main.css';

import { ClerkProvider } from '@clerk/clerk-react';
import { enUS, esES, frFR, koKR, zhCN } from '@clerk/localizations';
import type { Appearance } from '@clerk/types';
import { withThemeByClassName } from '@storybook/addon-themes';
import React from 'react';

// Map locale selector values to localization resources
const localeMap = {
en: enUS,
fr: frFR,
es: esES,
zh: zhCN,
kr: koKR,
} as const;

// Mock router functions for Storybook
const mockRouter = {
push: () => {},
replace: () => {},
};

const preview: any = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
layout: 'centered',
backgrounds: { disable: true },
},
globalTypes: {
locale: {
description: 'Locale',
toolbar: {
icon: 'globe',
items: [
{ value: 'en', right: '🇺🇸', title: 'English' },
{ value: 'fr', right: '🇫🇷', title: 'Français' },
{ value: 'es', right: '🇪🇸', title: 'Español' },
{ value: 'zh', right: '🇨🇳', title: '中文' },
{ value: 'kr', right: '🇰🇷', title: '한국어' },
],
},
},
},
initialGlobals: {
locale: 'en',
},
argTypes: {
// Color Variables
colorPrimary: {
description: 'Primary color used throughout the components',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorPrimaryForeground: {
description: 'Text color appearing on top of primary background',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorForeground: {
description: 'Default text color',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorBackground: {
description: 'Background color for the card container',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorInput: {
description: 'Background color for all input elements',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorInputForeground: {
description: 'Text color inside input elements',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorMuted: {
description: 'Background color for elements of lower importance',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorMutedForeground: {
description: 'Text color for elements of lower importance',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorDanger: {
description: 'Color used to indicate errors or destructive actions',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorSuccess: {
description: 'Color used to indicate successful actions or positive results',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorWarning: {
description: 'Color used for potentially destructive actions or when attention is required',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorNeutral: {
description: 'Neutral color used for borders, backgrounds, and hovered elements',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorBorder: {
description: 'Base border color used in the components',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
colorShadow: {
description: 'Base shadow color used in the components',
control: { type: 'color' },
table: { category: 'Appearance - Colors' },
},
// Typography Variables
fontFamily: {
description: 'Default font family for all components',
control: { type: 'text' },
table: { category: 'Appearance - Typography' },
},
fontFamilyButtons: {
description: 'Font family for all buttons',
control: { type: 'text' },
table: { category: 'Appearance - Typography' },
},
fontSize: {
description: 'Base font size (md value for calculating other scales)',
control: { type: 'text' },
table: { category: 'Appearance - Typography' },
},
// Layout Variables
borderRadius: {
description: 'Base border radius (md value for calculating other scales)',
control: { type: 'text' },
table: { category: 'Appearance - Layout' },
},
spacing: {
description: 'Base spacing for margins, paddings, and gaps',
control: { type: 'text' },
table: { category: 'Appearance - Layout' },
},
},
args: {
// Default values for appearance variables
colorPrimary: '#2F3037',
colorPrimaryForeground: '#FFFFFF',
colorBackground: '#FFFFFF',
colorInput: '#FFFFFF',
colorInputForeground: '#212126',
colorForeground: '#212126',
colorMutedForeground: '#747686',
colorDanger: '#EF4444',
colorSuccess: '#22C543',
colorWarning: '#F36B16',
colorNeutral: '#000000',
fontFamily: 'inherit',
fontFamilyButtons: 'inherit',
fontSize: '0.8125rem',
borderRadius: '0.375rem',
spacing: '1rem',
},
decorators: [
withThemeByClassName({
themes: {
light: 'light',
dark: 'dark',
},
defaultTheme: 'light',
}),
(Story: any, context: any) => {
const {
colorPrimary,
colorPrimaryForeground,
colorBackground,
colorInput,
colorInputForeground,
colorForeground,
colorMuted,
colorMutedForeground,
colorDanger,
colorSuccess,
colorWarning,
colorNeutral,
colorBorder,
colorShadow,
fontFamily,
fontFamilyButtons,
fontSize,
borderRadius,
spacing,
} = context.args;

// Get the selected locale with proper type safety
const selectedLocale = context.globals.locale as keyof typeof localeMap;
const localization = localeMap[selectedLocale] || localeMap.en;

// Build appearance object with individual variables
const appearance: Appearance = {
variables: {
colorPrimary,
colorPrimaryForeground,
colorBackground,
colorInput,
colorInputForeground,
colorForeground,
colorMuted,
colorMutedForeground,
colorDanger,
colorSuccess,
colorWarning,
colorNeutral,
colorBorder,
colorShadow,
fontFamily,
fontFamilyButtons,
fontSize,
borderRadius,
spacing,
},
};

return React.createElement(
ClerkProvider,
{
publishableKey: 'pk_test_dG91Y2hlZC1sYWR5YmlyZC0yMy5jbGVyay5hY2NvdW50cy5kZXYk',
appearance,
localization,
routerPush: mockRouter.push,
routerReplace: mockRouter.replace,
} as any,
React.createElement(Story),
);
},
],
};

export default preview;
53 changes: 53 additions & 0 deletions packages/storybook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# @clerk/storybook

This package provides Storybook documentation for Clerk components using the React component library.

## Why a Separate Package?

This approach offers several advantages over integrating Storybook directly into the `clerk-js` package:

1. **Clean Architecture**: Dedicated package for documentation with its own build setup
2. **React Components**: Uses `@clerk/clerk-react` components directly instead of wrapping vanilla JS
3. **Better TypeScript Support**: Proper typing and intellisense for React components
4. **Standard Patterns**: Follows conventional Storybook patterns for component documentation
5. **Easier Maintenance**: Isolated configuration and dependencies

## Components Documented

- **Authentication**: `SignIn`, `SignUp`, `SignInButton`, `SignUpButton`
- **User Management**: `UserButton`, `UserProfile`
- **Organization Management**: `OrganizationSwitcher`, `OrganizationProfile`, `CreateOrganization`, `OrganizationList`
- **Specialized**: `GoogleOneTap`, `Waitlist`, `PricingTable`, `APIKeys`
- **Control Components**: `SignedIn`, `SignedOut`, `Protect`, etc.

## Getting Started

```bash
# Install dependencies
pnpm install

# Start Storybook
pnpm dev

# Build Storybook
pnpm build
```

## Features

- **Appearance Controls**: Interactive controls for theming and customization
- **Localization**: Support for multiple languages via toolbar
- **Auto-generated Documentation**: Using Storybook's autodocs feature
- **Interactive Examples**: Live component examples with controls
- **TypeScript Support**: Full TypeScript support with proper types

## Development

Stories are located in the `stories/` directory. Each component has its own story file with multiple variants demonstrating different use cases and configurations.

The Storybook configuration includes:

- Global appearance and localization controls
- Auto-generated documentation
- Interactive controls for component props
- Proper ClerkProvider context for all stories
43 changes: 43 additions & 0 deletions packages/storybook/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"name": "@clerk/storybook",
"version": "0.0.0",
"private": true,
"description": "Storybook documentation for Clerk components",
"keywords": [
"clerk",
"storybook",
"documentation"
],
"license": "MIT",
"scripts": {
"build": "storybook build",
"build-storybook": "storybook build",
"dev": "storybook dev -p 6006",
"storybook": "storybook dev -p 6006"
},
"dependencies": {
"@clerk/clerk-react": "workspace:*",
"@clerk/localizations": "workspace:*",
"@clerk/shared": "workspace:*",
"@clerk/themes": "workspace:*",
"@clerk/types": "workspace:*",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"devDependencies": {
"@storybook/addon-docs": "^9.0.17",
"@storybook/addon-links": "^9.0.17",
"@storybook/addon-onboarding": "^9.0.17",
"@storybook/react-vite": "^9.0.17",
"@types/react": "18.2.37",
"@types/react-dom": "18.2.15",
"eslint-plugin-storybook": "9.0.17",
"storybook": "^9.0.17",
"typescript": "5.2.2",
"vite": "^6.0.0"
},
"peerDependencies": {
"react": ">=16.8.0",
"react-dom": ">=16.8.0"
}
}
Loading