@impulse-ui-native/theme 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,71 @@
1
+ import { StyleProps } from "../types";
2
+
3
+ export const StylePropsSet = new Set<keyof StyleProps>([
4
+ // Spacing
5
+ "margin",
6
+ "marginTop",
7
+ "marginBottom",
8
+ "marginLeft",
9
+ "marginRight",
10
+ "marginHorizontal",
11
+ "marginVertical",
12
+ "padding",
13
+ "paddingTop",
14
+ "paddingBottom",
15
+ "paddingLeft",
16
+ "paddingRight",
17
+ "paddingHorizontal",
18
+ "paddingVertical",
19
+
20
+ // Appearance
21
+ "color",
22
+ "backgroundColor",
23
+ "opacity",
24
+ "borderRadius",
25
+ "borderColor",
26
+ "borderWidth",
27
+ "borderStyle",
28
+ "shadowColor",
29
+ "shadowOpacity",
30
+ "shadowRadius",
31
+ "shadowOffset",
32
+ "elevation",
33
+
34
+ // Typography (TextStyle)
35
+ "fontSize",
36
+ "fontFamily",
37
+ "fontWeight",
38
+ "fontStyle",
39
+ "letterSpacing",
40
+ "lineHeight",
41
+ "textAlign",
42
+ "textDecorationLine",
43
+ "textDecorationStyle",
44
+ "textDecorationColor",
45
+ "textTransform",
46
+ "includeFontPadding",
47
+ "textAlignVertical",
48
+ "writingDirection",
49
+
50
+ // Flex (ViewStyle)
51
+ "flex",
52
+ "flexGrow",
53
+ "flexShrink",
54
+ "flexBasis",
55
+ "flexDirection",
56
+ "flexWrap",
57
+ "justifyContent",
58
+ "alignItems",
59
+ "alignSelf",
60
+ "alignContent",
61
+ "gap",
62
+ "rowGap",
63
+ "columnGap",
64
+
65
+ // Positioning (commonly used with flex layouts)
66
+ "position",
67
+ "top",
68
+ "right",
69
+ "bottom",
70
+ "left",
71
+ ]);
@@ -0,0 +1,7 @@
1
+ export { useBorder } from "./use-border.hook";
2
+ export { useColors } from "./use-colors.hook";
3
+ export { useRadii } from "./use-radii.hook";
4
+ export { useSpace } from "./use-space.hook";
5
+ export { useStyleProps } from "./use-style-props.hook";
6
+ export { useTheme } from "./use-theme.hook";
7
+ export { useThemedStyles } from "./use-themed-styles";
@@ -0,0 +1,9 @@
1
+ import { useMemo } from "react";
2
+ import { useThemeContext } from "../providers";
3
+ import { BorderSizes } from "../types";
4
+
5
+ export function useBorder(): BorderSizes {
6
+ const context = useThemeContext();
7
+
8
+ return useMemo(() => context.theme.borderSize, [context.theme.borderSize]);
9
+ }
@@ -0,0 +1,9 @@
1
+ import { useMemo } from "react";
2
+ import { useThemeContext } from "../providers";
3
+ import { ThemeColors } from "../theme/light.theme";
4
+
5
+ export function useColors(): ThemeColors {
6
+ const context = useThemeContext();
7
+
8
+ return useMemo(() => context.theme.colors, [context.theme.colors]);
9
+ }
@@ -0,0 +1,9 @@
1
+ import { useMemo } from "react";
2
+ import { useThemeContext } from "../providers";
3
+ import { Radii } from "../types";
4
+
5
+ export function useRadii(): Radii {
6
+ const context = useThemeContext();
7
+
8
+ return useMemo(() => context.theme.radii, [context.theme.radii]);
9
+ }
@@ -0,0 +1,9 @@
1
+ import { useMemo } from "react";
2
+ import { useThemeContext } from "../providers";
3
+ import { Space } from "../types";
4
+
5
+ export function useSpace(): Space {
6
+ const context = useThemeContext();
7
+
8
+ return useMemo(() => context.theme.spacing, [context.theme.spacing]);
9
+ }
@@ -0,0 +1,8 @@
1
+ import { useMemo } from 'react';
2
+ import { createStyleKey, extractStyleProps } from '../utils';
3
+
4
+ export function useStyleProps<Props extends object>(props: Props) {
5
+ const key = createStyleKey(props);
6
+
7
+ return useMemo(() => extractStyleProps(props), [key]);
8
+ }
@@ -0,0 +1,9 @@
1
+ import { useMemo } from "react";
2
+ import { useThemeContext } from "../providers";
3
+ import { AppTheme } from "../theme";
4
+
5
+ export function useTheme(): AppTheme {
6
+ const context = useThemeContext();
7
+
8
+ return useMemo(() => context.theme, [context.theme]);
9
+ }
@@ -0,0 +1,23 @@
1
+ import { useMemo } from "react";
2
+ import { StyleSheet } from "react-native";
3
+ import { AppTheme } from "../theme";
4
+ import { useTheme } from "./use-theme.hook";
5
+
6
+ type ThemedStyles<T extends StyleSheet.NamedStyles<T>> = T;
7
+
8
+ type StyledFunction<
9
+ Props extends object,
10
+ T extends StyleSheet.NamedStyles<T>
11
+ > = (theme: AppTheme, props: Props) => ThemedStyles<T>;
12
+
13
+ export const useThemedStyles = <
14
+ Props extends object,
15
+ T extends StyleSheet.NamedStyles<T>
16
+ >(
17
+ styledFunction: StyledFunction<Props, T>,
18
+ props: Props
19
+ ): ThemedStyles<T> => {
20
+ const theme = useTheme();
21
+
22
+ return useMemo(() => styledFunction(theme, props), [theme, props]);
23
+ };
package/src/index.ts ADDED
@@ -0,0 +1,6 @@
1
+ export * from './constants';
2
+ export * from './hooks';
3
+ export * from './providers';
4
+ export * from './theme';
5
+ export * from './types';
6
+ export * from './utils';
@@ -0,0 +1 @@
1
+ export * from './theme.provider';
@@ -0,0 +1,94 @@
1
+ import { DeepPartial, merge } from "@impulse-ui-native/core";
2
+ import {
3
+ createContext,
4
+ memo,
5
+ PropsWithChildren,
6
+ useContext,
7
+ useMemo,
8
+ } from "react";
9
+ import { AppTheme, DarkTheme, LightTheme } from "../theme";
10
+ import { ColorScheme, ThemeContextData } from "../types";
11
+
12
+ import {
13
+ Montserrat_100Thin,
14
+ Montserrat_100Thin_Italic,
15
+ Montserrat_200ExtraLight,
16
+ Montserrat_200ExtraLight_Italic,
17
+ Montserrat_300Light,
18
+ Montserrat_300Light_Italic,
19
+ Montserrat_400Regular,
20
+ Montserrat_400Regular_Italic,
21
+ Montserrat_500Medium,
22
+ Montserrat_500Medium_Italic,
23
+ Montserrat_600SemiBold,
24
+ Montserrat_600SemiBold_Italic,
25
+ Montserrat_700Bold,
26
+ Montserrat_700Bold_Italic,
27
+ Montserrat_800ExtraBold,
28
+ Montserrat_800ExtraBold_Italic,
29
+ Montserrat_900Black,
30
+ Montserrat_900Black_Italic,
31
+ useFonts,
32
+ } from "@expo-google-fonts/montserrat";
33
+
34
+ const ThemeContext = createContext<ThemeContextData | undefined>(undefined);
35
+
36
+ interface ThemeProviderProps {
37
+ scheme: ColorScheme;
38
+ theme: {
39
+ light: AppTheme;
40
+ dark: AppTheme;
41
+ };
42
+ }
43
+
44
+ export const ThemeProvider = memo(function ThemeProvider(
45
+ props: PropsWithChildren<DeepPartial<ThemeProviderProps>>
46
+ ) {
47
+ const scheme = props.scheme ?? "light";
48
+
49
+ const [loaded] = useFonts({
50
+ Montserrat_100Thin,
51
+ Montserrat_100Thin_Italic,
52
+ Montserrat_200ExtraLight,
53
+ Montserrat_200ExtraLight_Italic,
54
+ Montserrat_300Light,
55
+ Montserrat_300Light_Italic,
56
+ Montserrat_400Regular,
57
+ Montserrat_400Regular_Italic,
58
+ Montserrat_500Medium,
59
+ Montserrat_500Medium_Italic,
60
+ Montserrat_600SemiBold,
61
+ Montserrat_600SemiBold_Italic,
62
+ Montserrat_700Bold,
63
+ Montserrat_700Bold_Italic,
64
+ Montserrat_800ExtraBold,
65
+ Montserrat_800ExtraBold_Italic,
66
+ Montserrat_900Black,
67
+ Montserrat_900Black_Italic,
68
+ });
69
+
70
+ const context = useMemo<ThemeContextData>(() => {
71
+ const baseTheme = scheme === "light" ? LightTheme : DarkTheme;
72
+
73
+ return {
74
+ loaded,
75
+ theme: merge(baseTheme, props.theme?.[scheme]),
76
+ };
77
+ }, [props.theme?.light, props.theme?.dark]);
78
+
79
+ return (
80
+ <ThemeContext.Provider value={context}>
81
+ {props.children}
82
+ </ThemeContext.Provider>
83
+ );
84
+ });
85
+
86
+ export function useThemeContext() {
87
+ const context = useContext(ThemeContext);
88
+
89
+ if (!context) {
90
+ throw new Error("useThemeContext must be used within a ThemeProvider");
91
+ }
92
+
93
+ return context;
94
+ }
@@ -0,0 +1,36 @@
1
+ import { AppTheme } from './light.theme';
2
+ import {
3
+ BorderSizeTokens,
4
+ FontFamilyTokens,
5
+ FontSizeTokens,
6
+ NeutralColorTokens,
7
+ RadiiTokens,
8
+ SpaceTokens,
9
+ } from './tokens.theme';
10
+
11
+ export const DarkTheme: AppTheme = {
12
+ colors: {
13
+ primary: '#f55d6b',
14
+ secondary: '#f55d6b',
15
+ neutral: NeutralColorTokens,
16
+ white: NeutralColorTokens['0'],
17
+ black: NeutralColorTokens['13'],
18
+ error: '#c92a2a',
19
+ warning: '#e67700',
20
+ success: '#7a9908',
21
+ info: '#1864ab',
22
+ text: {
23
+ primary: NeutralColorTokens['11'],
24
+ secondary: NeutralColorTokens['10'],
25
+ },
26
+ background: {
27
+ primary: NeutralColorTokens['2'],
28
+ secondary: NeutralColorTokens['0'],
29
+ },
30
+ },
31
+ spacing: SpaceTokens,
32
+ radii: RadiiTokens,
33
+ borderSize: BorderSizeTokens,
34
+ fontFamily: FontFamilyTokens,
35
+ fontSize: FontSizeTokens,
36
+ };
@@ -0,0 +1,3 @@
1
+ export { LightTheme } from './light.theme';
2
+ export { DarkTheme } from './dark.theme';
3
+ export type { AppTheme } from './light.theme';
@@ -0,0 +1,84 @@
1
+ import { DeepPartial } from "@impulse-ui-native/core";
2
+ import {
3
+ BorderSizes,
4
+ FontFamilyMap,
5
+ FontSizes,
6
+ NeutralColors,
7
+ Radii,
8
+ Space,
9
+ TypographyPresetKey,
10
+ TypographyProps,
11
+ } from "../types";
12
+ import {
13
+ BorderSizeTokens,
14
+ FontFamilyTokens,
15
+ FontSizeTokens,
16
+ NeutralColorTokens,
17
+ RadiiTokens,
18
+ SpaceTokens,
19
+ } from "./tokens.theme";
20
+
21
+ export const LightTheme: AppTheme = {
22
+ colors: {
23
+ primary: "#f55d6b",
24
+ secondary: "#f55d6b",
25
+ neutral: NeutralColorTokens,
26
+ white: NeutralColorTokens["0"],
27
+ black: NeutralColorTokens["13"],
28
+ error: "#c92a2a",
29
+ warning: "#e67700",
30
+ success: "#7a9908",
31
+ info: "#1864ab",
32
+ text: {
33
+ primary: NeutralColorTokens["11"],
34
+ secondary: NeutralColorTokens["10"],
35
+ },
36
+ background: {
37
+ primary: NeutralColorTokens["2"],
38
+ secondary: NeutralColorTokens["0"],
39
+ },
40
+ },
41
+ spacing: SpaceTokens,
42
+ radii: RadiiTokens,
43
+ borderSize: BorderSizeTokens,
44
+ fontFamily: FontFamilyTokens,
45
+ fontSize: FontSizeTokens,
46
+ };
47
+
48
+ export interface ThemeColors {
49
+ primary: string;
50
+ secondary: string;
51
+ neutral: NeutralColors;
52
+
53
+ white: string;
54
+ black: string;
55
+
56
+ error: string;
57
+ warning: string;
58
+ success: string;
59
+ info: string;
60
+
61
+ text: {
62
+ primary: string;
63
+ secondary: string;
64
+ };
65
+
66
+ background: {
67
+ primary: string;
68
+ secondary: string;
69
+ };
70
+ }
71
+
72
+ export interface ComponentsThemes {
73
+ typography: Record<TypographyPresetKey, TypographyProps>;
74
+ }
75
+
76
+ export interface AppTheme {
77
+ colors: ThemeColors;
78
+ spacing: Space;
79
+ radii: Radii;
80
+ borderSize: BorderSizes;
81
+ fontFamily: FontFamilyMap;
82
+ fontSize: FontSizes;
83
+ components?: DeepPartial<ComponentsThemes>;
84
+ }
@@ -0,0 +1,105 @@
1
+ import {
2
+ BorderSizes,
3
+ FontFamilyMap,
4
+ FontSizes,
5
+ FontWeights,
6
+ NeutralColors,
7
+ Radii,
8
+ Space,
9
+ } from "../types";
10
+
11
+ export const SpaceTokens: Space = {
12
+ none: 0,
13
+ auto: "auto",
14
+ xxs: 4,
15
+ xs: 8,
16
+ mxs: 12,
17
+ sm: 16,
18
+ msm: 24,
19
+ md: 32,
20
+ lg: 48,
21
+ xl: 64,
22
+ xxl: 96,
23
+ };
24
+
25
+ export const BorderSizeTokens: BorderSizes = {
26
+ sm: 1,
27
+ md: 2,
28
+ lg: 3,
29
+ xl: 5,
30
+ xxl: 10,
31
+ };
32
+
33
+ export const RadiiTokens: Radii = {
34
+ sm: 4,
35
+ md: 8,
36
+ lg: 16,
37
+ xl: 32,
38
+ xxl: 64,
39
+ xxxl: 128,
40
+ round: 100000,
41
+ };
42
+
43
+ export const FontSizeTokens: FontSizes = {
44
+ xxs: 8,
45
+ xs: 12,
46
+ xsm: 14,
47
+ sm: 16,
48
+ md: 18,
49
+ lg: 20,
50
+ xl: 24,
51
+ xxl: 32,
52
+ xxxl: 40,
53
+ huge: 48,
54
+ massive: 56,
55
+ };
56
+
57
+ export const NeutralColorTokens: NeutralColors = {
58
+ "0": "#ffffff",
59
+ "1": "#f8f9fa",
60
+ "2": "#f1f3f5",
61
+ "3": "#e9ecef",
62
+ "4": "#dee2e6",
63
+ "5": "#ced4da",
64
+ "6": "#adb5bd",
65
+ "7": "#868e96",
66
+ "8": "#495057",
67
+ "9": "#343a40",
68
+ "10": "#212529",
69
+ "11": "#16191d",
70
+ "12": "#0d0f12",
71
+ "13": "#000000",
72
+ };
73
+
74
+ export const FontWeightTokens: FontWeights = {
75
+ thin: "100",
76
+ extraLight: "200",
77
+ light: "300",
78
+ regular: "400",
79
+ medium: "500",
80
+ semiBold: "600",
81
+ bold: "700",
82
+ extraBold: "800",
83
+ black: "900",
84
+ };
85
+
86
+ export const FontFamilyTokens: FontFamilyMap = {
87
+ thin: "Montserrat-Thin",
88
+ thinItalic: "Montserrat-ThinItalic",
89
+ extraLight: "Montserrat-ExtraLight",
90
+ extraLightItalic: "Montserrat-ExtraLightItalic",
91
+ light: "Montserrat-Light",
92
+ lightItalic: "Montserrat-LightItalic",
93
+ regular: "Montserrat-Regular",
94
+ italic: "Montserrat-Italic",
95
+ medium: "Montserrat-Medium",
96
+ mediumItalic: "Montserrat-MediumItalic",
97
+ semiBold: "Montserrat-SemiBold",
98
+ semiBoldItalic: "Montserrat-SemiBoldItalic",
99
+ bold: "Montserrat-Bold",
100
+ boldItalic: "Montserrat-BoldItalic",
101
+ extraBold: "Montserrat-ExtraBold",
102
+ extraBoldItalic: "Montserrat-ExtraBoldItalic",
103
+ black: "Montserrat-Black",
104
+ blackItalic: "Montserrat-BlackItalic",
105
+ };
@@ -0,0 +1,8 @@
1
+ import { ViewStyle } from 'react-native';
2
+
3
+ export type BorderProps = {
4
+ borderRadius?: ViewStyle['borderRadius'];
5
+ borderColor?: ViewStyle['borderColor'];
6
+ borderWidth?: ViewStyle['borderWidth'];
7
+ borderStyle?: ViewStyle['borderStyle'];
8
+ };
@@ -0,0 +1,7 @@
1
+ import { TextStyle, ViewStyle } from 'react-native';
2
+
3
+ export type ColorProps = {
4
+ color?: TextStyle['color'];
5
+ backgroundColor?: ViewStyle['backgroundColor'];
6
+ opacity?: ViewStyle['opacity'];
7
+ };
@@ -0,0 +1 @@
1
+ export type ComponentSize = "small" | "medium" | "large";
@@ -0,0 +1,27 @@
1
+ import { ViewStyle } from "react-native";
2
+
3
+ export type FlexProps = {
4
+ flex?: ViewStyle["flex"];
5
+ flexGrow?: ViewStyle["flexGrow"];
6
+ flexShrink?: ViewStyle["flexShrink"];
7
+ flexBasis?: ViewStyle["flexBasis"];
8
+
9
+ flexDirection?: ViewStyle["flexDirection"];
10
+ flexWrap?: ViewStyle["flexWrap"];
11
+
12
+ justifyContent?: ViewStyle["justifyContent"];
13
+ alignItems?: ViewStyle["alignItems"];
14
+ alignSelf?: ViewStyle["alignSelf"];
15
+ alignContent?: ViewStyle["alignContent"];
16
+
17
+ gap?: ViewStyle["gap"];
18
+ rowGap?: ViewStyle["rowGap"];
19
+ columnGap?: ViewStyle["columnGap"];
20
+
21
+ // positioning (often used with flex layouts)
22
+ position?: ViewStyle["position"];
23
+ top?: ViewStyle["top"];
24
+ right?: ViewStyle["right"];
25
+ bottom?: ViewStyle["bottom"];
26
+ left?: ViewStyle["left"];
27
+ };
@@ -0,0 +1,10 @@
1
+ export * from "./border.types";
2
+ export * from "./color.types";
3
+ export * from "./components.types";
4
+ export * from "./flex.types";
5
+ export * from "./shadow.types";
6
+ export * from "./spacing.types";
7
+ export * from "./style.types";
8
+ export * from "./theme-provider.types";
9
+ export * from "./tokens.types";
10
+ export * from "./typography.types";
@@ -0,0 +1,9 @@
1
+ import { ViewStyle } from 'react-native';
2
+
3
+ export type ShadowProps = {
4
+ shadowColor?: ViewStyle['shadowColor'];
5
+ shadowOpacity?: ViewStyle['shadowOpacity'];
6
+ shadowRadius?: ViewStyle['shadowRadius'];
7
+ shadowOffset?: ViewStyle['shadowOffset'];
8
+ elevation?: ViewStyle['elevation'];
9
+ };
@@ -0,0 +1,19 @@
1
+ import { ViewStyle } from 'react-native';
2
+
3
+ export type SpacingProps = {
4
+ margin?: ViewStyle['margin'];
5
+ marginTop?: ViewStyle['marginTop'];
6
+ marginBottom?: ViewStyle['marginBottom'];
7
+ marginLeft?: ViewStyle['marginLeft'];
8
+ marginRight?: ViewStyle['marginRight'];
9
+ marginHorizontal?: ViewStyle['marginHorizontal'];
10
+ marginVertical?: ViewStyle['marginVertical'];
11
+
12
+ padding?: ViewStyle['padding'];
13
+ paddingTop?: ViewStyle['paddingTop'];
14
+ paddingBottom?: ViewStyle['paddingBottom'];
15
+ paddingLeft?: ViewStyle['paddingLeft'];
16
+ paddingRight?: ViewStyle['paddingRight'];
17
+ paddingHorizontal?: ViewStyle['paddingHorizontal'];
18
+ paddingVertical?: ViewStyle['paddingVertical'];
19
+ };
@@ -0,0 +1,17 @@
1
+ import { BorderProps } from "./border.types";
2
+ import { ColorProps } from "./color.types";
3
+ import { FlexProps } from "./flex.types";
4
+ import { ShadowProps } from "./shadow.types";
5
+ import { SpacingProps } from "./spacing.types";
6
+ import { TypographyProps } from "./typography.types";
7
+
8
+ export type StyleProps = Partial<
9
+ SpacingProps &
10
+ ColorProps &
11
+ ShadowProps &
12
+ BorderProps &
13
+ TypographyProps &
14
+ FlexProps
15
+ >;
16
+
17
+ export type StylePropKey = keyof StyleProps;
@@ -0,0 +1,8 @@
1
+ import { AppTheme } from "../theme";
2
+
3
+ export interface ThemeContextData {
4
+ loaded: boolean;
5
+ theme: AppTheme;
6
+ }
7
+
8
+ export type ColorScheme = "light" | "dark";