Little hack that I made
SrBrahma opened this issue ยท 6 comments
Hey there. I just made this little code so I can keep all my fonts in a single file, and easily access and use them with Intellisense. I couldn't find a way to avoid the duplicate writing of the font in import and in the useFontsArg (as this lib also requires). However, still a nice hack!
// A 'fonts.ts' file
import {
NotoSans_400Regular,
NotoSans_700Bold
} from '@expo-google-fonts/noto-sans';
import {
Roboto_400Regular,
Roboto_500Medium,
Roboto_700Bold
} from '@expo-google-fonts/roboto';
export { useFonts } from 'expo-font';
export const useFontsArg = {
NotoSans_400Regular,
NotoSans_700Bold,
Roboto_400Regular,
Roboto_500Medium,
Roboto_700Bold
};
type PropsToString<Obj> = {
[K in keyof Obj]: string
}
export const fonts = { ...useFontsArg } as unknown as PropsToString<typeof useFontsArg>;
Object.keys(fonts).forEach((e: any) => (fonts as any)[e] = e );
// App.tsx
export default function App() {
const [fontsLoaded] = useFonts(useFontsArg);
if (!fontsLoaded)
return <AppLoading/>;
return <Text style={{fonts.Roboto_500Medium}}> Hey! </Text>` // Intellisense guides you! Yay!
}
Thanks so much for sharing, @SrBrahma! I'm already using it ๐ช
The styles prop should be something like this: style={{ fontFamily: fonts.Roboto_500Medium }}
though ๐
Thanks so much for sharing, @SrBrahma! I'm already using it ๐ช
The styles prop should be something like this:
style={{ fontFamily: fonts.Roboto_500Medium }}
though ๐
Fixed!
Thanks!
I've done an update right now as I am starting another project and I like to keep stuff smart ;)
It also allows to wait for Icons to be loaded.
fonts.ts
// https://docs.expo.dev/guides/using-custom-fonts/
// I first shared it in here: https://github.com/expo/google-fonts/issues/6
import { Platform } from 'react-native';
import {
MaterialCommunityIcons // Commonly used
} from '@expo/vector-icons';
// import * as DM_Sans from '@expo-google-fonts/dm-sans';
// import * as Inter from '@expo-google-fonts/inter';
import * as Roboto from '@expo-google-fonts/roboto'; // Aditional fontWeights and to be used on iOS
import { useFonts } from 'expo-font';
// Instead of using the useFonts(fontsArg) hook to get the loaded state, use useMyFonts().
/** Enter here your fonts to be loaded.
*
* You may enter in the object `...Roboto` for example to load the whole family,
* or `Roboto.Roboto_500Medium` to only load the specific font. */
const fontsToLoad = {
...Roboto,
};
// Enter here your icons to be used and loaded/cached on start.
/** Use the icons via `<Icons.MaterialCommunityIcons/>` */
export const Icons = {
MaterialCommunityIcons,
};
// Manual system fonts aliases to be added to F.
const additional = {
monospace: Platform.OS === 'ios' ? 'Courier' : 'monospace',
};
/** Fonts. `F` instead of `Fonts` as it's commonly used.
*
* In your component style, use `{ fontFamily: F.Roboto_500Medium }`.
*
* It's type-smart! Intellisense will autofill and TS will complain if it's wrong */
export const F = getFont()
// === Implementation ===
const iconsFonts = (Object.fromEntries(Object.entries(Icons).map(([iconName, icon]) => ([iconName, icon.font]))));
const useFontsArg = {
...(fontsToLoad as Omit<typeof fontsToLoad, '__metadata__' | 'useFonts'>),
...iconsFonts,
};
// Remove not-font stuff
delete (useFontsArg as any).__metadata__;
delete (useFontsArg as any).useFonts;
type PropsToString<Obj> = { [K in keyof Obj]: string };
// Prettify obj type
type Id<T> = unknown & { [P in keyof T]: T[P] };
function getFont() {
return {
...Object.fromEntries(Object.keys(useFontsArg).map((key) => [key, key])),
...additional,
} as Id<PropsToString<typeof useFontsArg> & typeof additional>;
}
export function useMyFonts(): [fontsLoaded: boolean, error: Error | null] {
return useFonts(useFontsArg);
}
Maybe I can make it into a npm package, if there is demand or I want to better use it on my projects.
It could be a single function, like
~ function getMyFonts(p: {icons, fonts, additional}): { useMyFonts, Icons, I, Fonts, F }
The above had some errors. I made the function I said. It's used like the following:
import { Platform } from 'react-native';
import { Entypo, MaterialCommunityIcons } from '@expo/vector-icons';
import * as Roboto from '@expo-google-fonts/roboto';
export const { F, Icons, useFonts } = createFontsToLoad({
fontsToLoad: {
...Roboto,
},
iconsToLoad: {
MaterialCommunityIcons,
Entypo,
},
aliasesToSystemFonts: {
monospace: Platform.OS === 'ios' ? 'Courier' : 'monospace',
},
});
// On App init
const [loaded, error] = useFonts();
// Font usage
const styles = StyleSheet.create({
text: {
fontFamily: F.Roboto_500Medium // Type safe and smart! And loaded!
},
monoText: {
fontFamily: F.monospace
}
});
// Icon usage. Loaded and quick to switch to other icons!
const component = <Icons.MaterialCommunityIcons ... />
Source
// https://docs.expo.dev/guides/using-custom-fonts/
// I first shared it in here: https://github.com/expo/google-fonts/issues/6
// Aditional fontWeights and to be used on iOS
import { useFonts as expoUseFonts } from 'expo-font';
/** Prettify obj type */
type Id<T> = unknown & { [P in keyof T]: T[P] };
type FontsToLoad = Id<{
useFonts?: any;
__metadata__?: any;
// [x in string]: any
}>;
type SystemAliases = Record<string, string>;
// Simplified version, so we don't need `@expo/vector-icons` pkg to `import { Icon } from '@expo/vector-icons/build/createIconSet'`.
type Icon = {font: Record<string, any>};
type Icons = Record<string, Icon>;
type OmitFontsMeta<F extends FontsToLoad> = Omit<F, '__metadata__' | 'useFonts'>;
/** Omits meta props and converts the other FontsToLoad props to string type. */
type FontsToLoadToFonts<F extends FontsToLoad> = {[K in keyof OmitFontsMeta<F>]: string};
/** @defaul */
type Fonts<F extends FontsToLoad, A extends SystemAliases> = Id<FontsToLoadToFonts<F> & A>;
type FontsToLoadProps<F extends FontsToLoad, I extends Icons, A extends SystemAliases> = {
/** The fonts to be loaded.
*
* @example
* ```ts
* // Load whole family
* import * as Roboto from '@expo-google-fonts/roboto'
* // Load single font
* import { Inter_900Black } from '@expo-google-fonts/inter';
*
* const { F, useFonts } = createFontsToLoad({
* fontsToLoad: {
* ...Roboto,
* Inter_900Black
* }
* })
* ```
* */
fontsToLoad: F;
/** Your icons to be loaded/cached on useFonts.
*
* @example
* ```ts
* import { Entypo, MaterialCommunityIcons } from '@expo/vector-icons';
*
* const { I, useFonts } = createFontsToLoad({
* iconsToLoad: {
* Entypo
* MaterialCommunityIcons
* }
* })
* ```
*/
iconsToLoad: I;
/** Manual system fonts aliases to be added to Fonts.
*
* System fonts don't need to be loaded, this only adds the alias to Fonts.
*
* @example
* ```ts
* const { F } = createFontsToLoad({
* aliasesToSystemFonts: {
* monospace: Platform.OS === 'ios' ? 'Courier' : 'monospace',
* }
* })
* ```
*
*
*/
aliasesToSystemFonts: A;
};
type FontsToLoadRtn<F extends FontsToLoad, I extends Icons, A extends SystemAliases> = {
/** Short alias to `Fonts`.
*
* To be used in a style like `{fontFamily: F.Roboto_500Medium }` */
F: Fonts<F, A>;
/** To be used in a style like `{fontFamily: Fonts.Roboto_500Medium }` */
Fonts: Fonts<F, A>;
/** To be used just like the default useFonts(args), without the args. */
useFonts: () => [fontsLoaded: boolean, error: Error | null];
/** Short alias to `Icons`.
*
* Icons to be used via `<Icons.MaterialCommunityIcons/>`. */
I: I;
/** Icons to be used via `<Icons.MaterialCommunityIcons/>`. */
Icons: I;
};
/** Instead of using the useFonts(fontsArg) hook to get the loaded state, use useMyFonts() on your App start. */
export function createFontsToLoad<F extends FontsToLoad, I extends Icons, A extends SystemAliases>({
fontsToLoad,
iconsToLoad,
aliasesToSystemFonts,
}: FontsToLoadProps<F, I, A>): FontsToLoadRtn<F, I, A> {
const fontsToLoadInternal = { ...fontsToLoad } as Id<Omit<typeof fontsToLoad, '__metadata__' | 'useFonts'>>;
// Remove non-font stuff
delete (fontsToLoadInternal as any).__metadata__;
delete (fontsToLoadInternal as any).useFonts;
const iconsFonts = Object.values(iconsToLoad)
.reduce((obj, icon) => ({ ...obj, ...icon.font }), {} as Record<string, number>);
const useFontsArg = {
...fontsToLoadInternal,
...iconsFonts,
};
const Fonts = {
...Object.fromEntries(Object.keys(fontsToLoadInternal).map((key) => [key, key])),
...aliasesToSystemFonts,
} as Fonts<F, A>;
const useFonts = () => expoUseFonts(useFontsArg);
return {
F: Fonts,
Fonts,
useFonts,
I: iconsToLoad,
Icons: iconsToLoad,
};
}
I am busy right now but eventually will turn it into a npm package. Or, I think it could be added to this lib or any other related Expo lib. Edit: It has been released! https://github.com/SrBrahma/expo-font-loader
how you use this for local font in assets?