From 3ec6701b8e14d0336cf28a0c347a4b1e05f3a7f4 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Tue, 12 Sep 2023 11:41:33 -0500 Subject: [PATCH] bop it (cherry picked from commit a5ea4d866241ce9794f1087a8218b71d95cdf7b7) --- package.json | 2 + src/Navigation.tsx | 6 + src/lib/design-system/__tests__/lib.test.ts | 109 ++++++ src/lib/design-system/index.tsx | 190 ++++++++++ src/lib/design-system/lib.ts | 383 ++++++++++++++++++++ src/lib/design-system/themes.ts | 108 ++++++ src/lib/routes/types.ts | 1 + src/routes.ts | 1 + src/view/screens/DesignSystem.tsx | 34 ++ src/view/screens/Settings.tsx | 14 + yarn.lock | 7 + 11 files changed, 855 insertions(+) create mode 100644 src/lib/design-system/__tests__/lib.test.ts create mode 100644 src/lib/design-system/index.tsx create mode 100644 src/lib/design-system/lib.ts create mode 100644 src/lib/design-system/themes.ts create mode 100644 src/view/screens/DesignSystem.tsx diff --git a/package.json b/package.json index 69ba6f6792..5fdc969241 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "lodash.chunk": "^4.2.0", "lodash.debounce": "^4.0.8", "lodash.isequal": "^4.5.0", + "lodash.merge": "^4.6.2", "lodash.omit": "^4.5.0", "lodash.once": "^4.1.1", "lodash.samplesize": "^4.2.0", @@ -163,6 +164,7 @@ "@types/lodash.chunk": "^4.2.7", "@types/lodash.debounce": "^4.0.7", "@types/lodash.isequal": "^4.5.6", + "@types/lodash.merge": "^4.6.7", "@types/lodash.omit": "^4.5.7", "@types/lodash.once": "^4.1.7", "@types/lodash.samplesize": "^4.2.7", diff --git a/src/Navigation.tsx b/src/Navigation.tsx index dac70dfc7e..1eaee80c35 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -68,6 +68,7 @@ import {bskyTitle} from 'lib/strings/headings' import {JSX} from 'react/jsx-runtime' import {timeout} from 'lib/async/timeout' import {PreferencesHomeFeed} from 'view/screens/PreferencesHomeFeed' +import {DesignSystemScreen} from 'view/screens/DesignSystem' const navigationRef = createNavigationContainerRef() @@ -225,6 +226,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { component={PreferencesHomeFeed} options={{title: title('Home Feed Preferences')}} /> + ) } diff --git a/src/lib/design-system/__tests__/lib.test.ts b/src/lib/design-system/__tests__/lib.test.ts new file mode 100644 index 0000000000..48da63189d --- /dev/null +++ b/src/lib/design-system/__tests__/lib.test.ts @@ -0,0 +1,109 @@ +import {describe, it, expect} from '@jest/globals' + +import {create} from '../lib' + +const theme = create({ + tokens: { + space: { + s: 10, + m: 16, + l: 24, + }, + color: { + primary: 'tomato', + }, + fontSize: { + s: 14, + m: 16, + l: 18, + }, + }, + properties: { + c: ['color'], + px: ['paddingLeft', 'paddingRight'], + fs: ['fontSize'], + }, + macros: { + font(value: 'inter' | 'mono') { + return { + fontFamily: value === 'inter' ? 'Inter' : 'Roboto Mono', + } + }, + }, + breakpoints: { + gtPhone: 640, + }, +}) + +describe('style: basic', () => { + it('works with configured properties', () => { + const styles = theme.style({ + color: 'primary', + paddingVertical: 's', + fontSize: 'm', + }) + + expect(styles).toEqual({ + color: theme.config.tokens.color.primary, + paddingTop: theme.config.tokens.space.s, + paddingBottom: theme.config.tokens.space.s, + fontSize: theme.config.tokens.fontSize.m, + }) + }) + + it('works with user-defined properties', () => { + const styles = theme.style({ + c: 'primary', + px: 's', + fontSize: 'm', + }) + + expect(styles).toEqual({ + color: theme.config.tokens.color.primary, + paddingLeft: theme.config.tokens.space.s, + paddingRight: theme.config.tokens.space.s, + fontSize: theme.config.tokens.fontSize.m, + }) + }) +}) + +describe('pick', () => { + it('works', () => { + const {styles, props} = theme.pick({ + c: 'primary', + gtPhone: { + px: 'm', + }, + accessibilityLabel: 'hello', + }) + + expect(styles).toEqual({ + default: { + c: 'primary', + }, + gtPhone: { + px: 'm', + }, + }) + expect(props).toEqual({ + accessibilityLabel: 'hello', + }) + }) +}) + +describe('getActiveBreakpoints', () => { + it('works', () => { + const activeBreakpoints = theme.getActiveBreakpoints({ + width: 1000, + }) + + expect(activeBreakpoints).toEqual({ + active: ['default', 'gtPhone'], + current: 'gtPhone', + }) + }) +}) + +describe('applyBreakpoints', () => { + it('works', () => {}) +}) diff --git a/src/lib/design-system/index.tsx b/src/lib/design-system/index.tsx new file mode 100644 index 0000000000..c8b7d5716d --- /dev/null +++ b/src/lib/design-system/index.tsx @@ -0,0 +1,190 @@ +import React from 'react' +import { + View, + Text as RNText, + Dimensions, + Platform, + ViewProps, + TextProps, + ImageProps, +} from 'react-native' +import merge from 'lodash.merge' + +import {Theme, light} from './themes' + +type ComponentProps = Partial +export type Props = Parameters>[0] & { + /** + * Debug mode will log the styles and props to the console + */ + debug?: boolean +} +type HeadingElements = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' +type TypeProps = Props & { + as?: HeadingElements +} + +const Context = React.createContext({theme: light}) + +export const ThemeProvider = ({ + children, + theme, +}: React.PropsWithChildren<{theme: Theme}>) => ( + {children} +) + +export function useBreakpoints() { + const {theme} = React.useContext(Context) + 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]) + + return breakpoints +} + +export function useStyles(props: Props & T) { + const {theme} = React.useContext(Context) + const breakpoints = useBreakpoints() + const { + styles: responsiveStyles, + props: {debug, ...rest}, + } = React.useMemo( + () => theme.pick>(props), + [props, theme], + ) + const styles = React.useMemo(() => { + return theme.style( + theme.applyBreakpoints(responsiveStyles, breakpoints.active), + ) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [responsiveStyles, breakpoints.current, theme]) + + if (debug) { + console.debug({styles, props: rest, breakpoints}) + } + + return {styles, props: rest} +} + +export const Box = React.forwardRef>( + ({children, style, ...props}, ref) => { + const {styles, props: rest} = useStyles(props) + return ( + + {children} + + ) + }, +) + +export const Text = React.forwardRef>( + ({children, style, ...props}, ref) => { + const {styles, props: rest} = useStyles({ + color: 'text', + ...props, + }) + return ( + + {children} + + ) + }, +) + +const asToAriaLevel = { + h1: 1, + h2: 2, + h3: 3, + h4: 4, + h5: 5, + h6: 6, +} + +const asToTypeStyles: { + [key in HeadingElements]: Props +} = { + h1: { + fontSize: 'l', + lineHeight: 'l', + gtPhone: { + fontSize: 'xl', + lineHeight: 'xl', + }, + }, + h2: { + fontSize: 24, + lineHeight: 32, + }, + h3: { + fontSize: 20, + lineHeight: 28, + }, + h4: { + fontSize: 16, + lineHeight: 24, + }, + h5: { + fontSize: 14, + lineHeight: 20, + }, + h6: { + fontSize: 12, + lineHeight: 16, + }, +} + +export const P = React.forwardRef((props, ref) => { + // @ts-expect-error role is web only + return +}) + +/** + * @see https://necolas.github.io/react-native-web/docs/accessibility/#semantic-html + * @see https://docs.expo.dev/develop/user-interface/fonts/ + */ +function createHeadingComponent(element: HeadingElements) { + return React.forwardRef( + ({children, style, as, ...props}, ref) => { + const asEl = as || element + const extra = Platform.select({ + web: { + 'aria-level': asToAriaLevel[element], + }, + default: {}, + }) + const {styles, props: rest} = useStyles({ + color: 'text', + ...merge(asToTypeStyles[asEl], props), + }) + + return ( + + {children} + + ) + }, + ) +} + +export const H1 = createHeadingComponent('h1') +export const H2 = createHeadingComponent('h2') +export const H3 = createHeadingComponent('h3') +export const H4 = createHeadingComponent('h4') +export const H5 = createHeadingComponent('h5') +export const H6 = createHeadingComponent('h6') diff --git a/src/lib/design-system/lib.ts b/src/lib/design-system/lib.ts new file mode 100644 index 0000000000..be4498c436 --- /dev/null +++ b/src/lib/design-system/lib.ts @@ -0,0 +1,383 @@ +import type { + ViewStyle, + TextStyle, + ImageStyle, + DimensionValue, + ViewProps, + TextProps, + ImageProps, +} 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 +} + +type Styles> = { + [Property in ColorProperties]?: 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[P[Property][0]] +} & { + [Property in keyof C]?: C[Property] extends ( + value: infer Value, + tokens: T, + ) => StyleObject + ? Value + : never +} + +type ResponsiveStyles< + B extends Breakpoints, + S extends Styles, +> = { + [Breakpoint in keyof B]?: S +} + +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 create = < + T extends Tokens, + P extends Properties, + C extends Macros, + B extends Breakpoints, +>({ + tokens, + properties: userProperties = {}, + macros: userMacros = {}, + breakpoints: userBreakpoints = {}, +}: { + tokens: T + properties?: Partial

+ macros?: Partial + breakpoints?: Partial +}) => { + const properties = Object.assign({}, propertyMapping, userProperties) as P + const macros = userMacros as C + const breakpoints = userBreakpoints as B + + const keyofProperties = Object.keys(properties) as (keyof P)[] + const keyofMacros = Object.keys(macros) as (keyof C)[] + const keyofBreakpoints = Object.keys(breakpoints) as (keyof B)[] + const allPropertyKeys = [...keyofProperties, ...keyofMacros] + + type InnerStyles = Styles + type InnerResponsiveStyles = ResponsiveStyles + + function pick>( + props: InnerStyles & InnerResponsiveStyles & Props, + ) { + const res = {styles: {default: {}}, props: {}} as { + styles: InnerResponsiveStyles & {default: InnerStyles} + props: Props + } + + for (const prop of Object.keys(props)) { + const value = props[prop] + if (value === undefined) continue + if (allPropertyKeys.includes(prop)) { + // @ts-ignore no index sig, it's fine + res.styles.default[prop] = value + } else if (keyofBreakpoints.includes(prop)) { + // @ts-ignore no index sig, it's fine + res.styles[prop] = value + } else { + // @ts-ignore no index sig, it's fine + res.props[prop] = value + } + } + + return res + } + + function style(styles: InnerStyles): StyleObject { + const s: StyleObject = {} + + for (const prop of Object.keys(styles)) { + const value = styles[prop] + + if (value === undefined) continue + + if (keyofProperties.includes(prop)) { + 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] + s[_prop] = t?.[value] || value + } + } else if (keyofMacros.includes(prop)) { + const property = macros[prop] + if (property) { + Object.assign(s, property(value, tokens)) + } + } else { + // @ts-ignore no index sig, it's fine + const t = specialTokenMapping[prop] + ? // @ts-ignore no index sig, it's fine + tokens[specialTokenMapping[prop]] + : // @ts-ignore no index sig, it's fine + tokens[prop] + s[prop as StyleObjectProperties] = t?.[value] || value + } + } + + return StyleSheet.create({sheet: s}).sheet + } + + function applyBreakpoints( + styles: InnerResponsiveStyles, + bp: (keyof typeof breakpoints | 'default')[], + ) { + let s = styles.default as InnerStyles + + for (const breakpoint of bp) { + s = {...s, ...styles[breakpoint]} + } + + return s + } + + function getActiveBreakpoints({width}: {width: number}) { + const active: (keyof typeof breakpoints | 'default')[] = ['default'] + + for (const breakpoint in breakpoints) { + if (width >= breakpoints[breakpoint]) { + active.push(breakpoint) + } + } + + return { + active, + current: active[active.length - 1], + } + } + + return { + config: { + tokens, + properties, + macros, + breakpoints, + }, + style, + pick, + applyBreakpoints, + getActiveBreakpoints, + } +} diff --git a/src/lib/design-system/themes.ts b/src/lib/design-system/themes.ts new file mode 100644 index 0000000000..eb73b8474e --- /dev/null +++ b/src/lib/design-system/themes.ts @@ -0,0 +1,108 @@ +import {create} from './lib' + +export type Theme = typeof light + +export const light = create({ + tokens: { + space: { + s: 10, + m: 16, + l: 24, + }, + color: { + theme: 'blue', + text: '#000', + }, + fontSize: { + xxs: 10, + xs: 12, + s: 14, + m: 16, + l: 24, + xl: 32, + xxl: 48, + }, + lineHeight: { + xxs: 10, + xs: 12, + s: 14, + m: 16, + l: 24, + xl: 32, + xxl: 48, + }, + fontFamily: { + inter: 'sans-serif', + roboto: 'monospace', + }, + }, + properties: { + d: ['display'], + w: ['width'], + h: ['height'], + c: ['color'], + bg: ['backgroundColor'], + ma: ['marginTop', 'marginBottom', 'marginLeft', 'marginRight'], + mt: ['marginTop'], + mb: ['marginBottom'], + ml: ['marginLeft'], + mr: ['marginRight'], + my: ['marginTop', 'marginBottom'], + mx: ['marginLeft', 'marginRight'], + pa: ['paddingTop', 'paddingBottom', 'paddingLeft', 'paddingRight'], + pt: ['paddingTop'], + pb: ['paddingBottom'], + pl: ['paddingLeft'], + pr: ['paddingRight'], + py: ['paddingTop', 'paddingBottom'], + px: ['paddingLeft', 'paddingRight'], + z: ['zIndex'], + fs: ['fontSize'], + ff: ['fontFamily'], + fw: ['fontWeight'], + lh: ['lineHeight'], + ta: ['textAlign'], + }, + macros: { + inline: (_: boolean) => ({flexDirection: 'row'}), + aic: (_: boolean) => ({alignItems: 'center'}), + aie: (_: boolean) => ({alignItems: 'flex-end'}), + jcs: (_: boolean) => ({justifyContent: 'flex-start'}), + jcc: (_: boolean) => ({justifyContent: 'center'}), + jce: (_: boolean) => ({justifyContent: 'flex-end'}), + jcb: (_: boolean) => ({justifyContent: 'space-between'}), + rel: (_: boolean) => ({position: 'relative'}), + abs: (_: boolean) => ({position: 'absolute'}), + cover: (_: boolean) => ({ + top: 0, + bottom: 0, + left: 0, + right: 0, + }), + tac: (_: boolean) => ({textAlign: 'center'}), + tar: (_: boolean) => ({textAlign: 'right'}), + mxa: (_: boolean) => ({marginLeft: 'auto', marginRight: 'auto'}), + mya: (_: boolean) => ({marginTop: 'auto', marginBottom: 'auto'}), + caps: (_: boolean) => ({textTransform: 'uppercase'}), + font(value: 'sans' | 'mono', tokens) { + return { + fontFamily: + value === 'sans' ? tokens.fontFamily.inter : tokens.fontFamily.roboto, + } + }, + }, + breakpoints: { + gtPhone: 640, + }, +}) + +export const dark: typeof light = create({ + ...light.config, + tokens: { + ...light.config.tokens, + color: { + theme: 'blue', + text: '#333', + }, + }, +}) diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts index 7159bcb51d..4e38d062b0 100644 --- a/src/lib/routes/types.ts +++ b/src/lib/routes/types.ts @@ -30,6 +30,7 @@ export type CommonNavigatorParams = { AppPasswords: undefined SavedFeeds: undefined PreferencesHomeFeed: undefined + DesignSystem: undefined } export type BottomTabNavigatorParams = CommonNavigatorParams & { diff --git a/src/routes.ts b/src/routes.ts index 45a8fa5724..0d6a27cce4 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -21,6 +21,7 @@ export const router = new Router({ CustomFeed: '/profile/:name/feed/:rkey', CustomFeedLikedBy: '/profile/:name/feed/:rkey/liked-by', Debug: '/sys/debug', + DesignSystem: '/sys/ds', Log: '/sys/log', AppPasswords: '/settings/app-passwords', PreferencesHomeFeed: '/settings/home-feed', diff --git a/src/view/screens/DesignSystem.tsx b/src/view/screens/DesignSystem.tsx new file mode 100644 index 0000000000..ab647d2fb6 --- /dev/null +++ b/src/view/screens/DesignSystem.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import {observer} from 'mobx-react-lite' +import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' +import {withAuthRequired} from 'view/com/auth/withAuthRequired' + +import {ThemeProvider, Box, Text, H1, H2, H3, P} from 'lib/design-system' +import * as themes from 'lib/design-system/themes' + +type Props = NativeStackScreenProps + +export const DesignSystemScreen = withAuthRequired( + observer(function DesignSystem({}: Props) { + return ( + + +

+ Heading 1 +

+

+ Heading 2 +

+

Heading 3

+

Paragraph

+ + + + + Monospace + + + + ) + }), +) diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index 8a543fa4c7..dcbd0e7c0a 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -192,6 +192,10 @@ export const SettingsScreen = withAuthRequired( navigation.navigate('Debug') }, [navigation]) + const onPressDesignSystem = React.useCallback(() => { + navigation.navigate('DesignSystem') + }, [navigation]) + const onPressSavedFeeds = React.useCallback(() => { navigation.navigate('SavedFeeds') }, [navigation]) @@ -564,6 +568,16 @@ export const SettingsScreen = withAuthRequired( Storybook
+ + + Design System + +