Migrate to storybook 7

This commit is contained in:
Joseph Garrone 2024-02-13 01:03:13 +01:00
parent f2100374dc
commit 8e1a34e8f0
18 changed files with 4902 additions and 334 deletions

View File

@ -1,11 +1,7 @@
module.exports = { module.exports = {
root: true, root: true,
env: { browser: true, es2020: true }, env: { browser: true, es2020: true },
extends: [ extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'],
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
ignorePatterns: ['dist', '.eslintrc.cjs'], ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
plugins: ['react-refresh'], plugins: ['react-refresh'],

20
.storybook/main.ts Normal file
View File

@ -0,0 +1,20 @@
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-onboarding",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/react-vite",
options: {},
},
docs: {
autodocs: "tag",
},
staticDirs: ["../public"]
};
export default config;

15
.storybook/preview.ts Normal file
View File

@ -0,0 +1,15 @@
import type { Preview } from "@storybook/react";
const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
};
export default preview;

View File

@ -11,7 +11,9 @@
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"build-keycloak-theme": "yarn build && keycloakify" "build-keycloak-theme": "yarn build && keycloakify",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}, },
"keycloakify": { "keycloakify": {
"themeName": "keycloakify-starter", "themeName": "keycloakify-starter",
@ -24,15 +26,23 @@
"keywords": [], "keywords": [],
"dependencies": { "dependencies": {
"evt": "^2.5.7", "evt": "^2.5.7",
"keycloakify": "9.4.0-rc.17",
"oidc-spa": "^4.0.0", "oidc-spa": "^4.0.0",
"keycloakify": "9.4.0-rc.16",
"powerhooks": "^1.0.8", "powerhooks": "^1.0.8",
"tsafe": "^1.6.6",
"zod": "^3.22.4",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0",
"tsafe": "^1.6.6",
"zod": "^3.22.4"
}, },
"devDependencies": { "devDependencies": {
"@storybook/addon-essentials": "^7.6.14",
"@storybook/addon-interactions": "^7.6.14",
"@storybook/addon-links": "^7.6.14",
"@storybook/addon-onboarding": "^1.0.11",
"@storybook/blocks": "^7.6.14",
"@storybook/react": "^7.6.14",
"@storybook/react-vite": "^7.6.14",
"@storybook/test": "^7.6.14",
"@types/react": "^18.2.43", "@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17", "@types/react-dom": "^18.2.17",
"@typescript-eslint/eslint-plugin": "^6.14.0", "@typescript-eslint/eslint-plugin": "^6.14.0",
@ -41,9 +51,14 @@
"eslint": "^8.55.0", "eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5", "eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-storybook": "^0.6.15",
"storybook": "^7.6.14",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.0.8", "vite": "^5.0.8",
"vite-plugin-commonjs": "^0.10.1", "vite-plugin-commonjs": "^0.10.1"
"@storybook/react": "^7.6.14" },
"_comment": "See https://github.com/storybookjs/storybook/issues/22431#issuecomment-1630086092",
"resolutions": {
"jackspeak": "2.1.1"
} }
} }

View File

@ -0,0 +1,36 @@
/*
This file is only meant to be used by Storybook
*/
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: normal; /*400*/
font-display: swap;
src: url("./worksans-regular-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url("./worksans-medium-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url("./worksans-semibold-webfont.woff2") format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: bold; /*700*/
font-display: swap;
src: url("./worksans-bold-webfont.woff2") format("woff2");
}

View File

@ -15,7 +15,13 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: {
storyPartialKcContext: params.kcContext storyPartialKcContext: params.kcContext
}); });
return <KcApp kcContext={kcContext} />; return (
<>
{/* If you import custom fonts in your index.html you have to import them in storybook as well*/}
<link rel="stylesheet" type="text/css" href={`${import.meta.env.BASE_URL}fonts/WorkSans/font.css`} />
<KcApp kcContext={kcContext} />
</>
);
} }

View File

@ -1,18 +1,22 @@
import { ComponentStory, ComponentMeta } from "@storybook/react"; import { Meta, StoryObj } from '@storybook/react';
import { createPageStory } from "../createPageStory"; import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({ const { PageStory } = createPageStory({
pageId: "password.ftl" pageId: "password.ftl"
}); });
export default { const meta = {
title: "account/Password", title: "account/Password",
component: PageStory, component: PageStory,
} as ComponentMeta<typeof PageStory>; } satisfies Meta<typeof PageStory>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: ComponentStory<typeof PageStory> = () => <PageStory export const Default: Story = {
render: () => <PageStory
kcContext={{ kcContext={{
message: { type: "success", summary: "This is a test message" } message: { type: "success", summary: "This is a test message" }
}} }}
/>; />
};

View File

@ -60,12 +60,12 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
style={{ "fontFamily": '"Work Sans"' }} style={{ "fontFamily": '"Work Sans"' }}
> >
{/* {/*
This is just to show you how it can be done but this is not the best option for importing assets. Here we are referencing the `keycloakify-logo.png` in the `public` directory.
See: https://docs.keycloakify.dev/importing-assets#importing-custom-assets When possible don't use this approach, instead ...
*/} */}
<img src={`${import.meta.env.BASE_URL}keycloakify-logo.png`} alt="Keycloakify logo" width={50} /> <img src={`${import.meta.env.BASE_URL}keycloakify-logo.png`} alt="Keycloakify logo" width={50} />
{msg("loginTitleHtml", realm.displayNameHtml)}!!! {msg("loginTitleHtml", realm.displayNameHtml)}!!!
{/* This is the preferred way to use assets */} {/* ...rely on the bundler to import your assets, it's more efficient */}
<img src={keycloakifyLogoPngUrl} alt="Keycloakify logo" width={50} /> <img src={keycloakifyLogoPngUrl} alt="Keycloakify logo" width={50} />
</div> </div>
</div> </div>

View File

@ -18,7 +18,7 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: {
return ( return (
<> <>
{/* If you import custom fonts in your index.html you have to import them in storybook as well*/} {/* If you import custom fonts in your index.html you have to import them in storybook as well*/}
<link rel="stylesheet" type="text/css" href="fonts/WorkSans/font.css" /> <link rel="stylesheet" type="text/css" href={`${import.meta.env.BASE_URL}fonts/WorkSans/font.css`} />
<KcApp kcContext={kcContext} /> <KcApp kcContext={kcContext} />
</> </>
); );

View File

@ -1,83 +1,68 @@
import { ComponentStory, ComponentMeta } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { createPageStory } from "../createPageStory"; import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({ const { PageStory } = createPageStory({
pageId: "login.ftl" pageId: "login.ftl"
}); });
export default { const meta = {
title: "login/Login", title: "login/Login",
component: PageStory, component: PageStory,
} as ComponentMeta<typeof PageStory>; } satisfies Meta<typeof PageStory>;
export const Default: ComponentStory<typeof PageStory> = () => <PageStory />; export default meta;
type Story = StoryObj<typeof meta>;
export const WithoutPasswordField: ComponentStory<typeof PageStory> = () => ( export const Default: Story = {
<PageStory render: () => <PageStory />,
kcContext={{ };
realm: { password: false }
}}
/>
);
export const WithoutRegistration: ComponentStory<typeof PageStory> = () => ( export const WithoutPasswordField: Story = {
<PageStory render: () => <PageStory kcContext={{ realm: { password: false } }} />,
kcContext={{ };
realm: { registrationAllowed: false }
}}
/>
);
export const WithoutRememberMe: ComponentStory<typeof PageStory> = () => ( export const WithoutRegistration: Story = {
<PageStory render: () => <PageStory kcContext={{ realm: { registrationAllowed: false } }} />,
kcContext={{ };
realm: { rememberMe: false }
}}
/>
);
export const WithoutPasswordReset: ComponentStory<typeof PageStory> = () => ( export const WithoutRememberMe: Story = {
<PageStory render: () => <PageStory kcContext={{ realm: { rememberMe: false } }} />,
kcContext={{ };
realm: { resetPasswordAllowed: false }
}}
/>
);
export const WithEmailAsUsername: ComponentStory<typeof PageStory> = () => ( export const WithoutPasswordReset: Story = {
<PageStory render: () => <PageStory kcContext={{ realm: { resetPasswordAllowed: false } }} />,
kcContext={{ };
realm: { loginWithEmailAllowed: false }
}}
/>
);
export const WithPresetUsername: ComponentStory<typeof PageStory> = () => ( export const WithEmailAsUsername: Story = {
<PageStory render: () => <PageStory kcContext={{ realm: { loginWithEmailAllowed: false } }} />,
kcContext={{ };
login: { username: "max.mustermann@mail.com" }
}}
/>
);
export const WithImmutablePresetUsername: ComponentStory<typeof PageStory> = () => ( export const WithPresetUsername: Story = {
render: () => <PageStory kcContext={{ login: { username: "max.mustermann@mail.com" } }} />,
};
export const WithImmutablePresetUsername: Story = {
render: () => (
<PageStory <PageStory
kcContext={{ kcContext={{
auth: { auth: {
attemptedUsername: "max.mustermann@mail.com", attemptedUsername: "max.mustermann@mail.com",
showUsername: true showUsername: true,
}, },
usernameHidden: true, usernameHidden: true,
message: { type: "info", summary: "Please re-authenticate to continue" } message: { type: "info", summary: "Please re-authenticate to continue" },
}} }}
/> />
); ),
};
export const WithSocialProviders: ComponentStory<typeof PageStory> = () => ( export const WithSocialProviders: Story = {
render: () => (
<PageStory <PageStory
kcContext={{ kcContext={{
social: { social: {
displayInfo: true, providers: [ displayInfo: true,
providers: [
{ loginUrl: 'google', alias: 'google', providerId: 'google', displayName: 'Google' }, { loginUrl: 'google', alias: 'google', providerId: 'google', displayName: 'Google' },
{ loginUrl: 'microsoft', alias: 'microsoft', providerId: 'microsoft', displayName: 'Microsoft' }, { loginUrl: 'microsoft', alias: 'microsoft', providerId: 'microsoft', displayName: 'Microsoft' },
{ loginUrl: 'facebook', alias: 'facebook', providerId: 'facebook', displayName: 'Facebook' }, { loginUrl: 'facebook', alias: 'facebook', providerId: 'facebook', displayName: 'Facebook' },
@ -90,9 +75,9 @@ export const WithSocialProviders: ComponentStory<typeof PageStory> = () => (
{ loginUrl: 'bitbucket', alias: 'bitbucket', providerId: 'bitbucket', displayName: 'Bitbucket' }, { loginUrl: 'bitbucket', alias: 'bitbucket', providerId: 'bitbucket', displayName: 'Bitbucket' },
{ loginUrl: 'paypal', alias: 'paypal', providerId: 'paypal', displayName: 'PayPal' }, { loginUrl: 'paypal', alias: 'paypal', providerId: 'paypal', displayName: 'PayPal' },
{ loginUrl: 'openshift', alias: 'openshift', providerId: 'openshift', displayName: 'OpenShift' }, { loginUrl: 'openshift', alias: 'openshift', providerId: 'openshift', displayName: 'OpenShift' },
],
] },
}
}} }}
/> />
); ),
};

View File

@ -1,23 +1,30 @@
//This is to show that you can create stories for pages that you haven't overloaded. //This is to show that you can create stories for pages that you haven't overloaded.
import { ComponentStory, ComponentMeta } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { createPageStory } from "../createPageStory"; import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({ const { PageStory } = createPageStory({
pageId: "login-reset-password.ftl" pageId: "login-reset-password.ftl"
}); });
export default { const meta = {
title: "login/LoginResetPassword", title: "login/LoginResetPassword",
component: PageStory, component: PageStory,
} as ComponentMeta<typeof PageStory>; } satisfies Meta<typeof PageStory>;
export const Default: ComponentStory<typeof PageStory> = () => <PageStory />; export default meta;
type Story = StoryObj<typeof meta>;
export const WithEmailAsUsername: ComponentStory<typeof PageStory> = () => ( export const Default: Story = {
render: () => <PageStory />
};
export const WithEmailAsUsername: Story = {
render: () => (
<PageStory <PageStory
kcContext={{ kcContext={{
realm: { loginWithEmailAllowed: true, registrationEmailAsUsername: true } realm: { loginWithEmailAllowed: true, registrationEmailAsUsername: true }
}} }}
/> />
); )
};

View File

@ -1,21 +1,28 @@
import { ComponentStory, ComponentMeta } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { createPageStory } from "../createPageStory"; import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({ const { PageStory } = createPageStory({
pageId: "my-extra-page-2.ftl" pageId: "my-extra-page-2.ftl"
}); });
export default { const meta = {
title: "login/MyExtraPage2", title: "login/MyExtraPage2",
component: PageStory, component: PageStory,
} as ComponentMeta<typeof PageStory>; } satisfies Meta<typeof PageStory>;
export const Default: ComponentStory<typeof PageStory> = () => <PageStory />; export default meta;
type Story = StoryObj<typeof meta>;
export const WitAbc: ComponentStory<typeof PageStory> = () => ( export const Default: Story = {
render: () => <PageStory />
};
export const WitAbc: Story = {
render: () => (
<PageStory <PageStory
kcContext={{ kcContext={{
someCustomValue: "abc" someCustomValue: "abc"
}} }}
/> />
); )
};

View File

@ -1,13 +0,0 @@
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({
pageId: "terms.ftl"
});
export default {
title: "login/Terms",
component: PageStory,
} as ComponentMeta<typeof PageStory>;
export const Primary: ComponentStory<typeof PageStory> = () => <PageStory />;

View File

@ -1,13 +1,18 @@
import { ComponentStory, ComponentMeta } from '@storybook/react'; import { Meta, StoryObj } from '@storybook/react';
import { createPageStory } from "../createPageStory"; import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({ const { PageStory } = createPageStory({
pageId: "terms.ftl" pageId: "terms.ftl"
}); });
export default { const meta = {
title: "login/Terms", title: "login/Terms",
component: PageStory, component: PageStory,
} as ComponentMeta<typeof PageStory>; } satisfies Meta<typeof PageStory>;
export const Primary: ComponentStory<typeof PageStory> = () => <PageStory />; export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
render: () => <PageStory />
};

View File

@ -7,8 +7,6 @@ import { evtTermMarkdown } from "keycloakify/login/lib/useDownloadTerms";
import type { KcContext } from "../kcContext"; import type { KcContext } from "../kcContext";
import type { I18n } from "../i18n"; import type { I18n } from "../i18n";
import { useDownloadTerms } from "keycloakify/login"; import { useDownloadTerms } from "keycloakify/login";
import tos_en_url from "../assets/tos_en.md";
import tos_fr_url from "../assets/tos_fr.md";
export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>, I18n>) { export default function Terms(props: PageProps<Extract<KcContext, { pageId: "terms.ftl" }>, I18n>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props; const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
@ -28,18 +26,11 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
const tos_url = (() => { const tos_url = (() => {
switch (currentLanguageTag) { switch (currentLanguageTag) {
case "fr": return tos_fr_url; case "fr": return `${import.meta.env.BASE_URL}terms/fr.md`;
default: return tos_en_url; default: return `${import.meta.env.BASE_URL}terms/en.md`;
} }
})(); })();
if ("__STORYBOOK_ADDONS" in window) {
// NOTE: In storybook, when you import a .md file you get the content of the file.
// In Create React App on the other hand you get an url to the file.
return tos_url;
}
const markdownString = await fetch(tos_url).then(response => response.text()); const markdownString = await fetch(tos_url).then(response => response.text());
return markdownString; return markdownString;

4772
yarn.lock

File diff suppressed because it is too large Load Diff