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 = {
root: true,
env: { browser: true, es2020: true },
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react-hooks/recommended',
],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended', 'plugin:storybook/recommended'],
ignorePatterns: ['dist', '.eslintrc.cjs'],
parser: '@typescript-eslint/parser',
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",
"homepage": "https://starter.keycloakify.dev",
"version": "5.2.0",
"description": "A starter/demo project for keycloakify",
"repository": {
"type": "git",
"url": "git://github.com/codegouvfr/keycloakify-starter.git"
},
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build-keycloak-theme": "yarn build && keycloakify"
},
"keycloakify": {
"themeName": "keycloakify-starter",
"extraThemeProperties": [
"foo=bar"
]
},
"author": "u/garronej",
"license": "MIT",
"keywords": [],
"dependencies": {
"evt": "^2.5.7",
"oidc-spa": "^4.0.0",
"keycloakify": "9.4.0-rc.16",
"powerhooks": "^1.0.8",
"tsafe": "^1.6.6",
"zod": "^3.22.4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
"@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",
"typescript": "^5.2.2",
"vite": "^5.0.8",
"vite-plugin-commonjs": "^0.10.1",
"@storybook/react": "^7.6.14"
}
"name": "keycloakify-starter",
"homepage": "https://starter.keycloakify.dev",
"version": "5.2.0",
"description": "A starter/demo project for keycloakify",
"repository": {
"type": "git",
"url": "git://github.com/codegouvfr/keycloakify-starter.git"
},
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"build-keycloak-theme": "yarn build && keycloakify",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
},
"keycloakify": {
"themeName": "keycloakify-starter",
"extraThemeProperties": [
"foo=bar"
]
},
"author": "u/garronej",
"license": "MIT",
"keywords": [],
"dependencies": {
"evt": "^2.5.7",
"keycloakify": "9.4.0-rc.17",
"oidc-spa": "^4.0.0",
"powerhooks": "^1.0.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tsafe": "^1.6.6",
"zod": "^3.22.4"
},
"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-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
});
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";
const { PageStory } = createPageStory({
pageId: "password.ftl"
});
export default {
const meta = {
title: "account/Password",
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
kcContext={{
message: { type: "success", summary: "This is a test message" }
}}
/>;
export const Default: Story = {
render: () => <PageStory
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"' }}
>
{/*
This is just to show you how it can be done but this is not the best option for importing assets.
See: https://docs.keycloakify.dev/importing-assets#importing-custom-assets
Here we are referencing the `keycloakify-logo.png` in the `public` directory.
When possible don't use this approach, instead ...
*/}
<img src={`${import.meta.env.BASE_URL}keycloakify-logo.png`} alt="Keycloakify logo" width={50} />
{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} />
</div>
</div>

View File

@ -18,7 +18,7 @@ export function createPageStory<PageId extends KcContext["pageId"]>(params: {
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="fonts/WorkSans/font.css" />
<link rel="stylesheet" type="text/css" href={`${import.meta.env.BASE_URL}fonts/WorkSans/font.css`} />
<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";
const { PageStory } = createPageStory({
pageId: "login.ftl"
});
export default {
const meta = {
title: "login/Login",
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> = () => (
<PageStory
kcContext={{
realm: { password: false }
}}
/>
);
export const Default: Story = {
render: () => <PageStory />,
};
export const WithoutRegistration: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
realm: { registrationAllowed: false }
}}
/>
);
export const WithoutPasswordField: Story = {
render: () => <PageStory kcContext={{ realm: { password: false } }} />,
};
export const WithoutRememberMe: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
realm: { rememberMe: false }
}}
/>
);
export const WithoutRegistration: Story = {
render: () => <PageStory kcContext={{ realm: { registrationAllowed: false } }} />,
};
export const WithoutPasswordReset: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
realm: { resetPasswordAllowed: false }
}}
/>
);
export const WithoutRememberMe: Story = {
render: () => <PageStory kcContext={{ realm: { rememberMe: false } }} />,
};
export const WithEmailAsUsername: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
realm: { loginWithEmailAllowed: false }
}}
/>
);
export const WithoutPasswordReset: Story = {
render: () => <PageStory kcContext={{ realm: { resetPasswordAllowed: false } }} />,
};
export const WithPresetUsername: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
login: { username: "max.mustermann@mail.com" }
}}
/>
);
export const WithEmailAsUsername: Story = {
render: () => <PageStory kcContext={{ realm: { loginWithEmailAllowed: false } }} />,
};
export const WithImmutablePresetUsername: ComponentStory<typeof PageStory> = () => (
<PageStory
kcContext={{
auth: {
attemptedUsername: "max.mustermann@mail.com",
showUsername: true
},
usernameHidden: true,
message: { type: "info", summary: "Please re-authenticate to continue" }
}}
/>
);
export const WithPresetUsername: Story = {
render: () => <PageStory kcContext={{ login: { username: "max.mustermann@mail.com" } }} />,
};
export const WithSocialProviders: ComponentStory<typeof PageStory> = () => (
<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' },
export const WithImmutablePresetUsername: Story = {
render: () => (
<PageStory
kcContext={{
auth: {
attemptedUsername: "max.mustermann@mail.com",
showUsername: true,
},
usernameHidden: true,
message: { type: "info", summary: "Please re-authenticate to continue" },
}}
/>
),
};
]
}
}}
/>
);
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.
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Meta, StoryObj } from '@storybook/react';
import { createPageStory } from "../createPageStory";
const { PageStory } = createPageStory({
pageId: "login-reset-password.ftl"
});
export default {
const meta = {
title: "login/LoginResetPassword",
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> = () => (
<PageStory
kcContext={{
realm: { loginWithEmailAllowed: true, registrationEmailAsUsername: true }
}}
/>
);
export const Default: Story = {
render: () => <PageStory />
};
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";
const { PageStory } = createPageStory({
pageId: "my-extra-page-2.ftl"
});
export default {
const meta = {
title: "login/MyExtraPage2",
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> = () => (
<PageStory
kcContext={{
someCustomValue: "abc"
}}
/>
);
export const Default: Story = {
render: () => <PageStory />
};
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";
const { PageStory } = createPageStory({
pageId: "terms.ftl"
});
export default {
const meta = {
title: "login/Terms",
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 { I18n } from "../i18n";
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>) {
const { kcContext, i18n, doUseDefaultCss, Template, classes } = props;
@ -28,18 +26,11 @@ export default function Terms(props: PageProps<Extract<KcContext, { pageId: "ter
const tos_url = (() => {
switch (currentLanguageTag) {
case "fr": return tos_fr_url;
default: return tos_en_url;
case "fr": return `${import.meta.env.BASE_URL}terms/fr.md`;
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());
return markdownString;

4772
yarn.lock

File diff suppressed because it is too large Load Diff