From 8ed85116c5f431f60f3432ebb9fcf797130c957d Mon Sep 17 00:00:00 2001 From: Sjaak Schilperoort Date: Tue, 31 Mar 2026 21:23:06 +0200 Subject: [PATCH 1/2] Move font and text style to theme --- package.json | 17 ++- src/@types/font.ts | 3 +- src/@types/theme.ts | 81 ---------- src/components/Accordion.tsx | 2 - src/components/BackButton.tsx | 2 +- src/components/BottomSheet.tsx | 8 +- src/components/BrandIcon.tsx | 2 +- src/components/Checkbox.tsx | 2 +- src/components/Chip.tsx | 9 +- src/components/ContentImage.tsx | 9 +- src/components/Date.tsx | 3 +- src/components/Disclose.tsx | 6 +- src/components/DocumentLink.tsx | 3 +- src/components/IconText.tsx | 3 +- src/components/InputField.tsx | 18 ++- src/components/LargeButton.tsx | 6 +- src/components/Lightbox.tsx | 10 +- src/components/Location.tsx | 3 +- src/components/Message.tsx | 5 +- src/components/MoreInfo.tsx | 3 +- src/components/NotificationPopup.tsx | 8 +- src/components/PageIndicator.tsx | 3 +- src/components/Panel.tsx | 3 +- src/components/Popup.tsx | 3 +- src/components/ProgressBarList.tsx | 3 +- src/components/TextLink.tsx | 15 +- src/components/Tooltip.tsx | 12 +- src/components/WebLink.tsx | 3 +- src/components/__tests__/LargeButton.test.tsx | 6 +- src/index.ts | 8 +- src/lib/LargeButtonStyles.ts | 2 +- src/lib/Utils.ts | 25 ++- src/styles/index.ts | 18 +-- src/styles/input.ts | 23 +-- src/styles/text.ts | 79 ---------- src/styles/theme.ts | 86 ----------- src/theme/ThemeProvider.tsx | 29 +++- src/theme/createInputStyles.ts | 16 ++ src/theme/createTheme.ts | 32 ++++ src/theme/defaultTheme.ts | 3 + src/theme/index.ts | 6 + src/theme/tokens/animation.ts | 7 + src/theme/tokens/color.ts | 30 ++++ src/theme/tokens/font.ts | 91 +++++++++++ src/theme/tokens/fontSize.ts | 10 ++ src/theme/tokens/gradient.ts | 6 + src/theme/tokens/icon.ts | 14 ++ src/theme/tokens/lineHeight.ts | 10 ++ src/theme/tokens/margin.ts | 11 ++ src/theme/tokens/overlay.ts | 11 ++ src/theme/tokens/text.ts | 64 ++++++++ src/theme/types.ts | 142 ++++++++++++++++++ yarn.lock | 37 +---- 53 files changed, 591 insertions(+), 420 deletions(-) delete mode 100644 src/@types/theme.ts delete mode 100644 src/styles/text.ts delete mode 100644 src/styles/theme.ts create mode 100644 src/theme/createInputStyles.ts create mode 100644 src/theme/createTheme.ts create mode 100644 src/theme/defaultTheme.ts create mode 100644 src/theme/index.ts create mode 100644 src/theme/tokens/animation.ts create mode 100644 src/theme/tokens/color.ts create mode 100644 src/theme/tokens/font.ts create mode 100644 src/theme/tokens/fontSize.ts create mode 100644 src/theme/tokens/gradient.ts create mode 100644 src/theme/tokens/icon.ts create mode 100644 src/theme/tokens/lineHeight.ts create mode 100644 src/theme/tokens/margin.ts create mode 100644 src/theme/tokens/overlay.ts create mode 100644 src/theme/tokens/text.ts create mode 100644 src/theme/types.ts diff --git a/package.json b/package.json index 7907606..7e06f56 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,12 @@ { "name": "@observation.org/react-native-components", - "version": "1.73.0", + "version": "1.74.0", "main": "src/index.ts", + "exports": { + ".": "./src/index.ts", + "./theme": "./src/theme/index.ts", + "./styles": "./src/styles/index.ts" + }, "repository": "git@github.com:observation/react-native-components.git", "author": "Observation.org", "license": "MIT", @@ -15,7 +20,6 @@ "@react-navigation/native": "^7.1.26", "@rnx-kit/align-deps": "^3.3.4", "@testing-library/react-native": "^13.3.3", - "@types/color": "^4.2.0", "@types/jest": "^30.0.0", "@types/react": "^19.2.0", "eslint": "^9.39.2", @@ -50,13 +54,14 @@ "src" ], "peerDependencies": { - "@react-navigation/native": "^6.0.8", - "react": "19.2.0", - "react-native": "0.83.1" + "@react-navigation/native": "^7.0.0", + "react": "^19.0.0", + "react-native": ">=0.83.0", + "react-native-svg": ">=15.0.0" }, "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.7.2", - "@fortawesome/free-brands-svg-icons": "6.7.2", + "@fortawesome/free-brands-svg-icons": "^6.7.2", "@fortawesome/pro-light-svg-icons": "^6.7.2", "@fortawesome/pro-solid-svg-icons": "^6.7.2", "@fortawesome/react-native-fontawesome": "^0.3.2", diff --git a/src/@types/font.ts b/src/@types/font.ts index 4773007..afdd498 100644 --- a/src/@types/font.ts +++ b/src/@types/font.ts @@ -11,7 +11,8 @@ import { MixedSizeCSSPropertiesKeys } from 'react-native-render-html' */ export type FontStyle = TextStyle & { overflow?: 'visible' | 'hidden' | undefined } & { [k in MixedSizeCSSPropertiesKeys]?: number | string -} +} & Required> + export type FontName = | 'extraSmall' | 'small' diff --git a/src/@types/theme.ts b/src/@types/theme.ts deleted file mode 100644 index 9ebd6d1..0000000 --- a/src/@types/theme.ts +++ /dev/null @@ -1,81 +0,0 @@ -export interface ThemeAnimation { - duration: { - medium: number - } -} - -export interface ThemeColor { - white: string - black: string - grey800: string - grey500: string - grey300: string - grey50: string - grey100: string - primary500: string - primary300: string - primary50: string - success500: string - success200: string - success700: string - success600: string - success400: string - success50: string - warning500: string - warning700: string - warning200: string - error500: string - error200: string - error700: string - accentLime400: string - accentLime50: string - accentSky400: string - accentSky50: string -} - -export interface ThemeOverlay { - white00: string - white05: string - white10: string - white70: string - white80: string - black50: string - grey60: string -} - -export interface ThemeGradient { - bottom: string[] - top: string[] -} - -export interface ThemeIcon { - size: { - xxs: number - xs: number - s: number - m: number - l: number - xl: number - xxl: number - xxxl: number - } -} - -export interface ThemeMargin { - eighth: number - quarter: number - half: number - common: number - large: number - double: number - huge: number -} - -export interface Theme { - animation: ThemeAnimation - icon: ThemeIcon - color: ThemeColor - overlay: ThemeOverlay - gradient: ThemeGradient - margin: ThemeMargin -} diff --git a/src/components/Accordion.tsx b/src/components/Accordion.tsx index c95ab80..1b67d51 100644 --- a/src/components/Accordion.tsx +++ b/src/components/Accordion.tsx @@ -6,7 +6,6 @@ import { View } from 'react-native' import { Collapse, CollapseBody, CollapseHeader } from 'accordion-collapse-react-native' import Log from '../lib/Log' -import { unsafeLayoutAnimation } from '../lib/Utils' type Item = { title: string @@ -26,7 +25,6 @@ const Accordion = ({ list, header, footer, body, onOpen, ListEmp const onToggle = (index: number, isExpanded: boolean) => { Log.debug('Accordion:onToggle', index, isExpanded) - unsafeLayoutAnimation('Accordion:onToggle') if (isExpanded) { setActiveIndex(index) onOpen(index) diff --git a/src/components/BackButton.tsx b/src/components/BackButton.tsx index 3a8530b..6334a3c 100644 --- a/src/components/BackButton.tsx +++ b/src/components/BackButton.tsx @@ -3,7 +3,7 @@ import React from 'react' import { NavigationProp, ParamListBase } from '@react-navigation/native' import IconButton from '../components/IconButton' -import { useTheme } from '../theme/ThemeProvider' +import { useTheme } from '../theme' type Props = { navigation: NavigationProp diff --git a/src/components/BottomSheet.tsx b/src/components/BottomSheet.tsx index 47d9b04..d44b385 100644 --- a/src/components/BottomSheet.tsx +++ b/src/components/BottomSheet.tsx @@ -4,10 +4,8 @@ import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native' import { useTheme as useNavigationTheme } from '@react-navigation/native' import LargeButton, { LargeButtonProps } from './LargeButton' -import { Theme } from '../@types/theme' import { shadow } from '../styles' -import textStyle from '../styles/text' -import { useStyles, useTheme } from '../theme/ThemeProvider' +import { Theme, useStyles, useTheme } from '../theme/' type Props = { title?: string @@ -31,13 +29,13 @@ const BottomSheet = ({ title, text, buttons = [], style, testID, children }: Pro {title && ( - {title} + {title} )} {text && ( - {text} + {text} )} {children} diff --git a/src/components/BrandIcon.tsx b/src/components/BrandIcon.tsx index ae42359..dd876aa 100644 --- a/src/components/BrandIcon.tsx +++ b/src/components/BrandIcon.tsx @@ -3,7 +3,7 @@ import React from 'react' import { FontAwesomeIcon } from '@fortawesome/react-native-fontawesome' import BrandIcons, { BrandIconName } from '../lib/BrandIcons' -import { useTheme } from '../theme/ThemeProvider' +import { useTheme } from '../theme' type Props = { name: BrandIconName diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx index ac9cd91..c3e8646 100644 --- a/src/components/Checkbox.tsx +++ b/src/components/Checkbox.tsx @@ -3,7 +3,7 @@ import { StyleProp, StyleSheet, TouchableOpacity, View, ViewStyle } from 'react- import { Icon } from './Icon' import Log from '../lib/Log' -import { useTheme } from '../theme/ThemeProvider' +import { useTheme } from '../theme' type Props = { enabled: boolean diff --git a/src/components/Chip.tsx b/src/components/Chip.tsx index 7ad39d2..ad18bb2 100644 --- a/src/components/Chip.tsx +++ b/src/components/Chip.tsx @@ -10,10 +10,7 @@ import { ViewStyle, } from 'react-native' -import { Theme } from '../@types/theme' -import { fontSize } from '../styles' -import appTextStyle from '../styles/text' -import { useStyles, useTheme } from '../theme/ThemeProvider' +import { Theme, useStyles, useTheme } from '../theme' type Props = { text?: string @@ -48,10 +45,10 @@ const createStyles = (theme: Theme) => justifyContent: 'center', }, chipText: { - ...appTextStyle.body, + ...theme.text.body, color: theme.color.white, lineHeight: theme.margin.common, - fontSize: fontSize.medium, + fontSize: theme.fontSize.medium, }, chipContainer: { backgroundColor: theme.color.accentLime400, diff --git a/src/components/ContentImage.tsx b/src/components/ContentImage.tsx index ce9b1c4..67b93e2 100644 --- a/src/components/ContentImage.tsx +++ b/src/components/ContentImage.tsx @@ -4,9 +4,8 @@ import { Dimensions, Image, StyleSheet, Text, TouchableOpacity, View } from 'rea import ScalableImage from 'react-native-scalable-image' import Lightbox from './Lightbox' -import { Theme } from '../@types/theme' -import { font, rounded, shadow } from '../styles' -import { useTheme } from '../theme/ThemeProvider' +import { rounded, shadow } from '../styles' +import { Theme, useTheme } from '../theme' type Props = { src: string @@ -89,12 +88,12 @@ const createStyles = (theme: Theme) => justifyContent: 'center', }, title: { - ...font.smallBold, + ...theme.font.smallBold, color: theme.color.black, marginBottom: theme.margin.quarter, }, description: { - ...font.small, + ...theme.font.small, color: theme.color.grey500, }, }) diff --git a/src/components/Date.tsx b/src/components/Date.tsx index ac85ce6..0f1820f 100644 --- a/src/components/Date.tsx +++ b/src/components/Date.tsx @@ -3,7 +3,6 @@ import { StyleProp, ViewStyle } from 'react-native' import { Icon } from './Icon' import IconText from './IconText' -import textStyle from '../styles/text' import { useTheme } from '../theme/ThemeProvider' type Props = { @@ -19,7 +18,7 @@ const Date = ({ date, containerStyle }: Props) => { text={date} style={{ containerStyle, - textStyle: textStyle.light, + textStyle: theme.text.light, }} singleLineText /> diff --git a/src/components/Disclose.tsx b/src/components/Disclose.tsx index eab5663..15ccebe 100644 --- a/src/components/Disclose.tsx +++ b/src/components/Disclose.tsx @@ -2,10 +2,8 @@ import React from 'react' import { StyleProp, StyleSheet, Text, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native' import { Icon } from './Icon' -import { Theme } from '../@types/theme' import Log from '../lib/Log' -import appTextStyle from '../styles/text' -import { useStyles, useTheme } from '../theme/ThemeProvider' +import { Theme, useStyles, useTheme } from '../theme' type Props = { text: string @@ -22,7 +20,7 @@ const Disclose = ({ text, onPress, textStyle, containerStyle }: Props) => { return ( - {text} + {text} diff --git a/src/components/DocumentLink.tsx b/src/components/DocumentLink.tsx index 3282568..05061b9 100644 --- a/src/components/DocumentLink.tsx +++ b/src/components/DocumentLink.tsx @@ -3,7 +3,6 @@ import { StyleProp, ViewStyle } from 'react-native' import { Icon } from './Icon' import IconText from './IconText' -import textStyle from '../styles/text' import { useTheme } from '../theme/ThemeProvider' type Props = { @@ -21,7 +20,7 @@ const DocumentLink = ({ onPress, containerStyle, label }: Props) => { text={label} style={{ containerStyle, - textStyle: textStyle.link, + textStyle: theme.text.link, }} onPress={onPress} /> diff --git a/src/components/IconText.tsx b/src/components/IconText.tsx index bea9cf6..252c8d7 100644 --- a/src/components/IconText.tsx +++ b/src/components/IconText.tsx @@ -1,8 +1,7 @@ import React from 'react' import { StyleProp, StyleSheet, Text, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native' -import { Theme } from '../@types/theme' -import { useStyles } from '../theme/ThemeProvider' +import { Theme, useStyles } from '../theme' type IconTextStyle = { containerStyle?: StyleProp diff --git a/src/components/InputField.tsx b/src/components/InputField.tsx index d6a23da..069c6c8 100644 --- a/src/components/InputField.tsx +++ b/src/components/InputField.tsx @@ -3,10 +3,8 @@ import { Platform, StyleProp, StyleSheet, Text, TextInput, TextInputProps, View, import { Icon } from './Icon' import IconText from './IconText' -import { Theme } from '../@types/theme' -import { font, inputStyles, layout, rounded } from '../styles' -import textStyle from '../styles/text' -import { useStyles, useTheme } from '../theme/ThemeProvider' +import { font, layout, rounded } from '../styles' +import { Theme, createInputStyles, useStyles, useTheme } from '../theme' type Props = { containerStyle?: StyleProp @@ -102,19 +100,22 @@ const InputField = ({ export default InputField -const createStyles = (theme: Theme) => - StyleSheet.create({ +// eslint-disable-next-line observation/no-function-without-logging +const createStyles = (theme: Theme) => { + const inputStyles = createInputStyles(theme) + + return StyleSheet.create({ containerStyle: { flexDirection: 'column', }, labelStyle: { - ...textStyle.inputLabel, + ...theme.text.inputLabel, marginBottom: theme.margin.half, }, inputStyle: { ...rounded.normal, ...inputStyles.input, - ...textStyle.input, + ...theme.text.input, }, rightIcon: { ...layout.absoluteRight, @@ -129,3 +130,4 @@ const createStyles = (theme: Theme) => color: theme.color.grey800, }, }) +} diff --git a/src/components/LargeButton.tsx b/src/components/LargeButton.tsx index 9eb307b..6a49e3f 100644 --- a/src/components/LargeButton.tsx +++ b/src/components/LargeButton.tsx @@ -2,14 +2,12 @@ import React from 'react' import { StyleProp, StyleSheet, Text, TextStyle, TouchableOpacity, View, ViewStyle } from 'react-native' import { Icon } from './Icon' -import { Theme } from '../@types/theme' import { IconName } from '../lib/Icons' import * as LargeButtonStyles from '../lib/LargeButtonStyles' import { LargeButtonStyle } from '../lib/LargeButtonStyles' import Log from '../lib/Log' import { rounded } from '../styles' -import appTextStyle from '../styles/text' -import { useStyles, useTheme } from '../theme/ThemeProvider' +import { Theme, useStyles, useTheme } from '../theme' type LargeButtonProps = { title: string @@ -100,7 +98,7 @@ const createStyles = (theme: Theme) => }, title: { textAlignVertical: 'center', - ...appTextStyle.lead, + ...theme.text.lead, }, titleContainer: { marginHorizontal: theme.margin.common, diff --git a/src/components/Lightbox.tsx b/src/components/Lightbox.tsx index 4958136..ae1cd28 100644 --- a/src/components/Lightbox.tsx +++ b/src/components/Lightbox.tsx @@ -6,10 +6,8 @@ import Color from 'color' import { Icon } from './Icon' import PageIndicator from './PageIndicator' -import { Theme } from '../@types/theme' -import { font, layout } from '../styles' -import textStyle from '../styles/text' -import { useStyles, useTheme } from '../theme/ThemeProvider' +import { layout } from '../styles' +import { Theme, useStyles, useTheme } from '../theme' const hitSlop = { top: 16, left: 16, bottom: 16, right: 16 } @@ -200,12 +198,12 @@ const createStyles = (theme: Theme) => marginVertical: theme.margin.quarter, }, title: { - ...font.largeBold, + ...theme.font.largeBold, lineHeight: 24, color: 'white', }, description: { - ...textStyle.body, + ...theme.text.body, color: theme.color.white, }, buttonsContainer: { diff --git a/src/components/Location.tsx b/src/components/Location.tsx index 7427f5c..9917514 100644 --- a/src/components/Location.tsx +++ b/src/components/Location.tsx @@ -3,7 +3,6 @@ import { StyleProp, ViewStyle } from 'react-native' import { Icon } from './Icon' import IconText from './IconText' -import textStyle from '../styles/text' import { useTheme } from '../theme/ThemeProvider' type Props = { @@ -19,7 +18,7 @@ const Location = ({ location, containerStyle }: Props) => { text={location} style={{ containerStyle, - textStyle: textStyle.light, + textStyle: theme.text.light, }} singleLineText /> diff --git a/src/components/Message.tsx b/src/components/Message.tsx index 8501e8b..fbe6236 100644 --- a/src/components/Message.tsx +++ b/src/components/Message.tsx @@ -2,7 +2,6 @@ import React from 'react' import { StyleProp, StyleSheet, Text, View, ViewStyle } from 'react-native' import LargeButton, { LargeButtonProps } from '../components/LargeButton' -import textStyle from '../styles/text' import { useTheme } from '../theme/ThemeProvider' type Props = { @@ -18,11 +17,11 @@ const Message = ({ title, text, buttons, style }: Props) => { {title && ( - {title} + {title} )} - {text} + {text} {buttons && buttons.length > 0 && ( { text={label} style={{ containerStyle, - textStyle: textStyle.link, + textStyle: theme.text.link, }} onPress={onPress} /> diff --git a/src/components/NotificationPopup.tsx b/src/components/NotificationPopup.tsx index 7bceb0e..a4c263b 100644 --- a/src/components/NotificationPopup.tsx +++ b/src/components/NotificationPopup.tsx @@ -2,11 +2,9 @@ import React from 'react' import { StyleSheet, Text, TouchableOpacity, View } from 'react-native' import { Icon } from './Icon' -import { Theme } from '../@types/theme' import LargeButton, { LargeButtonProps } from '../components/LargeButton' import Popup from '../components/Popup' -import textStyle from '../styles/text' -import { useStyles, useTheme } from '../theme/ThemeProvider' +import { Theme, useStyles, useTheme } from '../theme' type NotificationPopupStaticProps = { title: string @@ -36,7 +34,7 @@ const NotificationPopup = ({ visible, title, message, leftButton, rightButton, o )} - {message} + {message} @@ -66,7 +64,7 @@ const createStyles = (theme: Theme) => }, title: { flex: 1, - ...textStyle.title, + ...theme.text.title, }, closeButton: { marginLeft: theme.margin.half, diff --git a/src/components/PageIndicator.tsx b/src/components/PageIndicator.tsx index 9cc0b6d..e682bc7 100644 --- a/src/components/PageIndicator.tsx +++ b/src/components/PageIndicator.tsx @@ -1,10 +1,9 @@ import React from 'react' import { StyleSheet, View } from 'react-native' -import { Theme } from '../@types/theme' import Log from '../lib/Log' import { rounded } from '../styles' -import { useStyles } from '../theme/ThemeProvider' +import { Theme, useStyles } from '../theme' /** Maximum number of dots to display, should be odd */ const maximumNumberOfDots = 7 diff --git a/src/components/Panel.tsx b/src/components/Panel.tsx index bbef9c3..e5cb33e 100644 --- a/src/components/Panel.tsx +++ b/src/components/Panel.tsx @@ -1,9 +1,8 @@ import React from 'react' import { StyleProp, StyleSheet, View, ViewStyle } from 'react-native' -import { Theme } from '../@types/theme' import { shadow } from '../styles' -import { useStyles } from '../theme/ThemeProvider' +import { Theme, useStyles } from '../theme' type Props = { children?: React.ReactNode diff --git a/src/components/Popup.tsx b/src/components/Popup.tsx index 9039b97..f666c95 100644 --- a/src/components/Popup.tsx +++ b/src/components/Popup.tsx @@ -3,10 +3,9 @@ import { Modal, StyleSheet, View } from 'react-native' import { BlurView } from '@react-native-community/blur' -import { Theme } from '../@types/theme' import useShowBlurView from '../hooks/useShowBlurView' import { layout } from '../styles' -import { useStyles } from '../theme/ThemeProvider' +import { Theme, useStyles } from '../theme' type Props = { visible: boolean diff --git a/src/components/ProgressBarList.tsx b/src/components/ProgressBarList.tsx index 2430abe..90f8e7b 100644 --- a/src/components/ProgressBarList.tsx +++ b/src/components/ProgressBarList.tsx @@ -2,10 +2,9 @@ import React from 'react' import { StyleSheet, Text, View } from 'react-native' import { Icon } from './Icon' -import { Theme } from '../@types/theme' import ProgressBar from '../data/ProgressBar' import { font } from '../styles' -import { useStyles, useTheme } from '../theme/ThemeProvider' +import { Theme, useStyles, useTheme } from '../theme' type Props = { progressBars: ProgressBar[] diff --git a/src/components/TextLink.tsx b/src/components/TextLink.tsx index 2e4a969..4341dc1 100644 --- a/src/components/TextLink.tsx +++ b/src/components/TextLink.tsx @@ -1,7 +1,7 @@ import React from 'react' import { StyleProp, Text, TextStyle, TouchableOpacity, ViewStyle } from 'react-native' -import appTextStyle from '../styles/text' +import { useTheme } from '../theme' type Props = { text: string @@ -10,10 +10,13 @@ type Props = { textStyle?: StyleProp } -const TextLink = ({ text, onPress, containerStyle, textStyle }: Props) => ( - - {text} - -) +const TextLink = ({ text, onPress, containerStyle, textStyle }: Props) => { + const theme = useTheme() + return ( + + {text} + + ) +} export default TextLink diff --git a/src/components/Tooltip.tsx b/src/components/Tooltip.tsx index 320e66b..59bdac3 100644 --- a/src/components/Tooltip.tsx +++ b/src/components/Tooltip.tsx @@ -2,11 +2,9 @@ import React from 'react' import { StyleProp, StyleSheet, Text, TouchableOpacity, View, ViewStyle } from 'react-native' import { Icon, IconProps } from './Icon' -import { Theme } from '../@types/theme' import LargeButton, { LargeButtonProps } from '../components/LargeButton' -import { lineHeight, shadow } from '../styles' -import textStyle from '../styles/text' -import { useStyles, useTheme } from '../theme/ThemeProvider' +import { shadow } from '../styles' +import { Theme, useStyles, useTheme } from '../theme' type TooltipProps = { title: string @@ -45,7 +43,7 @@ const Tooltip = ({ )} - {title} + {title} {closable && ( @@ -57,7 +55,7 @@ const Tooltip = ({ {children} - {text} + {text} {buttons && buttons.length > 0 && ( margin: theme.margin.common, }, iconContainer: { - height: lineHeight.medium, + height: theme.lineHeight.medium, justifyContent: 'center', }, }) diff --git a/src/components/WebLink.tsx b/src/components/WebLink.tsx index 6d8d945..362feeb 100644 --- a/src/components/WebLink.tsx +++ b/src/components/WebLink.tsx @@ -3,7 +3,6 @@ import { StyleProp, TextStyle, ViewStyle } from 'react-native' import { Icon } from './Icon' import IconText from './IconText' -import textStyles from '../styles/text' import { useTheme } from '../theme/ThemeProvider' type Props = { @@ -21,7 +20,7 @@ const WebLink = ({ onPress, containerStyle, text, textStyle }: Props) => { text={text} style={{ containerStyle, - textStyle: [textStyles.link, textStyle], + textStyle: [theme.text.link, textStyle], }} onPress={onPress} /> diff --git a/src/components/__tests__/LargeButton.test.tsx b/src/components/__tests__/LargeButton.test.tsx index 35e17d1..bd552f6 100644 --- a/src/components/__tests__/LargeButton.test.tsx +++ b/src/components/__tests__/LargeButton.test.tsx @@ -2,12 +2,14 @@ import React from 'react' import { fireEvent, render } from '@testing-library/react-native' -import appTextStyle from '../../styles/text' +import { defaultTheme } from '../../theme/defaultTheme' import LargeButton from '../LargeButton' const onPress = jest.fn() const onPressIn = jest.fn() +const theme = defaultTheme + describe('LargeButton', () => { test('Rendering, enabled, primary', () => { const { toJSON } = render() @@ -58,7 +60,7 @@ describe('LargeButton', () => { , diff --git a/src/index.ts b/src/index.ts index b7a91b3..b6de246 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -import { Theme } from './@types/theme' import Accordion from './components/Accordion' import BackButton from './components/BackButton' import BackgroundImage from './components/BackgroundImage' @@ -73,11 +72,6 @@ export { useShowBlurView, } -export type { IconName, LargeButtonProps, NotificationPopupStaticProps, Theme, TooltipProps } +export type { IconName, LargeButtonProps, NotificationPopupStaticProps, TooltipProps } export * from './components/Icon' export * from './components/BrandIcon' - -export type { FontName, FontStyle } from './@types/font' -export * from './styles' - -export * from './theme/ThemeProvider' diff --git a/src/lib/LargeButtonStyles.ts b/src/lib/LargeButtonStyles.ts index ea19dbb..1c76187 100644 --- a/src/lib/LargeButtonStyles.ts +++ b/src/lib/LargeButtonStyles.ts @@ -1,6 +1,6 @@ import { StyleProp, TextStyle, ViewStyle } from 'react-native' -import { Theme } from '../@types/theme' +import { Theme } from '../theme/types' type LargeButtonStyle = { buttonStyle: StyleProp diff --git a/src/lib/Utils.ts b/src/lib/Utils.ts index b5cc9bc..780c4a3 100644 --- a/src/lib/Utils.ts +++ b/src/lib/Utils.ts @@ -1,17 +1,16 @@ -import { LayoutAnimation, Platform } from 'react-native' - import Log from '../lib/Log' -const safeLayoutAnimation = (tag: string) => { - Log.debug('Utils:safeLayoutAnimation', tag) - if (Platform.OS === 'ios') { - LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) - } -} +export const deepMerge = (target: T, source: Partial): T => { + Log.debug('Utils:deepMerge') + for (const key in source) { + const value = source[key] -const unsafeLayoutAnimation = (tag: string) => { - Log.debug('Utils:unsafeLayoutAnimation', tag) - LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) + if (value && typeof value === 'object' && !Array.isArray(value)) { + target[key] = deepMerge({ ...target[key] }, value) + } else { + // @ts-expect-error assignment is safe + target[key] = value + } + } + return target } - -export { safeLayoutAnimation, unsafeLayoutAnimation } diff --git a/src/styles/index.ts b/src/styles/index.ts index f44efee..9f38e4a 100644 --- a/src/styles/index.ts +++ b/src/styles/index.ts @@ -1,13 +1,7 @@ -import { font, fontSize, lineHeight } from './font' -import { inputStyles } from './input' -import { layout } from './layout' -import { rounded } from './rounded' -import { shadow } from './shadow' -import { default as text } from './text' -import { default as theme } from './theme' +export { font, fontSize, lineHeight } from './font' +export { createInputStyles } from './input' +export { layout } from './layout' +export { rounded } from './rounded' +export { shadow } from './shadow' -export { font, fontSize, inputStyles, lineHeight, layout, text, theme, shadow, rounded } - -// Export styles that are part of the Theme type, but are not used in a specific app -// Until the apps can specify theme overrides, they can import these styles to add them to the theme. -export * from './theme' +export type { FontName, FontStyle } from '../@types/font' diff --git a/src/styles/input.ts b/src/styles/input.ts index d28b515..f86bace 100644 --- a/src/styles/input.ts +++ b/src/styles/input.ts @@ -1,15 +1,16 @@ import { StyleSheet } from 'react-native' import { rounded } from './rounded' -import theme from './theme' +import { Theme } from '../theme' -export const inputStyles = StyleSheet.create({ - input: { - ...rounded.normal, - flex: 1, - minHeight: 40, - borderWidth: 2, - paddingLeft: theme.margin.half, - paddingRight: theme.margin.double, - }, -}) +export const createInputStyles = (theme: Theme) => + StyleSheet.create({ + input: { + ...rounded.normal, + flex: 1, + minHeight: 40, + borderWidth: 2, + paddingLeft: theme.margin.half, + paddingRight: theme.margin.double, + }, + }) diff --git a/src/styles/text.ts b/src/styles/text.ts deleted file mode 100644 index 90b4601..0000000 --- a/src/styles/text.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { TextStyle } from 'react-native' - -import { font } from './font' -import theme from './theme' - -type TextName = - | 'iconLabel' - | 'tabIconLabel' - | 'inputLabel' - | 'scientificName' - | 'body' - | 'light' - | 'lead' - | 'link' - | 'linkBold' - | 'input' - | 'subtitle' - | 'title' - | 'percentage' - | 'thumbnail' - -const text = { - ...font, - iconLabel: font.extraSmall, - tabIconLabel: font.small, - inputLabel: { - ...font.smallBold, - color: theme.color.grey800, - }, - scientificName: { - ...font.small, - fontStyle: 'italic', - color: theme.color.grey500, - }, - body: { - ...font.medium, - color: theme.color.grey800, - }, - light: { - ...font.medium, - color: theme.color.grey500, - }, - lead: { - ...font.mediumBold, - color: theme.color.black, - }, - link: { - ...font.medium, - color: theme.color.primary500, - }, - linkBold: { - ...font.mediumBold, - color: theme.color.primary500, - }, - input: { - ...font.large, - color: theme.color.black, - }, - subtitle: { - ...font.largeBold, - color: theme.color.black, - }, - title: { - ...font.extraLargeBold, - color: theme.color.black, - }, - percentage: { - ...font.smallLight, - color: theme.color.black, - }, - thumbnail: { - ...font.extraSmall, - lineHeight: 16, - fontWeight: 'bold', - color: theme.color.white, - }, -} satisfies Record - -export default text diff --git a/src/styles/theme.ts b/src/styles/theme.ts deleted file mode 100644 index 215b373..0000000 --- a/src/styles/theme.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Theme, ThemeAnimation, ThemeColor, ThemeGradient, ThemeIcon, ThemeMargin, ThemeOverlay } from '../@types/theme' - -const icon = { - size: { - xxs: 8, - xs: 10, - s: 12, - m: 14, - l: 16, - xl: 18, - xxl: 24, - xxxl: 48, - }, -} satisfies ThemeIcon - -const color = { - white: '#FFFFFF', - black: '#212121', - grey800: '#666666', - grey500: '#939393', - grey300: '#E6E6E6', - grey100: '#F0F0F0', - grey50: '#F9FAFB', - primary500: '#0066B1', - primary300: '#67A4D0', - primary50: '#E8F1F8', - success700: '#50701A', - success600: '#689023', - success500: '#85B92D', - success400: '#9BC454', - success200: '#CEE2AB', - success50: '#F7FBEF', - warning700: '#93730B', - warning500: '#F4C015', - warning200: '#FBE6A2', - error700: '#8B332D', - error500: '#EA554B', - error200: '#F7BAB6', - accentLime50: '#F7FBEF', - accentLime400: '#9BC454', - accentSky50: '#F0F5FF', - accentSky400: '#72A1FD', -} satisfies ThemeColor - -const overlay = { - white00: '#ffffff00', - white05: '#FFFFFF0D', - white10: '#FFFFFF1A', - white70: '#FFFFFFB3', - white80: '#FFFFFFCC', - black50: '#00000080', - grey60: '#66666699', -} satisfies ThemeOverlay - -const gradient = { - bottom: ['#30303000', '#30303059'], - top: ['#30303059', '#30303000'], -} satisfies ThemeGradient - -const margin = { - eighth: 2, - quarter: 4, - half: 8, - common: 16, - large: 24, - double: 32, - huge: 48, -} satisfies ThemeMargin - -const animation = { - duration: { - medium: 300, - }, -} satisfies ThemeAnimation - -export const defaultTheme = { - icon, - color, - overlay, - gradient, - animation, - margin, -} satisfies Theme - -// To make existing imports work without having to change the import path -export default defaultTheme diff --git a/src/theme/ThemeProvider.tsx b/src/theme/ThemeProvider.tsx index 1d45819..17646be 100644 --- a/src/theme/ThemeProvider.tsx +++ b/src/theme/ThemeProvider.tsx @@ -1,17 +1,34 @@ /* eslint-disable observation/no-function-without-logging */ import { createContext, useContext, useMemo } from 'react' -import { Theme } from '../@types/theme' -import defaultTheme from '../styles/theme' +import { defaultTheme } from './defaultTheme' +import { Theme } from './types' const ThemeContext = createContext(defaultTheme) -export const ThemeProvider = ({ theme, children }: { theme: Theme; children: React.ReactNode }) => { - return {children} -} +/** + * Provider component to supply the theme to the component tree. + * + * @param theme The theme object to provide to the component tree. + * @param children The child components that will have access to the theme. + * @returns A React element that provides the theme context to its children. + */ +export const ThemeProvider = ({ theme, children }: { theme: Theme; children: React.ReactNode }) => ( + {children} +) -export const useTheme = () => useContext(ThemeContext) +/** + * Hook to access the current theme. + * @returns The current theme object. + */ +export const useTheme = (): Theme => useContext(ThemeContext) +/** + * Hook to create memoized styles based on the current theme. + * + * @param stylesFactory A function that takes the current theme and returns a styles object. + * @returns The memoized styles object. + */ export const useStyles = (stylesFactory: (theme: Theme) => T): T => { const theme = useTheme() return useMemo(() => stylesFactory(theme), [theme]) diff --git a/src/theme/createInputStyles.ts b/src/theme/createInputStyles.ts new file mode 100644 index 0000000..930f1d2 --- /dev/null +++ b/src/theme/createInputStyles.ts @@ -0,0 +1,16 @@ +import { StyleSheet } from 'react-native' + +import { Theme } from './types' +import { rounded } from '../styles/rounded' + +export const createInputStyles = (theme: Theme) => + StyleSheet.create({ + input: { + ...rounded.normal, + flex: 1, + minHeight: 40, + borderWidth: 2, + paddingLeft: theme.margin.half, + paddingRight: theme.margin.double, + }, + }) diff --git a/src/theme/createTheme.ts b/src/theme/createTheme.ts new file mode 100644 index 0000000..889e8ba --- /dev/null +++ b/src/theme/createTheme.ts @@ -0,0 +1,32 @@ +import { deepMerge } from '../lib/Utils' +import { animation } from './tokens/animation' +import { color } from './tokens/color' +import { createFont } from './tokens/font' +import { fontSize } from './tokens/fontSize' +import { gradient } from './tokens/gradient' +import { icon } from './tokens/icon' +import { lineHeight } from './tokens/lineHeight' +import { margin } from './tokens/margin' +import { overlay } from './tokens/overlay' +import { createTextStyles } from './tokens/text' +import { Font, Text, Theme } from './types' + +export const createTheme = (overrides: Partial = {}): Theme => { + const theme: Theme = { + color, + overlay, + gradient, + margin, + icon, + animation, + fontSize, + lineHeight, + font: {} as Font, + text: {} as Text, + } + + theme.font = createFont(theme) + theme.text = createTextStyles(theme) + + return deepMerge(theme, overrides) +} diff --git a/src/theme/defaultTheme.ts b/src/theme/defaultTheme.ts new file mode 100644 index 0000000..bae766a --- /dev/null +++ b/src/theme/defaultTheme.ts @@ -0,0 +1,3 @@ +import { createTheme } from './createTheme' + +export const defaultTheme = createTheme() diff --git a/src/theme/index.ts b/src/theme/index.ts new file mode 100644 index 0000000..f2212e1 --- /dev/null +++ b/src/theme/index.ts @@ -0,0 +1,6 @@ +export { ThemeProvider, useTheme, useStyles } from './ThemeProvider' +export { createTheme } from './createTheme' +export { createInputStyles } from './createInputStyles' +export { defaultTheme } from './defaultTheme' + +export type * from './types' diff --git a/src/theme/tokens/animation.ts b/src/theme/tokens/animation.ts new file mode 100644 index 0000000..d7c1ae9 --- /dev/null +++ b/src/theme/tokens/animation.ts @@ -0,0 +1,7 @@ +import { Animation } from '../types' + +export const animation = { + duration: { + medium: 300, + }, +} satisfies Animation diff --git a/src/theme/tokens/color.ts b/src/theme/tokens/color.ts new file mode 100644 index 0000000..7afe59a --- /dev/null +++ b/src/theme/tokens/color.ts @@ -0,0 +1,30 @@ +import { Color } from '../types' + +export const color = { + white: '#FFFFFF', + black: '#212121', + grey800: '#666666', + grey500: '#939393', + grey300: '#E6E6E6', + grey100: '#F0F0F0', + grey50: '#F9FAFB', + primary500: '#0066B1', + primary300: '#67A4D0', + primary50: '#E8F1F8', + success700: '#50701A', + success600: '#689023', + success500: '#85B92D', + success400: '#9BC454', + success200: '#CEE2AB', + success50: '#F7FBEF', + warning700: '#93730B', + warning500: '#F4C015', + warning200: '#FBE6A2', + error700: '#8B332D', + error500: '#EA554B', + error200: '#F7BAB6', + accentLime50: '#F7FBEF', + accentLime400: '#9BC454', + accentSky50: '#F0F5FF', + accentSky400: '#72A1FD', +} satisfies Color diff --git a/src/theme/tokens/font.ts b/src/theme/tokens/font.ts new file mode 100644 index 0000000..bb84c31 --- /dev/null +++ b/src/theme/tokens/font.ts @@ -0,0 +1,91 @@ +import { Platform } from 'react-native' + +import { Font, Theme } from '../types' + +export const createFont = (theme: Theme): Font => + ({ + extraSmall: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.extraSmall, + lineHeight: theme.lineHeight.extraSmall, + fontWeight: 'normal', + }, + small: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.small, + lineHeight: theme.lineHeight.small, + fontWeight: 'normal', + }, + smallBold: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.small, + lineHeight: theme.lineHeight.small, + fontWeight: 'bold', + }, + smallLight: { + fontFamily: Platform.OS === 'android' ? 'Ubuntu-Light' : 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.small, + lineHeight: theme.lineHeight.small, + fontWeight: '100', + }, + medium: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.medium, + lineHeight: theme.lineHeight.medium, + fontWeight: 'normal', + }, + mediumBold: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.medium, + lineHeight: theme.lineHeight.medium, + fontWeight: 'bold', + }, + large: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.large, + lineHeight: theme.lineHeight.large, + fontWeight: 'normal', + }, + largeBold: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.large, + lineHeight: theme.lineHeight.large, + fontWeight: 'bold', + }, + extraLarge: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.extraLarge, + lineHeight: theme.lineHeight.extraLarge, + fontWeight: 'normal', + }, + extraLargeBold: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.extraLarge, + lineHeight: theme.lineHeight.extraLarge, + fontWeight: 'bold', + }, + huge: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.huge, + lineHeight: theme.lineHeight.huge, + fontWeight: 'normal', + }, + hugeBold: { + fontFamily: 'Ubuntu', + fontStyle: 'normal', + fontSize: theme.fontSize.huge, + lineHeight: theme.lineHeight.huge, + fontWeight: 'bold', + }, + }) satisfies Font diff --git a/src/theme/tokens/fontSize.ts b/src/theme/tokens/fontSize.ts new file mode 100644 index 0000000..5867c55 --- /dev/null +++ b/src/theme/tokens/fontSize.ts @@ -0,0 +1,10 @@ +import { FontSize } from '../types' + +export const fontSize = { + extraSmall: 10, + small: 12, + medium: 14, + large: 16, + extraLarge: 18, + huge: 31, +} satisfies FontSize diff --git a/src/theme/tokens/gradient.ts b/src/theme/tokens/gradient.ts new file mode 100644 index 0000000..22f986e --- /dev/null +++ b/src/theme/tokens/gradient.ts @@ -0,0 +1,6 @@ +import { Gradient } from '../types' + +export const gradient = { + bottom: ['#30303000', '#30303059'], + top: ['#30303059', '#30303000'], +} satisfies Gradient diff --git a/src/theme/tokens/icon.ts b/src/theme/tokens/icon.ts new file mode 100644 index 0000000..c3d4cdb --- /dev/null +++ b/src/theme/tokens/icon.ts @@ -0,0 +1,14 @@ +import { Icon } from '../types' + +export const icon = { + size: { + xxs: 8, + xs: 10, + s: 12, + m: 14, + l: 16, + xl: 18, + xxl: 24, + xxxl: 48, + }, +} satisfies Icon diff --git a/src/theme/tokens/lineHeight.ts b/src/theme/tokens/lineHeight.ts new file mode 100644 index 0000000..917f359 --- /dev/null +++ b/src/theme/tokens/lineHeight.ts @@ -0,0 +1,10 @@ +import { LineHeight } from '../types' + +export const lineHeight = { + extraSmall: 16, + small: 16, + medium: 20, + large: 24, + extraLarge: 28, + huge: 37, +} satisfies LineHeight diff --git a/src/theme/tokens/margin.ts b/src/theme/tokens/margin.ts new file mode 100644 index 0000000..f4e89e7 --- /dev/null +++ b/src/theme/tokens/margin.ts @@ -0,0 +1,11 @@ +import { Margin } from '../types' + +export const margin = { + eighth: 2, + quarter: 4, + half: 8, + common: 16, + large: 24, + double: 32, + huge: 48, +} satisfies Margin diff --git a/src/theme/tokens/overlay.ts b/src/theme/tokens/overlay.ts new file mode 100644 index 0000000..087c5d3 --- /dev/null +++ b/src/theme/tokens/overlay.ts @@ -0,0 +1,11 @@ +import { Overlay } from '../types' + +export const overlay = { + white00: '#ffffff00', + white05: '#FFFFFF0D', + white10: '#FFFFFF1A', + white70: '#FFFFFFB3', + white80: '#FFFFFFCC', + black50: '#00000080', + grey60: '#66666699', +} satisfies Overlay diff --git a/src/theme/tokens/text.ts b/src/theme/tokens/text.ts new file mode 100644 index 0000000..76abee3 --- /dev/null +++ b/src/theme/tokens/text.ts @@ -0,0 +1,64 @@ +import { Text, Theme } from '../types' + +export const createTextStyles = (theme: Theme) => + ({ + iconLabel: { + ...theme.font.extraSmall, + color: theme.color.grey800, + }, + tabIconLabel: { + ...theme.font.small, + color: theme.color.grey800, + }, + inputLabel: { + ...theme.font.smallBold, + color: theme.color.grey800, + }, + scientificName: { + ...theme.font.small, + fontStyle: 'italic', + color: theme.color.grey500, + }, + body: { + ...theme.font.medium, + color: theme.color.grey800, + }, + light: { + ...theme.font.medium, + color: theme.color.grey500, + }, + lead: { + ...theme.font.mediumBold, + color: theme.color.black, + }, + link: { + ...theme.font.medium, + color: theme.color.primary500, + }, + linkBold: { + ...theme.font.mediumBold, + color: theme.color.primary500, + }, + input: { + ...theme.font.large, + color: theme.color.black, + }, + subtitle: { + ...theme.font.largeBold, + color: theme.color.black, + }, + title: { + ...theme.font.extraLargeBold, + color: theme.color.black, + }, + percentage: { + ...theme.font.smallLight, + color: theme.color.black, + }, + thumbnail: { + ...theme.font.extraSmall, + lineHeight: 16, + fontWeight: 'bold', + color: theme.color.white, + }, + }) satisfies Text diff --git a/src/theme/types.ts b/src/theme/types.ts new file mode 100644 index 0000000..f327864 --- /dev/null +++ b/src/theme/types.ts @@ -0,0 +1,142 @@ +import { TextStyle } from 'react-native' + +export interface Theme { + animation: Animation + icon: Icon + color: Color + overlay: Overlay + gradient: Gradient + margin: Margin + fontSize: FontSize + lineHeight: LineHeight + font: Font + text: Text +} + +export interface Animation { + duration: { + medium: number + } +} + +export interface Color { + white: string + black: string + grey800: string + grey500: string + grey300: string + grey50: string + grey100: string + primary500: string + primary300: string + primary50: string + success500: string + success200: string + success700: string + success600: string + success400: string + success50: string + warning500: string + warning700: string + warning200: string + error500: string + error200: string + error700: string + accentLime400: string + accentLime50: string + accentSky400: string + accentSky50: string +} + +export interface Overlay { + white00: string + white05: string + white10: string + white70: string + white80: string + black50: string + grey60: string +} + +export interface Gradient { + bottom: string[] + top: string[] +} + +export interface Icon { + size: { + xxs: number + xs: number + s: number + m: number + l: number + xl: number + xxl: number + xxxl: number + } +} + +export interface Margin { + eighth: number + quarter: number + half: number + common: number + large: number + double: number + huge: number +} + +export type ScaleName = 'extraSmall' | 'small' | 'medium' | 'large' | 'extraLarge' | 'huge' + +export type FontSize = Record +export type LineHeight = Record + +export type FontName = + | 'extraSmall' + | 'small' + | 'smallBold' + | 'smallLight' + | 'medium' + | 'mediumBold' + | 'large' + | 'largeBold' + | 'extraLarge' + | 'extraLargeBold' + | 'huge' + | 'hugeBold' +export type FontFamily = 'Ubuntu' | 'Ubuntu-Light' +export type FontStyle = 'normal' +export type FontWeight = 'normal' | 'bold' | '100' + +export type FontToken = { + fontFamily: FontFamily + fontStyle: FontStyle + fontSize: number + lineHeight: number + fontWeight: FontWeight +} + +export type Font = Record + +export interface TextNameOverrides { + [key: string]: true | undefined +} + +export type TextName = + | 'iconLabel' + | 'tabIconLabel' + | 'inputLabel' + | 'scientificName' + | 'body' + | 'light' + | 'lead' + | 'link' + | 'linkBold' + | 'input' + | 'subtitle' + | 'title' + | 'percentage' + | 'thumbnail' + | keyof TextNameOverrides + +export type Text = Record diff --git a/yarn.lock b/yarn.lock index 9283d5c..9a3f30e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1920,7 +1920,7 @@ __metadata: languageName: node linkType: hard -"@fortawesome/free-brands-svg-icons@npm:6.7.2": +"@fortawesome/free-brands-svg-icons@npm:^6.7.2": version: 6.7.2 resolution: "@fortawesome/free-brands-svg-icons@npm:6.7.2" dependencies: @@ -2862,7 +2862,7 @@ __metadata: "@eslint/eslintrc": ^3.3.3 "@eslint/js": ^9.39.2 "@fortawesome/fontawesome-svg-core": ^6.7.2 - "@fortawesome/free-brands-svg-icons": 6.7.2 + "@fortawesome/free-brands-svg-icons": ^6.7.2 "@fortawesome/pro-light-svg-icons": ^6.7.2 "@fortawesome/pro-solid-svg-icons": ^6.7.2 "@fortawesome/react-native-fontawesome": ^0.3.2 @@ -2874,7 +2874,6 @@ __metadata: "@react-navigation/native": ^7.1.26 "@rnx-kit/align-deps": ^3.3.4 "@testing-library/react-native": ^13.3.3 - "@types/color": ^4.2.0 "@types/jest": ^30.0.0 "@types/react": ^19.2.0 accordion-collapse-react-native: ^1.1.1 @@ -2900,9 +2899,10 @@ __metadata: typescript: 5.8.3 typescript-eslint: ^8.50.0 peerDependencies: - "@react-navigation/native": ^6.0.8 - react: 19.2.0 - react-native: 0.83.1 + "@react-navigation/native": ^7.0.0 + react: ^19.0.0 + react-native: ">=0.83.0" + react-native-svg: ">=15.0.0" languageName: unknown linkType: soft @@ -3373,31 +3373,6 @@ __metadata: languageName: node linkType: hard -"@types/color-convert@npm:*": - version: 2.0.3 - resolution: "@types/color-convert@npm:2.0.3" - dependencies: - "@types/color-name": "*" - checksum: bb6649b49a9da85435c0076c0b00183b972ee2ccbebb7f2f20e58843f081616bba054280fad96fe19e8bb998b3494a3f4c105dea6fef31147f92097f2c08f1ca - languageName: node - linkType: hard - -"@types/color-name@npm:*": - version: 1.1.3 - resolution: "@types/color-name@npm:1.1.3" - checksum: 9060d16d0bce2cdf562d6da54e18c5f23e80308ccb58b725b9173a028818f27d8e01c8a5cd96952e76f11145a7388ed7d2f450fb4652f4760383834f2e698263 - languageName: node - linkType: hard - -"@types/color@npm:^4.2.0": - version: 4.2.0 - resolution: "@types/color@npm:4.2.0" - dependencies: - "@types/color-convert": "*" - checksum: 4f9ddddac07f9c2d79823a5dfaf8689330f3e102c183200c5f772545ac421942ed9e42a2591afe96bbccddf5d5a87ad1bb46671da6056583ebf9c9fdc238b54e - languageName: node - linkType: hard - "@types/eslint@npm:^8.21.1": version: 8.40.2 resolution: "@types/eslint@npm:8.40.2" From 00381ab56dca378a6580e9742056de40338c011d Mon Sep 17 00:00:00 2001 From: Sjaak Schilperoort Date: Mon, 6 Apr 2026 08:39:49 +0200 Subject: [PATCH 2/2] Process review comments --- src/components/Accordion.tsx | 2 ++ src/lib/Utils.ts | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/src/components/Accordion.tsx b/src/components/Accordion.tsx index 1b67d51..c95ab80 100644 --- a/src/components/Accordion.tsx +++ b/src/components/Accordion.tsx @@ -6,6 +6,7 @@ import { View } from 'react-native' import { Collapse, CollapseBody, CollapseHeader } from 'accordion-collapse-react-native' import Log from '../lib/Log' +import { unsafeLayoutAnimation } from '../lib/Utils' type Item = { title: string @@ -25,6 +26,7 @@ const Accordion = ({ list, header, footer, body, onOpen, ListEmp const onToggle = (index: number, isExpanded: boolean) => { Log.debug('Accordion:onToggle', index, isExpanded) + unsafeLayoutAnimation('Accordion:onToggle') if (isExpanded) { setActiveIndex(index) onOpen(index) diff --git a/src/lib/Utils.ts b/src/lib/Utils.ts index 780c4a3..f42b223 100644 --- a/src/lib/Utils.ts +++ b/src/lib/Utils.ts @@ -1,5 +1,12 @@ +import { LayoutAnimation } from 'react-native' + import Log from '../lib/Log' +export const unsafeLayoutAnimation = (tag: string) => { + Log.debug('Utils:unsafeLayoutAnimation', tag) + LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut) +} + export const deepMerge = (target: T, source: Partial): T => { Log.debug('Utils:deepMerge') for (const key in source) {