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

@ -1,49 +1,64 @@
{ {
"name": "keycloakify-starter", "name": "keycloakify-starter",
"homepage": "https://starter.keycloakify.dev", "homepage": "https://starter.keycloakify.dev",
"version": "5.2.0", "version": "5.2.0",
"description": "A starter/demo project for keycloakify", "description": "A starter/demo project for keycloakify",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git://github.com/codegouvfr/keycloakify-starter.git" "url": "git://github.com/codegouvfr/keycloakify-starter.git"
}, },
"type": "module", "type": "module",
"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",
"keycloakify": { "build-storybook": "storybook build"
"themeName": "keycloakify-starter", },
"extraThemeProperties": [ "keycloakify": {
"foo=bar" "themeName": "keycloakify-starter",
] "extraThemeProperties": [
}, "foo=bar"
"author": "u/garronej", ]
"license": "MIT", },
"keywords": [], "author": "u/garronej",
"dependencies": { "license": "MIT",
"evt": "^2.5.7", "keywords": [],
"oidc-spa": "^4.0.0", "dependencies": {
"keycloakify": "9.4.0-rc.16", "evt": "^2.5.7",
"powerhooks": "^1.0.8", "keycloakify": "9.4.0-rc.17",
"tsafe": "^1.6.6", "oidc-spa": "^4.0.0",
"zod": "^3.22.4", "powerhooks": "^1.0.8",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0" "react-dom": "^18.2.0",
}, "tsafe": "^1.6.6",
"devDependencies": { "zod": "^3.22.4"
"@types/react": "^18.2.43", },
"@types/react-dom": "^18.2.17", "devDependencies": {
"@typescript-eslint/eslint-plugin": "^6.14.0", "@storybook/addon-essentials": "^7.6.14",
"@typescript-eslint/parser": "^6.14.0", "@storybook/addon-interactions": "^7.6.14",
"@vitejs/plugin-react": "^4.2.1", "@storybook/addon-links": "^7.6.14",
"eslint": "^8.55.0", "@storybook/addon-onboarding": "^1.0.11",
"eslint-plugin-react-hooks": "^4.6.0", "@storybook/blocks": "^7.6.14",
"eslint-plugin-react-refresh": "^0.4.5", "@storybook/react": "^7.6.14",
"typescript": "^5.2.2", "@storybook/react-vite": "^7.6.14",
"vite": "^5.0.8", "@storybook/test": "^7.6.14",
"vite-plugin-commonjs": "^0.10.1", "@types/react": "^18.2.43",
"@storybook/react": "^7.6.14" "@types/react-dom": "^18.2.17",
} "@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
"@vitejs/plugin-react": "^4.2.1",
"eslint": "^8.55.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.5",
"eslint-plugin-storybook": "^0.6.15",
"storybook": "^7.6.14",
"typescript": "^5.2.2",
"vite": "^5.0.8",
"vite-plugin-commonjs": "^0.10.1"
},
"_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 = {
kcContext={{ render: () => <PageStory
message: { type: "success", summary: "This is a test message" } kcContext={{
}} 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,98 +1,83 @@
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 = {
<PageStory render: () => <PageStory kcContext={{ login: { username: "max.mustermann@mail.com" } }} />,
kcContext={{ };
auth: {
attemptedUsername: "max.mustermann@mail.com",
showUsername: true
},
usernameHidden: true,
message: { type: "info", summary: "Please re-authenticate to continue" }
}}
/>
);
export const WithSocialProviders: ComponentStory<typeof PageStory> = () => ( export const WithImmutablePresetUsername: Story = {
<PageStory render: () => (
kcContext={{ <PageStory
social: { kcContext={{
displayInfo: true, providers: [ auth: {
{ loginUrl: 'google', alias: 'google', providerId: 'google', displayName: 'Google' }, attemptedUsername: "max.mustermann@mail.com",
{ loginUrl: 'microsoft', alias: 'microsoft', providerId: 'microsoft', displayName: 'Microsoft' }, showUsername: true,
{ loginUrl: 'facebook', alias: 'facebook', providerId: 'facebook', displayName: 'Facebook' }, },
{ loginUrl: 'instagram', alias: 'instagram', providerId: 'instagram', displayName: 'Instagram' }, usernameHidden: true,
{ loginUrl: 'twitter', alias: 'twitter', providerId: 'twitter', displayName: 'Twitter' }, message: { type: "info", summary: "Please re-authenticate to continue" },
{ loginUrl: 'linkedin', alias: 'linkedin', providerId: 'linkedin', displayName: 'LinkedIn' }, }}
{ loginUrl: 'stackoverflow', alias: 'stackoverflow', providerId: 'stackoverflow', displayName: 'Stackoverflow' }, />
{ loginUrl: 'github', alias: 'github', providerId: 'github', displayName: 'Github' }, ),
{ loginUrl: 'gitlab', alias: 'gitlab', providerId: 'gitlab', displayName: 'Gitlab' }, };
{ loginUrl: 'bitbucket', alias: 'bitbucket', providerId: 'bitbucket', displayName: 'Bitbucket' },
{ loginUrl: 'paypal', alias: 'paypal', providerId: 'paypal', displayName: 'PayPal' },
{ loginUrl: 'openshift', alias: 'openshift', providerId: 'openshift', displayName: 'OpenShift' },
] export const WithSocialProviders: Story = {
} render: () => (
}} <PageStory
/> kcContext={{
); social: {
displayInfo: true,
providers: [
{ loginUrl: 'google', alias: 'google', providerId: 'google', displayName: 'Google' },
{ loginUrl: 'microsoft', alias: 'microsoft', providerId: 'microsoft', displayName: 'Microsoft' },
{ loginUrl: 'facebook', alias: 'facebook', providerId: 'facebook', displayName: 'Facebook' },
{ loginUrl: 'instagram', alias: 'instagram', providerId: 'instagram', displayName: 'Instagram' },
{ loginUrl: 'twitter', alias: 'twitter', providerId: 'twitter', displayName: 'Twitter' },
{ loginUrl: 'linkedin', alias: 'linkedin', providerId: 'linkedin', displayName: 'LinkedIn' },
{ loginUrl: 'stackoverflow', alias: 'stackoverflow', providerId: 'stackoverflow', displayName: 'Stackoverflow' },
{ loginUrl: 'github', alias: 'github', providerId: 'github', displayName: 'Github' },
{ loginUrl: 'gitlab', alias: 'gitlab', providerId: 'gitlab', displayName: 'Gitlab' },
{ loginUrl: 'bitbucket', alias: 'bitbucket', providerId: 'bitbucket', displayName: 'Bitbucket' },
{ loginUrl: 'paypal', alias: 'paypal', providerId: 'paypal', displayName: 'PayPal' },
{ 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 = {
<PageStory render: () => <PageStory />
kcContext={{ };
realm: { loginWithEmailAllowed: true, registrationEmailAsUsername: true }
}} export const WithEmailAsUsername: Story = {
/> render: () => (
); <PageStory
kcContext={{
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 = {
<PageStory render: () => <PageStory />
kcContext={{ };
someCustomValue: "abc"
}} export const WitAbc: Story = {
/> render: () => (
); <PageStory
kcContext={{
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