Alf go brrrr
This commit is contained in:
+21
-298
@@ -1,312 +1,35 @@
|
|||||||
# Application Layout Framework (ALF)
|
# Application Layout Framework (ALF)
|
||||||
|
|
||||||
ALF is a low-level styling system inspired by prior art like
|
|
||||||
[styled-system](https://github.com/styled-system/styled-system) and others.
|
|
||||||
|
|
||||||
It consists of two core parts: one or more **themes** and a single **system**.
|
|
||||||
Although a theme really comes first, you'll most often interact with the system,
|
|
||||||
so we'll start there.
|
|
||||||
|
|
||||||
## System
|
|
||||||
|
|
||||||
The _system_ is just the "layout system", and it's created from a set of themes.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import {createSystem} from '#/alf/lib/system'
|
|
||||||
import {light, dark} from '#/alf/themes'
|
|
||||||
|
|
||||||
const {
|
|
||||||
ThemeProvider,
|
|
||||||
styled,
|
|
||||||
useStyle,
|
|
||||||
useStyles,
|
|
||||||
useTheme,
|
|
||||||
useTokens,
|
|
||||||
useBreakpoints,
|
|
||||||
} = createSystem({
|
|
||||||
light,
|
|
||||||
dark,
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### ThemeProvider
|
|
||||||
|
|
||||||
The `ThemeProvider` expects a single prop `theme`, which corresponds to the
|
|
||||||
_keys_ of the object passed to `createSystem`, which should correspond to your
|
|
||||||
theme names. Using the above, `theme` should be `light` or `dark`.
|
|
||||||
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
<ThemeProvider theme='light'>...</ThemeProvider>
|
|
||||||
```
|
|
||||||
|
|
||||||
### styled
|
|
||||||
|
|
||||||
Convenience method to create a themeable component from another primitive.
|
|
||||||
Accepts a component and an object of default styles. Typically only be used in a
|
|
||||||
few places, like for creating primitive `Box` and `Text` components.
|
|
||||||
|
|
||||||
**Do not get carried away with this. Multiple levels of nesting is performance
|
|
||||||
intensive, and is hard to debug.**
|
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
import {styled} from '#/alf/system'
|
import { View } from 'react-native'
|
||||||
|
import { useAlf } from '#/alf'
|
||||||
|
import { H3 } from '#/view/com/Typography'
|
||||||
|
|
||||||
const Box = styled(View, {})
|
function App() {
|
||||||
|
const { styles, breakpoints } = useAlf()
|
||||||
|
|
||||||
<Box c='primary' gtMobile={{ pa: 'm' }} />
|
return (
|
||||||
```
|
<View style={[styles.flex.row, styles.padding.pa.xl]}>
|
||||||
|
<H3 style={[styles.padding.pb.m, styles.color.primary]}>I'm the blue color</H3>
|
||||||
> An additional feature of `styled` components is a boolean `debug` prop that
|
</View>
|
||||||
> will print the processed styles and properties to the console.
|
)
|
||||||
|
|
||||||
### useStyle
|
|
||||||
|
|
||||||
Mid-level hook to create themed styles with the full theme config at your
|
|
||||||
disposal. Most often helpful when styling 3rd party libraries, such as a
|
|
||||||
dropdown.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const styles = useStyle({
|
|
||||||
c: 'primary',
|
|
||||||
gtMobile: {
|
|
||||||
c: 'secondary',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
<DropdownItem style={styles} />
|
|
||||||
```
|
|
||||||
|
|
||||||
### useStyles
|
|
||||||
|
|
||||||
Mid-level hook to create _named_ and themed styles with the full theme config at
|
|
||||||
your disposal. Most often helpful when styling 3rd party libraries, such as a
|
|
||||||
dropdown. Think of this as similar to `StyleSheet.create`.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const { outer, header } = useStyles({
|
|
||||||
outer: {
|
|
||||||
px: 'm',
|
|
||||||
gtMobile: {
|
|
||||||
px: 'l',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
fontSize: 'xl',
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
<View style={outer}>
|
|
||||||
<Text style={header}>Hello</Text>
|
|
||||||
</View>
|
|
||||||
```
|
|
||||||
|
|
||||||
### useTheme
|
|
||||||
|
|
||||||
Returns the full currently-active theme object e.g. `light` or `dark`, and all utils attached.
|
|
||||||
Really only used when you need low-level access.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const theme = useTheme()
|
|
||||||
const styles = theme.style({
|
|
||||||
c: 'blue',
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### useTokens
|
|
||||||
|
|
||||||
Returns just the design tokens of the currently-active theme.
|
|
||||||
|
|
||||||
```tsx
|
|
||||||
const tokens = useTokens()
|
|
||||||
|
|
||||||
<View style={{ color: tokens.color.blue }} />
|
|
||||||
```
|
|
||||||
|
|
||||||
### useBreakpoints
|
|
||||||
|
|
||||||
Returns the current and active breakpoints, which are stored on the theme
|
|
||||||
context.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const { current, active } = useBreakpoints()
|
|
||||||
|
|
||||||
// =>
|
|
||||||
{
|
|
||||||
current: 'gtTablet',
|
|
||||||
active: [
|
|
||||||
'gtTablet',
|
|
||||||
'gtMobile',
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Themes
|
Is a little nicer than:
|
||||||
|
|
||||||
Themes are made up of a collection of utilities and created from a set of design
|
|
||||||
tokens and other configuration. They are external to React, and can be used
|
|
||||||
directly if low-level access is needed.
|
|
||||||
|
|
||||||
### Creating a theme
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import {createTheme} from '#/alf/lib/theme'
|
|
||||||
|
|
||||||
const light = createTheme(config)
|
|
||||||
```
|
|
||||||
|
|
||||||
Config consists of:
|
|
||||||
|
|
||||||
#### Tokens
|
|
||||||
|
|
||||||
Tokens a.k.a. "design tokens", are the smallest building block of the design
|
|
||||||
system. The name of each token directly corresponds to the name of the CSS
|
|
||||||
property, with the exception of `space`, which is used as a value source for
|
|
||||||
properties like `width` unless a specific `width` scale is configured.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
createTheme({
|
|
||||||
tokens: {
|
|
||||||
// special token in ALF
|
|
||||||
space: {
|
|
||||||
s: 8,
|
|
||||||
m: 12,
|
|
||||||
l: 18,
|
|
||||||
},
|
|
||||||
// matches CSS prop name exactly
|
|
||||||
color: {
|
|
||||||
blue: '#0000FF',
|
|
||||||
},
|
|
||||||
// matches CSS prop name exactly
|
|
||||||
fontSize: {
|
|
||||||
s: 14,
|
|
||||||
m: 16,
|
|
||||||
l: 18,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Properties
|
|
||||||
|
|
||||||
Properties are a mapping property names to actual CSS properties. Internally,
|
|
||||||
ALF specifies a mapping of all supported CSS properties. When creating a theme
|
|
||||||
is a time to specify "shorthands" or "aliases" a.k.a. syntax sugar. Docblocks
|
|
||||||
will persist and be available for intellisense.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
createTheme({
|
|
||||||
tokens: {...},
|
|
||||||
properties: {
|
|
||||||
/** Alias for `width` */
|
|
||||||
w: ['width'],
|
|
||||||
/** Alias for `color` */
|
|
||||||
c: ['color'],
|
|
||||||
/** Alias for all directional margin properties */
|
|
||||||
ma: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Breakpoints
|
|
||||||
|
|
||||||
In ALF, breakpoints are applied as the equivalent of `min-width` CSS media
|
|
||||||
queries, meaning your base styles are mobile, and breakpoints are then applied
|
|
||||||
in order. You can name these anything you want, but we recommend the `gt`
|
|
||||||
prefix; basically "greater than N".
|
|
||||||
|
|
||||||
Given the below, at 1000px wide, both the base and the styles applied in
|
|
||||||
`gtMobile` will be applied.
|
|
||||||
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
createTheme({
|
|
||||||
tokens: {...},
|
|
||||||
properties: {...},
|
|
||||||
breakpoints: {
|
|
||||||
/** Greater than 800 */
|
|
||||||
gtMobile: 800,
|
|
||||||
/** Greater than 1300 */
|
|
||||||
gtTablet: 1300,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Macros
|
|
||||||
|
|
||||||
Macros are further syntax sugar. They can be configured as boolean attributes,
|
|
||||||
as allowing a specific set of values, as simple generic methods, or a
|
|
||||||
combination.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
createTheme({
|
|
||||||
tokens: {...},
|
|
||||||
properties: {...},
|
|
||||||
breakpoints: {...},
|
|
||||||
macros: {
|
|
||||||
/** Shorthand for `flexDirection: 'row'` */
|
|
||||||
row: (_: boolean) => ({flexDirection: 'row', flex: 1}),
|
|
||||||
/**
|
|
||||||
* Shorthand for `flex: 1`. Optionally pass an integer to specify the
|
|
||||||
* col-span.
|
|
||||||
*
|
|
||||||
* Semantically this is helpful as a direct child of `<Box row>`
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* <Box row>
|
|
||||||
* <Box column>
|
|
||||||
* <Text>Hello</Text>
|
|
||||||
* </Box>
|
|
||||||
* <Box column={2}>
|
|
||||||
* <Text>Hello</Text>
|
|
||||||
* </Box>
|
|
||||||
* </Box>
|
|
||||||
*/
|
|
||||||
column: (span: boolean | number) => ({
|
|
||||||
flex: typeof span === 'number' ? span : 1,
|
|
||||||
}),
|
|
||||||
/** Shorthand for `alignItems: 'center'` */
|
|
||||||
aic: (_: boolean) => ({alignItems: 'center'}),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
Given the above config, creating a grid with `Box` (see next section) is as
|
|
||||||
simple as:
|
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
<Box row aic>
|
import { View } from 'react-native'
|
||||||
{/* 1/4 width */}
|
import { useTheme, styles } from '#/alf'
|
||||||
<Box column></Box>
|
import { H3 } from '#/view/com/Typography'
|
||||||
|
|
||||||
{/* 1/2 width */}
|
function App() {
|
||||||
<Box column={2}></Box>
|
const theme = useTheme()
|
||||||
|
|
||||||
{/* 1/4 width */}
|
return (
|
||||||
<Box column></Box>
|
<View style={[styles.flex.row, styles.padding.pa.xl]}>
|
||||||
</Box>
|
<H3 style={[styles.padding.pb.m, theme.color.primary]}>I'm the blue color</H3>
|
||||||
```
|
</View>
|
||||||
|
)
|
||||||
## Performance considerations
|
|
||||||
|
|
||||||
### Memoize style objects
|
|
||||||
|
|
||||||
For primitive components or those that render often, it's probably a good idea
|
|
||||||
to memoize your style objects prior to passing them to one of the hooks or
|
|
||||||
`Box`.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
function Component(props) {
|
|
||||||
const styles = useStyle(React.useMemo(() => ({
|
|
||||||
color: props.prop ? 'blue' : 'red',
|
|
||||||
}), [props.prop]))
|
|
||||||
|
|
||||||
return <View style={styles} />
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Pre-define styles for list views
|
|
||||||
|
|
||||||
For some simple `FlatList`s, you may be able to pre-define styles for list
|
|
||||||
components, and pass them in as properties, instead of computing new styles for
|
|
||||||
each component.
|
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
import {View} from 'react-native'
|
|
||||||
import {styled} from '#/alf/system'
|
|
||||||
export const Box = styled(View)
|
|
||||||
+89
-4
@@ -1,4 +1,89 @@
|
|||||||
export * from '#/alf/system'
|
import React from 'react'
|
||||||
export * from '#/alf/components/Box'
|
import {Dimensions} from 'react-native'
|
||||||
export * from '#/alf/components/Typography'
|
import * as themes from '#/alf/themes'
|
||||||
export * from '#/alf/components/Button'
|
|
||||||
|
export * as tokens from '#/alf/tokens'
|
||||||
|
export {styles} from '#/alf/styles'
|
||||||
|
export * from '#/alf/util/platform'
|
||||||
|
|
||||||
|
type BreakpointName = keyof typeof breakpoints
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Breakpoints
|
||||||
|
*/
|
||||||
|
const breakpoints: {
|
||||||
|
[key: string]: number
|
||||||
|
} = {
|
||||||
|
gtMobile: 800,
|
||||||
|
gtTablet: 1200,
|
||||||
|
}
|
||||||
|
function getActiveBreakpoints({width}: {width: number}) {
|
||||||
|
const active: (keyof typeof breakpoints)[] = Object.keys(breakpoints).filter(
|
||||||
|
breakpoint => width >= breakpoints[breakpoint],
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
active,
|
||||||
|
current: active[active.length - 1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Context
|
||||||
|
*/
|
||||||
|
export const Context = React.createContext<{
|
||||||
|
themeName: themes.ThemeName
|
||||||
|
styles: themes.Theme
|
||||||
|
breakpoints: {
|
||||||
|
current: BreakpointName | undefined
|
||||||
|
active: BreakpointName[]
|
||||||
|
}
|
||||||
|
}>({
|
||||||
|
themeName: 'light',
|
||||||
|
styles: themes.light,
|
||||||
|
breakpoints: {
|
||||||
|
current: undefined,
|
||||||
|
active: [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export function ThemeProvider({
|
||||||
|
children,
|
||||||
|
theme: themeName,
|
||||||
|
}: React.PropsWithChildren<{theme: themes.ThemeName}>) {
|
||||||
|
const theme = themes[themeName]
|
||||||
|
const [breakpoints, setBreakpoints] = React.useState(() =>
|
||||||
|
getActiveBreakpoints({width: Dimensions.get('window').width}),
|
||||||
|
)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const listener = Dimensions.addEventListener('change', ({window}) => {
|
||||||
|
const bp = getActiveBreakpoints({width: window.width})
|
||||||
|
if (bp.current !== breakpoints.current) setBreakpoints(bp)
|
||||||
|
})
|
||||||
|
|
||||||
|
return listener.remove
|
||||||
|
}, [breakpoints, setBreakpoints])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider
|
||||||
|
value={React.useMemo(
|
||||||
|
() => ({
|
||||||
|
themeName: themeName,
|
||||||
|
styles: theme,
|
||||||
|
breakpoints,
|
||||||
|
}),
|
||||||
|
[theme, themeName, breakpoints],
|
||||||
|
)}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useAlf() {
|
||||||
|
return React.useContext(Context)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useBreakpoints() {
|
||||||
|
return React.useContext(Context).breakpoints
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import type {ViewProps} from 'react-native'
|
|
||||||
import TestRenderer from 'react-test-renderer'
|
|
||||||
import {jest, test, expect} from '@jest/globals'
|
|
||||||
|
|
||||||
import {createTheme} from '../theme'
|
|
||||||
import {createSystem} from '../system'
|
|
||||||
|
|
||||||
jest.mock('react-native', () => ({
|
|
||||||
Dimensions: {
|
|
||||||
get: () => ({width: 0, height: 0}),
|
|
||||||
addEventListener() {
|
|
||||||
return () => {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
StyleSheet: {
|
|
||||||
create(style: any) {
|
|
||||||
return style
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
const theme = createTheme({
|
|
||||||
tokens: {
|
|
||||||
space: {
|
|
||||||
s: 4,
|
|
||||||
m: 8,
|
|
||||||
l: 16,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
theme: 'tomato',
|
|
||||||
text: 'black',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
px: ['paddingLeft', 'paddingRight'],
|
|
||||||
},
|
|
||||||
macros: {
|
|
||||||
caps: (_: boolean) => ({textTransform: 'uppercase'}),
|
|
||||||
},
|
|
||||||
breakpoints: {
|
|
||||||
gtPhone: 640,
|
|
||||||
gtTablet: 1024,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const {ThemeProvider, useTheme, useStyle, useStyles, styled} = createSystem({
|
|
||||||
theme,
|
|
||||||
})
|
|
||||||
|
|
||||||
function Root(props: React.PropsWithChildren<{}>) {
|
|
||||||
return <ThemeProvider theme="theme">{props.children}</ThemeProvider>
|
|
||||||
}
|
|
||||||
|
|
||||||
test('useTheme', () => {
|
|
||||||
function Component() {
|
|
||||||
const theme = useTheme()
|
|
||||||
return <>{JSON.stringify(theme)}</>
|
|
||||||
}
|
|
||||||
|
|
||||||
const dom = TestRenderer.create(
|
|
||||||
<Root>
|
|
||||||
<Component />
|
|
||||||
</Root>,
|
|
||||||
)
|
|
||||||
const json = JSON.parse(dom.toJSON() as unknown as string)
|
|
||||||
|
|
||||||
expect(json.themeName).toEqual('theme')
|
|
||||||
expect(json.theme).toBeTruthy()
|
|
||||||
expect(Object.keys(json.themes)).toEqual(['theme'])
|
|
||||||
})
|
|
||||||
|
|
||||||
test('useStyle', () => {
|
|
||||||
function Component() {
|
|
||||||
const style = useStyle({color: 'red'})
|
|
||||||
return <>{JSON.stringify(style)}</>
|
|
||||||
}
|
|
||||||
|
|
||||||
const dom = TestRenderer.create(
|
|
||||||
<Root>
|
|
||||||
<Component />
|
|
||||||
</Root>,
|
|
||||||
)
|
|
||||||
const json = JSON.parse(dom.toJSON() as unknown as string)
|
|
||||||
|
|
||||||
expect(json.color).toEqual('red')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('useStyles', () => {
|
|
||||||
function Component() {
|
|
||||||
const {text} = useStyles({
|
|
||||||
text: {color: 'red'},
|
|
||||||
})
|
|
||||||
return <>{JSON.stringify(text)}</>
|
|
||||||
}
|
|
||||||
|
|
||||||
const dom = TestRenderer.create(
|
|
||||||
<Root>
|
|
||||||
<Component />
|
|
||||||
</Root>,
|
|
||||||
)
|
|
||||||
const json = JSON.parse(dom.toJSON() as unknown as string)
|
|
||||||
|
|
||||||
expect(json.color).toEqual('red')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('styled', () => {
|
|
||||||
function Component(props: ViewProps) {
|
|
||||||
return <>{JSON.stringify(props)}</>
|
|
||||||
}
|
|
||||||
const Box = styled(Component, {
|
|
||||||
color: 'theme',
|
|
||||||
})
|
|
||||||
|
|
||||||
const dom = TestRenderer.create(
|
|
||||||
<Root>
|
|
||||||
<Box color="red" id="foo" style={{padding: 10}} />
|
|
||||||
</Root>,
|
|
||||||
)
|
|
||||||
const json = JSON.parse(dom.toJSON() as unknown as string)
|
|
||||||
|
|
||||||
expect(json.style).toEqual([{color: 'red'}, {padding: 10}])
|
|
||||||
expect(json.id).toEqual('foo')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('types', () => {
|
|
||||||
function Component(props: ViewProps & {foo: boolean}) {
|
|
||||||
// @ts-expect-error
|
|
||||||
const {foo} = useStyles({
|
|
||||||
text: {color: 'red'},
|
|
||||||
})
|
|
||||||
// just need a way to use `foo`
|
|
||||||
return <React.Fragment key={foo}>{JSON.stringify(props)}</React.Fragment>
|
|
||||||
}
|
|
||||||
const Box = styled(Component, {
|
|
||||||
id: true,
|
|
||||||
// @ts-expect-error
|
|
||||||
color: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
TestRenderer.create(
|
|
||||||
<Root>
|
|
||||||
{/* @ts-expect-error */}
|
|
||||||
<Box color="red" id="foo" style={{padding: 10}}>
|
|
||||||
<Component foo />
|
|
||||||
</Box>
|
|
||||||
</Root>,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
@@ -1,292 +0,0 @@
|
|||||||
import type {ViewProps} from 'react-native'
|
|
||||||
import {describe, test, expect} from '@jest/globals'
|
|
||||||
|
|
||||||
import {createTheme} from '../theme'
|
|
||||||
|
|
||||||
const theme = createTheme({
|
|
||||||
tokens: {
|
|
||||||
space: {
|
|
||||||
s: 4,
|
|
||||||
m: 8,
|
|
||||||
l: 16,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
theme: 'tomato',
|
|
||||||
text: 'black',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
px: ['paddingLeft', 'paddingRight'],
|
|
||||||
},
|
|
||||||
macros: {
|
|
||||||
caps: (_: boolean) => ({textTransform: 'uppercase'}),
|
|
||||||
column: (span: boolean | number) => ({
|
|
||||||
flex: typeof span === 'number' ? span : 1,
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
breakpoints: {
|
|
||||||
gtPhone: 640,
|
|
||||||
gtTablet: 1024,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('non-style props', () => {
|
|
||||||
test('e.g. className', () => {
|
|
||||||
const {styles, props} = theme.style<ViewProps>(
|
|
||||||
{
|
|
||||||
id: 'foo',
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({})
|
|
||||||
expect(props).toEqual({
|
|
||||||
id: 'foo',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('properties', () => {
|
|
||||||
test('standard', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
color: 'blue',
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({
|
|
||||||
color: 'blue',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('custom', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
px: 20,
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({
|
|
||||||
paddingLeft: 20,
|
|
||||||
paddingRight: 20,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('undefined values', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
px: undefined,
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('tokens', () => {
|
|
||||||
test('match', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
color: 'theme',
|
|
||||||
paddingTop: 's',
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({
|
|
||||||
color: 'tomato',
|
|
||||||
paddingTop: 4,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('no match', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
color: 'blue',
|
|
||||||
paddingTop: 20,
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({
|
|
||||||
color: 'blue',
|
|
||||||
paddingTop: 20,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('macros', () => {
|
|
||||||
test('truthy', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
caps: true,
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('falsy', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
caps: false,
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('custom', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
column: 2,
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({flex: 2})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('breakpoints', () => {
|
|
||||||
test('applies', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
paddingTop: 's',
|
|
||||||
gtPhone: {
|
|
||||||
paddingTop: 'm',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
['gtPhone'],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({
|
|
||||||
paddingTop: 8,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('not applies', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
paddingTop: 's',
|
|
||||||
gtPhone: {
|
|
||||||
paddingTop: 'm',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({
|
|
||||||
paddingTop: 4,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('applies in order, asc', () => {
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
paddingTop: 's',
|
|
||||||
gtPhone: {
|
|
||||||
paddingTop: 'm',
|
|
||||||
},
|
|
||||||
gtTablet: {
|
|
||||||
paddingTop: 'l',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
['gtTablet', 'gtPhone'],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({
|
|
||||||
paddingTop: 16,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
test('applies in order even if defined in reverse', () => {
|
|
||||||
const theme = createTheme({
|
|
||||||
tokens: {
|
|
||||||
space: {
|
|
||||||
s: 4,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
breakpoints: {
|
|
||||||
// reverse order
|
|
||||||
gtTablet: 1024,
|
|
||||||
gtPhone: 640,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const {styles} = theme.style(
|
|
||||||
{
|
|
||||||
paddingTop: 20,
|
|
||||||
gtTablet: {
|
|
||||||
paddingTop: 30,
|
|
||||||
},
|
|
||||||
gtPhone: {
|
|
||||||
paddingTop: 25,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
['gtTablet', 'gtPhone'],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(styles).toEqual({
|
|
||||||
paddingTop: 30,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('utils', () => {
|
|
||||||
test('getActiveBreakpoints', () => {
|
|
||||||
const breakpoints = theme.getActiveBreakpoints({width: 1000})
|
|
||||||
expect(breakpoints).toEqual({
|
|
||||||
current: 'gtPhone',
|
|
||||||
active: ['gtPhone'],
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('types', () => {
|
|
||||||
const theme = createTheme({
|
|
||||||
tokens: {
|
|
||||||
space: {
|
|
||||||
s: 4,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
theme: 'tomato',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
px: ['paddingLeft', 'paddingRight'],
|
|
||||||
},
|
|
||||||
macros: {
|
|
||||||
caps: (_: boolean) => ({textTransform: 'uppercase'}),
|
|
||||||
},
|
|
||||||
breakpoints: {
|
|
||||||
gtPhone: 640,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
test('style', () => {
|
|
||||||
theme.style(
|
|
||||||
{
|
|
||||||
// @ts-expect-error
|
|
||||||
color: false,
|
|
||||||
px: 20,
|
|
||||||
// @ts-expect-error
|
|
||||||
caps: 1,
|
|
||||||
},
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
theme.style(
|
|
||||||
{},
|
|
||||||
// @ts-expect-error
|
|
||||||
['taco'],
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,173 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {Dimensions} from 'react-native'
|
|
||||||
|
|
||||||
import {
|
|
||||||
Theme,
|
|
||||||
ResponsiveStyles,
|
|
||||||
Tokens,
|
|
||||||
Properties,
|
|
||||||
Macros,
|
|
||||||
Breakpoints,
|
|
||||||
} from './theme'
|
|
||||||
|
|
||||||
type ThemeConfig<
|
|
||||||
T extends Tokens,
|
|
||||||
P extends Properties,
|
|
||||||
M extends Macros<T>,
|
|
||||||
B extends Breakpoints,
|
|
||||||
> = {
|
|
||||||
[key: string]: Theme<T, P, M, B>
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createSystem<
|
|
||||||
T extends Tokens,
|
|
||||||
P extends Properties,
|
|
||||||
M extends Macros<T>,
|
|
||||||
B extends Breakpoints,
|
|
||||||
>(themes: ThemeConfig<T, P, M, B>) {
|
|
||||||
const defaultTheme = Object.values(themes)[0]
|
|
||||||
|
|
||||||
type SystemTheme = Theme<T, P, M, B>
|
|
||||||
type ResponsiveStyleProps = ResponsiveStyles<T, P, M, B>
|
|
||||||
type StylesAndProps = Parameters<typeof defaultTheme.style<any>>[0]
|
|
||||||
type DebugProps = {
|
|
||||||
/**
|
|
||||||
* Debug mode will log the styles and props to the console
|
|
||||||
*/
|
|
||||||
debug?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThemeName = keyof typeof themes
|
|
||||||
const Context = React.createContext<{
|
|
||||||
themeName: ThemeName
|
|
||||||
theme: SystemTheme
|
|
||||||
themes: {
|
|
||||||
[key in ThemeName]: SystemTheme
|
|
||||||
}
|
|
||||||
breakpoints: ReturnType<SystemTheme['getActiveBreakpoints']>
|
|
||||||
}>({
|
|
||||||
themeName: Object.keys(themes)[0],
|
|
||||||
theme: defaultTheme,
|
|
||||||
themes,
|
|
||||||
breakpoints: defaultTheme.getActiveBreakpoints({
|
|
||||||
width: Dimensions.get('window').width,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
|
|
||||||
const ThemeProvider = ({
|
|
||||||
children,
|
|
||||||
theme: themeName,
|
|
||||||
}: React.PropsWithChildren<{theme: ThemeName}>) => {
|
|
||||||
const theme = themes[themeName]
|
|
||||||
const [breakpoints, setBreakpoints] = React.useState(() =>
|
|
||||||
theme.getActiveBreakpoints({width: Dimensions.get('window').width}),
|
|
||||||
)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const listener = Dimensions.addEventListener('change', ({window}) => {
|
|
||||||
const bp = theme.getActiveBreakpoints({width: window.width})
|
|
||||||
if (bp.current !== breakpoints.current) setBreakpoints(bp)
|
|
||||||
})
|
|
||||||
|
|
||||||
return listener.remove
|
|
||||||
}, [breakpoints, theme, setBreakpoints])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Context.Provider
|
|
||||||
value={React.useMemo(
|
|
||||||
() => ({
|
|
||||||
themeName: themeName,
|
|
||||||
theme,
|
|
||||||
themes,
|
|
||||||
breakpoints,
|
|
||||||
}),
|
|
||||||
[theme, themeName, breakpoints],
|
|
||||||
)}>
|
|
||||||
{children}
|
|
||||||
</Context.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function useTheme() {
|
|
||||||
return React.useContext(Context)
|
|
||||||
}
|
|
||||||
|
|
||||||
function useTokens() {
|
|
||||||
return useTheme().theme.config.tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
function useBreakpoints() {
|
|
||||||
return useTheme().breakpoints
|
|
||||||
}
|
|
||||||
|
|
||||||
function useStyle(props: ResponsiveStyleProps) {
|
|
||||||
const {theme, breakpoints} = useTheme()
|
|
||||||
return React.useMemo(
|
|
||||||
() => theme.style(props as StylesAndProps, breakpoints.active).styles,
|
|
||||||
[breakpoints.active, props, theme],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO we don't get full intellisense from this hook, find out why
|
|
||||||
function useStyles<O extends Record<string, ResponsiveStyleProps>>(
|
|
||||||
styles: O,
|
|
||||||
): {
|
|
||||||
[Name in keyof O]: ReturnType<SystemTheme['style']>['styles']
|
|
||||||
} {
|
|
||||||
const {theme, breakpoints} = useTheme()
|
|
||||||
return React.useMemo(() => {
|
|
||||||
const acc = {} as {
|
|
||||||
[Name in keyof O]: ReturnType<SystemTheme['style']>['styles']
|
|
||||||
}
|
|
||||||
for (const key in styles) {
|
|
||||||
acc[key as keyof O] = theme.style(
|
|
||||||
styles[key] as StylesAndProps,
|
|
||||||
breakpoints.active,
|
|
||||||
).styles
|
|
||||||
}
|
|
||||||
return acc
|
|
||||||
}, [styles, breakpoints.active, theme])
|
|
||||||
}
|
|
||||||
|
|
||||||
function styled<Props extends Record<string, any>>(
|
|
||||||
Component: React.ComponentType<Props>,
|
|
||||||
defaultProps: ResponsiveStyleProps = {},
|
|
||||||
) {
|
|
||||||
const comp = React.forwardRef<
|
|
||||||
Props,
|
|
||||||
Props &
|
|
||||||
ResponsiveStyleProps &
|
|
||||||
DebugProps & {children?: React.ReactNode | React.ReactNodeArray}
|
|
||||||
>((props, ref) => {
|
|
||||||
const {theme, breakpoints} = useTheme()
|
|
||||||
const {styles, props: rest} = React.useMemo(
|
|
||||||
() =>
|
|
||||||
theme.style<Props>(
|
|
||||||
{
|
|
||||||
...defaultProps,
|
|
||||||
...props,
|
|
||||||
},
|
|
||||||
breakpoints.active,
|
|
||||||
),
|
|
||||||
[breakpoints.active, props, theme],
|
|
||||||
)
|
|
||||||
if (props.debug) console.log({styles, props: rest})
|
|
||||||
return <Component {...rest} style={[styles, rest.style]} ref={ref} />
|
|
||||||
})
|
|
||||||
|
|
||||||
comp.displayName = Component.displayName || 'NovaComponent'
|
|
||||||
|
|
||||||
return comp
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
Context,
|
|
||||||
ThemeProvider,
|
|
||||||
useTheme,
|
|
||||||
useTokens,
|
|
||||||
useBreakpoints,
|
|
||||||
useStyle,
|
|
||||||
useStyles,
|
|
||||||
styled,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,393 +0,0 @@
|
|||||||
import type {
|
|
||||||
ViewStyle,
|
|
||||||
TextStyle,
|
|
||||||
ImageStyle,
|
|
||||||
DimensionValue,
|
|
||||||
ColorValue,
|
|
||||||
} from 'react-native'
|
|
||||||
import {StyleSheet} from 'react-native'
|
|
||||||
|
|
||||||
type StyleObject = ViewStyle & TextStyle & ImageStyle
|
|
||||||
type StyleObjectProperties = keyof StyleObject
|
|
||||||
type ColorProperties =
|
|
||||||
| 'color'
|
|
||||||
| 'backgroundColor'
|
|
||||||
| 'borderColor'
|
|
||||||
| 'borderTopColor'
|
|
||||||
| 'borderRightColor'
|
|
||||||
| 'borderBottomColor'
|
|
||||||
| 'borderLeftColor'
|
|
||||||
type DimensionProperties =
|
|
||||||
| 'padding'
|
|
||||||
| 'paddingTop'
|
|
||||||
| 'paddingBottom'
|
|
||||||
| 'paddingLeft'
|
|
||||||
| 'paddingRight'
|
|
||||||
| 'paddingVertical'
|
|
||||||
| 'paddingHorizontal'
|
|
||||||
| 'margin'
|
|
||||||
| 'marginTop'
|
|
||||||
| 'marginBottom'
|
|
||||||
| 'marginLeft'
|
|
||||||
| 'marginRight'
|
|
||||||
| 'top'
|
|
||||||
| 'bottom'
|
|
||||||
| 'left'
|
|
||||||
| 'right'
|
|
||||||
| 'gap'
|
|
||||||
| 'rowGap'
|
|
||||||
| 'columnGap'
|
|
||||||
|
|
||||||
export type Tokens = {
|
|
||||||
space?: DimensionValue[] | Record<string, DimensionValue>
|
|
||||||
} & {
|
|
||||||
[Property in StyleObjectProperties]?: Record<
|
|
||||||
string,
|
|
||||||
Required<StyleObject>[Property]
|
|
||||||
>
|
|
||||||
}
|
|
||||||
export type Properties = Record<string, Readonly<StyleObjectProperties[]>>
|
|
||||||
export type Macros<T extends Tokens> = Record<
|
|
||||||
string,
|
|
||||||
(value: any, tokens: T) => StyleObject
|
|
||||||
>
|
|
||||||
export type Breakpoints = {
|
|
||||||
[Breakpoint: string]: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Styles<
|
|
||||||
T extends Tokens,
|
|
||||||
P extends Properties,
|
|
||||||
M extends Macros<T>,
|
|
||||||
> = {
|
|
||||||
[Property in ColorProperties]?:
|
|
||||||
| keyof T['color']
|
|
||||||
| Omit<ColorValue, keyof T['color']>
|
|
||||||
} & {
|
|
||||||
[Property in DimensionProperties]?: keyof T['space'] | DimensionValue
|
|
||||||
} & {
|
|
||||||
[Property in Exclude<
|
|
||||||
StyleObjectProperties,
|
|
||||||
ColorProperties | DimensionProperties
|
|
||||||
>]?: keyof T[Property] | StyleObject[Property]
|
|
||||||
} & {
|
|
||||||
// this empty P is to avoid circular references back to Properties type
|
|
||||||
[Property in keyof P]?: Styles<T, {}, M>[P[Property][0]]
|
|
||||||
} & {
|
|
||||||
[Property in keyof M]?: M[Property] extends (
|
|
||||||
value: infer Value,
|
|
||||||
tokens: T,
|
|
||||||
) => StyleObject
|
|
||||||
? Value
|
|
||||||
: never
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ResponsiveStyles<
|
|
||||||
T extends Tokens,
|
|
||||||
P extends Properties,
|
|
||||||
M extends Macros<T>,
|
|
||||||
B extends Breakpoints,
|
|
||||||
> =
|
|
||||||
| Styles<T, P, M>
|
|
||||||
| {
|
|
||||||
[Breakpoint in keyof B]?: Styles<T, P, M>
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Theme<
|
|
||||||
T extends Tokens,
|
|
||||||
P extends Properties,
|
|
||||||
M extends Macros<T>,
|
|
||||||
B extends Breakpoints,
|
|
||||||
> = {
|
|
||||||
config: {
|
|
||||||
tokens: T
|
|
||||||
properties: P
|
|
||||||
macros: M
|
|
||||||
breakpoints: B
|
|
||||||
}
|
|
||||||
style: <Props>(
|
|
||||||
props: ResponsiveStyles<T, P, M, B> & Omit<Props, keyof P>,
|
|
||||||
breakpoints?: (keyof B)[],
|
|
||||||
) => {
|
|
||||||
styles: StyleObject
|
|
||||||
props: Props
|
|
||||||
}
|
|
||||||
getActiveBreakpoints: ({width}: {width: number}) => {
|
|
||||||
active: (keyof B)[]
|
|
||||||
current: keyof B
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const specialTokenMapping = {
|
|
||||||
color: 'color',
|
|
||||||
backgroundColor: 'color',
|
|
||||||
borderColor: 'color',
|
|
||||||
borderTopColor: 'color',
|
|
||||||
borderRightColor: 'color',
|
|
||||||
borderBottomColor: 'color',
|
|
||||||
borderLeftColor: 'color',
|
|
||||||
padding: 'space',
|
|
||||||
paddingTop: 'space',
|
|
||||||
paddingBottom: 'space',
|
|
||||||
paddingLeft: 'space',
|
|
||||||
paddingRight: 'space',
|
|
||||||
paddingVertical: 'space',
|
|
||||||
paddingHorizontal: 'space',
|
|
||||||
margin: 'space',
|
|
||||||
marginTop: 'space',
|
|
||||||
marginBottom: 'space',
|
|
||||||
marginLeft: 'space',
|
|
||||||
marginRight: 'space',
|
|
||||||
top: 'space',
|
|
||||||
bottom: 'space',
|
|
||||||
left: 'space',
|
|
||||||
right: 'space',
|
|
||||||
gap: 'space',
|
|
||||||
rowGap: 'space',
|
|
||||||
columnGap: 'space',
|
|
||||||
}
|
|
||||||
|
|
||||||
const propertyMapping: Properties = {
|
|
||||||
// FlexStyle
|
|
||||||
alignContent: ['alignContent'],
|
|
||||||
alignItems: ['alignItems'],
|
|
||||||
alignSelf: ['alignSelf'],
|
|
||||||
aspectRatio: ['aspectRatio'],
|
|
||||||
borderBottomWidth: ['borderBottomWidth'],
|
|
||||||
borderEndWidth: ['borderEndWidth'],
|
|
||||||
borderLeftWidth: ['borderLeftWidth'],
|
|
||||||
borderRightWidth: ['borderRightWidth'],
|
|
||||||
borderStartWidth: ['borderStartWidth'],
|
|
||||||
borderTopWidth: ['borderTopWidth'],
|
|
||||||
borderWidth: ['borderWidth'],
|
|
||||||
bottom: ['bottom'],
|
|
||||||
display: ['display'],
|
|
||||||
end: ['end'],
|
|
||||||
flex: ['flex'],
|
|
||||||
flexBasis: ['flexBasis'],
|
|
||||||
flexDirection: ['flexDirection'],
|
|
||||||
rowGap: ['rowGap'],
|
|
||||||
gap: ['gap'],
|
|
||||||
columnGap: ['columnGap'],
|
|
||||||
flexGrow: ['flexGrow'],
|
|
||||||
flexShrink: ['flexShrink'],
|
|
||||||
flexWrap: ['flexWrap'],
|
|
||||||
height: ['height'],
|
|
||||||
justifyContent: ['justifyContent'],
|
|
||||||
left: ['left'],
|
|
||||||
margin: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
|
|
||||||
marginBottom: ['marginBottom'],
|
|
||||||
marginEnd: ['marginEnd'],
|
|
||||||
marginHorizontal: ['marginLeft', 'marginRight'],
|
|
||||||
marginLeft: ['marginLeft'],
|
|
||||||
marginRight: ['marginRight'],
|
|
||||||
marginStart: ['marginStart'],
|
|
||||||
marginTop: ['marginTop'],
|
|
||||||
marginVertical: ['marginTop', 'marginBottom'],
|
|
||||||
maxHeight: ['maxHeight'],
|
|
||||||
maxWidth: ['maxWidth'],
|
|
||||||
overflow: ['overflow'],
|
|
||||||
padding: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
|
|
||||||
paddingBottom: ['paddingBottom'],
|
|
||||||
paddingEnd: ['paddingEnd'],
|
|
||||||
paddingHorizontal: ['paddingLeft', 'paddingRight'],
|
|
||||||
paddingLeft: ['paddingLeft'],
|
|
||||||
paddingRight: ['paddingRight'],
|
|
||||||
paddingStart: ['paddingStart'],
|
|
||||||
paddingTop: ['paddingTop'],
|
|
||||||
paddingVertical: ['paddingTop', 'paddingBottom'],
|
|
||||||
position: ['position'],
|
|
||||||
right: ['right'],
|
|
||||||
start: ['start'],
|
|
||||||
top: ['top'],
|
|
||||||
width: ['width'],
|
|
||||||
zIndex: ['zIndex'],
|
|
||||||
direction: ['direction'],
|
|
||||||
|
|
||||||
// ShadowStyle
|
|
||||||
shadowColor: ['shadowColor'],
|
|
||||||
shadowOffset: ['shadowOffset'],
|
|
||||||
shadowOpacity: ['shadowOpacity'],
|
|
||||||
shadowRadius: ['shadowRadius'],
|
|
||||||
|
|
||||||
// TransformStyle
|
|
||||||
transform: ['transform'],
|
|
||||||
transformMatrix: ['transformMatrix'],
|
|
||||||
rotation: ['rotation'],
|
|
||||||
scaleX: ['scaleX'],
|
|
||||||
scaleY: ['scaleY'],
|
|
||||||
translateX: ['translateX'],
|
|
||||||
translateY: ['translateY'],
|
|
||||||
|
|
||||||
// ViewStyle
|
|
||||||
backfaceVisibility: ['backfaceVisibility'],
|
|
||||||
backgroundColor: ['backgroundColor'],
|
|
||||||
borderBlockColor: ['borderBlockColor'],
|
|
||||||
borderBlockEndColor: ['borderBlockEndColor'],
|
|
||||||
borderBlockStartColor: ['borderBlockStartColor'],
|
|
||||||
borerBottomColor: ['borderBottomColor'],
|
|
||||||
borderBottomEndRadius: ['borderBottomEndRadius'],
|
|
||||||
borderBottomLeftRadius: ['borderBottomLeftRadius'],
|
|
||||||
borderBottomRightRadius: ['borderBottomRightRadius'],
|
|
||||||
borderBottomStartRadius: ['borderBottomStartRadius'],
|
|
||||||
borderColor: ['borderColor'],
|
|
||||||
borderCurve: ['borderCurve'],
|
|
||||||
borderEndColor: ['borderEndColor'],
|
|
||||||
borderEndEndRadius: ['borderEndEndRadius'],
|
|
||||||
borderEndStartRadius: ['borderEndStartRadius'],
|
|
||||||
borderLeftColor: ['borderLeftColor'],
|
|
||||||
borderRadius: ['borderRadius'],
|
|
||||||
borderRightColor: ['borderRightColor'],
|
|
||||||
borderStartColor: ['borderStartColor'],
|
|
||||||
borderStartEndRadius: ['borderStartEndRadius'],
|
|
||||||
borderStartStartRadius: ['borderStartStartRadius'],
|
|
||||||
borderStyle: ['borderStyle'],
|
|
||||||
borderTopColor: ['borderTopColor'],
|
|
||||||
borderTopEndRadius: ['borderTopEndRadius'],
|
|
||||||
borderTopLeftRadius: ['borderTopLeftRadius'],
|
|
||||||
borderTopRightRadius: ['borderTopRightRadius'],
|
|
||||||
borderTopStartRadius: ['borderTopStartRadius'],
|
|
||||||
opacity: ['opacity'],
|
|
||||||
elevation: ['elevation'],
|
|
||||||
pointerEvents: ['pointerEvents'],
|
|
||||||
|
|
||||||
// TextStyleIOS
|
|
||||||
fontVariant: ['fontVariant'],
|
|
||||||
textDecorationColor: ['textDecorationColor'],
|
|
||||||
textDecorationStyle: ['textDecorationStyle'],
|
|
||||||
writingDirection: ['writingDirection'],
|
|
||||||
|
|
||||||
// TextStyleAndroid
|
|
||||||
textAlignVertical: ['textAlignVertical'],
|
|
||||||
verticalAlign: ['verticalAlign'],
|
|
||||||
includeFontPadding: ['includeFontPadding'],
|
|
||||||
|
|
||||||
// TextStyle
|
|
||||||
color: ['color'],
|
|
||||||
fontFamily: ['fontFamily'],
|
|
||||||
fontSize: ['fontSize'],
|
|
||||||
fontStyle: ['fontStyle'],
|
|
||||||
fontWeight: ['fontWeight'],
|
|
||||||
letterSpacing: ['letterSpacing'],
|
|
||||||
lineHeight: ['lineHeight'],
|
|
||||||
textAlign: ['textAlign'],
|
|
||||||
textDecorationLine: ['textDecorationLine'],
|
|
||||||
textShadowColor: ['textShadowColor'],
|
|
||||||
textShadowOffset: ['textShadowOffset'],
|
|
||||||
textTransform: ['textTransform'],
|
|
||||||
|
|
||||||
// ImageStyle
|
|
||||||
resizeMode: ['resizeMode'],
|
|
||||||
overlayColor: ['overlayColor'],
|
|
||||||
tintColor: ['tintColor'],
|
|
||||||
objectFit: ['objectFit'],
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createTheme = <
|
|
||||||
T extends Tokens,
|
|
||||||
P extends Properties,
|
|
||||||
M extends Macros<T>,
|
|
||||||
B extends Breakpoints,
|
|
||||||
>({
|
|
||||||
tokens,
|
|
||||||
properties: userProperties = {},
|
|
||||||
macros: userMacros = {},
|
|
||||||
breakpoints: userBreakpoints = {},
|
|
||||||
}: {
|
|
||||||
tokens: T
|
|
||||||
properties?: Partial<P>
|
|
||||||
macros?: Partial<M>
|
|
||||||
breakpoints?: Partial<B>
|
|
||||||
}): Theme<T, P, M, B> => {
|
|
||||||
const properties = Object.assign({}, propertyMapping, userProperties) as P
|
|
||||||
const macros = userMacros as M
|
|
||||||
const breakpoints = Object.entries(userBreakpoints)
|
|
||||||
.sort(([_a, a], [_b, b]) => {
|
|
||||||
return a - b
|
|
||||||
})
|
|
||||||
.reduce((breakpoints, [key, value]) => {
|
|
||||||
breakpoints[key as keyof B] = value
|
|
||||||
return breakpoints
|
|
||||||
}, {} as B)
|
|
||||||
|
|
||||||
type InnerTheme = Theme<
|
|
||||||
typeof tokens,
|
|
||||||
typeof properties,
|
|
||||||
typeof macros,
|
|
||||||
typeof breakpoints
|
|
||||||
>
|
|
||||||
|
|
||||||
function _applyStyleProperty(styles: StyleObject, prop: string, value: any) {
|
|
||||||
for (const _prop of properties[prop]) {
|
|
||||||
// @ts-ignore no index sig, it's fine
|
|
||||||
const t = specialTokenMapping[_prop]
|
|
||||||
? // @ts-ignore no index sig, it's fine
|
|
||||||
tokens[specialTokenMapping[_prop]]
|
|
||||||
: tokens[_prop]
|
|
||||||
styles[_prop] = t?.[value] || value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const style: InnerTheme['style'] = (rawProps, activeBreakpoints = []) => {
|
|
||||||
let styles: StyleObject = {}
|
|
||||||
const props: any = {}
|
|
||||||
|
|
||||||
Object.keys(rawProps).forEach(prop => {
|
|
||||||
// @ts-ignore no index sig, it's fine
|
|
||||||
const value = rawProps[prop]
|
|
||||||
|
|
||||||
if (value === undefined) return
|
|
||||||
|
|
||||||
if (properties[prop]) {
|
|
||||||
_applyStyleProperty(styles, prop, value)
|
|
||||||
} else if (macros[prop]) {
|
|
||||||
Object.assign(
|
|
||||||
styles,
|
|
||||||
rawProps[prop] !== false ? macros[prop](value, tokens) : {},
|
|
||||||
)
|
|
||||||
} else if (breakpoints[prop]) {
|
|
||||||
for (const b in breakpoints) {
|
|
||||||
if (activeBreakpoints.includes(b)) {
|
|
||||||
// @ts-ignore no index sig, it's fine
|
|
||||||
const breakpointStyles = rawProps[b] || {}
|
|
||||||
const r = style(breakpointStyles, activeBreakpoints)
|
|
||||||
Object.assign(styles, r.styles || {})
|
|
||||||
} else {
|
|
||||||
// @ts-ignore no index sig, it's fine
|
|
||||||
delete rawProps[b]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// @ts-ignore no index sig, it's fine
|
|
||||||
props[prop] = value
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
|
||||||
styles: StyleSheet.create({sheet: styles}).sheet,
|
|
||||||
props: props,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getActiveBreakpoints({width}: {width: number}) {
|
|
||||||
const active: (keyof typeof breakpoints)[] = Object.keys(
|
|
||||||
breakpoints,
|
|
||||||
).filter(breakpoint => width >= breakpoints[breakpoint])
|
|
||||||
|
|
||||||
return {
|
|
||||||
active,
|
|
||||||
current: active[active.length - 1],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
config: {
|
|
||||||
tokens,
|
|
||||||
properties,
|
|
||||||
macros,
|
|
||||||
breakpoints,
|
|
||||||
},
|
|
||||||
style,
|
|
||||||
getActiveBreakpoints,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,309 @@
|
|||||||
|
import {TextStyle} from 'react-native'
|
||||||
|
import * as tokens from '#/alf/tokens'
|
||||||
|
|
||||||
|
const gap = Object.keys(tokens.space).reduce((acc, key) => {
|
||||||
|
const k = key as tokens.Space
|
||||||
|
acc[k] = {
|
||||||
|
gap: tokens.space[k],
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as Record<tokens.Space, {gap: number}>)
|
||||||
|
|
||||||
|
const fontSize = Object.keys(tokens.fontSize).reduce((acc, key) => {
|
||||||
|
const k = key as tokens.FontSize
|
||||||
|
acc[k] = {
|
||||||
|
fontSize: tokens.fontSize[k],
|
||||||
|
lineHeight: tokens.fontSize[k],
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as Record<tokens.FontSize, {fontSize: number; lineHeight: number}>)
|
||||||
|
|
||||||
|
const lineHeight = Object.keys(tokens.lineHeight).reduce((acc, key) => {
|
||||||
|
const k = key as tokens.LineHeight
|
||||||
|
acc[k] = {
|
||||||
|
lineHeight: tokens.lineHeight[k],
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as Record<tokens.LineHeight, {lineHeight: string}>)
|
||||||
|
|
||||||
|
const fontWeight = Object.keys(tokens.fontWeight).reduce((acc, key) => {
|
||||||
|
const k = key as tokens.FontWeight
|
||||||
|
acc[k] = {
|
||||||
|
fontWeight: tokens.fontWeight[k],
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as Record<tokens.FontWeight, {fontWeight: TextStyle['fontWeight']}>)
|
||||||
|
|
||||||
|
const radius = Object.keys(tokens.borderRadius).reduce((acc, key) => {
|
||||||
|
const k = key as tokens.BorderRadius
|
||||||
|
acc[k] = {
|
||||||
|
borderRadius: tokens.borderRadius[k],
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as Record<tokens.BorderRadius, {borderRadius: number}>)
|
||||||
|
|
||||||
|
const padding = Object.keys(tokens.space).reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
const k = key as tokens.Space
|
||||||
|
const value = tokens.space[k]
|
||||||
|
return {
|
||||||
|
pa: {
|
||||||
|
...acc.pa,
|
||||||
|
[k]: {
|
||||||
|
paddingTop: value,
|
||||||
|
paddingBottom: value,
|
||||||
|
paddingLeft: value,
|
||||||
|
paddingRight: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
px: {
|
||||||
|
...acc.px,
|
||||||
|
[k]: {
|
||||||
|
paddingLeft: value,
|
||||||
|
paddingRight: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
py: {
|
||||||
|
...acc.py,
|
||||||
|
[k]: {
|
||||||
|
paddingTop: value,
|
||||||
|
paddingBottom: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pt: {
|
||||||
|
...acc.pt,
|
||||||
|
[k]: {
|
||||||
|
paddingTop: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pb: {
|
||||||
|
...acc.pb,
|
||||||
|
[k]: {
|
||||||
|
paddingBottom: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pl: {
|
||||||
|
...acc.pl,
|
||||||
|
[k]: {
|
||||||
|
paddingLeft: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pr: {
|
||||||
|
...acc.pr,
|
||||||
|
[k]: {
|
||||||
|
paddingRight: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{} as {
|
||||||
|
pa: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
paddingTop: number
|
||||||
|
paddingBottom: number
|
||||||
|
paddingLeft: number
|
||||||
|
paddingRight: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
px: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
paddingLeft: number
|
||||||
|
paddingRight: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
py: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
paddingTop: number
|
||||||
|
paddingBottom: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
pt: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
paddingTop: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
pb: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
paddingBottom: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
pl: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
paddingLeft: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
pr: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
paddingRight: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const margin = Object.keys(tokens.space).reduce(
|
||||||
|
(acc, key) => {
|
||||||
|
const k = key as tokens.Space
|
||||||
|
const value = tokens.space[k]
|
||||||
|
return {
|
||||||
|
pa: {
|
||||||
|
...acc.pa,
|
||||||
|
[k]: {
|
||||||
|
marginTop: value,
|
||||||
|
marginBottom: value,
|
||||||
|
marginLeft: value,
|
||||||
|
marginRight: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
px: {
|
||||||
|
...acc.px,
|
||||||
|
[k]: {
|
||||||
|
marginLeft: value,
|
||||||
|
marginRight: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
py: {
|
||||||
|
...acc.py,
|
||||||
|
[k]: {
|
||||||
|
marginTop: value,
|
||||||
|
marginBottom: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pt: {
|
||||||
|
...acc.pt,
|
||||||
|
[k]: {
|
||||||
|
marginTop: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pb: {
|
||||||
|
...acc.pb,
|
||||||
|
[k]: {
|
||||||
|
marginBottom: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pl: {
|
||||||
|
...acc.pl,
|
||||||
|
[k]: {
|
||||||
|
marginLeft: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pr: {
|
||||||
|
...acc.pr,
|
||||||
|
[k]: {
|
||||||
|
marginRight: value,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{} as {
|
||||||
|
pa: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
marginTop: number
|
||||||
|
marginBottom: number
|
||||||
|
marginLeft: number
|
||||||
|
marginRight: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
px: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
marginLeft: number
|
||||||
|
marginRight: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
py: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
marginTop: number
|
||||||
|
marginBottom: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
pt: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
marginTop: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
pb: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
marginBottom: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
pl: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
marginLeft: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
pr: Record<
|
||||||
|
tokens.Space,
|
||||||
|
{
|
||||||
|
marginRight: number
|
||||||
|
}
|
||||||
|
>
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export const styles = {
|
||||||
|
radius,
|
||||||
|
padding,
|
||||||
|
margin,
|
||||||
|
font: {
|
||||||
|
...fontSize,
|
||||||
|
...fontWeight,
|
||||||
|
...lineHeight,
|
||||||
|
},
|
||||||
|
flex: {
|
||||||
|
gap,
|
||||||
|
row: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
},
|
||||||
|
wrap: {
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
},
|
||||||
|
one: {
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
two: {
|
||||||
|
flex: 2,
|
||||||
|
},
|
||||||
|
three: {
|
||||||
|
flex: 3,
|
||||||
|
},
|
||||||
|
alignCenter: {
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
justifyCenter: {
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
justifyBetween: {
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
},
|
||||||
|
justifyEnd: {
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pos: {
|
||||||
|
abs: {
|
||||||
|
position: 'absolute',
|
||||||
|
},
|
||||||
|
rel: {
|
||||||
|
position: 'relative',
|
||||||
|
},
|
||||||
|
cover: {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
/**
|
|
||||||
* Library imports
|
|
||||||
*/
|
|
||||||
import {createSystem} from './lib/system'
|
|
||||||
import {light, dark} from './themes'
|
|
||||||
|
|
||||||
const {
|
|
||||||
ThemeProvider,
|
|
||||||
useTheme,
|
|
||||||
useTokens,
|
|
||||||
useBreakpoints,
|
|
||||||
useStyle,
|
|
||||||
useStyles,
|
|
||||||
styled,
|
|
||||||
} = createSystem({
|
|
||||||
light,
|
|
||||||
dark,
|
|
||||||
})
|
|
||||||
|
|
||||||
export {
|
|
||||||
ThemeProvider,
|
|
||||||
useTheme,
|
|
||||||
useTokens,
|
|
||||||
useBreakpoints,
|
|
||||||
useStyle,
|
|
||||||
useStyles,
|
|
||||||
styled,
|
|
||||||
}
|
|
||||||
+78
-190
@@ -1,195 +1,83 @@
|
|||||||
import {createTheme} from './lib/theme'
|
import * as tokens from '#/alf/tokens'
|
||||||
|
import {styles as sharedStyles} from '#/alf/styles'
|
||||||
|
|
||||||
const BLUE_HUE = 210
|
export type ThemeName = 'light' | 'dark'
|
||||||
const GRAYSCALE_SATURATION = 12
|
export type Theme = typeof light
|
||||||
|
|
||||||
export const palette = {
|
export type Palette = {
|
||||||
white: '#FFFFFF',
|
primary: string
|
||||||
|
positive: string
|
||||||
/**
|
negative: string
|
||||||
* Mathematical scale of grays, from lightest to darkest, all based on the
|
l0: string
|
||||||
* primary blue color
|
l1: string
|
||||||
*/
|
l2: string
|
||||||
gray1: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 95%)`,
|
l3: string
|
||||||
gray2: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 85%)`,
|
l4: string
|
||||||
gray3: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 75%)`,
|
l5: string
|
||||||
gray4: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 30%)`,
|
l6: string
|
||||||
gray5: `hsl(${BLUE_HUE} ${GRAYSCALE_SATURATION}%, 20%)`,
|
l7: string
|
||||||
gray6: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 10%)`,
|
|
||||||
black: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 5%)`,
|
|
||||||
|
|
||||||
blue: `hsl(${BLUE_HUE}, 100%, 53%)`,
|
|
||||||
green: '#54D469',
|
|
||||||
red: '#FB4566',
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const light = createTheme({
|
export const lightPalette: Palette = {
|
||||||
tokens: {
|
primary: tokens.color.blue,
|
||||||
space: {
|
positive: tokens.color.green,
|
||||||
xxs: 2,
|
negative: tokens.color.red,
|
||||||
xs: 4,
|
l0: tokens.color.white,
|
||||||
s: 8,
|
l1: tokens.color.gray1,
|
||||||
m: 12,
|
l2: tokens.color.gray2,
|
||||||
l: 18,
|
l3: tokens.color.gray3,
|
||||||
xl: 24,
|
l4: tokens.color.gray4,
|
||||||
xxl: 32,
|
l5: tokens.color.gray5,
|
||||||
},
|
l6: tokens.color.gray6,
|
||||||
color: {
|
l7: tokens.color.black,
|
||||||
primary: palette.blue,
|
} as const
|
||||||
positive: palette.green,
|
|
||||||
negative: palette.red,
|
|
||||||
l0: palette.white,
|
|
||||||
l1: palette.gray1,
|
|
||||||
l2: palette.gray2,
|
|
||||||
l3: palette.gray3,
|
|
||||||
l4: palette.gray4,
|
|
||||||
l5: palette.gray5,
|
|
||||||
l6: palette.gray6,
|
|
||||||
l7: palette.black,
|
|
||||||
},
|
|
||||||
fontSize: {
|
|
||||||
xxs: 10,
|
|
||||||
xs: 12,
|
|
||||||
s: 14,
|
|
||||||
m: 16,
|
|
||||||
l: 18,
|
|
||||||
xl: 22,
|
|
||||||
xxl: 26,
|
|
||||||
},
|
|
||||||
lineHeight: {
|
|
||||||
xxs: 10,
|
|
||||||
xs: 12,
|
|
||||||
s: 14,
|
|
||||||
m: 16,
|
|
||||||
l: 18,
|
|
||||||
xl: 22,
|
|
||||||
xxl: 26,
|
|
||||||
},
|
|
||||||
borderRadius: {
|
|
||||||
s: 8,
|
|
||||||
m: 12,
|
|
||||||
xl: 36,
|
|
||||||
round: 999,
|
|
||||||
},
|
|
||||||
fontWeight: {
|
|
||||||
normal: '400',
|
|
||||||
semi: '600',
|
|
||||||
bold: '900',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
properties: {
|
|
||||||
/** Alias for `width` */
|
|
||||||
w: ['width'],
|
|
||||||
/** Alias for `height` */
|
|
||||||
h: ['height'],
|
|
||||||
/** Alias for `color` */
|
|
||||||
c: ['color'],
|
|
||||||
/** Alias for `backgroundColor` */
|
|
||||||
bg: ['backgroundColor'],
|
|
||||||
/** Alias for all directional margin properties */
|
|
||||||
ma: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'],
|
|
||||||
/** Alias for `marginTop` */
|
|
||||||
mt: ['marginTop'],
|
|
||||||
/** Alias for `marginBottom` */
|
|
||||||
mb: ['marginBottom'],
|
|
||||||
/** Alias for `marginLeft` */
|
|
||||||
ml: ['marginLeft'],
|
|
||||||
/** Alias for `marginRight` */
|
|
||||||
mr: ['marginRight'],
|
|
||||||
/** Alias for `marginVertical` */
|
|
||||||
my: ['marginTop', 'marginBottom'],
|
|
||||||
/** Alias for `marginHorizontal` */
|
|
||||||
mx: ['marginLeft', 'marginRight'],
|
|
||||||
/** Alias for all directional padding properties */
|
|
||||||
pa: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'],
|
|
||||||
/** Alias for `paddingTop` */
|
|
||||||
pt: ['paddingTop'],
|
|
||||||
/** Alias for `paddingBottom` */
|
|
||||||
pb: ['paddingBottom'],
|
|
||||||
/** Alias for `paddingLeft` */
|
|
||||||
pl: ['paddingLeft'],
|
|
||||||
/** Alias for `paddingRight` */
|
|
||||||
pr: ['paddingRight'],
|
|
||||||
/** Alias for `paddingVertical` */
|
|
||||||
py: ['paddingTop', 'paddingBottom'],
|
|
||||||
/** Alias for `paddingHorizontal` */
|
|
||||||
px: ['paddingLeft', 'paddingRight'],
|
|
||||||
/** Alias for `zIndex` */
|
|
||||||
z: ['zIndex'],
|
|
||||||
/** Alias for `borderRadius` */
|
|
||||||
radius: ['borderRadius'],
|
|
||||||
},
|
|
||||||
macros: {
|
|
||||||
/** Shorthand for `flexDirection: 'row'` */
|
|
||||||
row: (_: boolean) => ({flexDirection: 'row', flex: 1}),
|
|
||||||
/**
|
|
||||||
* Shorthand for `flex: 1`. Optionally pass an integer to specify the
|
|
||||||
* col-span.
|
|
||||||
*
|
|
||||||
* Semantically this is helpful as a direct child of `<Box row>`
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* <Box row>
|
|
||||||
* <Box column>
|
|
||||||
* <Text>Hello</Text>
|
|
||||||
* </Box>
|
|
||||||
* <Box column={2}>
|
|
||||||
* <Text>Hello</Text>
|
|
||||||
* </Box>
|
|
||||||
* </Box>
|
|
||||||
*/
|
|
||||||
column: (span: boolean | number) => ({
|
|
||||||
flex: typeof span === 'number' ? span : 1,
|
|
||||||
}),
|
|
||||||
/** Shorthand for `alignItems: 'center'` */
|
|
||||||
aic: (_: boolean) => ({alignItems: 'center'}),
|
|
||||||
/** Shorthand for `justifyContent: 'center'` */
|
|
||||||
jcc: (_: boolean) => ({justifyContent: 'center'}),
|
|
||||||
/** Shorthand for `justifyContent: 'space-between'` */
|
|
||||||
jcb: (_: boolean) => ({justifyContent: 'space-between'}),
|
|
||||||
/** Shorthand for `position: 'absolute'` */
|
|
||||||
abs: (_: boolean) => ({position: 'absolute'}),
|
|
||||||
/** Shorthand for `StyleSheet.absoluteFillObject` */
|
|
||||||
cover: (_: boolean) => ({
|
|
||||||
top: 0,
|
|
||||||
bottom: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
}),
|
|
||||||
/** Shorthand for `textTransform: 'uppercase'` */
|
|
||||||
caps: (_: boolean) => ({textTransform: 'uppercase'}),
|
|
||||||
/**
|
|
||||||
* Shorthand for applying `fontSize` and `fontHeight`, according to our type scale.
|
|
||||||
*/
|
|
||||||
fontSize(value: 'xxs' | 'xs' | 's' | 'm' | 'l' | 'xl' | 'xxl', tokens) {
|
|
||||||
return {
|
|
||||||
fontSize: tokens.fontSize[value],
|
|
||||||
lineHeight: tokens.lineHeight[value],
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
breakpoints: {
|
|
||||||
/** Greater than 800 */
|
|
||||||
gtMobile: 800,
|
|
||||||
/** Greater than 1300 */
|
|
||||||
gtTablet: 1300,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export const dark = createTheme({
|
export const darkPalette: Palette = {
|
||||||
...light.config,
|
primary: tokens.color.blue,
|
||||||
tokens: {
|
positive: tokens.color.green,
|
||||||
...light.config.tokens,
|
negative: tokens.color.red,
|
||||||
color: {
|
l0: tokens.color.black,
|
||||||
...light.config.tokens.color,
|
l1: tokens.color.gray6,
|
||||||
l0: palette.black,
|
l2: tokens.color.gray5,
|
||||||
l1: palette.gray6,
|
l3: tokens.color.gray4,
|
||||||
l2: palette.gray5,
|
l4: tokens.color.gray3,
|
||||||
l3: palette.gray4,
|
l5: tokens.color.gray2,
|
||||||
l4: palette.gray3,
|
l6: tokens.color.gray1,
|
||||||
l5: palette.gray2,
|
l7: tokens.color.white,
|
||||||
l6: palette.gray1,
|
} as const
|
||||||
l7: palette.white,
|
|
||||||
},
|
export const light = {
|
||||||
},
|
...sharedStyles,
|
||||||
})
|
color: Object.keys(lightPalette).reduce((acc, key) => {
|
||||||
|
const k = key as keyof Palette
|
||||||
|
acc[k] = {
|
||||||
|
color: lightPalette[k],
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as Record<keyof Palette, {color: string}>),
|
||||||
|
backgroundColor: Object.keys(lightPalette).reduce((acc, key) => {
|
||||||
|
const k = key as keyof Palette
|
||||||
|
acc[k] = {
|
||||||
|
backgroundColor: lightPalette[k],
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as Record<keyof Palette, {backgroundColor: string}>),
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const dark = {
|
||||||
|
...sharedStyles,
|
||||||
|
color: Object.keys(darkPalette).reduce((acc, key) => {
|
||||||
|
const k = key as keyof Palette
|
||||||
|
acc[k] = {
|
||||||
|
color: darkPalette[k],
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as Record<keyof Palette, {color: string}>),
|
||||||
|
backgroundColor: Object.keys(darkPalette).reduce((acc, key) => {
|
||||||
|
const k = key as keyof Palette
|
||||||
|
acc[k] = {
|
||||||
|
backgroundColor: darkPalette[k],
|
||||||
|
}
|
||||||
|
return acc
|
||||||
|
}, {} as Record<keyof Palette, {backgroundColor: string}>),
|
||||||
|
} as const
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
const BLUE_HUE = 210
|
||||||
|
const GRAYSCALE_SATURATION = 12
|
||||||
|
|
||||||
|
export const color = {
|
||||||
|
white: '#FFFFFF',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mathematical scale of grays, from lightest to darkest, all based on the
|
||||||
|
* primary blue color
|
||||||
|
*/
|
||||||
|
gray1: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 95%)`,
|
||||||
|
gray2: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 85%)`,
|
||||||
|
gray3: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 75%)`,
|
||||||
|
gray4: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 30%)`,
|
||||||
|
gray5: `hsl(${BLUE_HUE} ${GRAYSCALE_SATURATION}%, 20%)`,
|
||||||
|
gray6: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 10%)`,
|
||||||
|
black: `hsl(${BLUE_HUE}, ${GRAYSCALE_SATURATION}%, 5%)`,
|
||||||
|
|
||||||
|
blue: `hsl(${BLUE_HUE}, 100%, 53%)`,
|
||||||
|
green: '#54D469',
|
||||||
|
red: '#FB4566',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const space = {
|
||||||
|
xxs: 2,
|
||||||
|
xs: 4,
|
||||||
|
s: 8,
|
||||||
|
m: 12,
|
||||||
|
l: 18,
|
||||||
|
xl: 24,
|
||||||
|
xxl: 32,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const fontSize = {
|
||||||
|
xxs: 10,
|
||||||
|
xs: 12,
|
||||||
|
s: 14,
|
||||||
|
m: 16,
|
||||||
|
l: 18,
|
||||||
|
xl: 22,
|
||||||
|
xxl: 26,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
// TODO test
|
||||||
|
export const lineHeight = {
|
||||||
|
tight: '1.0', // default
|
||||||
|
normal: '1.5',
|
||||||
|
relaxed: '1.625',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const borderRadius = {
|
||||||
|
s: 8,
|
||||||
|
m: 12,
|
||||||
|
xl: 36,
|
||||||
|
round: 999,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export const fontWeight = {
|
||||||
|
normal: '400',
|
||||||
|
semi: '600',
|
||||||
|
bold: '900',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export type Color = keyof typeof color
|
||||||
|
export type Space = keyof typeof space
|
||||||
|
export type FontSize = keyof typeof fontSize
|
||||||
|
export type LineHeight = keyof typeof lineHeight
|
||||||
|
export type BorderRadius = keyof typeof borderRadius
|
||||||
|
export type FontWeight = keyof typeof fontWeight
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {Pressable, Text} from 'react-native'
|
||||||
|
import {useAlf, tokens} from '#/alf'
|
||||||
|
|
||||||
|
export function Button({children, ...rest}: React.PropsWithChildren<any>) {
|
||||||
|
const {styles} = useAlf()
|
||||||
|
return (
|
||||||
|
<Pressable
|
||||||
|
{...rest}
|
||||||
|
style={[
|
||||||
|
styles.flex.row,
|
||||||
|
styles.flex.gap.m,
|
||||||
|
styles.padding.px.m,
|
||||||
|
styles.padding.py.s,
|
||||||
|
styles.radius.m,
|
||||||
|
styles.backgroundColor.primary,
|
||||||
|
]}>
|
||||||
|
{typeof children === 'string' ? (
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.font.s,
|
||||||
|
styles.font.semi,
|
||||||
|
{color: tokens.color.white},
|
||||||
|
]}>
|
||||||
|
{children}
|
||||||
|
</Text>
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
)}
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import {Text as RNText, TextProps} from 'react-native'
|
||||||
|
import {useAlf, web} from '#/alf'
|
||||||
|
|
||||||
|
export function Text({style, ...rest}: TextProps) {
|
||||||
|
const {styles} = useAlf()
|
||||||
|
return <RNText style={[styles.font.s, styles.color.l7, style]} {...rest} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export function H1({style, ...rest}: TextProps) {
|
||||||
|
const {styles} = useAlf()
|
||||||
|
const attr =
|
||||||
|
web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 1,
|
||||||
|
}) || {}
|
||||||
|
return (
|
||||||
|
<RNText
|
||||||
|
{...attr}
|
||||||
|
{...rest}
|
||||||
|
style={[styles.font.xxl, styles.font.bold, styles.color.l7, style]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function H2({style, ...rest}: TextProps) {
|
||||||
|
const {styles} = useAlf()
|
||||||
|
const attr =
|
||||||
|
web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 2,
|
||||||
|
}) || {}
|
||||||
|
return (
|
||||||
|
<RNText
|
||||||
|
{...attr}
|
||||||
|
{...rest}
|
||||||
|
style={[styles.font.l, styles.font.bold, styles.color.l7, style]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function H3({style, ...rest}: TextProps) {
|
||||||
|
const {styles} = useAlf()
|
||||||
|
const attr =
|
||||||
|
web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 3,
|
||||||
|
}) || {}
|
||||||
|
return (
|
||||||
|
<RNText
|
||||||
|
{...attr}
|
||||||
|
{...rest}
|
||||||
|
style={[styles.font.m, styles.font.bold, styles.color.l7, style]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function H4({style, ...rest}: TextProps) {
|
||||||
|
const {styles} = useAlf()
|
||||||
|
const attr =
|
||||||
|
web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 4,
|
||||||
|
}) || {}
|
||||||
|
return (
|
||||||
|
<RNText
|
||||||
|
{...attr}
|
||||||
|
{...rest}
|
||||||
|
style={[styles.font.s, styles.font.bold, styles.color.l7, style]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function H5({style, ...rest}: TextProps) {
|
||||||
|
const {styles} = useAlf()
|
||||||
|
const attr =
|
||||||
|
web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 5,
|
||||||
|
}) || {}
|
||||||
|
return (
|
||||||
|
<RNText
|
||||||
|
{...attr}
|
||||||
|
{...rest}
|
||||||
|
style={[styles.font.xs, styles.font.bold, styles.color.l7, style]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function H6({style, ...rest}: TextProps) {
|
||||||
|
const {styles} = useAlf()
|
||||||
|
const attr =
|
||||||
|
web({
|
||||||
|
role: 'heading',
|
||||||
|
'aria-level': 6,
|
||||||
|
}) || {}
|
||||||
|
return (
|
||||||
|
<RNText
|
||||||
|
{...attr}
|
||||||
|
{...rest}
|
||||||
|
style={[styles.font.xxs, styles.font.bold, styles.color.l7, style]}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
+218
-203
@@ -1,251 +1,266 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {View, Text as RNText} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
import {CenteredView, ScrollView} from '#/view/com/util/Views'
|
||||||
|
|
||||||
import {useSetColorMode} from '#/state/shell'
|
import {useSetColorMode} from '#/state/shell'
|
||||||
import {
|
import {useAlf, ThemeProvider as Alf} from '#/alf'
|
||||||
ThemeProvider as Alf,
|
import {Button} from '#/view/com/Button'
|
||||||
useBreakpoints,
|
import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography'
|
||||||
useStyle,
|
|
||||||
useStyles,
|
|
||||||
Box,
|
|
||||||
Text,
|
|
||||||
Button,
|
|
||||||
H1,
|
|
||||||
H2,
|
|
||||||
H3,
|
|
||||||
H4,
|
|
||||||
H5,
|
|
||||||
H6,
|
|
||||||
} from '#/alf'
|
|
||||||
|
|
||||||
function ThemeSelector() {
|
function ThemeSelector() {
|
||||||
const setColorMode = useSetColorMode()
|
const setColorMode = useSetColorMode()
|
||||||
|
const {styles} = useAlf()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box row gap="s">
|
<View style={[styles.flex.row, styles.flex.gap.m]}>
|
||||||
<Button onPress={() => setColorMode('system')}>
|
<Button onPress={() => setColorMode('system')}>System</Button>
|
||||||
<Text>System</Text>
|
<Button onPress={() => setColorMode('light')}>Light</Button>
|
||||||
</Button>
|
<Button onPress={() => setColorMode('dark')}>Dark</Button>
|
||||||
<Button onPress={() => setColorMode('light')}>
|
</View>
|
||||||
<Text>Light</Text>
|
|
||||||
</Button>
|
|
||||||
<Button onPress={() => setColorMode('dark')}>
|
|
||||||
<Text>Dark</Text>
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function BreakpointDebugger() {
|
function BreakpointDebugger() {
|
||||||
const breakpoints = useBreakpoints()
|
const {styles, breakpoints} = useAlf()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<View>
|
||||||
<H3 pb="s" fontWeight="bold">
|
<H3 style={[styles.padding.pb.m]}>Breakpoint Debugger</H3>
|
||||||
Breakpoint Debugger
|
<Text
|
||||||
</H3>
|
style={[
|
||||||
<Text pa="m" bg="l1" fontFamily="monospace">
|
styles.padding.pa.m,
|
||||||
|
styles.backgroundColor.l1,
|
||||||
|
{fontFamily: 'monospace'},
|
||||||
|
]}>
|
||||||
{JSON.stringify(breakpoints, null, 2)}
|
{JSON.stringify(breakpoints, null, 2)}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function Hooks() {
|
function ThemedSection() {
|
||||||
const outer = useStyle({
|
const {styles} = useAlf()
|
||||||
pa: 'm',
|
|
||||||
bg: 'l1',
|
|
||||||
})
|
|
||||||
const {heading} = useStyles({
|
|
||||||
heading: {
|
|
||||||
color: 'l7',
|
|
||||||
fontSize: 'l',
|
|
||||||
fontWeight: 'bold',
|
|
||||||
gtTablet: {
|
|
||||||
fontSize: 'xl',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={outer}>
|
<View style={[styles.backgroundColor.l0, styles.padding.pa.m]}>
|
||||||
<RNText style={heading}>Hooks</RNText>
|
<H3 style={[styles.padding.pb.m, styles.font.bold]}>
|
||||||
|
Colors (this theme is always light)
|
||||||
|
</H3>
|
||||||
|
<View style={[styles.flex.row, styles.flex.gap.m]}>
|
||||||
|
<View
|
||||||
|
style={[styles.flex.one, styles.backgroundColor.l0, {height: 60}]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[styles.flex.one, styles.backgroundColor.l1, {height: 60}]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[styles.flex.one, styles.backgroundColor.l2, {height: 60}]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[styles.flex.one, styles.backgroundColor.l3, {height: 60}]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[styles.flex.one, styles.backgroundColor.l4, {height: 60}]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[styles.flex.one, styles.backgroundColor.l5, {height: 60}]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[styles.flex.one, styles.backgroundColor.l6, {height: 60}]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[styles.flex.one, styles.backgroundColor.l7, {height: 60}]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DebugScreen() {
|
export function DebugScreen() {
|
||||||
const backgroundStyles = useStyle({
|
const {styles} = useAlf()
|
||||||
bg: 'l0',
|
|
||||||
})
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
<CenteredView style={backgroundStyles}>
|
<CenteredView style={[styles.backgroundColor.l0]}>
|
||||||
<Box pa="xl" pb={200} gap="xxl">
|
<View
|
||||||
|
style={[
|
||||||
|
styles.padding.pa.xl,
|
||||||
|
styles.flex.gap.xxl,
|
||||||
|
{paddingBottom: 200},
|
||||||
|
]}>
|
||||||
<ThemeSelector />
|
<ThemeSelector />
|
||||||
|
|
||||||
<Box>
|
|
||||||
<H3 pb="s" fontWeight="bold">
|
|
||||||
Colors
|
|
||||||
</H3>
|
|
||||||
<Box row gap="m">
|
|
||||||
<Box column bg="l0" h={60} />
|
|
||||||
<Box column bg="l1" h={60} />
|
|
||||||
<Box column bg="l2" h={60} />
|
|
||||||
<Box column bg="l3" h={60} />
|
|
||||||
<Box column bg="l4" h={60} />
|
|
||||||
<Box column bg="l5" h={60} />
|
|
||||||
<Box column bg="l6" h={60} />
|
|
||||||
<Box column bg="l7" h={60} />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<H3 pb="s" fontWeight="bold">
|
|
||||||
Spacing
|
|
||||||
</H3>
|
|
||||||
|
|
||||||
<Box gap="m">
|
|
||||||
<Box row alignItems="center">
|
|
||||||
<Text w={80}>xxs (2px)</Text>
|
|
||||||
<Box flex={1} w="100%" pt="xxs" bg="l3" />
|
|
||||||
</Box>
|
|
||||||
<Box row alignItems="center">
|
|
||||||
<Text w={80}>xs (4px)</Text>
|
|
||||||
<Box flex={1} w="100%" pt="xs" bg="l3" />
|
|
||||||
</Box>
|
|
||||||
<Box row alignItems="center">
|
|
||||||
<Text w={80}>s (8px)</Text>
|
|
||||||
<Box flex={1} w="100%" pt="s" bg="l3" />
|
|
||||||
</Box>
|
|
||||||
<Box row alignItems="center">
|
|
||||||
<Text w={80}>m (12px)</Text>
|
|
||||||
<Box flex={1} w="100%" pt="m" bg="l3" />
|
|
||||||
</Box>
|
|
||||||
<Box row alignItems="center">
|
|
||||||
<Text w={80}>l (18px)</Text>
|
|
||||||
<Box flex={1} w="100%" pt="l" bg="l3" />
|
|
||||||
</Box>
|
|
||||||
<Box row alignItems="center">
|
|
||||||
<Text w={80}>xl (24px)</Text>
|
|
||||||
<Box flex={1} w="100%" pt="xl" bg="l3" />
|
|
||||||
</Box>
|
|
||||||
<Box row alignItems="center">
|
|
||||||
<Text w={80}>xxl (32px)</Text>
|
|
||||||
<Box flex={1} w="100%" pt="xxl" bg="l3" />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<BreakpointDebugger />
|
<BreakpointDebugger />
|
||||||
|
|
||||||
<Box>
|
<View>
|
||||||
<H3 pb="s" fontWeight="bold">
|
<H3 style={[styles.padding.pb.m, styles.font.bold]}>Colors</H3>
|
||||||
Typography
|
<View style={[styles.flex.row, styles.flex.gap.m]}>
|
||||||
</H3>
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.backgroundColor.l0,
|
||||||
|
{height: 60},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.backgroundColor.l1,
|
||||||
|
{height: 60},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.backgroundColor.l2,
|
||||||
|
{height: 60},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.backgroundColor.l3,
|
||||||
|
{height: 60},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.backgroundColor.l4,
|
||||||
|
{height: 60},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.backgroundColor.l5,
|
||||||
|
{height: 60},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.backgroundColor.l6,
|
||||||
|
{height: 60},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.backgroundColor.l7,
|
||||||
|
{height: 60},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
<Box gap="m" pa="m" bg="l1">
|
<View>
|
||||||
|
<H3 style={[styles.padding.pb.m, styles.font.bold]}>Spacing</H3>
|
||||||
|
|
||||||
|
<View style={[styles.flex.gap.m]}>
|
||||||
|
<View style={[styles.flex.row, styles.flex.alignCenter]}>
|
||||||
|
<Text style={{width: 80}}>xxs (2px)</Text>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.padding.pt.xxs,
|
||||||
|
styles.backgroundColor.l3,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.flex.row, styles.flex.alignCenter]}>
|
||||||
|
<Text style={{width: 80}}>xs (4px)</Text>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.padding.pt.xs,
|
||||||
|
styles.backgroundColor.l3,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.flex.row, styles.flex.alignCenter]}>
|
||||||
|
<Text style={{width: 80}}>s (8px)</Text>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.padding.pt.s,
|
||||||
|
styles.backgroundColor.l3,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.flex.row, styles.flex.alignCenter]}>
|
||||||
|
<Text style={{width: 80}}>m (12px)</Text>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.padding.pt.m,
|
||||||
|
styles.backgroundColor.l3,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.flex.row, styles.flex.alignCenter]}>
|
||||||
|
<Text style={{width: 80}}>l (18px)</Text>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.padding.pt.l,
|
||||||
|
styles.backgroundColor.l3,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.flex.row, styles.flex.alignCenter]}>
|
||||||
|
<Text style={{width: 80}}>xl (24px)</Text>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.padding.pt.xl,
|
||||||
|
styles.backgroundColor.l3,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={[styles.flex.row, styles.flex.alignCenter]}>
|
||||||
|
<Text style={{width: 80}}>xxl (32px)</Text>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.one,
|
||||||
|
styles.padding.pt.xxl,
|
||||||
|
styles.backgroundColor.l3,
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View>
|
||||||
|
<H3 style={[styles.padding.pb.m, styles.font.bold]}>Typography</H3>
|
||||||
|
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.flex.gap.m,
|
||||||
|
styles.padding.pa.m,
|
||||||
|
styles.backgroundColor.l1,
|
||||||
|
]}>
|
||||||
<H1>Heading 1</H1>
|
<H1>Heading 1</H1>
|
||||||
<H2>Heading 2</H2>
|
<H2>Heading 2</H2>
|
||||||
<H3>Heading 3</H3>
|
<H3>Heading 3</H3>
|
||||||
<H4>Heading 4</H4>
|
<H4>Heading 4</H4>
|
||||||
<H5>Heading 5</H5>
|
<H5>Heading 5</H5>
|
||||||
<H6>Heading 6</H6>
|
<H6>Heading 6</H6>
|
||||||
<Text fontSize="xl" fontWeight="bold">
|
|
||||||
H1 Size Text
|
|
||||||
</Text>
|
|
||||||
<Text fontSize="l">H2 Size Text</Text>
|
|
||||||
<Text fontSize="m">H3 Size Text</Text>
|
|
||||||
<Text fontSize="s">H4 Size Text</Text>
|
|
||||||
<Text fontSize="xs">H5 Size Text</Text>
|
|
||||||
<Text fontSize="xxs">H6 Size Text</Text>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box>
|
<Text style={[styles.font.xxl]}>H1 Size Text</Text>
|
||||||
<H3 pb="s" fontWeight="bold">
|
<Text style={[styles.font.xl]}>H2 Size Text</Text>
|
||||||
Grid
|
<Text style={[styles.font.l]}>H3 Size Text</Text>
|
||||||
</H3>
|
<Text style={[styles.font.m]}>H4 Size Text</Text>
|
||||||
<Box pa="m" gap="m" bg="l1">
|
<Text style={[styles.font.s]}>H5 Size Text</Text>
|
||||||
<Box row gap="s">
|
<Text style={[styles.font.xs]}>H6 Size Text</Text>
|
||||||
<Box pa="s" bg="l6" />
|
<Text style={[styles.font.xxs]}>Very Small Size Text</Text>
|
||||||
<Box pa="s" bg="l6" />
|
</View>
|
||||||
<Box pa="s" bg="l6" />
|
</View>
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box row gap="s">
|
|
||||||
<Box column pa="s" bg="l6" />
|
|
||||||
<Box column pa="s" bg="l6" />
|
|
||||||
<Box column pa="s" bg="l6" />
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box row gap="s">
|
|
||||||
<Box column pa="s" bg="l6" debug />
|
|
||||||
<Box column={2} pa="s" bg="l6" debug />
|
|
||||||
<Box column pa="s" bg="l6" />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box>
|
|
||||||
<H3 pb="s" fontWeight="bold">
|
|
||||||
Breakpoint styles
|
|
||||||
</H3>
|
|
||||||
|
|
||||||
<Box
|
|
||||||
row
|
|
||||||
justifyContent="center"
|
|
||||||
px="l"
|
|
||||||
py="l"
|
|
||||||
bg="l2"
|
|
||||||
gtMobile={{
|
|
||||||
py: 'xl',
|
|
||||||
bg: 'l4',
|
|
||||||
}}
|
|
||||||
gtTablet={{
|
|
||||||
py: 64,
|
|
||||||
bg: 'l6',
|
|
||||||
}}>
|
|
||||||
<Box w={64} h={64} bg="primary" />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Hooks />
|
|
||||||
|
|
||||||
<Alf theme="light">
|
<Alf theme="light">
|
||||||
<Box bg="l0" pa="m">
|
<ThemedSection />
|
||||||
<H3 pb="s" fontWeight="bold">
|
|
||||||
Nested theme context.
|
|
||||||
</H3>
|
|
||||||
<Text pb="s">This theme is always light.</Text>
|
|
||||||
<Box row gap="m">
|
|
||||||
<Box column bg="l0" h={60} />
|
|
||||||
<Box column bg="l1" h={60} />
|
|
||||||
<Box column bg="l2" h={60} />
|
|
||||||
<Box column bg="l3" h={60} />
|
|
||||||
<Box column bg="l4" h={60} />
|
|
||||||
<Box column bg="l5" h={60} />
|
|
||||||
<Box column bg="l6" h={60} />
|
|
||||||
<Box column bg="l7" h={60} />
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Alf>
|
</Alf>
|
||||||
|
</View>
|
||||||
<Box>
|
|
||||||
<H3 pb="s" fontWeight="bold">
|
|
||||||
Breakpoint styles
|
|
||||||
</H3>
|
|
||||||
|
|
||||||
<Box gap="m">
|
|
||||||
<Button>Primary</Button>
|
|
||||||
<Button type="secondary">Secondary</Button>
|
|
||||||
<Button type="positive">Positive</Button>
|
|
||||||
<Button type="negative">Negative</Button>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user