Compare commits
2 commits
94b95e884e
...
e8efe9859b
Author | SHA1 | Date | |
---|---|---|---|
e8efe9859b | |||
cef6fd45a9 |
23 changed files with 235 additions and 310 deletions
|
@ -10,5 +10,5 @@ export const FetchLoader: FC<FetchLoaderProps> = ({ children, active, placeholde
|
|||
if (!active) {
|
||||
return <>{children}</>;
|
||||
}
|
||||
return <div className="animate-pulse flex-1 py-1">{placeholder ?? null}</div>;
|
||||
return <div className="animate-pulse flex-1 py-1">{placeholder}</div>;
|
||||
};
|
||||
|
|
|
@ -20,7 +20,7 @@ export type BaseQueryFetchParams<Body> = {
|
|||
url: string;
|
||||
};
|
||||
|
||||
export type BaseQueryWithReauthParams = {
|
||||
export type BaseQueryWithRefreshParams = {
|
||||
authentication: {
|
||||
baseUrl: string;
|
||||
clientId: string;
|
||||
|
@ -70,11 +70,11 @@ export const baseQuery =
|
|||
}
|
||||
};
|
||||
|
||||
export const baseQueryWithReauth =
|
||||
export const baseQueryWithRefresh =
|
||||
<Response = { data: unknown }, RequestBody = any>({
|
||||
baseUrl,
|
||||
authentication,
|
||||
}: BaseQueryWithReauthParams): BaseQueryFn<BaseQueryFetchParams<RequestBody>, Response, FetchBaseQueryError> =>
|
||||
}: BaseQueryWithRefreshParams): BaseQueryFn<BaseQueryFetchParams<RequestBody>, Response, FetchBaseQueryError> =>
|
||||
async (args, api, extraOptions) => {
|
||||
const authReducer: any = (api.getState() as AuthRootState).procyon[AUTH_REDUCER_NAME];
|
||||
const accessTokenExpired = new Date(authReducer.accessExpiresTime) < new Date();
|
||||
|
|
38
packages/auth/src/hook/useAuthCode.ts
Normal file
38
packages/auth/src/hook/useAuthCode.ts
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'wouter';
|
||||
|
||||
import { setAuthTokens } from '@procyon/auth/slice';
|
||||
|
||||
import { fetchTokens } from '../actions';
|
||||
import { UseAuthParams } from '../types';
|
||||
|
||||
export const useAuthCode = ({ baseUrl, clientId, tokenEndpoint, redirectUri }: UseAuthParams) => {
|
||||
const dispatch = useDispatch<any>();
|
||||
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
return useCallback(async (code: string) => {
|
||||
const { data: tokens } = await fetchTokens({
|
||||
baseUrl,
|
||||
tokenEndpoint,
|
||||
clientId,
|
||||
redirectUri: redirectUri,
|
||||
grantType: 'authorization_code',
|
||||
code,
|
||||
});
|
||||
if (tokens) {
|
||||
dispatch(
|
||||
setAuthTokens({
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: tokens.refresh_token,
|
||||
idToken: tokens.id_token,
|
||||
accessExpiresIn: tokens.expires_in,
|
||||
refreshExpiresIn: tokens.refresh_expires_in,
|
||||
sessionState: tokens.session_state,
|
||||
})
|
||||
);
|
||||
setLocation('/');
|
||||
}
|
||||
}, []);
|
||||
};
|
29
packages/auth/src/hook/useLogin.ts
Normal file
29
packages/auth/src/hook/useLogin.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { useCallback } from 'react';
|
||||
|
||||
import { UseAuthParams } from '../types';
|
||||
|
||||
const generateState = () =>
|
||||
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = (Math.random() * 16) | 0,
|
||||
v = c == 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
|
||||
export const useLogin = ({ baseUrl, clientId, authEndpoint, redirectUri }: UseAuthParams) => {
|
||||
return useCallback(() => {
|
||||
const state = generateState();
|
||||
const urlParams: Record<string, string> = {
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
response_mode: 'fragment',
|
||||
response_type: 'code',
|
||||
scope: 'openid profile',
|
||||
state,
|
||||
};
|
||||
const connectionURI = new URL(`${baseUrl}${authEndpoint}`);
|
||||
for (const key of Object.keys(urlParams)) {
|
||||
connectionURI.searchParams.append(key, urlParams[key]);
|
||||
}
|
||||
window.location.assign(connectionURI);
|
||||
}, []);
|
||||
};
|
|
@ -1,72 +0,0 @@
|
|||
import { useCallback } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useLocation } from 'wouter';
|
||||
|
||||
import { setAuthTokens } from '@procyon/auth/slice';
|
||||
|
||||
import { fetchTokens } from '../actions';
|
||||
import { UseOAuthParams } from '../types';
|
||||
|
||||
const generateState = () =>
|
||||
'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||
const r = (Math.random() * 16) | 0,
|
||||
v = c == 'x' ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
|
||||
/**
|
||||
* test
|
||||
* @param param0 test
|
||||
* @returns
|
||||
*/
|
||||
export const useOAuth = ({ baseUrl, clientId, authEndpoint, tokenEndpoint, redirectUri }: UseOAuthParams) => {
|
||||
const dispatch = useDispatch<any>();
|
||||
|
||||
const [, setLocation] = useLocation();
|
||||
|
||||
const redirect = useCallback(() => {
|
||||
const state = generateState();
|
||||
const urlParams: Record<string, string> = {
|
||||
client_id: clientId,
|
||||
redirect_uri: redirectUri,
|
||||
response_mode: 'fragment',
|
||||
response_type: 'code',
|
||||
scope: 'openid profile',
|
||||
state,
|
||||
};
|
||||
const conectionURI = new URL(`${baseUrl}${authEndpoint}`);
|
||||
for (const key of Object.keys(urlParams)) {
|
||||
conectionURI.searchParams.append(key, urlParams[key]);
|
||||
}
|
||||
window.location.assign(conectionURI);
|
||||
}, []);
|
||||
|
||||
const obtainTokens = useCallback(async (code: string) => {
|
||||
const { data: tokens } = await fetchTokens({
|
||||
baseUrl,
|
||||
tokenEndpoint,
|
||||
clientId,
|
||||
redirectUri: redirectUri,
|
||||
grantType: 'authorization_code',
|
||||
code,
|
||||
});
|
||||
if (tokens) {
|
||||
dispatch(
|
||||
setAuthTokens({
|
||||
accessToken: tokens.access_token,
|
||||
refreshToken: tokens.refresh_token,
|
||||
idToken: tokens.id_token,
|
||||
accessExpiresIn: tokens.expires_in,
|
||||
refreshExpiresIn: tokens.refresh_expires_in,
|
||||
sessionState: tokens.session_state,
|
||||
})
|
||||
);
|
||||
setLocation('/');
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
redirect,
|
||||
obtainTokens,
|
||||
};
|
||||
};
|
|
@ -1,7 +1,7 @@
|
|||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { addSeconds } from 'date-fns';
|
||||
|
||||
import { AUTH_REDUCER_NAME, AuthAttributes, AuthRootState, AuthTokens, ModifyAuthAttribute } from './types';
|
||||
import { AUTH_REDUCER_NAME, AuthAttributes, AuthRootState, AuthTokenPayload, ModifyAuthAttribute } from './types';
|
||||
|
||||
const initialState: AuthAttributes = {
|
||||
authenticated: undefined,
|
||||
|
@ -19,7 +19,7 @@ const authSlice = createSlice({
|
|||
name: AUTH_REDUCER_NAME,
|
||||
initialState,
|
||||
reducers: {
|
||||
setAuthTokens: (_, action: PayloadAction<AuthTokens>) => {
|
||||
setAuthTokens: (_, action: PayloadAction<AuthTokenPayload>) => {
|
||||
const { accessExpiresIn, refreshExpiresIn } = action.payload;
|
||||
return {
|
||||
...action.payload,
|
||||
|
|
|
@ -2,7 +2,7 @@ import { ROOT_REDUCER_NAME } from '@procyon/constants/redux';
|
|||
|
||||
export const AUTH_REDUCER_NAME = 'auth';
|
||||
|
||||
export type AuthTokens = {
|
||||
export type AuthTokenPayload = {
|
||||
accessExpiresIn?: number;
|
||||
accessToken?: string;
|
||||
idToken?: string;
|
||||
|
@ -16,7 +16,7 @@ export type AuthAttributes<CustomAttributes = unknown> = {
|
|||
authenticated?: boolean;
|
||||
refreshExpiresTime?: string;
|
||||
} & CustomAttributes &
|
||||
AuthTokens;
|
||||
AuthTokenPayload;
|
||||
|
||||
export type ModifyAuthAttribute<P = unknown> = {
|
||||
key: keyof AuthAttributes<P>;
|
||||
|
@ -29,7 +29,7 @@ export type AuthRootState = {
|
|||
};
|
||||
};
|
||||
|
||||
export type UseOAuthParams = {
|
||||
export type UseAuthParams = {
|
||||
authEndpoint: string;
|
||||
baseUrl: string;
|
||||
clientId: string;
|
||||
|
@ -38,7 +38,7 @@ export type UseOAuthParams = {
|
|||
tokenEndpoint: string;
|
||||
};
|
||||
|
||||
export type FetchTokensParams = Pick<UseOAuthParams, 'baseUrl' | 'tokenEndpoint' | 'clientId'> & {
|
||||
export type FetchTokensParams = Pick<UseAuthParams, 'baseUrl' | 'tokenEndpoint' | 'clientId'> & {
|
||||
code: string;
|
||||
grantType: 'authorization_code';
|
||||
redirectUri: string;
|
||||
|
|
|
@ -2,9 +2,10 @@ import React, { FC, Fragment, useEffect, useMemo, useState } from 'react';
|
|||
import { range } from 'ramda';
|
||||
import { add, endOfMonth, endOfWeek, getWeeksInMonth, startOfMonth, startOfWeek, sub } from 'date-fns';
|
||||
|
||||
import { SizeEnum } from '@procyon/types/common';
|
||||
import { isNilOrEmpty } from '@procyon/utils';
|
||||
|
||||
import { Title, TitleSizeEnum } from '../../Title';
|
||||
import { Text } from '../../Text';
|
||||
import { CalendarCell, CalendarCellProps } from './CalendarCell';
|
||||
import { CalendarDays, CalendarDaysProps } from './CalendarDays';
|
||||
import { Controller, ControllerProps } from './Controller';
|
||||
|
@ -181,7 +182,7 @@ export function Calendar({
|
|||
|
||||
return (
|
||||
<>
|
||||
{!isNilOrEmpty(title) && <Title size={TitleSizeEnum.md}>{title}</Title>}
|
||||
{!isNilOrEmpty(title) && <Text size={SizeEnum.md}>{title}</Text>}
|
||||
<Control
|
||||
layout={layout}
|
||||
onNextClick={handleClickNext}
|
||||
|
|
|
@ -1,25 +1,14 @@
|
|||
import React, { FC } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { DirectionEnum } from '@procyon/types/common';
|
||||
|
||||
export type SectionProps = {
|
||||
Title: FC;
|
||||
children: any;
|
||||
space?: Partial<Record<DirectionEnum, number>>;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Section: FC<SectionProps> = ({ Title, children, space = { [DirectionEnum.bottom]: 4 } }) => {
|
||||
const spaces: string[] = [];
|
||||
for (const direct in space) {
|
||||
spaces.push(`m${direct}-${space[direct]}`);
|
||||
}
|
||||
return (
|
||||
<div className={clsx(spaces)}>
|
||||
export const Section: FC<SectionProps> = ({ Title, children, className }) => (
|
||||
<div className={className}>
|
||||
{Title && <Title />}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Section;
|
||||
)
|
25
packages/components/src/Text.tsx
Normal file
25
packages/components/src/Text.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React, { FC, ReactElement } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { SizeEnum } from '@procyon/types/common';
|
||||
|
||||
export type TextProps = {
|
||||
children?: ReactElement | string;
|
||||
className?: string;
|
||||
size?: SizeEnum;
|
||||
};
|
||||
|
||||
export const Text: FC<TextProps> = ({ size = SizeEnum.sm, children, className }) => (
|
||||
<div
|
||||
className={clsx(
|
||||
{
|
||||
'text-base': size === SizeEnum.sm,
|
||||
'text-2xl mb-1': size === SizeEnum.md,
|
||||
'text-3xl mb-2': size === SizeEnum.lg,
|
||||
'text-4xl mb-3': size === SizeEnum.xl,
|
||||
},
|
||||
className
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
|
@ -1,39 +0,0 @@
|
|||
import React, { FC, ReactElement } from 'react';
|
||||
import clsx from 'clsx';
|
||||
|
||||
import { DirectionEnum, SizeEnum } from '@procyon/types/common';
|
||||
|
||||
export enum TitleSizeEnum {
|
||||
lg = 'lg',
|
||||
md = 'md',
|
||||
xl = 'xl',
|
||||
}
|
||||
|
||||
export type TitleProps = {
|
||||
children?: ReactElement | string;
|
||||
className?: string;
|
||||
margin?: Partial<Record<DirectionEnum, number>>;
|
||||
size?: TitleSizeEnum;
|
||||
};
|
||||
|
||||
export const Title: FC<TitleProps> = ({ size = SizeEnum.sm, children, className, margin }) => {
|
||||
const margins: string[] = [];
|
||||
for (const direct in margin) {
|
||||
margins.push(`m${direct}-${margin[direct]}`);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
{
|
||||
'text-base': size === SizeEnum.sm,
|
||||
'text-2xl mb-1': size === SizeEnum.md,
|
||||
'text-3xl mb-2': size === SizeEnum.lg,
|
||||
'text-4xl mb-3': size === SizeEnum.xl,
|
||||
},
|
||||
margins,
|
||||
className
|
||||
)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,3 +0,0 @@
|
|||
export const EMPTY_OBJECT = {};
|
||||
|
||||
export const EMPTY_ARRAY: [] = [];
|
|
@ -43,13 +43,11 @@ module.exports = config;
|
|||
|
||||
<Unstyled>
|
||||
<Message status={StatusEnum.info}>
|
||||
If you want to use automatical reobtain access token, do not forget to use `baseQueryWithReauth` instead of
|
||||
`baseQuery` from `@procyon/api/query`. Pass same parameters as to `useOauth` hook.
|
||||
If you want to use automatically extend access token, use `baseQueryWithRefresh` instead of
|
||||
`baseQuery` from `@procyon/api/query`.
|
||||
</Message>
|
||||
</Unstyled>
|
||||
|
||||
<br />
|
||||
|
||||
```ts
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import { Canvas, Meta, Source, Unstyled } from '@storybook/blocks';
|
||||
|
||||
import Message from '@procyon/components/Message';
|
||||
import { StatusEnum } from '@procyon/types/common';
|
||||
import { Canvas, Meta, Source } from '@storybook/blocks';
|
||||
|
||||
import apiContent from './helpers/api?raw';
|
||||
import { FetchComponent } from './helpers/FetchComponent';
|
||||
|
@ -16,15 +13,6 @@ import reduxContent from './helpers/redux?raw';
|
|||
|
||||
`@procyon/api` module for fetch.
|
||||
|
||||
<Unstyled>
|
||||
<Message status={StatusEnum.info}>
|
||||
If you want to use automatical reobtain access token, do not forget to use `baseQueryWithReauth` instead of
|
||||
`baseQuery` from `@procyon/api/query`. Pass same parameters as to `useOauth` hook.
|
||||
</Message>
|
||||
</Unstyled>
|
||||
|
||||
<br />
|
||||
|
||||
## Preparation
|
||||
|
||||
### Define API via RTQuery
|
||||
|
|
|
@ -4,6 +4,7 @@ import Message from '@procyon/components/Message';
|
|||
import { StatusEnum } from '@procyon/types/common';
|
||||
|
||||
import storeCode from './helpers/redux?raw';
|
||||
import hookCode from './helpers/hook?raw';
|
||||
|
||||
<Meta title="Auth/Introduction" />
|
||||
|
||||
|
@ -11,44 +12,29 @@ import storeCode from './helpers/redux?raw';
|
|||
|
||||
`@procyon/auth` module for authenticated.
|
||||
|
||||
For work with IdP (Identity Provider server) use `useOAuth` hook.
|
||||
For work with IdP (Identity Provider) server use `useLogin` and `useAuthCode` hooks.
|
||||
|
||||
<Unstyled>
|
||||
<Message status={StatusEnum.info}>
|
||||
After reload page, reducer data are synchronized with browser session storage. So they are saved until close browser
|
||||
tab.
|
||||
When refresh page, data are synchronized with browser session storage.
|
||||
</Message>
|
||||
</Unstyled>
|
||||
|
||||
<br />
|
||||
|
||||
<Unstyled>
|
||||
<Message status={StatusEnum.error}>
|
||||
If you want to use automatical reobtain access token, do not forget to use `baseQueryWithReauth` instead of
|
||||
`baseQuery` from `@procyon/api/query` and pass same parameters as to `useOauth` hook.
|
||||
<Message status={StatusEnum.info}>
|
||||
If you want to use automatically extend access token, use `baseQueryWithRefresh` instead of
|
||||
`baseQuery` from `@procyon/api/query`.
|
||||
</Message>
|
||||
</Unstyled>
|
||||
|
||||
<br />
|
||||
|
||||
## Redux store
|
||||
|
||||
Redux is used as storage. `@procyon/api` use this storage for pickup access token for fetch auth header.
|
||||
|
||||
<Unstyled>
|
||||
<Message status={StatusEnum.warning}>
|
||||
Is important to to use auth reducer under <b>procyon</b> main reducer.
|
||||
</Message>
|
||||
</Unstyled>
|
||||
|
||||
<br />
|
||||
|
||||
### example
|
||||
### Redux storage
|
||||
|
||||
<Source language="ts" code={storeCode} />
|
||||
|
||||
### stored data
|
||||
|
||||
<PureArgsTable
|
||||
rows={{
|
||||
authenticated: {
|
||||
|
@ -86,3 +72,7 @@ Redux is used as storage. `@procyon/api` use this storage for pickup access toke
|
|||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### Sample source
|
||||
|
||||
<Source language="ts" code={hookCode} />
|
|
@ -1,60 +0,0 @@
|
|||
import { Meta, PureArgsTable, Source } from '@storybook/blocks';
|
||||
|
||||
import hookCode from './helpers/hook?raw';
|
||||
|
||||
<Meta title="Auth/useOAuth" />
|
||||
|
||||
# `useOAuth` hook
|
||||
|
||||
`import { useOAuth } from @procyon/auth/hook/useOAuth`
|
||||
|
||||
Hook will communicate with IdP server like a Keycloak.
|
||||
|
||||
<Source language="ts" code={hookCode} />
|
||||
|
||||
### Params
|
||||
|
||||
<PureArgsTable
|
||||
rows={{
|
||||
baseUrl: {
|
||||
description: 'Base Identity Provider URL.',
|
||||
name: 'baseUrl',
|
||||
},
|
||||
clientId: {
|
||||
description: 'Client ID defined in IdP',
|
||||
name: 'clientId',
|
||||
},
|
||||
authEndpoint: {
|
||||
description: 'URI for redirect to IdP login form',
|
||||
name: 'authEndpoint',
|
||||
},
|
||||
tokenEndpoint: {
|
||||
description: 'Endpoint whitch is called for obtain tokends',
|
||||
name: 'tokenEndpoint',
|
||||
},
|
||||
logoutEndpoint: {
|
||||
description: 'Endpoint for revoke session',
|
||||
name: 'logoutEndpoint',
|
||||
},
|
||||
redirectUrl: {
|
||||
description: 'URL to redirect from IdP back to application',
|
||||
name: 'redirectUrl',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
|
||||
### Returns
|
||||
|
||||
<PureArgsTable
|
||||
rows={{
|
||||
redirect: {
|
||||
description: 'Function. When call, make redirecto to IdP login page',
|
||||
name: 'redirect',
|
||||
},
|
||||
obtainTokens: {
|
||||
description:
|
||||
'Function. When return from IdP, app will obtain auth code which allow to fetch access tokens. After obtain tokens, make redirect to root route (/).',
|
||||
name: 'obtainTokens',
|
||||
},
|
||||
}}
|
||||
/>
|
|
@ -1,32 +1,36 @@
|
|||
import React, { FC } from 'react';
|
||||
import { useLocation } from 'wouter';
|
||||
|
||||
import { useOAuth } from '@procyon/auth/hook/useOAuth';
|
||||
import { useAuthCode } from '@procyon/auth/hook/useAuthCode';
|
||||
import { useLogin } from '@procyon/auth/hook/useLogin';
|
||||
|
||||
const authConfig = {
|
||||
baseUrl: 'http://keycloak.local',
|
||||
clientId: 'client_id',
|
||||
tokenEndpoint: '/realm/localdev/token',
|
||||
authEndpoint: '/realm/localdev/auth',
|
||||
redirectUri: 'http://localhost:3330/login/callback',
|
||||
logoutEndpoint: '/realm/localdev/logout',
|
||||
};
|
||||
|
||||
export const Component: FC = () => {
|
||||
const [location] = useLocation();
|
||||
|
||||
const { redirect, obtainTokens } = useOAuth({
|
||||
baseUrl: 'http://keycloak.local',
|
||||
clientId: 'client_id',
|
||||
tokenEndpoint: '/realm/localdev/token',
|
||||
authEndpoint: '/realm/localdev/auth',
|
||||
redirectUri: 'http://localhost:3330/login/callback',
|
||||
logoutEndpoint: '/realm/localdev/logout',
|
||||
});
|
||||
const doLogin = useLogin(authConfig);
|
||||
const changeCodeForTokens = useAuthCode(authConfig);
|
||||
|
||||
if (location === '/login/callback') {
|
||||
const params = new URLSearchParams(window.location.href);
|
||||
const code = params.get('code') ?? null;
|
||||
if (code) {
|
||||
obtainTokens(code);
|
||||
changeCodeForTokens(code);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={redirect}>login</button>
|
||||
<button onClick={doLogin}>login</button>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import React from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import Section from '@procyon/components/Section';
|
||||
import { Title, TitleSizeEnum } from '@procyon/components/Title';
|
||||
import { DirectionEnum } from '@procyon/types/common';
|
||||
import { Section } from '@procyon/components/Section';
|
||||
import { Text } from '@procyon/components/Text';
|
||||
import { SizeEnum } from '@procyon/types/common';
|
||||
|
||||
type Story = StoryObj<typeof Section>;
|
||||
|
||||
|
@ -21,7 +21,7 @@ export default {
|
|||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
Title: () => <Title>Section</Title>,
|
||||
Title: () => <Text>Section</Text>,
|
||||
children: 'Content',
|
||||
},
|
||||
};
|
||||
|
@ -34,24 +34,21 @@ export const WithoutTitle: Story = {
|
|||
|
||||
export const Medium: Story = {
|
||||
args: {
|
||||
Title: () => <Title size={TitleSizeEnum.md}>Section</Title>,
|
||||
Title: () => <Text size={SizeEnum.md}>Section</Text>,
|
||||
children: 'content',
|
||||
space: { [DirectionEnum.y]: 2 },
|
||||
},
|
||||
};
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
Title: () => <Title size={TitleSizeEnum.lg}>Section</Title>,
|
||||
Title: () => <Text size={SizeEnum.lg}>Section</Text>,
|
||||
children: 'content',
|
||||
space: { [DirectionEnum.y]: 2, [DirectionEnum.top]: 3 },
|
||||
},
|
||||
};
|
||||
|
||||
export const ExtraLarge: Story = {
|
||||
args: {
|
||||
Title: () => <Title size={TitleSizeEnum.xl}>Section</Title>,
|
||||
Title: () => <Text size={SizeEnum.xl}>Section</Text>,
|
||||
children: 'content',
|
||||
space: { [DirectionEnum.y]: 2, [DirectionEnum.top]: 4 },
|
||||
},
|
||||
};
|
||||
|
|
61
stories/components/Text.stories.tsx
Normal file
61
stories/components/Text.stories.tsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
import React from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Text } from '@procyon/components/Text';
|
||||
import { SizeEnum } from '@procyon/types/common';
|
||||
|
||||
type Story = StoryObj<typeof Text>;
|
||||
|
||||
export default {
|
||||
component: Text,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<>
|
||||
<Story />
|
||||
content
|
||||
</>
|
||||
),
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: "`import { Text } from '@procyon/components/Text';`",
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} as Meta;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: 'Default Text',
|
||||
},
|
||||
};
|
||||
|
||||
export const Standard: Story = {
|
||||
args: {
|
||||
children: 'Head title',
|
||||
size: SizeEnum.sm,
|
||||
},
|
||||
};
|
||||
|
||||
export const Middle: Story = {
|
||||
args: {
|
||||
children: 'Head title',
|
||||
size: SizeEnum.md,
|
||||
},
|
||||
};
|
||||
|
||||
export const Large: Story = {
|
||||
args: {
|
||||
children: 'Head title',
|
||||
size: SizeEnum.lg,
|
||||
},
|
||||
};
|
||||
|
||||
export const ExtraLarge: Story = {
|
||||
args: {
|
||||
children: 'Head title',
|
||||
size: SizeEnum.xl,
|
||||
},
|
||||
};
|
|
@ -1,48 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
import { Title, TitleSizeEnum } from '@procyon/components/Title';
|
||||
import { DirectionEnum } from '@procyon/types/common';
|
||||
|
||||
type Story = StoryObj<typeof Title>;
|
||||
|
||||
export default {
|
||||
component: Title,
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<>
|
||||
<Story />
|
||||
content
|
||||
</>
|
||||
),
|
||||
],
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: "`import { Title } from '@procyon/components/Title';`",
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
} as Meta;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
children: 'Default Title',
|
||||
},
|
||||
};
|
||||
|
||||
export const Header: Story = {
|
||||
args: {
|
||||
children: 'Head title',
|
||||
size: TitleSizeEnum.md,
|
||||
},
|
||||
};
|
||||
|
||||
export const HeaderWithSpace: Story = {
|
||||
args: {
|
||||
children: 'Head title',
|
||||
size: TitleSizeEnum.md,
|
||||
margin: { [DirectionEnum.bottom]: 4 },
|
||||
},
|
||||
};
|
|
@ -49,7 +49,7 @@ export const Hook: Story = {
|
|||
onClick={() =>
|
||||
showToaster({
|
||||
id: 'toaster1',
|
||||
title: 'Title',
|
||||
title: 'Text',
|
||||
message: 'Message',
|
||||
status: StatusEnum.info,
|
||||
})
|
||||
|
@ -76,7 +76,7 @@ export const OutsideReact: Story = {
|
|||
onClick={() =>
|
||||
showToaster({
|
||||
id: 'toaster2',
|
||||
title: 'Title',
|
||||
title: 'Text',
|
||||
message: 'Message',
|
||||
status: StatusEnum.success,
|
||||
})
|
||||
|
|
27
stories/typography/Introduction.mdx
Normal file
27
stories/typography/Introduction.mdx
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { Meta } from '@storybook/blocks';
|
||||
|
||||
<Meta title="Typography/Introduction" />
|
||||
|
||||
# Typography
|
||||
|
||||
## Sizes
|
||||
|
||||
Component for show text base on size is `Text` from `import { Text } from '@procyon/components/Text';`
|
||||
|
||||
### Small (sm)
|
||||
|
||||
- Standard font size for all page objects
|
||||
|
||||
### Medium (md)
|
||||
|
||||
- Section title
|
||||
- Multiple times per page
|
||||
|
||||
### Large (lg)
|
||||
|
||||
- Page title
|
||||
- Only once
|
||||
|
||||
### Extra Large (xl)
|
||||
|
||||
- For single page sections, like login or registration
|
Loading…
Add table
Reference in a new issue