Skip to content

Commit

Permalink
feat(context): add valhalla contexts
Browse files Browse the repository at this point in the history
  • Loading branch information
Michael2Gray committed Mar 1, 2022
1 parent f7dc6cb commit d2197b6
Show file tree
Hide file tree
Showing 123 changed files with 2,224 additions and 75 deletions.
9 changes: 6 additions & 3 deletions .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module.exports = {
addons: [
'@storybook/addon-links',
'@storybook/addon-essentials',
'@storybook/addon-a11y',
{
name: '@storybook/addon-postcss',
options: {
Expand All @@ -12,7 +11,11 @@ module.exports = {
},
},
},
'@storybook/addon-docs',
],

framework: '@storybook/react',
// https://storybook.js.org/docs/react/configure/typescript#mainjs-configuration
typescript: {
check: true, // type-check stories during Storybook build
reactDocgen: 'react-docgen',
},
};
20 changes: 13 additions & 7 deletions .storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import '../src/style.css';
import { DefaultTestProviders } from '../src/utils';

// https://storybook.js.org/docs/react/writing-stories/parameters#global-parameters
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
// https://storybook.js.org/docs/react/essentials/actions#automatically-matching-args
actions: { argTypesRegex: '^on.*' },
viewMode: 'docs',
};

export const decorators = [
(Story) => (
<DefaultTestProviders>
<Story />
</DefaultTestProviders>
),
];
28 changes: 18 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,14 @@
"semantic-release": "semantic-release"
},
"peerDependencies": {
"@radix-ui/react-tooltip": "^0.1.7",
"clsx": "^1.1.1",
"date-fns": "^2.28.0",
"framer-motion": "4.1.17",
"react": ">=16",
"react-hook-form": "^7.27.0",
"react-icons": "^4.3.1"
"react-icons": "^4.3.1",
"react-intl": "^5.24.6"
},
"husky": {
"hooks": {
Expand Down Expand Up @@ -68,16 +70,16 @@
"@babel/core": "^7.17.0",
"@commitlint/cli": "^16.1.0",
"@commitlint/config-conventional": "^16.0.0",
"@radix-ui/react-tooltip": "^0.1.7",
"@size-limit/preset-small-lib": "^7.0.8",
"@storybook/addon-a11y": "^6.4.12",
"@storybook/addon-actions": "^6.4.12",
"@storybook/addon-essentials": "^6.4.18",
"@storybook/addon-info": "^5.3.21",
"@storybook/addon-links": "^6.4.18",
"@storybook/addon-postcss": "^2.0.0",
"@storybook/addons": "^6.4.18",
"@storybook/react": "^6.4.18",
"@storybook/storybook-deployer": "^2.8.10",
"@storybook/addon-docs": "6.5.0-alpha.41",
"@storybook/addon-essentials": "6.4.18",
"@storybook/addon-info": "5.3.21",
"@storybook/addon-links": "6.4.18",
"@storybook/addon-postcss": "2.0.0",
"@storybook/addons": "6.4.18",
"@storybook/react": "6.4.18",
"@storybook/storybook-deployer": "2.8.10",
"@tailwindcss/forms": "^0.4.0",
"@testing-library/jest-dom": "^5.16.2",
"@testing-library/react": "^12.1.2",
Expand Down Expand Up @@ -108,6 +110,7 @@
"react-dom": "^17.0.2",
"react-hook-form": "^7.27.0",
"react-icons": "^4.3.1",
"react-intl": "^5.24.6",
"react-is": "^17.0.2",
"regenerator-runtime": "^0.13.9",
"rollup-plugin-postcss": "^4.0.2",
Expand All @@ -118,6 +121,11 @@
"tslib": "^2.3.1",
"typescript": "^4.5.5"
},
"commitlint": {
"extends": [
"@commitlint/config-conventional"
]
},
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
Expand Down
6 changes: 6 additions & 0 deletions src/context/formatting/formatting.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Formatting } from './formatting.model';

export const DEFAULT_FORMATTING: Formatting = {
date: 'E, d MMM yyyy',
dateTime: 'E, d MMM yyy HH:mm:ss',
};
22 changes: 22 additions & 0 deletions src/context/formatting/formatting.context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React, { createContext, ReactNode } from 'react';

import { useContextFallback } from '../../hooks';
import { DEFAULT_FORMATTING } from './formatting.constant';
import { Formatting } from './formatting.model';

export const FormattingContext = createContext<Formatting | undefined>(
undefined
);

type FormattingProps = { children: ReactNode; formatting?: Formatting };

export const FormattingProvider = ({
children,
formatting = DEFAULT_FORMATTING,
}: FormattingProps) => (
<FormattingContext.Provider value={formatting}>
{children}
</FormattingContext.Provider>
);

export const useFormatting = () => useContextFallback(FormattingContext);
4 changes: 4 additions & 0 deletions src/context/formatting/formatting.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export type Formatting = {
date: string;
dateTime: string;
};
3 changes: 3 additions & 0 deletions src/context/formatting/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './formatting.constant';
export * from './formatting.context';
export * from './formatting.model';
3 changes: 3 additions & 0 deletions src/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './formatting';
export * from './notifications';
export * from './valhalla.context';
1 change: 1 addition & 0 deletions src/context/notifications/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './notifications.context';
73 changes: 73 additions & 0 deletions src/context/notifications/notifications.context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import React, {
createContext,
ReactNode,
useCallback,
useMemo,
useState,
} from 'react';

import { useContextFallback } from '../../hooks';
import { NotificationGateway } from '../../ui';
import { Notification } from '../../ui/notification/notification.model';

export type NotificationsState = {
notifications: Notification[];
addNotification: (notification: Omit<Notification, 'id'>) => void;
removeNotification: (id: string) => void;
};

export const NotificationsContext = createContext<
NotificationsState | undefined
>(undefined);

type NotificationsProviderProps = {
children: ReactNode;
};

let notificationCounter = 0;

export const NotificationsProvider = ({
children,
}: NotificationsProviderProps) => {
const [notifications, setNotifications] = useState<Notification[]>([]);

const addNotification = useCallback(
(notification: Omit<Notification, 'id'>) => {
const id = `${notificationCounter++}`;

setNotifications((notifications) => [
...notifications,
{
...notification,
id,
},
]);
},
[]
);

const removeNotification = useCallback((id: string) => {
setNotifications((notifications) =>
notifications.filter((notification) => notification.id !== id)
);
}, []);

const value = useMemo(
() => ({
notifications,
addNotification,
removeNotification,
}),
[notifications, addNotification, removeNotification]
);

return (
<NotificationsContext.Provider value={value}>
<NotificationGateway />

{children}
</NotificationsContext.Provider>
);
};

export const useNotifications = () => useContextFallback(NotificationsContext);
1 change: 1 addition & 0 deletions src/context/tooltip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './tooltip.context';
3 changes: 3 additions & 0 deletions src/context/tooltip/tooltip.context.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Provider } from '@radix-ui/react-tooltip';

export const TooltipProvider = Provider;
32 changes: 32 additions & 0 deletions src/context/valhalla.context.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { ReactNode } from 'react';

import { I18n } from '../i18n';
import {
DEFAULT_FORMATTING,
Formatting,
FormattingProvider,
} from './formatting';
import { NotificationsProvider } from './notifications';
import { TooltipProvider } from './tooltip';

type ValhallaProviderProps<Locales extends 'en'> = {
children: ReactNode;
language: Locales;
locales: Record<Locales, any>;
formatting?: Formatting;
};

export const ValhallaProvider = <Locales extends 'en'>({
children,
formatting = DEFAULT_FORMATTING,
language,
locales,
}: ValhallaProviderProps<Locales>) => (
<I18n language={language} locales={locales}>
<FormattingProvider formatting={formatting}>
<NotificationsProvider>
<TooltipProvider>{children}</TooltipProvider>
</NotificationsProvider>
</FormattingProvider>
</I18n>
);
45 changes: 45 additions & 0 deletions src/i18n/i18n.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { IntlProvider } from 'react-intl';

import { AVAILABLE_LOCALES } from './i18n.constant';
import { flattenMessages } from './i18n.utils';

type I18nProps<Locales extends 'en'> = {
/**
* language which should be used in this context
*
* @defaultValue en
*/
language: Locales;
locales: Record<Locales, any>;
children?: React.ReactNode;
};

export const I18n = <Locales extends 'en'>({
language,
locales,
children,
}: I18nProps<Locales>) => {
const supportedLanguages = Object.keys(locales);
const currentLocale = supportedLanguages.some((l) => l === language)
? language
: ('en' as Locales);

const mergedMessages =
currentLocale in AVAILABLE_LOCALES
? {
...AVAILABLE_LOCALES[currentLocale],
...locales[currentLocale],
}
: locales[currentLocale];

return (
<IntlProvider
locale={currentLocale}
messages={flattenMessages(mergedMessages)}
defaultLocale={currentLocale}
>
{children}
</IntlProvider>
);
};
8 changes: 8 additions & 0 deletions src/i18n/i18n.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as en from './locales/en.json';
import * as storybook from './locales/storybook.json';

export const AVAILABLE_LOCALES = {
en: (en as any).default || en,
};

export const STORYBOOK_LOCALES = (storybook as any).default || storybook;
42 changes: 42 additions & 0 deletions src/i18n/i18n.utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* flattenMessages traverse nested messages object and return it
* as a flat object
*
* @example
* flattenMessages({
* A: {
* B: {
* C: 'TEST'
* },
* D: 'Hello!'
* }
* }) == {
* 'A.B.C': 'TEST',
* 'A.D': 'Hello!',
* }
*
* @remarks
*
* flattenMessages use recursion underneath for unwrapping nested objects
*
* @param object - nested messages object
* @param [prefix=''] - prefix that should be prepended to each key in flat object, shouldn't be used by developer
*
* @returns flat messages object
*
* @private
*/
export const flattenMessages = (
object: Record<string, any>,
prefix: string = ''
) =>
Object.keys(object).reduce((translations: any, key) => {
const value = object[key];
const prefixedKey: string = prefix ? `${prefix}.${key}` : key;
if (typeof value === 'string') {
translations[prefixedKey] = value;
} else {
Object.assign(translations, flattenMessages(value, prefixedKey));
}
return translations;
}, {});
2 changes: 2 additions & 0 deletions src/i18n/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { VALHALLA_EN_LOCALES } from './locales';
export * from './i18n.component';
15 changes: 15 additions & 0 deletions src/i18n/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"VALHALLA": {
"ACTIONS": {
"CLOSE": "Close"
},
"CHECKBOX": {
"ALL": "All"
},
"PAGINATION": {
"NEXT": "Next",
"PREV": "Previous",
"RESULTS": "{from}-{to} of {total}"
}
}
}
3 changes: 3 additions & 0 deletions src/i18n/locales/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as COMMON_EN_LOCALES from './en.json';

export { COMMON_EN_LOCALES as VALHALLA_EN_LOCALES };
Loading

0 comments on commit d2197b6

Please sign in to comment.