Skip to content

Commit cb1cbfb

Browse files
committed
chore: Merge PR #19 from chore/add-icon-docs
2 parents a100a26 + e02a09b commit cb1cbfb

File tree

9 files changed

+268
-364
lines changed

9 files changed

+268
-364
lines changed

.storybook/docs/Icons.stories.mdx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { Meta } from '@storybook/addon-docs'
2+
import StorybookLinkTransformer from '../../packages/@aetherspace/docs/helpers/StorybookLinkTransformer'
3+
import StorybookFontTransformer from '../../packages/@aetherspace/docs/helpers/StorybookFontTransformer'
4+
import IconsMD from '../../packages/@registries/ICONS.md'
5+
6+
<StorybookLinkTransformer />
7+
<StorybookFontTransformer />
8+
9+
<Meta title="Aetherspace / Icon Management" />
10+
11+
<IconsMD />

.storybook/main.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module.exports = {
1010
'./docs/Quickstart.stories.mdx',
1111
'./docs/Schemas.stories.mdx',
1212
'./docs/Automation.stories.mdx',
13+
'./docs/Icons.stories.mdx',
1314
'./docs/Deployment.stories.mdx',
1415
'./other/License.stories.mdx',
1516
// -- Other documentation --
@@ -26,6 +27,10 @@ module.exports = {
2627
'@a110/storybook-expand-all',
2728
'aetherspace/docs/addons',
2829
],
30+
staticDirs: [
31+
'./public',
32+
'../apps/next/public',
33+
],
2934
webpackFinal: (config) => {
3035
// Add TS & react-native-web support
3136
config.module.rules.push({
890 KB
Loading

README.mdx

Lines changed: 0 additions & 338 deletions
This file was deleted.

README.stories.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Meta } from '@storybook/addon-docs'
22
import StorybookLinkTransformer from './packages/@aetherspace/docs/helpers/StorybookLinkTransformer'
33
import StorybookFontTransformer from './packages/@aetherspace/docs/helpers/StorybookFontTransformer'
4-
import ReadMe from './README.mdx'
4+
import ReadMe from './README.md'
55

66
<StorybookLinkTransformer />
77
<StorybookFontTransformer />
@@ -51,7 +51,7 @@ import ReadMe from './README.mdx'
5151
3. [**Universal style & theming** for Web, SSR & Mobile with tailwind -- Build your own Primitives]() (TODO)
5252
4. [Shared and platform specific **routing for Expo & Next.js**]() (TODO)
5353
5. [GraphQL and API routes -- **Schemas and single sources of truth**](?path=/story/aetherspace-schemas--page)
54-
6. [Automatic **test and docs generation** for components and data apis](?path=/docs/aetherspace-automation--page)
54+
6. [Automatic **docs generation** for components and data apis](?path=/docs/aetherspace-automation--page)
5555
7. [CI & near instant **Deploys for web and App Stores** -- With Expo & Turborepo]() (TODO)
5656

5757

features/app-core/hooks/useLoadFonts.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ import {
1212
/* --- useLoadFonts() -------------------------------------------------------------------------- */
1313

1414
const useLoadFonts = () => {
15-
// -- Google Fonts ---
16-
1715
const fontsToLoad = {
16+
// - Google Fonts -
1817
Roboto: Roboto_400Regular,
1918
RobotoLight: Roboto_300Light,
2019
RobotoRegular: Roboto_400Regular,
@@ -29,6 +28,8 @@ const useLoadFonts = () => {
2928
Roboto700: Roboto_700Bold,
3029
Roboto800: Roboto_900Black, // Fallback
3130
Roboto900: Roboto_900Black,
31+
// - Icon Fonts -
32+
AntDesign: require('@expo/vector-icons/build/vendor/react-native-vector-icons/Fonts/AntDesign.ttf'),
3233
}
3334

3435
const [googleFontsLoaded, googleFontsError] = useFonts(fontsToLoad)

features/app-core/tailwind.config.js

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,22 +33,23 @@ module.exports = {
3333
/* --- Intellisense Setup ---------------------------------------------------------------------- */
3434
// Enable intellisense by installing the "Tailwind CSS Intellisense" vscode plugin
3535
// Also add the following to your vscode's .settings file to get the addon to work properly:
36-
// --
37-
// "tailwindCSS.classAttributes": [
38-
// "class",
39-
// "className",
40-
// "tw",
41-
// "tailwind",
42-
// "style"
43-
// ],
44-
// "tailwindCSS.experimental.classRegex": [
45-
// "tw`([^`]*)", // tw`...`
46-
// "tw=\"([^\"]*)", // <div tw="..." />
47-
// "tw={\"([^\"}]*)", // <div tw={"..."} />
48-
// "tw\\.\\w+`([^`]*)", // tw.xxx`...`
49-
// "tw\\(.*?\\)`([^`]*)" // tw(Component)`...`
50-
// ],
51-
// "tailwindCSS.includeLanguages": {
52-
// "typescript": "javascript",
53-
// "typescriptreact": "javascript"
54-
// },
36+
/* --
37+
"tailwindCSS.classAttributes": [
38+
"class",
39+
"className",
40+
"tw",
41+
"tailwind",
42+
"style"
43+
],
44+
"tailwindCSS.experimental.classRegex": [
45+
"tw`([^`]*)", // tw`...`
46+
"tw=\"([^\"]*)", // <div tw="..." />
47+
"tw={\"([^\"}]*)", // <div tw={"..."} />
48+
"tw\\.\\w+`([^`]*)", // tw.xxx`...`
49+
"tw\\(.*?\\)`([^`]*)" // tw(Component)`...`
50+
],
51+
"tailwindCSS.includeLanguages": {
52+
"typescript": "javascript",
53+
"typescriptreact": "javascript"
54+
},
55+
-- */

packages/@aetherspace/docs/helpers/StorybookLinkTransformer.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ import React, { useEffect } from 'react'
33
/* --- Transform links ------------------------------------------------------------------------- */
44

55
const transformLinks = (replaceMap: Record<string, string>) => {
6-
const $allLinks = document.querySelectorAll('a[href]')
7-
$allLinks.forEach(($link) => {
6+
const $allLinks = Array.from(document.querySelectorAll('a[href]'))
7+
const $allSrcs = Array.from(document.querySelectorAll('img[src]'))
8+
const $allLinksAndSrcs = [...$allLinks, ...$allSrcs]
9+
$allLinksAndSrcs.forEach(($link) => {
10+
// Replace hrefs
811
const href = $link.getAttribute('href') || ''
9-
if (replaceMap[href]) $link.setAttribute('href', replaceMap[href])
12+
if (href && replaceMap[href]) $link.setAttribute('href', replaceMap[href])
13+
// Replace src attributes
14+
const src = $link.getAttribute('src') || ''
15+
if (src && replaceMap[src]) $link.setAttribute('src', replaceMap[src])
1016
})
1117
}
1218

@@ -20,6 +26,7 @@ const StorybookLinkTransformer = (props) => {
2026

2127
useEffect(() => {
2228
transformLinks({
29+
'/.storybook/public/TransformToolsExampleRNSVG.png': '/TransformToolsExampleRNSVG.png', // prettier-ignore
2330
'?path=/packages/@registries/README.md': '?path=/docs/aetherspace-automation--page',
2431
'?path=/packages/@aetherspace/schemas/README.md': '?path=/docs/aetherspace-single-sources-of-truth--page', // prettier-ignore
2532
'?path=/.github/workflows/README.md': '?path=/docs/aetherspace-deployment--page',

packages/@registries/ICONS.md

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
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+
![TransformToolsExampleRNSVG.png](/.storybook/public/TransformToolsExampleRNSVG.png)
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

Comments
 (0)