diff --git a/src/alf/README.md b/src/alf/README.md index 235b8feec7..175b7d2b92 100644 --- a/src/alf/README.md +++ b/src/alf/README.md @@ -1,312 +1,35 @@ # 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 -... -``` - -### 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 -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() - -``` - -> An additional feature of `styled` components is a boolean `debug` prop that -> 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', - }, -}) - - -``` - -### 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', - } -}) - - - Hello - -``` - -### 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() - - -``` - -### useBreakpoints - -Returns the current and active breakpoints, which are stored on the theme -context. - -```typescript -const { current, active } = useBreakpoints() - -// => -{ - current: 'gtTablet', - active: [ - 'gtTablet', - 'gtMobile', - ] + return ( + +

I'm the blue color

+
+ ) } ``` -## Themes - -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 `` - * - * @example - * - * - * Hello - * - * - * Hello - * - * - */ - 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: +Is a little nicer than: ```tsx - - {/* 1/4 width */} - +import { View } from 'react-native' +import { useTheme, styles } from '#/alf' +import { H3 } from '#/view/com/Typography' - {/* 1/2 width */} - +function App() { + const theme = useTheme() - {/* 1/4 width */} - - -``` - -## 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 + return ( + +

I'm the blue color

+
+ ) } ``` - -### 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. diff --git a/src/alf/components/Box.tsx b/src/alf/components/Box.tsx deleted file mode 100644 index 8f960aed73..0000000000 --- a/src/alf/components/Box.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import {View} from 'react-native' -import {styled} from '#/alf/system' -export const Box = styled(View) diff --git a/src/alf/index.tsx b/src/alf/index.tsx index fc4e308a9c..9b6d7b995e 100644 --- a/src/alf/index.tsx +++ b/src/alf/index.tsx @@ -1,4 +1,89 @@ -export * from '#/alf/system' -export * from '#/alf/components/Box' -export * from '#/alf/components/Typography' -export * from '#/alf/components/Button' +import React from 'react' +import {Dimensions} from 'react-native' +import * as themes from '#/alf/themes' + +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 ( + ({ + themeName: themeName, + styles: theme, + breakpoints, + }), + [theme, themeName, breakpoints], + )}> + {children} + + ) +} + +export function useAlf() { + return React.useContext(Context) +} + +export function useBreakpoints() { + return React.useContext(Context).breakpoints +} diff --git a/src/alf/lib/__tests__/system.test.tsx b/src/alf/lib/__tests__/system.test.tsx deleted file mode 100644 index e2d80e750a..0000000000 --- a/src/alf/lib/__tests__/system.test.tsx +++ /dev/null @@ -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 {props.children} -} - -test('useTheme', () => { - function Component() { - const theme = useTheme() - return <>{JSON.stringify(theme)} - } - - const dom = TestRenderer.create( - - - , - ) - 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( - - - , - ) - 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( - - - , - ) - 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( - - - , - ) - 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 {JSON.stringify(props)} - } - const Box = styled(Component, { - id: true, - // @ts-expect-error - color: true, - }) - - TestRenderer.create( - - {/* @ts-expect-error */} - - - - , - ) -}) diff --git a/src/alf/lib/__tests__/theme.test.ts b/src/alf/lib/__tests__/theme.test.ts deleted file mode 100644 index 73959c3923..0000000000 --- a/src/alf/lib/__tests__/theme.test.ts +++ /dev/null @@ -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( - { - 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'], - ) - }) -}) diff --git a/src/alf/lib/system.tsx b/src/alf/lib/system.tsx deleted file mode 100644 index ef4abb1e2f..0000000000 --- a/src/alf/lib/system.tsx +++ /dev/null @@ -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, - B extends Breakpoints, -> = { - [key: string]: Theme -} - -export function createSystem< - T extends Tokens, - P extends Properties, - M extends Macros, - B extends Breakpoints, ->(themes: ThemeConfig) { - const defaultTheme = Object.values(themes)[0] - - type SystemTheme = Theme - type ResponsiveStyleProps = ResponsiveStyles - type StylesAndProps = Parameters>[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 - }>({ - 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 ( - ({ - themeName: themeName, - theme, - themes, - breakpoints, - }), - [theme, themeName, breakpoints], - )}> - {children} - - ) - } - - 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>( - styles: O, - ): { - [Name in keyof O]: ReturnType['styles'] - } { - const {theme, breakpoints} = useTheme() - return React.useMemo(() => { - const acc = {} as { - [Name in keyof O]: ReturnType['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>( - Component: React.ComponentType, - 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( - { - ...defaultProps, - ...props, - }, - breakpoints.active, - ), - [breakpoints.active, props, theme], - ) - if (props.debug) console.log({styles, props: rest}) - return - }) - - comp.displayName = Component.displayName || 'NovaComponent' - - return comp - } - - return { - Context, - ThemeProvider, - useTheme, - useTokens, - useBreakpoints, - useStyle, - useStyles, - styled, - } -} diff --git a/src/alf/lib/theme.ts b/src/alf/lib/theme.ts deleted file mode 100644 index 40fd82b824..0000000000 --- a/src/alf/lib/theme.ts +++ /dev/null @@ -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 -} & { - [Property in StyleObjectProperties]?: Record< - string, - Required[Property] - > -} -export type Properties = Record> -export type Macros = 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, -> = { - [Property in ColorProperties]?: - | keyof T['color'] - | Omit -} & { - [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[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, - B extends Breakpoints, -> = - | Styles - | { - [Breakpoint in keyof B]?: Styles - } - -export type Theme< - T extends Tokens, - P extends Properties, - M extends Macros, - B extends Breakpoints, -> = { - config: { - tokens: T - properties: P - macros: M - breakpoints: B - } - style: ( - props: ResponsiveStyles & Omit, - 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, - B extends Breakpoints, ->({ - tokens, - properties: userProperties = {}, - macros: userMacros = {}, - breakpoints: userBreakpoints = {}, -}: { - tokens: T - properties?: Partial

- macros?: Partial - breakpoints?: Partial -}): Theme => { - 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, - } -} diff --git a/src/alf/styles.ts b/src/alf/styles.ts new file mode 100644 index 0000000000..7e5b4044ff --- /dev/null +++ b/src/alf/styles.ts @@ -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) + +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) + +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) + +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) + +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) + +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 diff --git a/src/alf/system.ts b/src/alf/system.ts deleted file mode 100644 index 98e0f499b8..0000000000 --- a/src/alf/system.ts +++ /dev/null @@ -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, -} diff --git a/src/alf/themes.ts b/src/alf/themes.ts index 55f1904c6c..51c2939b65 100644 --- a/src/alf/themes.ts +++ b/src/alf/themes.ts @@ -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 -const GRAYSCALE_SATURATION = 12 +export type ThemeName = 'light' | 'dark' +export type Theme = typeof light -export const palette = { - 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', +export type Palette = { + primary: string + positive: string + negative: string + l0: string + l1: string + l2: string + l3: string + l4: string + l5: string + l6: string + l7: string } -export const light = createTheme({ - tokens: { - space: { - xxs: 2, - xs: 4, - s: 8, - m: 12, - l: 18, - xl: 24, - xxl: 32, - }, - color: { - primary: palette.blue, - 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 `` - * - * @example - * - * - * Hello - * - * - * Hello - * - * - */ - 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 lightPalette: Palette = { + primary: tokens.color.blue, + positive: tokens.color.green, + negative: tokens.color.red, + l0: tokens.color.white, + l1: tokens.color.gray1, + l2: tokens.color.gray2, + l3: tokens.color.gray3, + l4: tokens.color.gray4, + l5: tokens.color.gray5, + l6: tokens.color.gray6, + l7: tokens.color.black, +} as const -export const dark = createTheme({ - ...light.config, - tokens: { - ...light.config.tokens, - color: { - ...light.config.tokens.color, - l0: palette.black, - l1: palette.gray6, - l2: palette.gray5, - l3: palette.gray4, - l4: palette.gray3, - l5: palette.gray2, - l6: palette.gray1, - l7: palette.white, - }, - }, -}) +export const darkPalette: Palette = { + primary: tokens.color.blue, + positive: tokens.color.green, + negative: tokens.color.red, + l0: tokens.color.black, + l1: tokens.color.gray6, + l2: tokens.color.gray5, + l3: tokens.color.gray4, + l4: tokens.color.gray3, + l5: tokens.color.gray2, + l6: tokens.color.gray1, + l7: tokens.color.white, +} as const + +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), + backgroundColor: Object.keys(lightPalette).reduce((acc, key) => { + const k = key as keyof Palette + acc[k] = { + backgroundColor: lightPalette[k], + } + return acc + }, {} as Record), +} 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), + backgroundColor: Object.keys(darkPalette).reduce((acc, key) => { + const k = key as keyof Palette + acc[k] = { + backgroundColor: darkPalette[k], + } + return acc + }, {} as Record), +} as const diff --git a/src/alf/tokens.ts b/src/alf/tokens.ts new file mode 100644 index 0000000000..fff50a551a --- /dev/null +++ b/src/alf/tokens.ts @@ -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 diff --git a/src/view/com/Button.tsx b/src/view/com/Button.tsx new file mode 100644 index 0000000000..fb632d5647 --- /dev/null +++ b/src/view/com/Button.tsx @@ -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) { + const {styles} = useAlf() + return ( + + {typeof children === 'string' ? ( + + {children} + + ) : ( + children + )} + + ) +} diff --git a/src/view/com/Typography.tsx b/src/view/com/Typography.tsx new file mode 100644 index 0000000000..c2a8139a8a --- /dev/null +++ b/src/view/com/Typography.tsx @@ -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 +} + +export function H1({style, ...rest}: TextProps) { + const {styles} = useAlf() + const attr = + web({ + role: 'heading', + 'aria-level': 1, + }) || {} + return ( + + ) +} + +export function H2({style, ...rest}: TextProps) { + const {styles} = useAlf() + const attr = + web({ + role: 'heading', + 'aria-level': 2, + }) || {} + return ( + + ) +} + +export function H3({style, ...rest}: TextProps) { + const {styles} = useAlf() + const attr = + web({ + role: 'heading', + 'aria-level': 3, + }) || {} + return ( + + ) +} + +export function H4({style, ...rest}: TextProps) { + const {styles} = useAlf() + const attr = + web({ + role: 'heading', + 'aria-level': 4, + }) || {} + return ( + + ) +} + +export function H5({style, ...rest}: TextProps) { + const {styles} = useAlf() + const attr = + web({ + role: 'heading', + 'aria-level': 5, + }) || {} + return ( + + ) +} + +export function H6({style, ...rest}: TextProps) { + const {styles} = useAlf() + const attr = + web({ + role: 'heading', + 'aria-level': 6, + }) || {} + return ( + + ) +} diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx index a8b0bd21a8..e402ef3ba7 100644 --- a/src/view/screens/DebugNew.tsx +++ b/src/view/screens/DebugNew.tsx @@ -1,251 +1,266 @@ 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 {useSetColorMode} from '#/state/shell' -import { - ThemeProvider as Alf, - useBreakpoints, - useStyle, - useStyles, - Box, - Text, - Button, - H1, - H2, - H3, - H4, - H5, - H6, -} from '#/alf' +import {useAlf, ThemeProvider as Alf} from '#/alf' +import {Button} from '#/view/com/Button' +import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography' function ThemeSelector() { const setColorMode = useSetColorMode() + const {styles} = useAlf() return ( - - - - - + + + + + ) } function BreakpointDebugger() { - const breakpoints = useBreakpoints() + const {styles, breakpoints} = useAlf() return ( - -

- Breakpoint Debugger -

- + +

Breakpoint Debugger

+ {JSON.stringify(breakpoints, null, 2)} -
+
) } -function Hooks() { - const outer = useStyle({ - pa: 'm', - bg: 'l1', - }) - const {heading} = useStyles({ - heading: { - color: 'l7', - fontSize: 'l', - fontWeight: 'bold', - gtTablet: { - fontSize: 'xl', - }, - }, - }) +function ThemedSection() { + const {styles} = useAlf() return ( - - Hooks + +

+ Colors (this theme is always light) +

+ + + + + + + + + + ) } export function DebugScreen() { - const backgroundStyles = useStyle({ - bg: 'l0', - }) + const {styles} = useAlf() return ( - - + + - -

- Colors -

- - - - - - - - - - - - - -

- Spacing -

- - - - xxs (2px) - - - - xs (4px) - - - - s (8px) - - - - m (12px) - - - - l (18px) - - - - xl (24px) - - - - xxl (32px) - - - - - - -

- Typography -

+ +

Colors

+ + + + + + + + + + + - + +

Spacing

+ + + + xxs (2px) + + + + xs (4px) + + + + s (8px) + + + + m (12px) + + + + l (18px) + + + + xl (24px) + + + + xxl (32px) + + + + + + +

Typography

+ +

Heading 1

Heading 2

Heading 3

Heading 4

Heading 5
Heading 6
- - H1 Size Text - - H2 Size Text - H3 Size Text - H4 Size Text - H5 Size Text - H6 Size Text -
-
- -

- Grid -

- - - - - - - - - - - - - - - - - - - - - - -

- Breakpoint styles -

- - - - - - - + H1 Size Text + H2 Size Text + H3 Size Text + H4 Size Text + H5 Size Text + H6 Size Text + Very Small Size Text +
+
- -

- Nested theme context. -

- This theme is always light. - - - - - - - - - - - +
- - -

- Breakpoint styles -

- - - - - - - -
-
+ )