@spelyco/react-lib 1.2.0-e4f7457e → 1.3.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.
package/README.md CHANGED
@@ -85,18 +85,15 @@ function Search() {
85
85
 
86
86
  ---
87
87
 
88
- ## Utils
88
+ ## Theme types
89
89
 
90
- ### `theme`
91
-
92
- A pre-configured Mantine theme object with Spelyco defaults. Used internally by `SpelycoProvider`.
90
+ Framework-agnostic theme contract consumed by both `@spelyco/react` and `@spelyco/react-native`.
93
91
 
94
92
  ```ts
95
- import { theme } from "@spelyco/react-lib";
96
- // MantineTheme with defaultRadius: "md" and button defaults
93
+ import type { SpelycoTheme, SpelycoThemeOverride, ColorTuple } from "@spelyco/react-lib";
97
94
  ```
98
95
 
99
- You probably don't need this directlyit's applied automatically by `SpelycoProvider`.
96
+ The runtime helpers that produce a theme live in the platform packages `theme()` from `@spelyco/react` for web (Mantine), and `SpelycoProvider`'s defaults for React Native.
100
97
 
101
98
  ---
102
99
 
package/dist/index.d.mts CHANGED
@@ -1,5 +1,125 @@
1
- import * as _mantine_core from '@mantine/core';
2
- import { MantineColor } from '@mantine/core';
1
+ import * as react from 'react';
2
+ import { ReactElement } from 'react';
3
+
4
+ /**
5
+ * 10-shade color tuple. Index 0 = lightest, 9 = darkest.
6
+ * Matches Mantine's color palette structure so palettes are portable across platforms.
7
+ */
8
+ type ColorTuple = readonly [
9
+ string,
10
+ string,
11
+ string,
12
+ string,
13
+ string,
14
+ string,
15
+ string,
16
+ string,
17
+ string,
18
+ string
19
+ ];
20
+ type SpelycoColorScheme = "light" | "dark" | "auto";
21
+ type SpelycoComputedColorScheme = "light" | "dark";
22
+ type SpelycoSize = "xs" | "sm" | "md" | "lg" | "xl";
23
+ type SpelycoRadiusKey = SpelycoSize | "pill";
24
+ type SpelycoColors = Record<string, ColorTuple>;
25
+ type SpelycoSpacing = Record<SpelycoSize, number>;
26
+ type SpelycoRadius = Record<SpelycoRadiusKey, number>;
27
+ type SpelycoFontSizes = Record<SpelycoSize, number>;
28
+ type SpelycoLineHeights = Record<SpelycoSize, number>;
29
+ type SpelycoShadows = Record<SpelycoSize, string>;
30
+ type SpelycoBreakpoints = Record<SpelycoSize, number>;
31
+ /**
32
+ * Which shade index of a color tuple becomes the "primary" color.
33
+ * Use a single number for both schemes, or an object to differ between light/dark.
34
+ */
35
+ type SpelycoPrimaryShade = number | {
36
+ light: number;
37
+ dark: number;
38
+ };
39
+ /**
40
+ * Status/navigation bar config used by the RN provider via expo-system-ui and
41
+ * react-native-edge-to-edge. Web ignores this field.
42
+ */
43
+ interface SpelycoSystemBars {
44
+ statusBarStyle: "light" | "dark" | "auto";
45
+ statusBarBackgroundColor?: string;
46
+ navigationBarColor?: string;
47
+ navigationBarStyle?: "light" | "dark";
48
+ }
49
+ /**
50
+ * Per-component theme override. Keyed by component name (e.g. "Button").
51
+ * Generic over the component's props so `defaultProps` is typed.
52
+ */
53
+ interface SpelycoComponentOverride<TProps = unknown> {
54
+ defaultProps?: Partial<TProps>;
55
+ }
56
+ type SpelycoComponents = Record<string, SpelycoComponentOverride>;
57
+ /**
58
+ * Fully resolved theme. Produced by `resolveTheme(override)` and consumed by
59
+ * components and the provider. All fields are required at this stage.
60
+ */
61
+ interface SpelycoTheme {
62
+ colors: SpelycoColors;
63
+ primaryColor: string;
64
+ primaryShade: SpelycoPrimaryShade;
65
+ spacing: SpelycoSpacing;
66
+ radius: SpelycoRadius;
67
+ fontSizes: SpelycoFontSizes;
68
+ lineHeights: SpelycoLineHeights;
69
+ shadows: SpelycoShadows;
70
+ breakpoints: SpelycoBreakpoints;
71
+ fontFamily: string;
72
+ fontFamilyMono: string;
73
+ defaultRadius: SpelycoRadiusKey | number;
74
+ components: SpelycoComponents;
75
+ /** Only consumed by RN provider. Web ignores. */
76
+ systemBars?: {
77
+ light: SpelycoSystemBars;
78
+ dark: SpelycoSystemBars;
79
+ };
80
+ /** Free-form custom tokens. Consumers augment via module declaration. */
81
+ other: Record<string, unknown>;
82
+ }
83
+ /**
84
+ * What you pass to `<SpelycoProvider theme={...}>`. Every field is optional and
85
+ * gets deep-merged with the defaults.
86
+ */
87
+ type SpelycoThemeOverride = Partial<SpelycoTheme>;
88
+
89
+ interface CreateComponentConfig<TProps extends object> {
90
+ /** Used as the key for `theme.components[name]` overrides. */
91
+ name: string;
92
+ defaultProps?: Partial<TProps>;
93
+ render: (props: TProps) => ReactElement | null;
94
+ }
95
+ interface SpelycoComponent<TProps extends object> {
96
+ (props: TProps): ReactElement | null;
97
+ displayName: string;
98
+ /**
99
+ * Helper for building a `theme.components[name]` override object — purely
100
+ * sugar, returns its argument with the right shape.
101
+ */
102
+ extend: (override: SpelycoComponentOverride<TProps>) => SpelycoComponentOverride<TProps>;
103
+ }
104
+ /**
105
+ * Wraps a render function into a Spelyco component that:
106
+ * - reads `theme.components[name].defaultProps` for runtime overrides
107
+ * - merges factory defaults → theme defaults → user props (later wins)
108
+ * - exposes `.extend()` so consumers can produce override objects
109
+ * - sets `displayName` to the provided `name`
110
+ */
111
+ declare function createComponent<TProps extends object>(config: CreateComponentConfig<TProps>): SpelycoComponent<TProps>;
112
+
113
+ /**
114
+ * Merges three sources of props in precedence order (later wins):
115
+ * 1. factory defaults — declared inside `createComponent({ defaultProps })`
116
+ * 2. theme defaults — declared via `theme.components[name].defaultProps`
117
+ * 3. user props — passed at the call site
118
+ *
119
+ * `undefined` values in user props are skipped so partially-controlled inputs
120
+ * fall back to defaults instead of forcing `undefined`.
121
+ */
122
+ declare function useProps<TProps extends object>(componentName: string, factoryDefaults: Partial<TProps> | undefined, props: TProps): TProps;
3
123
 
4
124
  interface UseBooleanReturn {
5
125
  value: boolean;
@@ -12,152 +132,58 @@ declare function useBoolean(initialValue?: boolean): UseBooleanReturn;
12
132
 
13
133
  declare function useDebounce<T>(value: T, delay?: number): T;
14
134
 
15
- declare const theme: (primaryColor?: MantineColor) => {
16
- focusRing?: "auto" | "always" | "never" | undefined;
17
- scale?: number | undefined;
18
- fontSmoothing?: boolean | undefined;
19
- white?: string | undefined;
20
- black?: string | undefined;
21
- colors?: {
22
- [x: string & {}]: _mantine_core.MantineColorsTuple | undefined;
23
- dark?: _mantine_core.MantineColorsTuple | undefined;
24
- gray?: _mantine_core.MantineColorsTuple | undefined;
25
- red?: _mantine_core.MantineColorsTuple | undefined;
26
- pink?: _mantine_core.MantineColorsTuple | undefined;
27
- grape?: _mantine_core.MantineColorsTuple | undefined;
28
- violet?: _mantine_core.MantineColorsTuple | undefined;
29
- indigo?: _mantine_core.MantineColorsTuple | undefined;
30
- blue?: _mantine_core.MantineColorsTuple | undefined;
31
- cyan?: _mantine_core.MantineColorsTuple | undefined;
32
- green?: _mantine_core.MantineColorsTuple | undefined;
33
- lime?: _mantine_core.MantineColorsTuple | undefined;
34
- yellow?: _mantine_core.MantineColorsTuple | undefined;
35
- orange?: _mantine_core.MantineColorsTuple | undefined;
36
- teal?: _mantine_core.MantineColorsTuple | undefined;
37
- } | undefined;
38
- primaryShade?: _mantine_core.MantineColorShade | {
39
- light?: _mantine_core.MantineColorShade | undefined;
40
- dark?: _mantine_core.MantineColorShade | undefined;
41
- } | undefined;
42
- primaryColor?: string | undefined;
43
- variantColorResolver?: _mantine_core.VariantColorsResolver | undefined;
44
- autoContrast?: boolean | undefined;
45
- luminanceThreshold?: number | undefined;
46
- fontFamily?: string | undefined;
47
- fontFamilyMonospace?: string | undefined;
48
- headings?: {
49
- fontFamily?: string | undefined;
50
- fontWeight?: string | undefined;
51
- textWrap?: "wrap" | "nowrap" | "balance" | "pretty" | "stable" | undefined;
52
- sizes?: {
53
- h1?: {
54
- fontSize?: string | undefined;
55
- fontWeight?: string | undefined;
56
- lineHeight?: string | undefined;
57
- } | undefined;
58
- h2?: {
59
- fontSize?: string | undefined;
60
- fontWeight?: string | undefined;
61
- lineHeight?: string | undefined;
62
- } | undefined;
63
- h3?: {
64
- fontSize?: string | undefined;
65
- fontWeight?: string | undefined;
66
- lineHeight?: string | undefined;
67
- } | undefined;
68
- h4?: {
69
- fontSize?: string | undefined;
70
- fontWeight?: string | undefined;
71
- lineHeight?: string | undefined;
72
- } | undefined;
73
- h5?: {
74
- fontSize?: string | undefined;
75
- fontWeight?: string | undefined;
76
- lineHeight?: string | undefined;
77
- } | undefined;
78
- h6?: {
79
- fontSize?: string | undefined;
80
- fontWeight?: string | undefined;
81
- lineHeight?: string | undefined;
82
- } | undefined;
83
- } | undefined;
84
- } | undefined;
85
- radius?: {
86
- [x: string & {}]: string | undefined;
87
- md?: string | undefined;
88
- xs?: string | undefined;
89
- sm?: string | undefined;
90
- lg?: string | undefined;
91
- xl?: string | undefined;
92
- } | undefined;
93
- defaultRadius?: _mantine_core.MantineRadius | undefined;
94
- spacing?: {
95
- [x: number]: string | undefined;
96
- [x: string & {}]: string | undefined;
97
- md?: string | undefined;
98
- xs?: string | undefined;
99
- sm?: string | undefined;
100
- lg?: string | undefined;
101
- xl?: string | undefined;
102
- } | undefined;
103
- fontSizes?: {
104
- [x: string & {}]: string | undefined;
105
- md?: string | undefined;
106
- xs?: string | undefined;
107
- sm?: string | undefined;
108
- lg?: string | undefined;
109
- xl?: string | undefined;
110
- } | undefined;
111
- lineHeights?: {
112
- [x: string & {}]: string | undefined;
113
- md?: string | undefined;
114
- xs?: string | undefined;
115
- sm?: string | undefined;
116
- lg?: string | undefined;
117
- xl?: string | undefined;
118
- } | undefined;
119
- fontWeights?: {
120
- [x: string & {}]: string | undefined;
121
- bold?: string | undefined;
122
- regular?: string | undefined;
123
- medium?: string | undefined;
124
- } | undefined;
125
- breakpoints?: {
126
- [x: string & {}]: string | undefined;
127
- md?: string | undefined;
128
- xs?: string | undefined;
129
- sm?: string | undefined;
130
- lg?: string | undefined;
131
- xl?: string | undefined;
132
- } | undefined;
133
- shadows?: {
134
- [x: string & {}]: string | undefined;
135
- md?: string | undefined;
136
- xs?: string | undefined;
137
- sm?: string | undefined;
138
- lg?: string | undefined;
139
- xl?: string | undefined;
140
- } | undefined;
141
- respectReducedMotion?: boolean | undefined;
142
- cursorType?: "default" | "pointer" | undefined;
143
- defaultGradient?: {
144
- from?: string | undefined;
145
- to?: string | undefined;
146
- deg?: number | undefined;
147
- } | undefined;
148
- activeClassName?: string | undefined;
149
- focusClassName?: string | undefined;
150
- components?: {
151
- [x: string]: {
152
- classNames?: any;
153
- styles?: any;
154
- vars?: any;
155
- defaultProps?: any;
156
- } | undefined;
157
- } | undefined;
158
- other?: {
159
- [x: string]: any;
160
- } | undefined;
161
- };
135
+ /**
136
+ * Wraps an array of 10 color strings into a typed `ColorTuple`. Throws if the
137
+ * array isn't exactly 10 items, which catches palette typos at boot time.
138
+ *
139
+ * For algorithmic shade generation from a single base color, prefer importing
140
+ * shades from a curated palette (e.g. Tailwind) — this lib intentionally ships
141
+ * without an HSL-based generator to keep the surface area small.
142
+ */
143
+ declare function createColorTuple(shades: readonly string[]): ColorTuple;
144
+
145
+ /**
146
+ * Default palettes. Index 0 is the lightest, 9 is the darkest. Sourced from
147
+ * Tailwind's 50–900 ramps (the 950 step is dropped to keep the tuple length
148
+ * at 10).
149
+ */
150
+ declare const DEFAULT_BRAND: ColorTuple;
151
+ declare const DEFAULT_NEUTRAL: ColorTuple;
152
+ declare const DEFAULT_RED: ColorTuple;
153
+ declare const DEFAULT_GREEN: ColorTuple;
154
+ declare const DEFAULT_COLORS: SpelycoColors;
155
+ declare const DEFAULT_SPACING: SpelycoSpacing;
156
+ declare const DEFAULT_RADIUS: SpelycoRadius;
157
+ declare const DEFAULT_FONT_SIZES: SpelycoFontSizes;
158
+ declare const DEFAULT_LINE_HEIGHTS: SpelycoLineHeights;
159
+ /**
160
+ * CSS box-shadow strings. RN consumers can map these to elevation locally.
161
+ */
162
+ declare const DEFAULT_SHADOWS: SpelycoShadows;
163
+ /**
164
+ * `xs: 0` is intentional — Unistyles treats it as the always-active breakpoint.
165
+ */
166
+ declare const DEFAULT_BREAKPOINTS: SpelycoBreakpoints;
167
+ declare const DEFAULT_SYSTEM_BARS_LIGHT: SpelycoSystemBars;
168
+ declare const DEFAULT_SYSTEM_BARS_DARK: SpelycoSystemBars;
169
+ /**
170
+ * Fully-resolved default theme. Override fields with `<SpelycoProvider theme>`.
171
+ */
172
+ declare const DEFAULT_THEME: SpelycoTheme;
173
+
174
+ /**
175
+ * Merges a partial theme override with the default theme, returning a fully
176
+ * resolved `SpelycoTheme`. Color tuples and arrays are replaced wholesale;
177
+ * plain objects (spacing, components, etc.) are merged key-by-key.
178
+ */
179
+ declare function resolveTheme(override?: SpelycoThemeOverride): SpelycoTheme;
180
+
181
+ /**
182
+ * Holds the resolved theme. Platform-specific providers (`SpelycoProvider` in
183
+ * the RN and web packages) write to this context after deep-merging user
184
+ * overrides with `DEFAULT_THEME`.
185
+ */
186
+ declare const SpelycoThemeContext: react.Context<SpelycoTheme>;
187
+ declare function useSpelycoTheme(): SpelycoTheme;
162
188
 
163
- export { theme, useBoolean, useDebounce };
189
+ export { type ColorTuple, type CreateComponentConfig, DEFAULT_BRAND, DEFAULT_BREAKPOINTS, DEFAULT_COLORS, DEFAULT_FONT_SIZES, DEFAULT_GREEN, DEFAULT_LINE_HEIGHTS, DEFAULT_NEUTRAL, DEFAULT_RADIUS, DEFAULT_RED, DEFAULT_SHADOWS, DEFAULT_SPACING, DEFAULT_SYSTEM_BARS_DARK, DEFAULT_SYSTEM_BARS_LIGHT, DEFAULT_THEME, type SpelycoBreakpoints, type SpelycoColorScheme, type SpelycoColors, type SpelycoComponent, type SpelycoComponentOverride, type SpelycoComponents, type SpelycoComputedColorScheme, type SpelycoFontSizes, type SpelycoLineHeights, type SpelycoPrimaryShade, type SpelycoRadius, type SpelycoRadiusKey, type SpelycoShadows, type SpelycoSize, type SpelycoSpacing, type SpelycoSystemBars, type SpelycoTheme, SpelycoThemeContext, type SpelycoThemeOverride, createColorTuple, createComponent, resolveTheme, useBoolean, useDebounce, useProps, useSpelycoTheme };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,125 @@
1
- import * as _mantine_core from '@mantine/core';
2
- import { MantineColor } from '@mantine/core';
1
+ import * as react from 'react';
2
+ import { ReactElement } from 'react';
3
+
4
+ /**
5
+ * 10-shade color tuple. Index 0 = lightest, 9 = darkest.
6
+ * Matches Mantine's color palette structure so palettes are portable across platforms.
7
+ */
8
+ type ColorTuple = readonly [
9
+ string,
10
+ string,
11
+ string,
12
+ string,
13
+ string,
14
+ string,
15
+ string,
16
+ string,
17
+ string,
18
+ string
19
+ ];
20
+ type SpelycoColorScheme = "light" | "dark" | "auto";
21
+ type SpelycoComputedColorScheme = "light" | "dark";
22
+ type SpelycoSize = "xs" | "sm" | "md" | "lg" | "xl";
23
+ type SpelycoRadiusKey = SpelycoSize | "pill";
24
+ type SpelycoColors = Record<string, ColorTuple>;
25
+ type SpelycoSpacing = Record<SpelycoSize, number>;
26
+ type SpelycoRadius = Record<SpelycoRadiusKey, number>;
27
+ type SpelycoFontSizes = Record<SpelycoSize, number>;
28
+ type SpelycoLineHeights = Record<SpelycoSize, number>;
29
+ type SpelycoShadows = Record<SpelycoSize, string>;
30
+ type SpelycoBreakpoints = Record<SpelycoSize, number>;
31
+ /**
32
+ * Which shade index of a color tuple becomes the "primary" color.
33
+ * Use a single number for both schemes, or an object to differ between light/dark.
34
+ */
35
+ type SpelycoPrimaryShade = number | {
36
+ light: number;
37
+ dark: number;
38
+ };
39
+ /**
40
+ * Status/navigation bar config used by the RN provider via expo-system-ui and
41
+ * react-native-edge-to-edge. Web ignores this field.
42
+ */
43
+ interface SpelycoSystemBars {
44
+ statusBarStyle: "light" | "dark" | "auto";
45
+ statusBarBackgroundColor?: string;
46
+ navigationBarColor?: string;
47
+ navigationBarStyle?: "light" | "dark";
48
+ }
49
+ /**
50
+ * Per-component theme override. Keyed by component name (e.g. "Button").
51
+ * Generic over the component's props so `defaultProps` is typed.
52
+ */
53
+ interface SpelycoComponentOverride<TProps = unknown> {
54
+ defaultProps?: Partial<TProps>;
55
+ }
56
+ type SpelycoComponents = Record<string, SpelycoComponentOverride>;
57
+ /**
58
+ * Fully resolved theme. Produced by `resolveTheme(override)` and consumed by
59
+ * components and the provider. All fields are required at this stage.
60
+ */
61
+ interface SpelycoTheme {
62
+ colors: SpelycoColors;
63
+ primaryColor: string;
64
+ primaryShade: SpelycoPrimaryShade;
65
+ spacing: SpelycoSpacing;
66
+ radius: SpelycoRadius;
67
+ fontSizes: SpelycoFontSizes;
68
+ lineHeights: SpelycoLineHeights;
69
+ shadows: SpelycoShadows;
70
+ breakpoints: SpelycoBreakpoints;
71
+ fontFamily: string;
72
+ fontFamilyMono: string;
73
+ defaultRadius: SpelycoRadiusKey | number;
74
+ components: SpelycoComponents;
75
+ /** Only consumed by RN provider. Web ignores. */
76
+ systemBars?: {
77
+ light: SpelycoSystemBars;
78
+ dark: SpelycoSystemBars;
79
+ };
80
+ /** Free-form custom tokens. Consumers augment via module declaration. */
81
+ other: Record<string, unknown>;
82
+ }
83
+ /**
84
+ * What you pass to `<SpelycoProvider theme={...}>`. Every field is optional and
85
+ * gets deep-merged with the defaults.
86
+ */
87
+ type SpelycoThemeOverride = Partial<SpelycoTheme>;
88
+
89
+ interface CreateComponentConfig<TProps extends object> {
90
+ /** Used as the key for `theme.components[name]` overrides. */
91
+ name: string;
92
+ defaultProps?: Partial<TProps>;
93
+ render: (props: TProps) => ReactElement | null;
94
+ }
95
+ interface SpelycoComponent<TProps extends object> {
96
+ (props: TProps): ReactElement | null;
97
+ displayName: string;
98
+ /**
99
+ * Helper for building a `theme.components[name]` override object — purely
100
+ * sugar, returns its argument with the right shape.
101
+ */
102
+ extend: (override: SpelycoComponentOverride<TProps>) => SpelycoComponentOverride<TProps>;
103
+ }
104
+ /**
105
+ * Wraps a render function into a Spelyco component that:
106
+ * - reads `theme.components[name].defaultProps` for runtime overrides
107
+ * - merges factory defaults → theme defaults → user props (later wins)
108
+ * - exposes `.extend()` so consumers can produce override objects
109
+ * - sets `displayName` to the provided `name`
110
+ */
111
+ declare function createComponent<TProps extends object>(config: CreateComponentConfig<TProps>): SpelycoComponent<TProps>;
112
+
113
+ /**
114
+ * Merges three sources of props in precedence order (later wins):
115
+ * 1. factory defaults — declared inside `createComponent({ defaultProps })`
116
+ * 2. theme defaults — declared via `theme.components[name].defaultProps`
117
+ * 3. user props — passed at the call site
118
+ *
119
+ * `undefined` values in user props are skipped so partially-controlled inputs
120
+ * fall back to defaults instead of forcing `undefined`.
121
+ */
122
+ declare function useProps<TProps extends object>(componentName: string, factoryDefaults: Partial<TProps> | undefined, props: TProps): TProps;
3
123
 
4
124
  interface UseBooleanReturn {
5
125
  value: boolean;
@@ -12,152 +132,58 @@ declare function useBoolean(initialValue?: boolean): UseBooleanReturn;
12
132
 
13
133
  declare function useDebounce<T>(value: T, delay?: number): T;
14
134
 
15
- declare const theme: (primaryColor?: MantineColor) => {
16
- focusRing?: "auto" | "always" | "never" | undefined;
17
- scale?: number | undefined;
18
- fontSmoothing?: boolean | undefined;
19
- white?: string | undefined;
20
- black?: string | undefined;
21
- colors?: {
22
- [x: string & {}]: _mantine_core.MantineColorsTuple | undefined;
23
- dark?: _mantine_core.MantineColorsTuple | undefined;
24
- gray?: _mantine_core.MantineColorsTuple | undefined;
25
- red?: _mantine_core.MantineColorsTuple | undefined;
26
- pink?: _mantine_core.MantineColorsTuple | undefined;
27
- grape?: _mantine_core.MantineColorsTuple | undefined;
28
- violet?: _mantine_core.MantineColorsTuple | undefined;
29
- indigo?: _mantine_core.MantineColorsTuple | undefined;
30
- blue?: _mantine_core.MantineColorsTuple | undefined;
31
- cyan?: _mantine_core.MantineColorsTuple | undefined;
32
- green?: _mantine_core.MantineColorsTuple | undefined;
33
- lime?: _mantine_core.MantineColorsTuple | undefined;
34
- yellow?: _mantine_core.MantineColorsTuple | undefined;
35
- orange?: _mantine_core.MantineColorsTuple | undefined;
36
- teal?: _mantine_core.MantineColorsTuple | undefined;
37
- } | undefined;
38
- primaryShade?: _mantine_core.MantineColorShade | {
39
- light?: _mantine_core.MantineColorShade | undefined;
40
- dark?: _mantine_core.MantineColorShade | undefined;
41
- } | undefined;
42
- primaryColor?: string | undefined;
43
- variantColorResolver?: _mantine_core.VariantColorsResolver | undefined;
44
- autoContrast?: boolean | undefined;
45
- luminanceThreshold?: number | undefined;
46
- fontFamily?: string | undefined;
47
- fontFamilyMonospace?: string | undefined;
48
- headings?: {
49
- fontFamily?: string | undefined;
50
- fontWeight?: string | undefined;
51
- textWrap?: "wrap" | "nowrap" | "balance" | "pretty" | "stable" | undefined;
52
- sizes?: {
53
- h1?: {
54
- fontSize?: string | undefined;
55
- fontWeight?: string | undefined;
56
- lineHeight?: string | undefined;
57
- } | undefined;
58
- h2?: {
59
- fontSize?: string | undefined;
60
- fontWeight?: string | undefined;
61
- lineHeight?: string | undefined;
62
- } | undefined;
63
- h3?: {
64
- fontSize?: string | undefined;
65
- fontWeight?: string | undefined;
66
- lineHeight?: string | undefined;
67
- } | undefined;
68
- h4?: {
69
- fontSize?: string | undefined;
70
- fontWeight?: string | undefined;
71
- lineHeight?: string | undefined;
72
- } | undefined;
73
- h5?: {
74
- fontSize?: string | undefined;
75
- fontWeight?: string | undefined;
76
- lineHeight?: string | undefined;
77
- } | undefined;
78
- h6?: {
79
- fontSize?: string | undefined;
80
- fontWeight?: string | undefined;
81
- lineHeight?: string | undefined;
82
- } | undefined;
83
- } | undefined;
84
- } | undefined;
85
- radius?: {
86
- [x: string & {}]: string | undefined;
87
- md?: string | undefined;
88
- xs?: string | undefined;
89
- sm?: string | undefined;
90
- lg?: string | undefined;
91
- xl?: string | undefined;
92
- } | undefined;
93
- defaultRadius?: _mantine_core.MantineRadius | undefined;
94
- spacing?: {
95
- [x: number]: string | undefined;
96
- [x: string & {}]: string | undefined;
97
- md?: string | undefined;
98
- xs?: string | undefined;
99
- sm?: string | undefined;
100
- lg?: string | undefined;
101
- xl?: string | undefined;
102
- } | undefined;
103
- fontSizes?: {
104
- [x: string & {}]: string | undefined;
105
- md?: string | undefined;
106
- xs?: string | undefined;
107
- sm?: string | undefined;
108
- lg?: string | undefined;
109
- xl?: string | undefined;
110
- } | undefined;
111
- lineHeights?: {
112
- [x: string & {}]: string | undefined;
113
- md?: string | undefined;
114
- xs?: string | undefined;
115
- sm?: string | undefined;
116
- lg?: string | undefined;
117
- xl?: string | undefined;
118
- } | undefined;
119
- fontWeights?: {
120
- [x: string & {}]: string | undefined;
121
- bold?: string | undefined;
122
- regular?: string | undefined;
123
- medium?: string | undefined;
124
- } | undefined;
125
- breakpoints?: {
126
- [x: string & {}]: string | undefined;
127
- md?: string | undefined;
128
- xs?: string | undefined;
129
- sm?: string | undefined;
130
- lg?: string | undefined;
131
- xl?: string | undefined;
132
- } | undefined;
133
- shadows?: {
134
- [x: string & {}]: string | undefined;
135
- md?: string | undefined;
136
- xs?: string | undefined;
137
- sm?: string | undefined;
138
- lg?: string | undefined;
139
- xl?: string | undefined;
140
- } | undefined;
141
- respectReducedMotion?: boolean | undefined;
142
- cursorType?: "default" | "pointer" | undefined;
143
- defaultGradient?: {
144
- from?: string | undefined;
145
- to?: string | undefined;
146
- deg?: number | undefined;
147
- } | undefined;
148
- activeClassName?: string | undefined;
149
- focusClassName?: string | undefined;
150
- components?: {
151
- [x: string]: {
152
- classNames?: any;
153
- styles?: any;
154
- vars?: any;
155
- defaultProps?: any;
156
- } | undefined;
157
- } | undefined;
158
- other?: {
159
- [x: string]: any;
160
- } | undefined;
161
- };
135
+ /**
136
+ * Wraps an array of 10 color strings into a typed `ColorTuple`. Throws if the
137
+ * array isn't exactly 10 items, which catches palette typos at boot time.
138
+ *
139
+ * For algorithmic shade generation from a single base color, prefer importing
140
+ * shades from a curated palette (e.g. Tailwind) — this lib intentionally ships
141
+ * without an HSL-based generator to keep the surface area small.
142
+ */
143
+ declare function createColorTuple(shades: readonly string[]): ColorTuple;
144
+
145
+ /**
146
+ * Default palettes. Index 0 is the lightest, 9 is the darkest. Sourced from
147
+ * Tailwind's 50–900 ramps (the 950 step is dropped to keep the tuple length
148
+ * at 10).
149
+ */
150
+ declare const DEFAULT_BRAND: ColorTuple;
151
+ declare const DEFAULT_NEUTRAL: ColorTuple;
152
+ declare const DEFAULT_RED: ColorTuple;
153
+ declare const DEFAULT_GREEN: ColorTuple;
154
+ declare const DEFAULT_COLORS: SpelycoColors;
155
+ declare const DEFAULT_SPACING: SpelycoSpacing;
156
+ declare const DEFAULT_RADIUS: SpelycoRadius;
157
+ declare const DEFAULT_FONT_SIZES: SpelycoFontSizes;
158
+ declare const DEFAULT_LINE_HEIGHTS: SpelycoLineHeights;
159
+ /**
160
+ * CSS box-shadow strings. RN consumers can map these to elevation locally.
161
+ */
162
+ declare const DEFAULT_SHADOWS: SpelycoShadows;
163
+ /**
164
+ * `xs: 0` is intentional — Unistyles treats it as the always-active breakpoint.
165
+ */
166
+ declare const DEFAULT_BREAKPOINTS: SpelycoBreakpoints;
167
+ declare const DEFAULT_SYSTEM_BARS_LIGHT: SpelycoSystemBars;
168
+ declare const DEFAULT_SYSTEM_BARS_DARK: SpelycoSystemBars;
169
+ /**
170
+ * Fully-resolved default theme. Override fields with `<SpelycoProvider theme>`.
171
+ */
172
+ declare const DEFAULT_THEME: SpelycoTheme;
173
+
174
+ /**
175
+ * Merges a partial theme override with the default theme, returning a fully
176
+ * resolved `SpelycoTheme`. Color tuples and arrays are replaced wholesale;
177
+ * plain objects (spacing, components, etc.) are merged key-by-key.
178
+ */
179
+ declare function resolveTheme(override?: SpelycoThemeOverride): SpelycoTheme;
180
+
181
+ /**
182
+ * Holds the resolved theme. Platform-specific providers (`SpelycoProvider` in
183
+ * the RN and web packages) write to this context after deep-merging user
184
+ * overrides with `DEFAULT_THEME`.
185
+ */
186
+ declare const SpelycoThemeContext: react.Context<SpelycoTheme>;
187
+ declare function useSpelycoTheme(): SpelycoTheme;
162
188
 
163
- export { theme, useBoolean, useDebounce };
189
+ export { type ColorTuple, type CreateComponentConfig, DEFAULT_BRAND, DEFAULT_BREAKPOINTS, DEFAULT_COLORS, DEFAULT_FONT_SIZES, DEFAULT_GREEN, DEFAULT_LINE_HEIGHTS, DEFAULT_NEUTRAL, DEFAULT_RADIUS, DEFAULT_RED, DEFAULT_SHADOWS, DEFAULT_SPACING, DEFAULT_SYSTEM_BARS_DARK, DEFAULT_SYSTEM_BARS_LIGHT, DEFAULT_THEME, type SpelycoBreakpoints, type SpelycoColorScheme, type SpelycoColors, type SpelycoComponent, type SpelycoComponentOverride, type SpelycoComponents, type SpelycoComputedColorScheme, type SpelycoFontSizes, type SpelycoLineHeights, type SpelycoPrimaryShade, type SpelycoRadius, type SpelycoRadiusKey, type SpelycoShadows, type SpelycoSize, type SpelycoSpacing, type SpelycoSystemBars, type SpelycoTheme, SpelycoThemeContext, type SpelycoThemeOverride, createColorTuple, createComponent, resolveTheme, useBoolean, useDebounce, useProps, useSpelycoTheme };
package/dist/index.js CHANGED
@@ -1,9 +1,183 @@
1
1
  'use strict';
2
2
 
3
3
  var react = require('react');
4
- var core = require('@mantine/core');
5
4
 
6
- // src/hooks/useBoolean.ts
5
+ // src/theme/ThemeContext.ts
6
+
7
+ // src/theme/createColorTuple.ts
8
+ function createColorTuple(shades) {
9
+ if (shades.length !== 10) {
10
+ throw new Error(`createColorTuple expects exactly 10 shades, got ${shades.length}`);
11
+ }
12
+ return shades;
13
+ }
14
+
15
+ // src/theme/defaults.ts
16
+ var DEFAULT_BRAND = createColorTuple([
17
+ "#eef2ff",
18
+ "#e0e7ff",
19
+ "#c7d2fe",
20
+ "#a5b4fc",
21
+ "#818cf8",
22
+ "#6366f1",
23
+ "#4f46e5",
24
+ "#4338ca",
25
+ "#3730a3",
26
+ "#312e81"
27
+ ]);
28
+ var DEFAULT_NEUTRAL = createColorTuple([
29
+ "#fafafa",
30
+ "#f4f4f5",
31
+ "#e4e4e7",
32
+ "#d4d4d8",
33
+ "#a1a1aa",
34
+ "#71717a",
35
+ "#52525b",
36
+ "#3f3f46",
37
+ "#27272a",
38
+ "#18181b"
39
+ ]);
40
+ var DEFAULT_RED = createColorTuple([
41
+ "#fef2f2",
42
+ "#fee2e2",
43
+ "#fecaca",
44
+ "#fca5a5",
45
+ "#f87171",
46
+ "#ef4444",
47
+ "#dc2626",
48
+ "#b91c1c",
49
+ "#991b1b",
50
+ "#7f1d1d"
51
+ ]);
52
+ var DEFAULT_GREEN = createColorTuple([
53
+ "#ecfdf5",
54
+ "#d1fae5",
55
+ "#a7f3d0",
56
+ "#6ee7b7",
57
+ "#34d399",
58
+ "#10b981",
59
+ "#059669",
60
+ "#047857",
61
+ "#065f46",
62
+ "#064e3b"
63
+ ]);
64
+ var DEFAULT_COLORS = {
65
+ brand: DEFAULT_BRAND,
66
+ neutral: DEFAULT_NEUTRAL,
67
+ red: DEFAULT_RED,
68
+ green: DEFAULT_GREEN
69
+ };
70
+ var DEFAULT_SPACING = {
71
+ xs: 4,
72
+ sm: 8,
73
+ md: 12,
74
+ lg: 16,
75
+ xl: 24
76
+ };
77
+ var DEFAULT_RADIUS = {
78
+ xs: 2,
79
+ sm: 4,
80
+ md: 8,
81
+ lg: 12,
82
+ xl: 16,
83
+ pill: 9999
84
+ };
85
+ var DEFAULT_FONT_SIZES = {
86
+ xs: 12,
87
+ sm: 14,
88
+ md: 16,
89
+ lg: 18,
90
+ xl: 20
91
+ };
92
+ var DEFAULT_LINE_HEIGHTS = {
93
+ xs: 1.4,
94
+ sm: 1.45,
95
+ md: 1.55,
96
+ lg: 1.6,
97
+ xl: 1.65
98
+ };
99
+ var DEFAULT_SHADOWS = {
100
+ xs: "0 1px 2px rgba(0, 0, 0, 0.05)",
101
+ sm: "0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04)",
102
+ md: "0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.04)",
103
+ lg: "0 10px 15px rgba(0, 0, 0, 0.08), 0 4px 6px rgba(0, 0, 0, 0.04)",
104
+ xl: "0 20px 25px rgba(0, 0, 0, 0.08), 0 10px 10px rgba(0, 0, 0, 0.03)"
105
+ };
106
+ var DEFAULT_BREAKPOINTS = {
107
+ xs: 0,
108
+ sm: 360,
109
+ md: 768,
110
+ lg: 1024,
111
+ xl: 1280
112
+ };
113
+ var DEFAULT_SYSTEM_BARS_LIGHT = {
114
+ statusBarStyle: "dark",
115
+ statusBarBackgroundColor: "#ffffff",
116
+ navigationBarColor: "#ffffff",
117
+ navigationBarStyle: "dark"
118
+ };
119
+ var DEFAULT_SYSTEM_BARS_DARK = {
120
+ statusBarStyle: "light",
121
+ statusBarBackgroundColor: "#0b0b0f",
122
+ navigationBarColor: "#0b0b0f",
123
+ navigationBarStyle: "light"
124
+ };
125
+ var DEFAULT_THEME = {
126
+ colors: DEFAULT_COLORS,
127
+ primaryColor: "brand",
128
+ primaryShade: { light: 6, dark: 4 },
129
+ spacing: DEFAULT_SPACING,
130
+ radius: DEFAULT_RADIUS,
131
+ fontSizes: DEFAULT_FONT_SIZES,
132
+ lineHeights: DEFAULT_LINE_HEIGHTS,
133
+ shadows: DEFAULT_SHADOWS,
134
+ breakpoints: DEFAULT_BREAKPOINTS,
135
+ fontFamily: "System",
136
+ fontFamilyMono: "Menlo, monospace",
137
+ defaultRadius: "md",
138
+ components: {},
139
+ systemBars: {
140
+ light: DEFAULT_SYSTEM_BARS_LIGHT,
141
+ dark: DEFAULT_SYSTEM_BARS_DARK
142
+ },
143
+ other: {}
144
+ };
145
+
146
+ // src/theme/ThemeContext.ts
147
+ var SpelycoThemeContext = react.createContext(DEFAULT_THEME);
148
+ function useSpelycoTheme() {
149
+ return react.useContext(SpelycoThemeContext);
150
+ }
151
+
152
+ // src/factory/useProps.ts
153
+ function useProps(componentName, factoryDefaults, props) {
154
+ const theme = useSpelycoTheme();
155
+ const themeOverride = theme.components[componentName];
156
+ const themeDefaults = themeOverride?.defaultProps;
157
+ const merged = {
158
+ ...factoryDefaults,
159
+ ...themeDefaults
160
+ };
161
+ const propsRecord = props;
162
+ for (const key in propsRecord) {
163
+ if (propsRecord[key] !== void 0) {
164
+ merged[key] = propsRecord[key];
165
+ }
166
+ }
167
+ return merged;
168
+ }
169
+
170
+ // src/factory/createComponent.ts
171
+ function createComponent(config) {
172
+ const { name, defaultProps, render } = config;
173
+ const Component = ((props) => {
174
+ const merged = useProps(name, defaultProps, props);
175
+ return render(merged);
176
+ });
177
+ Component.displayName = name;
178
+ Component.extend = (override) => override;
179
+ return Component;
180
+ }
7
181
  function useBoolean(initialValue = false) {
8
182
  const [value, setValue] = react.useState(initialValue);
9
183
  const setTrue = react.useCallback(() => setValue(true), []);
@@ -19,31 +193,52 @@ function useDebounce(value, delay = 300) {
19
193
  }, [value, delay]);
20
194
  return debouncedValue;
21
195
  }
22
- var theme = (primaryColor) => core.createTheme({
23
- primaryColor: primaryColor ?? "dark",
24
- defaultRadius: "md",
25
- components: {
26
- Button: core.Button.extend({
27
- defaultProps: {
28
- variant: "default"
29
- }
30
- }),
31
- ActionIcon: core.ActionIcon.extend({
32
- defaultProps: {
33
- variant: "transparent"
34
- }
35
- }),
36
- NavLink: core.NavLink.extend({
37
- defaultProps: {
38
- color: "gray",
39
- variant: "light"
40
- }
41
- })
196
+
197
+ // src/theme/resolveTheme.ts
198
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
199
+ function deepMerge(base, override) {
200
+ const result = { ...base };
201
+ for (const key of Object.keys(override)) {
202
+ const overrideValue = override[key];
203
+ if (overrideValue === void 0) continue;
204
+ const baseValue = base[key];
205
+ if (isPlainObject(baseValue) && isPlainObject(overrideValue)) {
206
+ result[key] = deepMerge(baseValue, overrideValue);
207
+ } else {
208
+ result[key] = overrideValue;
209
+ }
42
210
  }
43
- });
211
+ return result;
212
+ }
213
+ function resolveTheme(override) {
214
+ if (!override) return DEFAULT_THEME;
215
+ return deepMerge(
216
+ DEFAULT_THEME,
217
+ override
218
+ );
219
+ }
44
220
 
45
- exports.theme = theme;
221
+ exports.DEFAULT_BRAND = DEFAULT_BRAND;
222
+ exports.DEFAULT_BREAKPOINTS = DEFAULT_BREAKPOINTS;
223
+ exports.DEFAULT_COLORS = DEFAULT_COLORS;
224
+ exports.DEFAULT_FONT_SIZES = DEFAULT_FONT_SIZES;
225
+ exports.DEFAULT_GREEN = DEFAULT_GREEN;
226
+ exports.DEFAULT_LINE_HEIGHTS = DEFAULT_LINE_HEIGHTS;
227
+ exports.DEFAULT_NEUTRAL = DEFAULT_NEUTRAL;
228
+ exports.DEFAULT_RADIUS = DEFAULT_RADIUS;
229
+ exports.DEFAULT_RED = DEFAULT_RED;
230
+ exports.DEFAULT_SHADOWS = DEFAULT_SHADOWS;
231
+ exports.DEFAULT_SPACING = DEFAULT_SPACING;
232
+ exports.DEFAULT_SYSTEM_BARS_DARK = DEFAULT_SYSTEM_BARS_DARK;
233
+ exports.DEFAULT_SYSTEM_BARS_LIGHT = DEFAULT_SYSTEM_BARS_LIGHT;
234
+ exports.DEFAULT_THEME = DEFAULT_THEME;
235
+ exports.SpelycoThemeContext = SpelycoThemeContext;
236
+ exports.createColorTuple = createColorTuple;
237
+ exports.createComponent = createComponent;
238
+ exports.resolveTheme = resolveTheme;
46
239
  exports.useBoolean = useBoolean;
47
240
  exports.useDebounce = useDebounce;
241
+ exports.useProps = useProps;
242
+ exports.useSpelycoTheme = useSpelycoTheme;
48
243
  //# sourceMappingURL=index.js.map
49
244
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useBoolean.ts","../src/hooks/useDebounce.ts","../src/utils/theme.ts"],"names":["useState","useCallback","useEffect","createTheme","Button","ActionIcon","NavLink"],"mappings":";;;;;;AAUO,SAAS,UAAA,CAAW,eAAe,KAAA,EAAyB;AACjE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAIA,eAAS,YAAY,CAAA;AAE/C,EAAA,MAAM,UAAUC,iBAAA,CAAY,MAAM,SAAS,IAAI,CAAA,EAAG,EAAE,CAAA;AACpD,EAAA,MAAM,WAAWA,iBAAA,CAAY,MAAM,SAAS,KAAK,CAAA,EAAG,EAAE,CAAA;AACtD,EAAA,MAAM,MAAA,GAASA,iBAAA,CAAY,MAAM,QAAA,CAAS,CAAC,MAAM,CAAC,CAAC,CAAA,EAAG,EAAE,CAAA;AAExD,EAAA,OAAO,EAAE,KAAA,EAAO,OAAA,EAAS,QAAA,EAAU,QAAQ,QAAA,EAAS;AACtD;AChBO,SAAS,WAAA,CAAe,KAAA,EAAU,KAAA,GAAQ,GAAA,EAAQ;AACvD,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAID,eAAY,KAAK,CAAA;AAE7D,EAAAE,eAAA,CAAU,MAAM;AACd,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,iBAAA,CAAkB,KAAK,GAAG,KAAK,CAAA;AAC9D,IAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,KAAA,EAAO,KAAK,CAAC,CAAA;AAEjB,EAAA,OAAO,cAAA;AACT;ACTO,IAAM,KAAA,GAAQ,CAAC,YAAA,KACpBC,gBAAA,CAAY;AAAA,EACV,cAAc,YAAA,IAAgB,MAAA;AAAA,EAC9B,aAAA,EAAe,IAAA;AAAA,EACf,UAAA,EAAY;AAAA,IACV,MAAA,EAAQC,YAAO,MAAA,CAAO;AAAA,MACpB,YAAA,EAAc;AAAA,QACZ,OAAA,EAAS;AAAA;AACX,KACD,CAAA;AAAA,IACD,UAAA,EAAYC,gBAAW,MAAA,CAAO;AAAA,MAC5B,YAAA,EAAc;AAAA,QACZ,OAAA,EAAS;AAAA;AACX,KACD,CAAA;AAAA,IACD,OAAA,EAASC,aAAQ,MAAA,CAAO;AAAA,MACtB,YAAA,EAAc;AAAA,QACZ,KAAA,EAAO,MAAA;AAAA,QACP,OAAA,EAAS;AAAA;AACX,KACD;AAAA;AAEL,CAAC","file":"index.js","sourcesContent":["import { useCallback, useState } from \"react\";\n\nexport interface UseBooleanReturn {\n value: boolean;\n setTrue: () => void;\n setFalse: () => void;\n toggle: () => void;\n setValue: (value: boolean) => void;\n}\n\nexport function useBoolean(initialValue = false): UseBooleanReturn {\n const [value, setValue] = useState(initialValue);\n\n const setTrue = useCallback(() => setValue(true), []);\n const setFalse = useCallback(() => setValue(false), []);\n const toggle = useCallback(() => setValue((v) => !v), []);\n\n return { value, setTrue, setFalse, toggle, setValue };\n}\n","import { useEffect, useState } from \"react\";\n\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedValue(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n\n return debouncedValue;\n}\n","import { ActionIcon, Button, createTheme, type MantineColor, NavLink } from \"@mantine/core\";\n\nexport const theme = (primaryColor?: MantineColor) =>\n createTheme({\n primaryColor: primaryColor ?? \"dark\",\n defaultRadius: \"md\",\n components: {\n Button: Button.extend({\n defaultProps: {\n variant: \"default\",\n },\n }),\n ActionIcon: ActionIcon.extend({\n defaultProps: {\n variant: \"transparent\",\n },\n }),\n NavLink: NavLink.extend({\n defaultProps: {\n color: \"gray\",\n variant: \"light\",\n },\n }),\n },\n });\n"]}
1
+ {"version":3,"sources":["../src/theme/createColorTuple.ts","../src/theme/defaults.ts","../src/theme/ThemeContext.ts","../src/factory/useProps.ts","../src/factory/createComponent.ts","../src/hooks/useBoolean.ts","../src/hooks/useDebounce.ts","../src/theme/resolveTheme.ts"],"names":["createContext","useContext","useState","useCallback","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import type { ColorTuple } from \"./types\";\n\n/**\n * Wraps an array of 10 color strings into a typed `ColorTuple`. Throws if the\n * array isn't exactly 10 items, which catches palette typos at boot time.\n *\n * For algorithmic shade generation from a single base color, prefer importing\n * shades from a curated palette (e.g. Tailwind) — this lib intentionally ships\n * without an HSL-based generator to keep the surface area small.\n */\nexport function createColorTuple(shades: readonly string[]): ColorTuple {\n if (shades.length !== 10) {\n throw new Error(`createColorTuple expects exactly 10 shades, got ${shades.length}`);\n }\n return shades as unknown as ColorTuple;\n}\n","import { createColorTuple } from \"./createColorTuple\";\nimport type {\n ColorTuple,\n SpelycoBreakpoints,\n SpelycoColors,\n SpelycoFontSizes,\n SpelycoLineHeights,\n SpelycoRadius,\n SpelycoShadows,\n SpelycoSpacing,\n SpelycoSystemBars,\n SpelycoTheme,\n} from \"./types\";\n\n/**\n * Default palettes. Index 0 is the lightest, 9 is the darkest. Sourced from\n * Tailwind's 50–900 ramps (the 950 step is dropped to keep the tuple length\n * at 10).\n */\nexport const DEFAULT_BRAND: ColorTuple = createColorTuple([\n \"#eef2ff\",\n \"#e0e7ff\",\n \"#c7d2fe\",\n \"#a5b4fc\",\n \"#818cf8\",\n \"#6366f1\",\n \"#4f46e5\",\n \"#4338ca\",\n \"#3730a3\",\n \"#312e81\",\n]);\n\nexport const DEFAULT_NEUTRAL: ColorTuple = createColorTuple([\n \"#fafafa\",\n \"#f4f4f5\",\n \"#e4e4e7\",\n \"#d4d4d8\",\n \"#a1a1aa\",\n \"#71717a\",\n \"#52525b\",\n \"#3f3f46\",\n \"#27272a\",\n \"#18181b\",\n]);\n\nexport const DEFAULT_RED: ColorTuple = createColorTuple([\n \"#fef2f2\",\n \"#fee2e2\",\n \"#fecaca\",\n \"#fca5a5\",\n \"#f87171\",\n \"#ef4444\",\n \"#dc2626\",\n \"#b91c1c\",\n \"#991b1b\",\n \"#7f1d1d\",\n]);\n\nexport const DEFAULT_GREEN: ColorTuple = createColorTuple([\n \"#ecfdf5\",\n \"#d1fae5\",\n \"#a7f3d0\",\n \"#6ee7b7\",\n \"#34d399\",\n \"#10b981\",\n \"#059669\",\n \"#047857\",\n \"#065f46\",\n \"#064e3b\",\n]);\n\nexport const DEFAULT_COLORS: SpelycoColors = {\n brand: DEFAULT_BRAND,\n neutral: DEFAULT_NEUTRAL,\n red: DEFAULT_RED,\n green: DEFAULT_GREEN,\n};\n\nexport const DEFAULT_SPACING: SpelycoSpacing = {\n xs: 4,\n sm: 8,\n md: 12,\n lg: 16,\n xl: 24,\n};\n\nexport const DEFAULT_RADIUS: SpelycoRadius = {\n xs: 2,\n sm: 4,\n md: 8,\n lg: 12,\n xl: 16,\n pill: 9999,\n};\n\nexport const DEFAULT_FONT_SIZES: SpelycoFontSizes = {\n xs: 12,\n sm: 14,\n md: 16,\n lg: 18,\n xl: 20,\n};\n\nexport const DEFAULT_LINE_HEIGHTS: SpelycoLineHeights = {\n xs: 1.4,\n sm: 1.45,\n md: 1.55,\n lg: 1.6,\n xl: 1.65,\n};\n\n/**\n * CSS box-shadow strings. RN consumers can map these to elevation locally.\n */\nexport const DEFAULT_SHADOWS: SpelycoShadows = {\n xs: \"0 1px 2px rgba(0, 0, 0, 0.05)\",\n sm: \"0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04)\",\n md: \"0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.04)\",\n lg: \"0 10px 15px rgba(0, 0, 0, 0.08), 0 4px 6px rgba(0, 0, 0, 0.04)\",\n xl: \"0 20px 25px rgba(0, 0, 0, 0.08), 0 10px 10px rgba(0, 0, 0, 0.03)\",\n};\n\n/**\n * `xs: 0` is intentional — Unistyles treats it as the always-active breakpoint.\n */\nexport const DEFAULT_BREAKPOINTS: SpelycoBreakpoints = {\n xs: 0,\n sm: 360,\n md: 768,\n lg: 1024,\n xl: 1280,\n};\n\nexport const DEFAULT_SYSTEM_BARS_LIGHT: SpelycoSystemBars = {\n statusBarStyle: \"dark\",\n statusBarBackgroundColor: \"#ffffff\",\n navigationBarColor: \"#ffffff\",\n navigationBarStyle: \"dark\",\n};\n\nexport const DEFAULT_SYSTEM_BARS_DARK: SpelycoSystemBars = {\n statusBarStyle: \"light\",\n statusBarBackgroundColor: \"#0b0b0f\",\n navigationBarColor: \"#0b0b0f\",\n navigationBarStyle: \"light\",\n};\n\n/**\n * Fully-resolved default theme. Override fields with `<SpelycoProvider theme>`.\n */\nexport const DEFAULT_THEME: SpelycoTheme = {\n colors: DEFAULT_COLORS,\n primaryColor: \"brand\",\n primaryShade: { light: 6, dark: 4 },\n\n spacing: DEFAULT_SPACING,\n radius: DEFAULT_RADIUS,\n fontSizes: DEFAULT_FONT_SIZES,\n lineHeights: DEFAULT_LINE_HEIGHTS,\n shadows: DEFAULT_SHADOWS,\n breakpoints: DEFAULT_BREAKPOINTS,\n\n fontFamily: \"System\",\n fontFamilyMono: \"Menlo, monospace\",\n\n defaultRadius: \"md\",\n\n components: {},\n\n systemBars: {\n light: DEFAULT_SYSTEM_BARS_LIGHT,\n dark: DEFAULT_SYSTEM_BARS_DARK,\n },\n\n other: {},\n};\n","import { createContext, useContext } from \"react\";\nimport { DEFAULT_THEME } from \"./defaults\";\nimport type { SpelycoTheme } from \"./types\";\n\n/**\n * Holds the resolved theme. Platform-specific providers (`SpelycoProvider` in\n * the RN and web packages) write to this context after deep-merging user\n * overrides with `DEFAULT_THEME`.\n */\nexport const SpelycoThemeContext = createContext<SpelycoTheme>(DEFAULT_THEME);\n\nexport function useSpelycoTheme(): SpelycoTheme {\n return useContext(SpelycoThemeContext);\n}\n","import { useSpelycoTheme } from \"../theme/ThemeContext\";\nimport type { SpelycoComponentOverride } from \"../theme/types\";\n\n/**\n * Merges three sources of props in precedence order (later wins):\n * 1. factory defaults — declared inside `createComponent({ defaultProps })`\n * 2. theme defaults — declared via `theme.components[name].defaultProps`\n * 3. user props — passed at the call site\n *\n * `undefined` values in user props are skipped so partially-controlled inputs\n * fall back to defaults instead of forcing `undefined`.\n */\nexport function useProps<TProps extends object>(\n componentName: string,\n factoryDefaults: Partial<TProps> | undefined,\n props: TProps\n): TProps {\n const theme = useSpelycoTheme();\n const themeOverride = theme.components[componentName] as\n | SpelycoComponentOverride<TProps>\n | undefined;\n const themeDefaults = themeOverride?.defaultProps;\n\n const merged: Record<string, unknown> = {\n ...factoryDefaults,\n ...themeDefaults,\n };\n\n const propsRecord = props as Record<string, unknown>;\n for (const key in propsRecord) {\n if (propsRecord[key] !== undefined) {\n merged[key] = propsRecord[key];\n }\n }\n\n return merged as TProps;\n}\n","import type { ReactElement } from \"react\";\nimport type { SpelycoComponentOverride } from \"../theme/types\";\nimport { useProps } from \"./useProps\";\n\nexport interface CreateComponentConfig<TProps extends object> {\n /** Used as the key for `theme.components[name]` overrides. */\n name: string;\n defaultProps?: Partial<TProps>;\n render: (props: TProps) => ReactElement | null;\n}\n\nexport interface SpelycoComponent<TProps extends object> {\n (props: TProps): ReactElement | null;\n displayName: string;\n /**\n * Helper for building a `theme.components[name]` override object — purely\n * sugar, returns its argument with the right shape.\n */\n extend: (override: SpelycoComponentOverride<TProps>) => SpelycoComponentOverride<TProps>;\n}\n\n/**\n * Wraps a render function into a Spelyco component that:\n * - reads `theme.components[name].defaultProps` for runtime overrides\n * - merges factory defaults → theme defaults → user props (later wins)\n * - exposes `.extend()` so consumers can produce override objects\n * - sets `displayName` to the provided `name`\n */\nexport function createComponent<TProps extends object>(\n config: CreateComponentConfig<TProps>\n): SpelycoComponent<TProps> {\n const { name, defaultProps, render } = config;\n\n const Component = ((props: TProps) => {\n const merged = useProps<TProps>(name, defaultProps, props);\n return render(merged);\n }) as SpelycoComponent<TProps>;\n\n Component.displayName = name;\n Component.extend = (override) => override;\n\n return Component;\n}\n","import { useCallback, useState } from \"react\";\n\nexport interface UseBooleanReturn {\n value: boolean;\n setTrue: () => void;\n setFalse: () => void;\n toggle: () => void;\n setValue: (value: boolean) => void;\n}\n\nexport function useBoolean(initialValue = false): UseBooleanReturn {\n const [value, setValue] = useState(initialValue);\n\n const setTrue = useCallback(() => setValue(true), []);\n const setFalse = useCallback(() => setValue(false), []);\n const toggle = useCallback(() => setValue((v) => !v), []);\n\n return { value, setTrue, setFalse, toggle, setValue };\n}\n","import { useEffect, useState } from \"react\";\n\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedValue(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n\n return debouncedValue;\n}\n","import { DEFAULT_THEME } from \"./defaults\";\nimport type { SpelycoTheme, SpelycoThemeOverride } from \"./types\";\n\ntype PlainObject = Record<string, unknown>;\n\nconst isPlainObject = (value: unknown): value is PlainObject =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nfunction deepMerge(base: PlainObject, override: PlainObject): PlainObject {\n const result: PlainObject = { ...base };\n for (const key of Object.keys(override)) {\n const overrideValue = override[key];\n if (overrideValue === undefined) continue;\n\n const baseValue = base[key];\n if (isPlainObject(baseValue) && isPlainObject(overrideValue)) {\n result[key] = deepMerge(baseValue, overrideValue);\n } else {\n result[key] = overrideValue;\n }\n }\n return result;\n}\n\n/**\n * Merges a partial theme override with the default theme, returning a fully\n * resolved `SpelycoTheme`. Color tuples and arrays are replaced wholesale;\n * plain objects (spacing, components, etc.) are merged key-by-key.\n */\nexport function resolveTheme(override?: SpelycoThemeOverride): SpelycoTheme {\n if (!override) return DEFAULT_THEME;\n return deepMerge(\n DEFAULT_THEME as unknown as PlainObject,\n override as unknown as PlainObject\n ) as unknown as SpelycoTheme;\n}\n"]}
package/dist/index.mjs CHANGED
@@ -1,7 +1,181 @@
1
- import { useState, useCallback, useEffect } from 'react';
2
- import { createTheme, NavLink, ActionIcon, Button } from '@mantine/core';
1
+ import { createContext, useContext, useState, useCallback, useEffect } from 'react';
3
2
 
4
- // src/hooks/useBoolean.ts
3
+ // src/theme/ThemeContext.ts
4
+
5
+ // src/theme/createColorTuple.ts
6
+ function createColorTuple(shades) {
7
+ if (shades.length !== 10) {
8
+ throw new Error(`createColorTuple expects exactly 10 shades, got ${shades.length}`);
9
+ }
10
+ return shades;
11
+ }
12
+
13
+ // src/theme/defaults.ts
14
+ var DEFAULT_BRAND = createColorTuple([
15
+ "#eef2ff",
16
+ "#e0e7ff",
17
+ "#c7d2fe",
18
+ "#a5b4fc",
19
+ "#818cf8",
20
+ "#6366f1",
21
+ "#4f46e5",
22
+ "#4338ca",
23
+ "#3730a3",
24
+ "#312e81"
25
+ ]);
26
+ var DEFAULT_NEUTRAL = createColorTuple([
27
+ "#fafafa",
28
+ "#f4f4f5",
29
+ "#e4e4e7",
30
+ "#d4d4d8",
31
+ "#a1a1aa",
32
+ "#71717a",
33
+ "#52525b",
34
+ "#3f3f46",
35
+ "#27272a",
36
+ "#18181b"
37
+ ]);
38
+ var DEFAULT_RED = createColorTuple([
39
+ "#fef2f2",
40
+ "#fee2e2",
41
+ "#fecaca",
42
+ "#fca5a5",
43
+ "#f87171",
44
+ "#ef4444",
45
+ "#dc2626",
46
+ "#b91c1c",
47
+ "#991b1b",
48
+ "#7f1d1d"
49
+ ]);
50
+ var DEFAULT_GREEN = createColorTuple([
51
+ "#ecfdf5",
52
+ "#d1fae5",
53
+ "#a7f3d0",
54
+ "#6ee7b7",
55
+ "#34d399",
56
+ "#10b981",
57
+ "#059669",
58
+ "#047857",
59
+ "#065f46",
60
+ "#064e3b"
61
+ ]);
62
+ var DEFAULT_COLORS = {
63
+ brand: DEFAULT_BRAND,
64
+ neutral: DEFAULT_NEUTRAL,
65
+ red: DEFAULT_RED,
66
+ green: DEFAULT_GREEN
67
+ };
68
+ var DEFAULT_SPACING = {
69
+ xs: 4,
70
+ sm: 8,
71
+ md: 12,
72
+ lg: 16,
73
+ xl: 24
74
+ };
75
+ var DEFAULT_RADIUS = {
76
+ xs: 2,
77
+ sm: 4,
78
+ md: 8,
79
+ lg: 12,
80
+ xl: 16,
81
+ pill: 9999
82
+ };
83
+ var DEFAULT_FONT_SIZES = {
84
+ xs: 12,
85
+ sm: 14,
86
+ md: 16,
87
+ lg: 18,
88
+ xl: 20
89
+ };
90
+ var DEFAULT_LINE_HEIGHTS = {
91
+ xs: 1.4,
92
+ sm: 1.45,
93
+ md: 1.55,
94
+ lg: 1.6,
95
+ xl: 1.65
96
+ };
97
+ var DEFAULT_SHADOWS = {
98
+ xs: "0 1px 2px rgba(0, 0, 0, 0.05)",
99
+ sm: "0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04)",
100
+ md: "0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.04)",
101
+ lg: "0 10px 15px rgba(0, 0, 0, 0.08), 0 4px 6px rgba(0, 0, 0, 0.04)",
102
+ xl: "0 20px 25px rgba(0, 0, 0, 0.08), 0 10px 10px rgba(0, 0, 0, 0.03)"
103
+ };
104
+ var DEFAULT_BREAKPOINTS = {
105
+ xs: 0,
106
+ sm: 360,
107
+ md: 768,
108
+ lg: 1024,
109
+ xl: 1280
110
+ };
111
+ var DEFAULT_SYSTEM_BARS_LIGHT = {
112
+ statusBarStyle: "dark",
113
+ statusBarBackgroundColor: "#ffffff",
114
+ navigationBarColor: "#ffffff",
115
+ navigationBarStyle: "dark"
116
+ };
117
+ var DEFAULT_SYSTEM_BARS_DARK = {
118
+ statusBarStyle: "light",
119
+ statusBarBackgroundColor: "#0b0b0f",
120
+ navigationBarColor: "#0b0b0f",
121
+ navigationBarStyle: "light"
122
+ };
123
+ var DEFAULT_THEME = {
124
+ colors: DEFAULT_COLORS,
125
+ primaryColor: "brand",
126
+ primaryShade: { light: 6, dark: 4 },
127
+ spacing: DEFAULT_SPACING,
128
+ radius: DEFAULT_RADIUS,
129
+ fontSizes: DEFAULT_FONT_SIZES,
130
+ lineHeights: DEFAULT_LINE_HEIGHTS,
131
+ shadows: DEFAULT_SHADOWS,
132
+ breakpoints: DEFAULT_BREAKPOINTS,
133
+ fontFamily: "System",
134
+ fontFamilyMono: "Menlo, monospace",
135
+ defaultRadius: "md",
136
+ components: {},
137
+ systemBars: {
138
+ light: DEFAULT_SYSTEM_BARS_LIGHT,
139
+ dark: DEFAULT_SYSTEM_BARS_DARK
140
+ },
141
+ other: {}
142
+ };
143
+
144
+ // src/theme/ThemeContext.ts
145
+ var SpelycoThemeContext = createContext(DEFAULT_THEME);
146
+ function useSpelycoTheme() {
147
+ return useContext(SpelycoThemeContext);
148
+ }
149
+
150
+ // src/factory/useProps.ts
151
+ function useProps(componentName, factoryDefaults, props) {
152
+ const theme = useSpelycoTheme();
153
+ const themeOverride = theme.components[componentName];
154
+ const themeDefaults = themeOverride?.defaultProps;
155
+ const merged = {
156
+ ...factoryDefaults,
157
+ ...themeDefaults
158
+ };
159
+ const propsRecord = props;
160
+ for (const key in propsRecord) {
161
+ if (propsRecord[key] !== void 0) {
162
+ merged[key] = propsRecord[key];
163
+ }
164
+ }
165
+ return merged;
166
+ }
167
+
168
+ // src/factory/createComponent.ts
169
+ function createComponent(config) {
170
+ const { name, defaultProps, render } = config;
171
+ const Component = ((props) => {
172
+ const merged = useProps(name, defaultProps, props);
173
+ return render(merged);
174
+ });
175
+ Component.displayName = name;
176
+ Component.extend = (override) => override;
177
+ return Component;
178
+ }
5
179
  function useBoolean(initialValue = false) {
6
180
  const [value, setValue] = useState(initialValue);
7
181
  const setTrue = useCallback(() => setValue(true), []);
@@ -17,29 +191,31 @@ function useDebounce(value, delay = 300) {
17
191
  }, [value, delay]);
18
192
  return debouncedValue;
19
193
  }
20
- var theme = (primaryColor) => createTheme({
21
- primaryColor: primaryColor ?? "dark",
22
- defaultRadius: "md",
23
- components: {
24
- Button: Button.extend({
25
- defaultProps: {
26
- variant: "default"
27
- }
28
- }),
29
- ActionIcon: ActionIcon.extend({
30
- defaultProps: {
31
- variant: "transparent"
32
- }
33
- }),
34
- NavLink: NavLink.extend({
35
- defaultProps: {
36
- color: "gray",
37
- variant: "light"
38
- }
39
- })
194
+
195
+ // src/theme/resolveTheme.ts
196
+ var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
197
+ function deepMerge(base, override) {
198
+ const result = { ...base };
199
+ for (const key of Object.keys(override)) {
200
+ const overrideValue = override[key];
201
+ if (overrideValue === void 0) continue;
202
+ const baseValue = base[key];
203
+ if (isPlainObject(baseValue) && isPlainObject(overrideValue)) {
204
+ result[key] = deepMerge(baseValue, overrideValue);
205
+ } else {
206
+ result[key] = overrideValue;
207
+ }
40
208
  }
41
- });
209
+ return result;
210
+ }
211
+ function resolveTheme(override) {
212
+ if (!override) return DEFAULT_THEME;
213
+ return deepMerge(
214
+ DEFAULT_THEME,
215
+ override
216
+ );
217
+ }
42
218
 
43
- export { theme, useBoolean, useDebounce };
219
+ export { DEFAULT_BRAND, DEFAULT_BREAKPOINTS, DEFAULT_COLORS, DEFAULT_FONT_SIZES, DEFAULT_GREEN, DEFAULT_LINE_HEIGHTS, DEFAULT_NEUTRAL, DEFAULT_RADIUS, DEFAULT_RED, DEFAULT_SHADOWS, DEFAULT_SPACING, DEFAULT_SYSTEM_BARS_DARK, DEFAULT_SYSTEM_BARS_LIGHT, DEFAULT_THEME, SpelycoThemeContext, createColorTuple, createComponent, resolveTheme, useBoolean, useDebounce, useProps, useSpelycoTheme };
44
220
  //# sourceMappingURL=index.mjs.map
45
221
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/useBoolean.ts","../src/hooks/useDebounce.ts","../src/utils/theme.ts"],"names":["useState"],"mappings":";;;;AAUO,SAAS,UAAA,CAAW,eAAe,KAAA,EAAyB;AACjE,EAAA,MAAM,CAAC,KAAA,EAAO,QAAQ,CAAA,GAAI,SAAS,YAAY,CAAA;AAE/C,EAAA,MAAM,UAAU,WAAA,CAAY,MAAM,SAAS,IAAI,CAAA,EAAG,EAAE,CAAA;AACpD,EAAA,MAAM,WAAW,WAAA,CAAY,MAAM,SAAS,KAAK,CAAA,EAAG,EAAE,CAAA;AACtD,EAAA,MAAM,MAAA,GAAS,WAAA,CAAY,MAAM,QAAA,CAAS,CAAC,MAAM,CAAC,CAAC,CAAA,EAAG,EAAE,CAAA;AAExD,EAAA,OAAO,EAAE,KAAA,EAAO,OAAA,EAAS,QAAA,EAAU,QAAQ,QAAA,EAAS;AACtD;AChBO,SAAS,WAAA,CAAe,KAAA,EAAU,KAAA,GAAQ,GAAA,EAAQ;AACvD,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAIA,SAAY,KAAK,CAAA;AAE7D,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,QAAQ,UAAA,CAAW,MAAM,iBAAA,CAAkB,KAAK,GAAG,KAAK,CAAA;AAC9D,IAAA,OAAO,MAAM,aAAa,KAAK,CAAA;AAAA,EACjC,CAAA,EAAG,CAAC,KAAA,EAAO,KAAK,CAAC,CAAA;AAEjB,EAAA,OAAO,cAAA;AACT;ACTO,IAAM,KAAA,GAAQ,CAAC,YAAA,KACpB,WAAA,CAAY;AAAA,EACV,cAAc,YAAA,IAAgB,MAAA;AAAA,EAC9B,aAAA,EAAe,IAAA;AAAA,EACf,UAAA,EAAY;AAAA,IACV,MAAA,EAAQ,OAAO,MAAA,CAAO;AAAA,MACpB,YAAA,EAAc;AAAA,QACZ,OAAA,EAAS;AAAA;AACX,KACD,CAAA;AAAA,IACD,UAAA,EAAY,WAAW,MAAA,CAAO;AAAA,MAC5B,YAAA,EAAc;AAAA,QACZ,OAAA,EAAS;AAAA;AACX,KACD,CAAA;AAAA,IACD,OAAA,EAAS,QAAQ,MAAA,CAAO;AAAA,MACtB,YAAA,EAAc;AAAA,QACZ,KAAA,EAAO,MAAA;AAAA,QACP,OAAA,EAAS;AAAA;AACX,KACD;AAAA;AAEL,CAAC","file":"index.mjs","sourcesContent":["import { useCallback, useState } from \"react\";\n\nexport interface UseBooleanReturn {\n value: boolean;\n setTrue: () => void;\n setFalse: () => void;\n toggle: () => void;\n setValue: (value: boolean) => void;\n}\n\nexport function useBoolean(initialValue = false): UseBooleanReturn {\n const [value, setValue] = useState(initialValue);\n\n const setTrue = useCallback(() => setValue(true), []);\n const setFalse = useCallback(() => setValue(false), []);\n const toggle = useCallback(() => setValue((v) => !v), []);\n\n return { value, setTrue, setFalse, toggle, setValue };\n}\n","import { useEffect, useState } from \"react\";\n\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedValue(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n\n return debouncedValue;\n}\n","import { ActionIcon, Button, createTheme, type MantineColor, NavLink } from \"@mantine/core\";\n\nexport const theme = (primaryColor?: MantineColor) =>\n createTheme({\n primaryColor: primaryColor ?? \"dark\",\n defaultRadius: \"md\",\n components: {\n Button: Button.extend({\n defaultProps: {\n variant: \"default\",\n },\n }),\n ActionIcon: ActionIcon.extend({\n defaultProps: {\n variant: \"transparent\",\n },\n }),\n NavLink: NavLink.extend({\n defaultProps: {\n color: \"gray\",\n variant: \"light\",\n },\n }),\n },\n });\n"]}
1
+ {"version":3,"sources":["../src/theme/createColorTuple.ts","../src/theme/defaults.ts","../src/theme/ThemeContext.ts","../src/factory/useProps.ts","../src/factory/createComponent.ts","../src/hooks/useBoolean.ts","../src/hooks/useDebounce.ts","../src/theme/resolveTheme.ts"],"names":["useState"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { ColorTuple } from \"./types\";\n\n/**\n * Wraps an array of 10 color strings into a typed `ColorTuple`. Throws if the\n * array isn't exactly 10 items, which catches palette typos at boot time.\n *\n * For algorithmic shade generation from a single base color, prefer importing\n * shades from a curated palette (e.g. Tailwind) — this lib intentionally ships\n * without an HSL-based generator to keep the surface area small.\n */\nexport function createColorTuple(shades: readonly string[]): ColorTuple {\n if (shades.length !== 10) {\n throw new Error(`createColorTuple expects exactly 10 shades, got ${shades.length}`);\n }\n return shades as unknown as ColorTuple;\n}\n","import { createColorTuple } from \"./createColorTuple\";\nimport type {\n ColorTuple,\n SpelycoBreakpoints,\n SpelycoColors,\n SpelycoFontSizes,\n SpelycoLineHeights,\n SpelycoRadius,\n SpelycoShadows,\n SpelycoSpacing,\n SpelycoSystemBars,\n SpelycoTheme,\n} from \"./types\";\n\n/**\n * Default palettes. Index 0 is the lightest, 9 is the darkest. Sourced from\n * Tailwind's 50–900 ramps (the 950 step is dropped to keep the tuple length\n * at 10).\n */\nexport const DEFAULT_BRAND: ColorTuple = createColorTuple([\n \"#eef2ff\",\n \"#e0e7ff\",\n \"#c7d2fe\",\n \"#a5b4fc\",\n \"#818cf8\",\n \"#6366f1\",\n \"#4f46e5\",\n \"#4338ca\",\n \"#3730a3\",\n \"#312e81\",\n]);\n\nexport const DEFAULT_NEUTRAL: ColorTuple = createColorTuple([\n \"#fafafa\",\n \"#f4f4f5\",\n \"#e4e4e7\",\n \"#d4d4d8\",\n \"#a1a1aa\",\n \"#71717a\",\n \"#52525b\",\n \"#3f3f46\",\n \"#27272a\",\n \"#18181b\",\n]);\n\nexport const DEFAULT_RED: ColorTuple = createColorTuple([\n \"#fef2f2\",\n \"#fee2e2\",\n \"#fecaca\",\n \"#fca5a5\",\n \"#f87171\",\n \"#ef4444\",\n \"#dc2626\",\n \"#b91c1c\",\n \"#991b1b\",\n \"#7f1d1d\",\n]);\n\nexport const DEFAULT_GREEN: ColorTuple = createColorTuple([\n \"#ecfdf5\",\n \"#d1fae5\",\n \"#a7f3d0\",\n \"#6ee7b7\",\n \"#34d399\",\n \"#10b981\",\n \"#059669\",\n \"#047857\",\n \"#065f46\",\n \"#064e3b\",\n]);\n\nexport const DEFAULT_COLORS: SpelycoColors = {\n brand: DEFAULT_BRAND,\n neutral: DEFAULT_NEUTRAL,\n red: DEFAULT_RED,\n green: DEFAULT_GREEN,\n};\n\nexport const DEFAULT_SPACING: SpelycoSpacing = {\n xs: 4,\n sm: 8,\n md: 12,\n lg: 16,\n xl: 24,\n};\n\nexport const DEFAULT_RADIUS: SpelycoRadius = {\n xs: 2,\n sm: 4,\n md: 8,\n lg: 12,\n xl: 16,\n pill: 9999,\n};\n\nexport const DEFAULT_FONT_SIZES: SpelycoFontSizes = {\n xs: 12,\n sm: 14,\n md: 16,\n lg: 18,\n xl: 20,\n};\n\nexport const DEFAULT_LINE_HEIGHTS: SpelycoLineHeights = {\n xs: 1.4,\n sm: 1.45,\n md: 1.55,\n lg: 1.6,\n xl: 1.65,\n};\n\n/**\n * CSS box-shadow strings. RN consumers can map these to elevation locally.\n */\nexport const DEFAULT_SHADOWS: SpelycoShadows = {\n xs: \"0 1px 2px rgba(0, 0, 0, 0.05)\",\n sm: \"0 1px 3px rgba(0, 0, 0, 0.08), 0 1px 2px rgba(0, 0, 0, 0.04)\",\n md: \"0 4px 6px rgba(0, 0, 0, 0.07), 0 2px 4px rgba(0, 0, 0, 0.04)\",\n lg: \"0 10px 15px rgba(0, 0, 0, 0.08), 0 4px 6px rgba(0, 0, 0, 0.04)\",\n xl: \"0 20px 25px rgba(0, 0, 0, 0.08), 0 10px 10px rgba(0, 0, 0, 0.03)\",\n};\n\n/**\n * `xs: 0` is intentional — Unistyles treats it as the always-active breakpoint.\n */\nexport const DEFAULT_BREAKPOINTS: SpelycoBreakpoints = {\n xs: 0,\n sm: 360,\n md: 768,\n lg: 1024,\n xl: 1280,\n};\n\nexport const DEFAULT_SYSTEM_BARS_LIGHT: SpelycoSystemBars = {\n statusBarStyle: \"dark\",\n statusBarBackgroundColor: \"#ffffff\",\n navigationBarColor: \"#ffffff\",\n navigationBarStyle: \"dark\",\n};\n\nexport const DEFAULT_SYSTEM_BARS_DARK: SpelycoSystemBars = {\n statusBarStyle: \"light\",\n statusBarBackgroundColor: \"#0b0b0f\",\n navigationBarColor: \"#0b0b0f\",\n navigationBarStyle: \"light\",\n};\n\n/**\n * Fully-resolved default theme. Override fields with `<SpelycoProvider theme>`.\n */\nexport const DEFAULT_THEME: SpelycoTheme = {\n colors: DEFAULT_COLORS,\n primaryColor: \"brand\",\n primaryShade: { light: 6, dark: 4 },\n\n spacing: DEFAULT_SPACING,\n radius: DEFAULT_RADIUS,\n fontSizes: DEFAULT_FONT_SIZES,\n lineHeights: DEFAULT_LINE_HEIGHTS,\n shadows: DEFAULT_SHADOWS,\n breakpoints: DEFAULT_BREAKPOINTS,\n\n fontFamily: \"System\",\n fontFamilyMono: \"Menlo, monospace\",\n\n defaultRadius: \"md\",\n\n components: {},\n\n systemBars: {\n light: DEFAULT_SYSTEM_BARS_LIGHT,\n dark: DEFAULT_SYSTEM_BARS_DARK,\n },\n\n other: {},\n};\n","import { createContext, useContext } from \"react\";\nimport { DEFAULT_THEME } from \"./defaults\";\nimport type { SpelycoTheme } from \"./types\";\n\n/**\n * Holds the resolved theme. Platform-specific providers (`SpelycoProvider` in\n * the RN and web packages) write to this context after deep-merging user\n * overrides with `DEFAULT_THEME`.\n */\nexport const SpelycoThemeContext = createContext<SpelycoTheme>(DEFAULT_THEME);\n\nexport function useSpelycoTheme(): SpelycoTheme {\n return useContext(SpelycoThemeContext);\n}\n","import { useSpelycoTheme } from \"../theme/ThemeContext\";\nimport type { SpelycoComponentOverride } from \"../theme/types\";\n\n/**\n * Merges three sources of props in precedence order (later wins):\n * 1. factory defaults — declared inside `createComponent({ defaultProps })`\n * 2. theme defaults — declared via `theme.components[name].defaultProps`\n * 3. user props — passed at the call site\n *\n * `undefined` values in user props are skipped so partially-controlled inputs\n * fall back to defaults instead of forcing `undefined`.\n */\nexport function useProps<TProps extends object>(\n componentName: string,\n factoryDefaults: Partial<TProps> | undefined,\n props: TProps\n): TProps {\n const theme = useSpelycoTheme();\n const themeOverride = theme.components[componentName] as\n | SpelycoComponentOverride<TProps>\n | undefined;\n const themeDefaults = themeOverride?.defaultProps;\n\n const merged: Record<string, unknown> = {\n ...factoryDefaults,\n ...themeDefaults,\n };\n\n const propsRecord = props as Record<string, unknown>;\n for (const key in propsRecord) {\n if (propsRecord[key] !== undefined) {\n merged[key] = propsRecord[key];\n }\n }\n\n return merged as TProps;\n}\n","import type { ReactElement } from \"react\";\nimport type { SpelycoComponentOverride } from \"../theme/types\";\nimport { useProps } from \"./useProps\";\n\nexport interface CreateComponentConfig<TProps extends object> {\n /** Used as the key for `theme.components[name]` overrides. */\n name: string;\n defaultProps?: Partial<TProps>;\n render: (props: TProps) => ReactElement | null;\n}\n\nexport interface SpelycoComponent<TProps extends object> {\n (props: TProps): ReactElement | null;\n displayName: string;\n /**\n * Helper for building a `theme.components[name]` override object — purely\n * sugar, returns its argument with the right shape.\n */\n extend: (override: SpelycoComponentOverride<TProps>) => SpelycoComponentOverride<TProps>;\n}\n\n/**\n * Wraps a render function into a Spelyco component that:\n * - reads `theme.components[name].defaultProps` for runtime overrides\n * - merges factory defaults → theme defaults → user props (later wins)\n * - exposes `.extend()` so consumers can produce override objects\n * - sets `displayName` to the provided `name`\n */\nexport function createComponent<TProps extends object>(\n config: CreateComponentConfig<TProps>\n): SpelycoComponent<TProps> {\n const { name, defaultProps, render } = config;\n\n const Component = ((props: TProps) => {\n const merged = useProps<TProps>(name, defaultProps, props);\n return render(merged);\n }) as SpelycoComponent<TProps>;\n\n Component.displayName = name;\n Component.extend = (override) => override;\n\n return Component;\n}\n","import { useCallback, useState } from \"react\";\n\nexport interface UseBooleanReturn {\n value: boolean;\n setTrue: () => void;\n setFalse: () => void;\n toggle: () => void;\n setValue: (value: boolean) => void;\n}\n\nexport function useBoolean(initialValue = false): UseBooleanReturn {\n const [value, setValue] = useState(initialValue);\n\n const setTrue = useCallback(() => setValue(true), []);\n const setFalse = useCallback(() => setValue(false), []);\n const toggle = useCallback(() => setValue((v) => !v), []);\n\n return { value, setTrue, setFalse, toggle, setValue };\n}\n","import { useEffect, useState } from \"react\";\n\nexport function useDebounce<T>(value: T, delay = 300): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const timer = setTimeout(() => setDebouncedValue(value), delay);\n return () => clearTimeout(timer);\n }, [value, delay]);\n\n return debouncedValue;\n}\n","import { DEFAULT_THEME } from \"./defaults\";\nimport type { SpelycoTheme, SpelycoThemeOverride } from \"./types\";\n\ntype PlainObject = Record<string, unknown>;\n\nconst isPlainObject = (value: unknown): value is PlainObject =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nfunction deepMerge(base: PlainObject, override: PlainObject): PlainObject {\n const result: PlainObject = { ...base };\n for (const key of Object.keys(override)) {\n const overrideValue = override[key];\n if (overrideValue === undefined) continue;\n\n const baseValue = base[key];\n if (isPlainObject(baseValue) && isPlainObject(overrideValue)) {\n result[key] = deepMerge(baseValue, overrideValue);\n } else {\n result[key] = overrideValue;\n }\n }\n return result;\n}\n\n/**\n * Merges a partial theme override with the default theme, returning a fully\n * resolved `SpelycoTheme`. Color tuples and arrays are replaced wholesale;\n * plain objects (spacing, components, etc.) are merged key-by-key.\n */\nexport function resolveTheme(override?: SpelycoThemeOverride): SpelycoTheme {\n if (!override) return DEFAULT_THEME;\n return deepMerge(\n DEFAULT_THEME as unknown as PlainObject,\n override as unknown as PlainObject\n ) as unknown as SpelycoTheme;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spelyco/react-lib",
3
- "version": "1.2.0-e4f7457e",
3
+ "version": "1.3.0",
4
4
  "description": "Shared React hooks and utilities for Spelyco UI packages",
5
5
  "keywords": [
6
6
  "react",
@@ -32,11 +32,9 @@
32
32
  "clean": "rm -rf dist"
33
33
  },
34
34
  "peerDependencies": {
35
- "@mantine/core": ">=9",
36
35
  "react": ">=18"
37
36
  },
38
37
  "devDependencies": {
39
- "@mantine/core": "^9.0.1",
40
38
  "@spelyco/tsconfig": "*",
41
39
  "@testing-library/react": "^16.1.0",
42
40
  "@types/react": "^19.2.14",