Skip to content

Commit

Permalink
feat: Update Aetherspace Docs, add Form Management Docs
Browse files Browse the repository at this point in the history
  • Loading branch information
codinsonn committed Dec 10, 2023
1 parent 63553e0 commit 7ff8942
Show file tree
Hide file tree
Showing 8 changed files with 268 additions and 50 deletions.
11 changes: 11 additions & 0 deletions .storybook/docs/Forms.stories.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Meta } from '@storybook/addon-docs'
import StorybookLinkTransformer from '../../packages/@aetherspace/docs/helpers/StorybookLinkTransformer'
import StorybookFontTransformer from '../../packages/@aetherspace/docs/helpers/StorybookFontTransformer'
import FormManagementMD from '../../packages/@aetherspace/forms/README.md'

<StorybookLinkTransformer />
<StorybookFontTransformer />

<Meta title="Aetherspace / Form Management" />

<FormManagementMD />
1 change: 1 addition & 0 deletions .storybook/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
'./docs/Styling.stories.mdx',
'./docs/UniversalRouting.stories.mdx',
'./docs/GraphQL.stories.mdx',
'./docs/Forms.stories.mdx',
'./docs/Automation.stories.mdx',
'./docs/Icons.stories.mdx',
'./docs/Deployment.stories.mdx',
Expand Down
83 changes: 44 additions & 39 deletions packages/@aetherspace/core/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
# Universal from the start
# Aetherspace Core Concepts

To help you decide if Aetherspace is the right tool for you or your project,
I've written this page to explain the core opinions and benefits when using Aetherspace:

## Full-Product Universal Apps from the start

<p>
<a href="https://aetherspace-green-stack-starter.vercel.app/" target="_blank">
Expand All @@ -17,26 +22,25 @@

The dream of React development has always been “write-once, use anywhere”.

With Aetherspace, you can apply that concept to building with Expo, React-Native and Next.js, and have a Web, iOS and Android app from the get-go. Maximising your reach from the start.
With Aetherspace, you can apply that concept to building with Expo, React-Native and Next.js, and have a Web, iOS and Android app from the get-go. You'll be maximising your reach right from project kickstart by forking the Aetherspace repo.

> Users increasingly prefer mobile apps over the web. That is just fact. SEO however, is still the best option for organic traffic. But things like push notifications and taking up a spot on the users smartphone, allow for higher conversions overall. By choosing Aetherspace, you immediately get the best of both worlds and set your project up for cross-platform success.
> While SEO is still the best option for new organic traffic for your project, daily active users will increasingly prefer a mobile app for use on the fly. Things like push notifications and taking up a spot on the users smartphone, will also allow for higher conversions overall. Choosing Aetherspace, you immediately get the best of both worlds and are set up for cross-platform success.
Therefore, using the helpers and scripts from `packages/@aetherspace` enables this write-once, reuse anywhere pattern for:
- Routing
- UI (Components, Styling)
- Business logic (Front & Backend)
Therefore, using the helpful resources from `packages/@aetherspace` enables this write-once, reuse anywhere pattern for:
- Routing and Universal Links - for maximum shareability & bookmarkability
- UI - with fully cross-platform Components and styling
- Business logic
- Animations
- Icons, assets, ...
- and more...
- Icons, assets and more

...while still being optimised for each platform you're targeting.

> "Web vs. Native is dead. Web **and** Native is the future."
> - Evan Bacon, expo-router maintainer
# Take what works, make it better
## Take what works, make it better

One way we achieve building universal apps from the start is by taking what works and making it better. We feel like it would be huge waste to throw away a decade of open-source (and e.g. comporated to Flutter, native) learnings by rewriting everything from the ground up. Instead, we optimize by combining existing tools, patching in cross-platform support or expanding them with supersets.
One way I achieve building universal apps from the start is by taking what works and making it better. We feel like it would be huge waste to throw away a decade of open-source learnings by rewriting everything from the ground up. Instead, I optimize by combining existing tools, patching in cross-platform support or expanding them with supersets.

### The GREEN stack

Expand Down Expand Up @@ -70,11 +74,11 @@ Aside from those core technologies, Aetherspace also sets you up with and is bui
- `@expo/html-elements`, for semantic HTML while still using React-Native
- `Storybook`, for interactive documentation that drives adoption

These are opinionated choices, but best-in-class ones that we're convinced are here to stay.
These are opinionated choices, but best-in-class ones that I'm convinced are here to stay.

Note that all other tool decisions are completely up to you and can be installed in any workspace manually or, if available in the [premium version](/LICENSE.md), merged through handy plugin branches. That means you can bring your own preferred state management, testing, database, and other choices and still benefit from the universal setup.

# Single sources of truth
## Thinking in Single sources of truth

<p>
<a href="https://zod.dev" target="_blank">
Expand All @@ -85,19 +89,38 @@ Note that all other tool decisions are completely up to you and can be installed
To further help keep things write-once and not repeat yourself, we’ve chosen Zod, a typescript-first schema validation library, as the way to define your data-structure just once for all your:
- Types and in-editor hints
- Resolver Arguments and Responses
- Form states and validations
- GraphQL types
- Documentation controls
- Component props
- Documentation controls

Anything you can define in Typescript, you can define with Zod.

Check out some examples on our [Schemas and Single Sources of Truth](/packages/@aetherspace/schemas/README.md) docs page.

# Designed for copy-paste
## Documentation drives adoption.

<p>
<a href="https://main--62c9a236ee16e6611d719e94.chromatic.com/?path=/story/readme-md--page" target="_blank">
<img alt="Docs with Storybook" longdesc="Documentated with Storybook" src="https://img.shields.io/badge/-Storybook-FF4785?style=for-the-badge&logo=storybook&logoColor=fff" />
</a>
</p>

A great quote by Storybook and the reason Aetherspace comes with it already set-up for you. Because down the road, when you’re scaling and bringing in new developers, the easier it is for new people to know what’s already available, the faster they can be onboarded. (and the less likely they are to reinvent the wheel)

Docs take time however, and it’s easy to get caught up putting a lot of effort writing docs. When you’re a startup or scaling, it’s not necessarily the thing you’d want to put so much time into. You need to be building first and foremost. So, in essence, when you haven't shipped anything yet:

> The best docs are the ones you don’t have to write yourself.
And this is where Aetherspace, using single sources of truth and Storybook are a great match. Using Aetherspace, documentation becomes just a side-effect of you writing zod schemas to describe and type your component’s props. Our scripts will pick-up on that and generate storybook files with interactive controls and descriptions for you.

You can read more about all of this in the [Single sources of truth](/packages/@aetherspace/schemas/README.md) and [Automations](/packages/@aetherspace/scripts/README.md) docs.

## Customisable, but designed for copy-paste:

We want your fork of the Aetherspace template repo to evolve into your own personalised template repo you can use for most of your projects. However, not every project is the same, which is why the monorepo setup promotes colocating UI, business logic, routing and assets by `/features/` and `/packages/` workspaces. Ideally, you want to be able to merge or copy-paste these folders into a new project and have it just work out of the box.

To facilitate this, we suggest you keep the following folder structure Aetherspace comes with:
To facilitate this, I suggest you keep the following folder structure Aetherspace comes with:

```shell
│── features/
Expand Down Expand Up @@ -141,7 +164,7 @@ This way, thanks to the startup scripts, copying a folder into another project w
- Bring in all related assets and automatically copy them to the public dir(s)
- Add autogenerated docs for that feature or package’s components

…and ofcourse allow you to import any other reusables from that package or feature
…and ofcourse allow you to import any other reusables from that package or feature.

### Why not NPM packages?

Expand All @@ -151,24 +174,6 @@ A major benefit of going with copy-pastable or mergeable folders for recurring f
Similar to adding recurring features, removing features or packages from a freshly forked repo then also becomes as simple as removing a folder.

# "Documentation drives adoption"

<p>
<a href="https://main--62c9a236ee16e6611d719e94.chromatic.com/?path=/story/readme-md--page" target="_blank">
<img alt="Docs with Storybook" longdesc="Documentated with Storybook" src="https://img.shields.io/badge/-Storybook-FF4785?style=for-the-badge&logo=storybook&logoColor=fff" />
</a>
</p>

A great quote by Storybook and the reason Aetherspace comes with it already set-up for you. Because down the road, when you’re scaling and bringing in new developers, the easier it is for new people to know what’s already available, the faster they can be onboarded. (and the less likely they are to reinvent the wheel)

Docs take time however, and it’s easy to get caught up putting a lot of effort writing docs. When you’re a startup or scaling, it’s not necessarily the thing you’d want to put so much time into. You need to be building first and foremost. So, in essence, when you haven't shipped anything yet:

> The best docs are the ones you don’t have to write yourself.
And this is where Aetherspace, using single sources of truth and Storybook are a great match. Using Aetherspace, documentation becomes just a side-effect of you writing zod schemas to describe and type your component’s props. Our scripts will pick-up on that and generate storybook files with interactive controls and descriptions for you.

You can read more about all of this in the [Single sources of truth](/packages/@aetherspace/schemas/README.md) and [Automations](/packages/@aetherspace/scripts/README.md) docs.

## Getting started with Aetherspace

- [Quickstart](/packages/@aetherspace/README.md)
Expand All @@ -184,18 +189,18 @@ You can read more about all of this in the [Single sources of truth](/packages/@

---

We firmly believe the opinionated toolbelt and core-concepts provided by the template repo will bring major benefits in terms of speed and efficiency.
I firmly believe the opinionated toolbelt and core-concepts provided by the template repo will bring major benefits in terms of speed and efficiency.

However, if you wish, you can actually ignore most of these core-concepts Aetherspace promotes and still benefit (only) from the universal setup.

For example:
- You can avoid using `SWR`, `@expo/html-elements` or even `Zod` schemas yourself, even when keeping automations
- Ignoring file and folder conventions is fine, automations will just do nothing and you'll need to link routes on your own
- Ignoring file and folder conventions is fine, automations will just ignore those files, but you'll need to link routes on your own
- You can ignore the primitives and `tailwind` styling and bring your own preferred styling solution instead
- Ignoring the `graphResolver` or other named exports is fine, but you'll need to bring your own GraphQL setup then
- Ignoring the `graphResolver` or other named exports is fine, but you'll need to bring your own GraphQL setup
- If you don't care for docs at all, you can remove the `.storybook/` folder and disable all automations in `next.config.js`

Though, if you do, you might be better served with a Tamgui or Solito starter instead.
Though, if you do, you might be better served with a Tamagui or Solito starter instead.

</details>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const StorybookLinkTransformer = (props) => {
// Props
const { children } = props

// -- Memoizations --
// -- Effects --

useEffect(() => {
transformLinks({
Expand All @@ -36,6 +36,7 @@ const StorybookLinkTransformer = (props) => {
'?path=/packages/@aetherspace/styles/README.md': '?path=/story/aetherspace-cross-platform-styling--page', // prettier-ignore
'?path=/packages/@aetherspace/navigation/README.md': '?path=/docs/aetherspace-universal-routing--page', // prettier-ignore
'?path=/packages/@aetherspace/navigation/AetherPage/README.md': '?path=/docs/aetherspace-graphql-data-fetching--page', // prettier-ignore
'?path=/packages/@aetherspace/forms/README.md': '?path=/docs/aetherspace-form-management--page', // prettier-ignore
'?path=/packages/@aetherspace/components/AetherIcon/README.md': '?path=/docs/aetherspace-icon-management--page', // prettier-ignore
'?path=/packages/@aetherspace/scripts/README.md': '?path=/docs/aetherspace-automation--page',
'?path=/packages/@aetherspace/schemas/README.md': '?path=/docs/aetherspace-single-sources-of-truth--page', // prettier-ignore
Expand Down
166 changes: 166 additions & 0 deletions packages/@aetherspace/forms/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# Form Management in Aetherspace

One of our overall goals is to enable the use of Zod schemas as the ultimate source of truth for your app's datastructure, validation and types. We've extended this concept to include form management as well.

```tsx
import { useFormState } from 'aetherspace/forms'

// Define a Zod schema for your form state (or use an existing one)
const formStateSchema = aetherSchema('SomeFormState', {
username: z.string().nonempty(),
email: z.string().email(),
password: z.string().min(8),
twoFactorCode: z.number().length(2),
})

// Create a set of form state utils to use in your components
const formState = useFormState({
stateSchema: formStateSchema,
initialValues: {
username: '',
email: '',
password: '',
twoFactorCode: 0,
},
})
```

## Typed form state values

`formState.values` is typed according to the Zod schema you provided as `stateSchema`

```tsx
formState.values.username // string
formState.values.email // string
formState.values.password // string
formState.values.twoFactorCode // number
```

Alternatively, you can use `formState.getValue('some-key')` to get a specific value from the form state. The 'some-key' argument is any key in the Zod schema you provided as `stateSchema` and the available keys will he hinted by your IDE.

```tsx
// Hinted keys: 'username' | 'email' | 'password' | 'twoFactorCode'
formState.getValue('username') // string
```

Updating the formState values can similarly be done in two ways:

```tsx
// Update a single value in the form state by (hinted) key
formState.handleChange('username', 'codinsonn.dev') // OK
formState.handleChange('twoFactorCode', 'some-string') // ERROR
```

```tsx
// Update multiple values in the form state by passing an object with keys and values
formState.setValues({
username: 'codinsonn.dev', // OK
email: '[email protected]', // OK
password: '*******', // OK
twoFactorCode: 'some-string', // ERROR: Type 'string' is not assignable to type 'number'
})
```

Typescript and your IDE will help you out with the available keys and allowed values through hints and error markings if you try to set a value that doesn't match the Zod schema.

## Form validation & error messages

The form state also includes a `formState.errors` object that contains any errors that have been found in the form state when validating it against the Zod schema.

To do that, you can call `formState.validate()`

```tsx
// Validate the form state against the Zod schema
// true if the form state is valid
// false if the form state is invalid -> sets the formState.errors object
if (formState.validate()) {
// Do something...
}
```

```tsx
console.log(formState.errors) // { username: ['Required'], email: ['Invalid email'] }
```

```tsx
formState.hasError('username') // true
formState.getErrors('email') // ['Invalid email']
```

You can specify custom error messages on each validation step when defining your Zod schema, and even translate them using libraries like `i18next`:

```tsx
const formStateSchema = aetherSchema('SomeFormState', {
username: z
.string()
.nonempty(i18n._('We need a username to associate with your account')),
email: z
.string()
.email(i18n._('Please provide a valid email adress')),
password: z
.string()
.min(8, i18n._('Password must be at least 8 characters long')),
twoFactorCode: z
.number()
.length(2, i18n._('Two factor code must be 2 digits')),
})
```

> When dealing with complex state with e.g. nested objects, keep in mind that calling `formState.validate()`, the error messages will be flattened and only contain the error messages for the top level keys.
To update or clear the error messages manually, you can use the `formState.updateErrors()` functions:

```tsx
// e.g. Clear all error messages by passing an empty object or empty arrays
formState.updateErrors({
username: [],
email: [],
password: [],
twoFactorCode: [],
})
```

## Integrating with React components

You can integrate your formState for specific fields with React components by using the `formState.getInputProps()` function. This will return an object with the `value` and `onChange` props that you can pass to your component, as well as a `hasError` flag + `onBlur()` and `onFocus()` handlers.

```tsx
<TextInput {...formState.getInputProps('username')} />
```

vs. manually assigning everything:

```tsx
<TextInput
value={formState.values.username}
onChangeText={(value) => formState.handleChange('username', value)}
onBlur={() => formState.validate()}
hasError={formState.hasError('username')}
/>
```

## Resetting the form state

You can reset the form state to its initial values by calling `formState.resetForm()`, e.g.

```tsx
<Button onTouch={() => formState.resetForm()}>
<Text>Reset form<Text>
</Button>
```

## Other formState helpers

`formState.isValid` - boolean, true if the form state is valid, false if it's invalid

`formState.isUnsaved` - boolean, true if the form state has been changed from its initial values, false if it hasn't

`formState.isDefaultState` - boolean, true if the form state is equal to its initial values, false if it isn't

`formState.transformValues()` - function, will transform the form state values according to the optional `transformValues` function you provided to `useFormState()`, or simply apply the schema defaults if you didn't provide one. Handy for e.g. converting values to a different format before submitting them to an API.

## Learn more about Aetherspace:

- [Zod & Single Sources of Truth](/packages/@aetherspace/schemas/README.md)
- [GraphQL and Data-Fetching](/packages/@aetherspace/navigation/AetherPage/README.md)
- [Styling your form & screen components with Tailwind](/packages/@aetherspace/styles/README.md)
Loading

0 comments on commit 7ff8942

Please sign in to comment.