Files
cunningham/packages/react/src/components/Provider/index.tsx
Nathan Vasse 1787df0853 (react) add ToastProvider to CunninghamProvider
This allows the consumer to use useToastProvider very easily, it avoids
thinking of explicity adding ToastProvider to apps that needs toasts.
2024-01-05 16:38:09 +01:00

108 lines
2.8 KiB
TypeScript

import React, {
createContext,
PropsWithChildren,
useContext,
useEffect,
useMemo,
} from "react";
import * as enUS from ":/locales/en-US.json";
import * as frFR from ":/locales/fr-FR.json";
import { PartialNested } from ":/types";
import { Locales } from ":/components/Provider/Locales";
import { ToastProvider } from ":/components/Toast/ToastProvider";
type TranslationSet = PartialNested<typeof enUS>;
const CunninghamContext = createContext<
| undefined
| {
t: (key: string, vars?: Record<string, string | number>) => string;
currentLocale: string;
}
>(undefined);
export const useCunningham = () => {
const context = useContext(CunninghamContext);
if (context === undefined) {
throw new Error("useCunningham must be used within a CunninghamProvider.");
}
return context;
};
interface Props extends PropsWithChildren {
customLocales?: Record<string, TranslationSet>;
currentLocale?: string;
theme?: string;
}
export const DEFAULT_LOCALE = Locales.enUS;
export const DEFAULT_THEME = "default";
export const SUPPORTED_LOCALES = Object.values(Locales);
const THEME_CLASSNAME_PREFIX = "cunningham-theme--";
const findTranslation = (
key: string,
locale: TranslationSet,
): string | undefined => {
const [namespace, ...keys] = key.split(".");
return keys.reduce((acc, subKey) => acc[subKey], (locale as any)[namespace]);
};
export const CunninghamProvider = ({
currentLocale = DEFAULT_LOCALE,
customLocales,
theme = DEFAULT_THEME,
children,
}: Props) => {
const locales: Record<string, TranslationSet> = useMemo(
() => ({
[DEFAULT_LOCALE]: enUS,
"fr-FR": frFR,
...customLocales,
}),
[customLocales],
);
const locale = useMemo(() => {
return (locales[currentLocale] && currentLocale) || DEFAULT_LOCALE;
}, [currentLocale, locales]);
const context = useMemo(
() => ({
t: (key: string, vars?: Record<string, string | number>) => {
let message: string =
findTranslation(key, locales[locale]) ??
findTranslation(key, locales[DEFAULT_LOCALE]) ??
key;
// Replace vars in message from vars in form of {varName}.
if (vars) {
Object.keys(vars).forEach((varName) => {
message = message?.replace(`{${varName}}`, "" + vars[varName]);
});
}
return message;
},
currentLocale: locale,
}),
[currentLocale, locales],
);
useEffect(() => {
const root = document.querySelector(":root")!;
root.classList.forEach((className) => {
if (className.startsWith(THEME_CLASSNAME_PREFIX)) {
root.classList.remove(className);
}
});
root.classList.add(THEME_CLASSNAME_PREFIX + theme);
}, [theme]);
return (
<CunninghamContext.Provider value={context}>
<ToastProvider>{children}</ToastProvider>
</CunninghamContext.Provider>
);
};