feat(devx): introduce storybook
First shot at introducing storybook, with stories for the keycloak theme pages already in the repo.
This commit is contained in:
parent
38dd7a946e
commit
8cdc6b7730
39
.storybook/data.ts
Normal file
39
.storybook/data.ts
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
import { KcContextBase, getKcContext } from "keycloakify/lib/getKcContext";
|
||||||
|
import type { DeepPartial } from "keycloakify/lib/tools/DeepPartial";
|
||||||
|
import type { ExtendsKcContextBase } from "keycloakify/lib/getKcContext/getKcContextFromWindow";
|
||||||
|
|
||||||
|
type KcContextExt =
|
||||||
|
| { pageId: "my-extra-page-1.ftl"; }
|
||||||
|
| { pageId: "my-extra-page-2.ftl"; someCustomValue: string; }
|
||||||
|
// NOTE: register.ftl is deprecated in favor of register-user-profile.ftl
|
||||||
|
// but let's say we use it anyway and have this plugin enabled: https://github.com/micedre/keycloak-mail-whitelisting
|
||||||
|
// keycloak-mail-whitelisting define the non standard ftl global authorizedMailDomains, we declare it here.
|
||||||
|
| { pageId: "register.ftl"; authorizedMailDomains: string[]; }
|
||||||
|
|
||||||
|
export const useKcStoryData = (mockData: (
|
||||||
|
{ pageId: KcContextBase['pageId'] | KcContextExt['pageId'] } & DeepPartial<ExtendsKcContextBase<KcContextExt>>
|
||||||
|
)) => {
|
||||||
|
|
||||||
|
const { kcContext } = getKcContext<KcContextExt>({
|
||||||
|
mockPageId: mockData.pageId,
|
||||||
|
mockData: [mockData]
|
||||||
|
})
|
||||||
|
if (!kcContext) throw new Error("no kcContext")
|
||||||
|
return { kcContext }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const socialProviders = [
|
||||||
|
{ 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' },
|
||||||
|
]
|
18
.storybook/main.js
Normal file
18
.storybook/main.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
module.exports = {
|
||||||
|
"stories": [
|
||||||
|
"../src/keycloak-theme/pages/Login.stories.tsx",
|
||||||
|
"../src/keycloak-theme/pages/Register.stories.tsx",
|
||||||
|
"../src/keycloak-theme/pages/MyExtraPage1.stories.tsx",
|
||||||
|
"../src/keycloak-theme/pages/MyExtraPage2.stories.tsx",
|
||||||
|
],
|
||||||
|
"addons": [
|
||||||
|
"@storybook/addon-links",
|
||||||
|
"@storybook/addon-essentials",
|
||||||
|
"@storybook/addon-interactions",
|
||||||
|
"@storybook/preset-create-react-app"
|
||||||
|
],
|
||||||
|
"framework": "@storybook/react",
|
||||||
|
"core": {
|
||||||
|
"builder": "@storybook/builder-webpack5"
|
||||||
|
}
|
||||||
|
}
|
9
.storybook/preview.js
Normal file
9
.storybook/preview.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
export const parameters = {
|
||||||
|
actions: { argTypesRegex: "^on[A-Z].*" },
|
||||||
|
controls: {
|
||||||
|
matchers: {
|
||||||
|
color: /(background|color)$/i,
|
||||||
|
date: /Date$/,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
140
package.json
140
package.json
|
@ -1,65 +1,87 @@
|
||||||
{
|
{
|
||||||
"name": "keycloakify-starter",
|
"name": "keycloakify-starter",
|
||||||
"homepage": "https://starter.keycloakify.dev",
|
"homepage": "https://starter.keycloakify.dev",
|
||||||
"version": "3.0.0",
|
"version": "3.0.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"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"start": "react-scripts start",
|
||||||
|
"build": "react-scripts build",
|
||||||
|
"build-keycloak-theme": "yarn build ; keycloakify",
|
||||||
|
"download-builtin-keycloak-theme": "download-builtin-keycloak-theme 15.0.2",
|
||||||
|
"storybook": "start-storybook -p 6006 -s public",
|
||||||
|
"build-storybook": "build-storybook -s public"
|
||||||
|
},
|
||||||
|
"keycloakify": {
|
||||||
|
"extraPages": [
|
||||||
|
"my-extra-page-1.ftl",
|
||||||
|
"my-extra-page-2.ftl"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"author": "u/garronej",
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [],
|
||||||
|
"dependencies": {
|
||||||
|
"evt": "^2.4.15",
|
||||||
|
"jwt-decode": "^3.1.2",
|
||||||
|
"keycloak-js": "^21.0.1",
|
||||||
|
"keycloakify": "^6.12.4",
|
||||||
|
"powerhooks": "^0.26.2",
|
||||||
|
"react": "18.1.0",
|
||||||
|
"react-dom": "18.1.0",
|
||||||
|
"tsafe": "^1.4.3"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@storybook/addon-actions": "^6.5.16",
|
||||||
|
"@storybook/addon-essentials": "^6.5.16",
|
||||||
|
"@storybook/addon-interactions": "^6.5.16",
|
||||||
|
"@storybook/addon-links": "^6.5.16",
|
||||||
|
"@storybook/builder-webpack5": "^6.5.16",
|
||||||
|
"@storybook/manager-webpack5": "^6.5.16",
|
||||||
|
"@storybook/node-logger": "^6.5.16",
|
||||||
|
"@storybook/preset-create-react-app": "^4.1.2",
|
||||||
|
"@storybook/react": "^6.5.16",
|
||||||
|
"@storybook/testing-library": "^0.0.13",
|
||||||
|
"@types/node": "^15.3.1",
|
||||||
|
"@types/react": "18.0.9",
|
||||||
|
"@types/react-dom": "18.0.4",
|
||||||
|
"react-scripts": "5.0.0",
|
||||||
|
"typescript": "~4.8.0"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
],
|
||||||
|
"rules": {
|
||||||
|
"react-hooks/exhaustive-deps": "off",
|
||||||
|
"@typescript-eslint/no-redeclare": "off",
|
||||||
|
"no-labels": "off"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"overrides": [
|
||||||
"start": "react-scripts start",
|
{
|
||||||
"build": "react-scripts build",
|
"files": [
|
||||||
"build-keycloak-theme": "yarn build && keycloakify",
|
"**/*.stories.*"
|
||||||
"download-builtin-keycloak-theme": "download-builtin-keycloak-theme 15.0.2"
|
|
||||||
},
|
|
||||||
"keycloakify": {
|
|
||||||
"extraPages": [
|
|
||||||
"my-extra-page-1.ftl",
|
|
||||||
"my-extra-page-2.ftl"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"author": "u/garronej",
|
|
||||||
"license": "MIT",
|
|
||||||
"keywords": [],
|
|
||||||
"dependencies": {
|
|
||||||
"evt": "^2.4.15",
|
|
||||||
"jwt-decode": "^3.1.2",
|
|
||||||
"keycloak-js": "^21.0.1",
|
|
||||||
"keycloakify": "^6.12.4",
|
|
||||||
"powerhooks": "^0.26.2",
|
|
||||||
"react": "18.1.0",
|
|
||||||
"react-dom": "18.1.0",
|
|
||||||
"tsafe": "^1.4.3"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@types/node": "^15.3.1",
|
|
||||||
"@types/react": "18.0.9",
|
|
||||||
"@types/react-dom": "18.0.4",
|
|
||||||
"react-scripts": "5.0.0",
|
|
||||||
"typescript": "~4.8.0"
|
|
||||||
},
|
|
||||||
"eslintConfig": {
|
|
||||||
"extends": [
|
|
||||||
"react-app",
|
|
||||||
"react-app/jest"
|
|
||||||
],
|
],
|
||||||
"rules": {
|
"rules": {
|
||||||
"react-hooks/exhaustive-deps": "off",
|
"import/no-anonymous-default-export": "off"
|
||||||
"@typescript-eslint/no-redeclare": "off",
|
|
||||||
"no-labels": "off"
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
"browserslist": {
|
]
|
||||||
"production": [
|
},
|
||||||
">0.2%",
|
"browserslist": {
|
||||||
"not dead",
|
"production": [
|
||||||
"not op_mini all"
|
">0.2%",
|
||||||
],
|
"not dead",
|
||||||
"development": [
|
"not op_mini all"
|
||||||
"last 1 chrome version",
|
],
|
||||||
"last 1 firefox version",
|
"development": [
|
||||||
"last 1 safari version"
|
"last 1 chrome version",
|
||||||
]
|
"last 1 firefox version",
|
||||||
}
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
97
src/keycloak-theme/pages/Login.stories.tsx
Normal file
97
src/keycloak-theme/pages/Login.stories.tsx
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
import { ComponentMeta } from '@storybook/react';
|
||||||
|
import KcApp from '../KcApp';
|
||||||
|
|
||||||
|
import { useKcStoryData, socialProviders } from '../../../.storybook/data'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Login',
|
||||||
|
component: KcApp,
|
||||||
|
parameters: {
|
||||||
|
layout: 'fullscreen',
|
||||||
|
},
|
||||||
|
} as ComponentMeta<typeof KcApp>;
|
||||||
|
|
||||||
|
const pageId = 'login.ftl'
|
||||||
|
|
||||||
|
export const Default = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InFrench = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined, locale: { currentLanguageTag: 'fr' } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RealmDisplayNameIsHtml = () => {
|
||||||
|
const { kcContext } = useKcStoryData({
|
||||||
|
pageId, message: undefined, realm: {
|
||||||
|
displayNameHtml: '<marquee>my realm</marquee>'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NoInternationalization = () => {
|
||||||
|
const { kcContext } = useKcStoryData({
|
||||||
|
pageId, message: undefined, realm: {
|
||||||
|
internationalizationEnabled: false,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NoPasswordField = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined, realm: { password: false } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RegistrationNotAllowed = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined, realm: { registrationAllowed: false } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const RememberMeNotAllowed = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined, realm: { rememberMe: false } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PasswordResetNotAllowed = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined, realm: { resetPasswordAllowed: false } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmailIsUsername = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined, realm: { loginWithEmailAllowed: false } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const TryAnotherWay = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined, auth: { showTryAnotherWayLink: true } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PresetUsername = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined, login: { username: 'max.mustermann@mail.com' } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ReadOnlyPresetUsername = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined, login: { username: 'max.mustermann@mail.com' }, usernameEditDisabled: true })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithSocialProviders = () => {
|
||||||
|
const { kcContext } = useKcStoryData({
|
||||||
|
pageId, message: undefined, social: {
|
||||||
|
displayInfo: true,
|
||||||
|
providers: socialProviders
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithError = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: { type: "error", summary: "This is an error" } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
24
src/keycloak-theme/pages/MyExtraPage1.stories.tsx
Normal file
24
src/keycloak-theme/pages/MyExtraPage1.stories.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import { ComponentMeta } from '@storybook/react';
|
||||||
|
import KcApp from '../KcApp';
|
||||||
|
|
||||||
|
import { useKcStoryData } from '../../../.storybook/data'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'My Extra Page 1',
|
||||||
|
component: KcApp,
|
||||||
|
parameters: {
|
||||||
|
layout: 'fullscreen',
|
||||||
|
},
|
||||||
|
} as ComponentMeta<typeof KcApp>;
|
||||||
|
|
||||||
|
const pageId = 'my-extra-page-1.ftl'
|
||||||
|
|
||||||
|
export const Default = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InFrench = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, locale: { currentLanguageTag: 'fr' } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
29
src/keycloak-theme/pages/MyExtraPage2.stories.tsx
Normal file
29
src/keycloak-theme/pages/MyExtraPage2.stories.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { ComponentMeta } from '@storybook/react';
|
||||||
|
import KcApp from '../KcApp';
|
||||||
|
|
||||||
|
import { useKcStoryData } from '../../../.storybook/data'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'My Extra Page 2',
|
||||||
|
component: KcApp,
|
||||||
|
parameters: {
|
||||||
|
layout: 'fullscreen',
|
||||||
|
},
|
||||||
|
} as ComponentMeta<typeof KcApp>;
|
||||||
|
|
||||||
|
const pageId = 'my-extra-page-2.ftl'
|
||||||
|
|
||||||
|
export const Default = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InFrench = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, locale: { currentLanguageTag: 'fr' } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithCustomValue = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, someCustomValue: 'Foo Bar Baz' })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
69
src/keycloak-theme/pages/Register.stories.tsx
Normal file
69
src/keycloak-theme/pages/Register.stories.tsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import { ComponentMeta } from '@storybook/react';
|
||||||
|
import KcApp from '../KcApp';
|
||||||
|
|
||||||
|
import { useKcStoryData, socialProviders } from '../../../.storybook/data'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'Register',
|
||||||
|
component: KcApp,
|
||||||
|
parameters: {
|
||||||
|
layout: 'fullscreen',
|
||||||
|
},
|
||||||
|
} as ComponentMeta<typeof KcApp>;
|
||||||
|
|
||||||
|
const pageId = 'register.ftl'
|
||||||
|
|
||||||
|
export const Default = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InFrench = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: undefined, locale: { currentLanguageTag: 'fr' } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithError = () => {
|
||||||
|
const { kcContext } = useKcStoryData({ pageId, message: { type: "error", summary: "This is an error" } })
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const EmailIsUsername = () => {
|
||||||
|
const { kcContext } = useKcStoryData({
|
||||||
|
pageId, message: undefined,
|
||||||
|
realm: { registrationEmailAsUsername: true }
|
||||||
|
})
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NoPassword = () => {
|
||||||
|
const { kcContext } = useKcStoryData({
|
||||||
|
pageId, message: undefined, passwordRequired: false
|
||||||
|
})
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithRecaptcha = () => {
|
||||||
|
const { kcContext } = useKcStoryData({
|
||||||
|
pageId, message: undefined,
|
||||||
|
recaptchaRequired: true,
|
||||||
|
recaptchaSiteKey: 'foobar'
|
||||||
|
})
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WithPresets = () => {
|
||||||
|
const { kcContext } = useKcStoryData({
|
||||||
|
pageId, message: undefined,
|
||||||
|
register: {
|
||||||
|
formData: {
|
||||||
|
firstName: 'Max',
|
||||||
|
lastName: 'Mustermann',
|
||||||
|
email: 'max.mustermann@mail.com',
|
||||||
|
username: 'max.mustermann'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return <KcApp kcContext={kcContext} />
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue