|
| 1 | +# Managing Icons |
| 2 | + |
| 3 | +There’s many different ways to use icons and icon sets in Aetherspace. Here’s the full list by order of recommendation: |
| 4 | + |
| 5 | +1. SVG components using `react-native-svg` |
| 6 | +→ SSR support, can [convert from .svg file](https://transform.tools/svg-to-react-native), but could be more time-consuming |
| 7 | +2. Third party icon libraries, such as `@expo/vector-icons` |
| 8 | +→ Fast & easy, but needs icon font to be preloaded, possible display delay on web |
| 9 | +3. Image icons through src urls |
| 10 | +→ Straight forward, easy to implement, not super optimised, display delay on web |
| 11 | + |
| 12 | +## Better DX with `AetherIcon` |
| 13 | + |
| 14 | +> Whichever you choose, or even when choosing multiple methods, you can improve your DX or apply the icons dynamicly using Aetherspace’s icon registry pattern and `AetherIcon` component. |
| 15 | +
|
| 16 | +This will enable: |
| 17 | + |
| 18 | +- Type hints in your editor for the `name` prop on the AetherIcon component |
| 19 | +- Each workspace to define their own icons, optimising the feature or package for copy-paste |
| 20 | +- An importable list of all registered icons for e.g. building an IconPicker component |
| 21 | + |
| 22 | +Example registration (in e.g. `feature/{workspace}/icons/registry.tsx`) |
| 23 | + |
| 24 | +```tsx |
| 25 | +import { AetherImage } from 'aetherspace/primitives' |
| 26 | +import { registerIconRenderer } from 'aetherspace/utils' |
| 27 | + |
| 28 | +export const iconRegistry = { |
| 29 | + 'some-icon-key': MySvgComponent, // Make sure it has a 'size' & 'fill' prop |
| 30 | + // - OR - |
| 31 | + 'some-img-icon': ({ size, ...restIconProps }) => ( |
| 32 | + <AetherImage |
| 33 | + src="/img/icon.svg" |
| 34 | + width={size} |
| 35 | + height={size} |
| 36 | + {...restIconProps} |
| 37 | + /> |
| 38 | + ) |
| 39 | + // - OR - |
| 40 | + ...registerIconRenderer( |
| 41 | + ['caret-up', 'caret-down', ...] as const, |
| 42 | + ({ name, size, fill, ...restIconProps }) => ( |
| 43 | + <ThirdPartyIconLib name={name} size={size}, color={fill} {...restIconProps} /> |
| 44 | + ), |
| 45 | + ), |
| 46 | +} |
| 47 | +``` |
| 48 | + |
| 49 | +Example codegen |
| 50 | + |
| 51 | +```bash |
| 52 | +yarn ats collect-icons |
| 53 | +# or just "yarn dev" as it's part of the next.config.js automations |
| 54 | +``` |
| 55 | + |
| 56 | +```tsx |
| 57 | +----------------------------------------------------------------- |
| 58 | +-i- Successfully created icon registry at: |
| 59 | +----------------------------------------------------------------- |
| 60 | +✅ packages/@registries/icons.generated.ts |
| 61 | +``` |
| 62 | + |
| 63 | +```tsx |
| 64 | +// icons.generated.ts |
| 65 | + |
| 66 | +// -i- Auto generated with 'yarn ats collect-icons' & reused in AetherIcon |
| 67 | +import { iconRegistry as someFeatureIcons } from '../../features/some-feature/icons/registry' |
| 68 | +import { iconRegistry as somePackageIcons } from '../../packages/some-package/icons/registry' |
| 69 | + |
| 70 | +/* --- Exports --------------------------------------------------------------------------------- */ |
| 71 | + |
| 72 | +export const REGISTERED_ICONS = { |
| 73 | + ...someFeatureIcons, |
| 74 | + ...somePackageIcons, |
| 75 | +} as const // prettier-ignore |
| 76 | +``` |
| 77 | + |
| 78 | +Example usage of `AetherIcon` (which uses the generated icon registry under the hood) |
| 79 | + |
| 80 | +```tsx |
| 81 | +<AetherIcon name="some-icon-key" size={24} /> |
| 82 | +// ?^ 'some-icon-key' | 'some-img-icon' | 'caret-up' | 'caret-down' | ... |
| 83 | +``` |
| 84 | + |
| 85 | +The AetherIcon way of working is fully optional and only exists to keep your feature and packages workspaces copy-pastable between Aetherspace projects by applying some minimal codegen on top. |
| 86 | + |
| 87 | +If you prefer skipping this and work with the icons directly, we have more simple examples below: |
| 88 | + |
| 89 | +## Using Icon Components |
| 90 | + |
| 91 | +[SVG to React Native](https://transform.tools/svg-to-react-native) |
| 92 | + |
| 93 | +> Use tools like [https://transform.tools](https://transform.tools) to drop in your .svg files and generate a usable React-Native component from that upload. |
| 94 | +> |
| 95 | +
|
| 96 | + |
| 97 | + |
| 98 | +→ Save to e.g. `/features/{workspace}/icons/MySvgComponent.tsx` (and edit it if you wish) |
| 99 | + |
| 100 | +> Using your Icon Component: |
| 101 | +> |
| 102 | +
|
| 103 | +```tsx |
| 104 | +import MySvgComponent from '../icons/SvgCompont' |
| 105 | + |
| 106 | +// ...later, in JSX return... |
| 107 | + |
| 108 | +<MySvgComponent width={24} height={24} fill={#333333} /> |
| 109 | +``` |
| 110 | + |
| 111 | +Benefits of this strategy: |
| 112 | + |
| 113 | +- It’s just another react component, use it in JSX and add props to it. |
| 114 | +- Since it’s using SVG jsx under the hood, this works and show immediately with SSR. |
| 115 | +- Can be used for more than just icons, can be entire illustrations. |
| 116 | + |
| 117 | +Downsides of this strategy: |
| 118 | + |
| 119 | +- Time consuming: Requires some copy-pasting between transform tools and your code |
| 120 | +- Therefore, even if it’s most reliable and configurable, also not very fast or scalable |
| 121 | + |
| 122 | +## Using `@expo/vector-icons` |
| 123 | + |
| 124 | +[Expo Docs: @expo/vector-icons](https://docs.expo.dev/guides/icons/) |
| 125 | + |
| 126 | +> Check which iconset you want to use and how to use them on [https://icons.expo.fyi/](https://icons.expo.fyi/) |
| 127 | +
|
| 128 | +Benefits of using this strategy: |
| 129 | + |
| 130 | +- Fast, choose icons from existing third party icon providers |
| 131 | +- Most @expo/vector-icons are also compatible with the web |
| 132 | + |
| 133 | +Downsides of using icon fonts under the hood: |
| 134 | + |
| 135 | +- You’ll need to preload your icon font somehow, easy on mobile but harder on web |
| 136 | +- Meaning you might not see the icon immediately if the icon font isn’t loaded yet |
| 137 | + |
| 138 | +Example usage: |
| 139 | + |
| 140 | +```tsx |
| 141 | +import { AntDesign } from '@expo/vector-icons' |
| 142 | + |
| 143 | +<AntDesign |
| 144 | + name="caretup" |
| 145 | + size={24} |
| 146 | + color="#333333" |
| 147 | +/> |
| 148 | +``` |
| 149 | + |
| 150 | +Example preloading of icon font on mobile: |
| 151 | + |
| 152 | +`/features/app-core/hooks/useLoadFonts.ts` |
| 153 | + |
| 154 | +```tsx |
| 155 | +'use client' |
| 156 | +import { useFonts } from 'expo-font' |
| 157 | +import { /* Google Fonts */ } from '@expo-google-fonts/roboto' |
| 158 | + |
| 159 | +/* --- useLoadFonts() -------------------------------------------------------------------------- */ |
| 160 | + |
| 161 | +const useLoadFonts = () => { |
| 162 | + const fontsToLoad = { |
| 163 | + // - Google Fonts - |
| 164 | + /* ... */ |
| 165 | + // - Icon Fonts - |
| 166 | + AntDesign: require('@expo/vector-icons/build/vendor/react-native-vector-icons/Fonts/AntDesign.ttf'), |
| 167 | + // -!- important: always double check this path (^) for your icon fonts |
| 168 | + } |
| 169 | + |
| 170 | + const [fontsLoaded, fontsError] = useFonts(fontsToLoad) |
| 171 | + |
| 172 | + if (fontsError) console.error('fontErrors:', fontsLoaded) |
| 173 | + |
| 174 | + // -- Return -- |
| 175 | + |
| 176 | + return fontsLoaded // <- Keep splash screen open until this is true |
| 177 | +} |
| 178 | + |
| 179 | +/* --- Exports --------------------------------------------------------------------------------- */ |
| 180 | + |
| 181 | +export default useLoadFonts |
| 182 | +``` |
| 183 | + |
| 184 | +Example usage with `<AetherIcon/>` |
| 185 | + |
| 186 | +`/packages/{workspace-folder}/icons/registry.tsx` |
| 187 | + |
| 188 | +```tsx |
| 189 | +import React from 'react' |
| 190 | +import { registerIconRenderer } from 'aetherspace/utils' |
| 191 | +import { AntDesign } from '@expo/vector-icons' |
| 192 | +import { ComponentProps } from 'react' |
| 193 | + |
| 194 | +/** --- iconRegistry --------------------------------------------------------------------------- */ |
| 195 | +/** -i- Register any icons by preferred AetherIcon "name" key */ |
| 196 | +export const iconRegistry = { |
| 197 | + // Register any icons from e.g. AntDesign you want by |
| 198 | + // registering them by strings 👇 array (readonly) + render function |
| 199 | + ...registerIconRenderer(['caretup'] as const, ({ name, size, fill, ...restIconProps }) => ( |
| 200 | + <AntDesign |
| 201 | + name={name as ComponentProps<typeof AntDesign>['name']} |
| 202 | + size={size} |
| 203 | + color={fill} |
| 204 | + {...restIconProps} |
| 205 | + /> |
| 206 | + )), |
| 207 | +} as const // <-- Readonly is important here for accurate type hints |
| 208 | +``` |
| 209 | + |
| 210 | +```tsx |
| 211 | +<AetherIcon name="caretup" size={24} fill="#333333" /> |
| 212 | +``` |
| 213 | + |
| 214 | +## Continue learning: |
| 215 | + |
| 216 | +- [Automation based on Schemas: Storybook & GraphQL](/packages/@registries/README.md) |
| 217 | +- [Single Sources of Truth for your Web & Mobile apps](/packages/@aetherspace/schemas/README.md) |
0 commit comments