@newtonedev/components 0.1.0 → 0.1.2
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/dist/AppShell/AppShell.d.ts +4 -0
- package/dist/AppShell/AppShell.d.ts.map +1 -0
- package/dist/AppShell/AppShell.styles.d.ts +16 -0
- package/dist/AppShell/AppShell.styles.d.ts.map +1 -0
- package/dist/AppShell/AppShell.types.d.ts +8 -0
- package/dist/AppShell/AppShell.types.d.ts.map +1 -0
- package/dist/AppShell/index.d.ts +3 -0
- package/dist/AppShell/index.d.ts.map +1 -0
- package/dist/Button/Button.d.ts +9 -4
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.styles.d.ts +33 -26
- package/dist/Button/Button.styles.d.ts.map +1 -1
- package/dist/Button/Button.types.d.ts +17 -2
- package/dist/Button/Button.types.d.ts.map +1 -1
- package/dist/ColorScaleSlider/ColorScaleSlider.d.ts +13 -0
- package/dist/ColorScaleSlider/ColorScaleSlider.d.ts.map +1 -0
- package/dist/ColorScaleSlider/ColorScaleSlider.styles.d.ts +54 -0
- package/dist/ColorScaleSlider/ColorScaleSlider.styles.d.ts.map +1 -0
- package/dist/ColorScaleSlider/ColorScaleSlider.types.d.ts +25 -0
- package/dist/ColorScaleSlider/ColorScaleSlider.types.d.ts.map +1 -0
- package/dist/ColorScaleSlider/index.d.ts +3 -0
- package/dist/ColorScaleSlider/index.d.ts.map +1 -0
- package/dist/Frame/Frame.d.ts +48 -0
- package/dist/Frame/Frame.d.ts.map +1 -0
- package/dist/Frame/Frame.styles.d.ts +39 -0
- package/dist/Frame/Frame.styles.d.ts.map +1 -0
- package/dist/Frame/Frame.types.d.ts +115 -0
- package/dist/Frame/Frame.types.d.ts.map +1 -0
- package/dist/Frame/Frame.utils.d.ts +39 -0
- package/dist/Frame/Frame.utils.d.ts.map +1 -0
- package/dist/Frame/index.d.ts +4 -0
- package/dist/Frame/index.d.ts.map +1 -0
- package/dist/HueSlider/HueSlider.d.ts +1 -1
- package/dist/HueSlider/HueSlider.d.ts.map +1 -1
- package/dist/HueSlider/HueSlider.styles.d.ts +47 -5
- package/dist/HueSlider/HueSlider.styles.d.ts.map +1 -1
- package/dist/HueSlider/HueSlider.types.d.ts +1 -0
- package/dist/HueSlider/HueSlider.types.d.ts.map +1 -1
- package/dist/Icon/Icon.d.ts +36 -0
- package/dist/Icon/Icon.d.ts.map +1 -0
- package/dist/Navbar/Navbar.d.ts +4 -0
- package/dist/Navbar/Navbar.d.ts.map +1 -0
- package/dist/Navbar/Navbar.styles.d.ts +31 -0
- package/dist/Navbar/Navbar.styles.d.ts.map +1 -0
- package/dist/Navbar/Navbar.types.d.ts +14 -0
- package/dist/Navbar/Navbar.types.d.ts.map +1 -0
- package/dist/Navbar/index.d.ts +3 -0
- package/dist/Navbar/index.d.ts.map +1 -0
- package/dist/Popover/Popover.d.ts +4 -0
- package/dist/Popover/Popover.d.ts.map +1 -0
- package/dist/Popover/Popover.styles.d.ts +9 -0
- package/dist/Popover/Popover.styles.d.ts.map +1 -0
- package/dist/Popover/Popover.types.d.ts +37 -0
- package/dist/Popover/Popover.types.d.ts.map +1 -0
- package/dist/Popover/index.d.ts +4 -0
- package/dist/Popover/index.d.ts.map +1 -0
- package/dist/Popover/usePopover.d.ts +3 -0
- package/dist/Popover/usePopover.d.ts.map +1 -0
- package/dist/Select/Select.d.ts +1 -8
- package/dist/Select/Select.d.ts.map +1 -1
- package/dist/Select/Select.styles.d.ts +32 -5
- package/dist/Select/Select.styles.d.ts.map +1 -1
- package/dist/Select/Select.types.d.ts +25 -1
- package/dist/Select/Select.types.d.ts.map +1 -1
- package/dist/Select/SelectOption.d.ts +13 -0
- package/dist/Select/SelectOption.d.ts.map +1 -0
- package/dist/Select/useSelect.d.ts +15 -0
- package/dist/Select/useSelect.d.ts.map +1 -0
- package/dist/Sidebar/Sidebar.d.ts +4 -0
- package/dist/Sidebar/Sidebar.d.ts.map +1 -0
- package/dist/Sidebar/Sidebar.styles.d.ts +31 -0
- package/dist/Sidebar/Sidebar.styles.d.ts.map +1 -0
- package/dist/Sidebar/Sidebar.types.d.ts +14 -0
- package/dist/Sidebar/Sidebar.types.d.ts.map +1 -0
- package/dist/Sidebar/index.d.ts +3 -0
- package/dist/Sidebar/index.d.ts.map +1 -0
- package/dist/Slider/Slider.d.ts +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.styles.d.ts +48 -8
- package/dist/Slider/Slider.styles.d.ts.map +1 -1
- package/dist/Slider/Slider.types.d.ts +1 -0
- package/dist/Slider/Slider.types.d.ts.map +1 -1
- package/dist/TextInput/TextInput.styles.d.ts +3 -1
- package/dist/TextInput/TextInput.styles.d.ts.map +1 -1
- package/dist/Toggle/Toggle.styles.d.ts +2 -1
- package/dist/Toggle/Toggle.styles.d.ts.map +1 -1
- package/dist/fonts/GoogleFontLoader.d.ts +19 -0
- package/dist/fonts/GoogleFontLoader.d.ts.map +1 -0
- package/dist/fonts/IconFontLoader.d.ts +13 -0
- package/dist/fonts/IconFontLoader.d.ts.map +1 -0
- package/dist/fonts/buildGoogleFontsUrl.d.ts +17 -0
- package/dist/fonts/buildGoogleFontsUrl.d.ts.map +1 -0
- package/dist/fonts/googleFonts.d.ts +20 -0
- package/dist/fonts/googleFonts.d.ts.map +1 -0
- package/dist/index.cjs +2303 -205
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +27 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2279 -200
- package/dist/index.js.map +1 -1
- package/dist/registry/codegen.d.ts +11 -0
- package/dist/registry/codegen.d.ts.map +1 -0
- package/dist/registry/index.d.ts +4 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/registry.d.ts +7 -0
- package/dist/registry/registry.d.ts.map +1 -0
- package/dist/registry/types.d.ts +32 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/theme/FrameContext.d.ts +24 -0
- package/dist/theme/FrameContext.d.ts.map +1 -0
- package/dist/theme/NewtoneProvider.d.ts.map +1 -1
- package/dist/theme/defaults.d.ts.map +1 -1
- package/dist/theme/types.d.ts +64 -1
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/tokens/computeTokens.d.ts +55 -3
- package/dist/tokens/computeTokens.d.ts.map +1 -1
- package/dist/tokens/types.d.ts +52 -0
- package/dist/tokens/types.d.ts.map +1 -1
- package/dist/tokens/useTokens.d.ts +12 -9
- package/dist/tokens/useTokens.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/AppShell/AppShell.styles.ts +20 -0
- package/src/AppShell/AppShell.tsx +17 -0
- package/src/AppShell/AppShell.types.ts +8 -0
- package/src/AppShell/index.ts +2 -0
- package/src/Button/Button.styles.ts +74 -41
- package/src/Button/Button.tsx +36 -17
- package/src/Button/Button.types.ts +20 -2
- package/src/Card/Card.styles.ts +2 -2
- package/src/ColorScaleSlider/ColorScaleSlider.styles.ts +60 -0
- package/src/ColorScaleSlider/ColorScaleSlider.tsx +156 -0
- package/src/ColorScaleSlider/ColorScaleSlider.types.ts +25 -0
- package/src/ColorScaleSlider/index.ts +2 -0
- package/src/Frame/Frame.styles.ts +213 -0
- package/src/Frame/Frame.tsx +242 -0
- package/src/Frame/Frame.types.ts +181 -0
- package/src/Frame/Frame.utils.ts +189 -0
- package/src/Frame/index.ts +21 -0
- package/src/HueSlider/HueSlider.styles.ts +58 -39
- package/src/HueSlider/HueSlider.tsx +97 -25
- package/src/HueSlider/HueSlider.types.ts +1 -0
- package/src/Icon/Icon.tsx +76 -0
- package/src/Navbar/Navbar.styles.ts +37 -0
- package/src/Navbar/Navbar.tsx +32 -0
- package/src/Navbar/Navbar.types.ts +14 -0
- package/src/Navbar/index.ts +2 -0
- package/src/Popover/Popover.styles.ts +39 -0
- package/src/Popover/Popover.tsx +103 -0
- package/src/Popover/Popover.types.ts +40 -0
- package/src/Popover/index.ts +3 -0
- package/src/Popover/usePopover.ts +26 -0
- package/src/Select/Select.styles.ts +49 -10
- package/src/Select/Select.tsx +127 -36
- package/src/Select/Select.types.ts +30 -1
- package/src/Select/SelectOption.tsx +104 -0
- package/src/Select/useSelect.ts +129 -0
- package/src/Sidebar/Sidebar.styles.ts +37 -0
- package/src/Sidebar/Sidebar.tsx +27 -0
- package/src/Sidebar/Sidebar.types.ts +14 -0
- package/src/Sidebar/index.ts +2 -0
- package/src/Slider/Slider.styles.ts +53 -25
- package/src/Slider/Slider.tsx +89 -24
- package/src/Slider/Slider.types.ts +1 -0
- package/src/TextInput/TextInput.styles.ts +9 -7
- package/src/Toggle/Toggle.styles.ts +4 -3
- package/src/fonts/GoogleFontLoader.tsx +63 -0
- package/src/fonts/IconFontLoader.tsx +49 -0
- package/src/fonts/buildGoogleFontsUrl.ts +31 -0
- package/src/fonts/googleFonts.ts +87 -0
- package/src/index.ts +70 -2
- package/src/registry/codegen.ts +132 -0
- package/src/registry/index.ts +17 -0
- package/src/registry/registry.ts +402 -0
- package/src/registry/types.ts +35 -0
- package/src/theme/FrameContext.tsx +29 -0
- package/src/theme/NewtoneProvider.tsx +9 -1
- package/src/theme/defaults.ts +51 -0
- package/src/theme/types.ts +66 -1
- package/src/tokens/computeTokens.ts +103 -46
- package/src/tokens/types.ts +52 -0
- package/src/tokens/useTokens.ts +30 -15
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { View, Pressable, Text } from 'react-native';
|
|
3
|
+
import type { ViewStyle, TextStyle } from 'react-native';
|
|
4
|
+
import type { FrameProps } from './Frame.types';
|
|
5
|
+
import type { ElevationLevel, FrameElevation } from '../theme/types';
|
|
6
|
+
import { srgbToHex } from 'newtone';
|
|
7
|
+
import { FrameContext, useFrameContext } from '../theme/FrameContext';
|
|
8
|
+
import { useNewtoneTheme } from '../theme/NewtoneProvider';
|
|
9
|
+
import { computeTokens } from '../tokens/computeTokens';
|
|
10
|
+
import { getFrameStyles } from './Frame.styles';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Wrap raw string/number children in <Text> to prevent
|
|
14
|
+
* react-native-web "Unexpected text node" console errors.
|
|
15
|
+
*/
|
|
16
|
+
function wrapTextChildren(
|
|
17
|
+
children: React.ReactNode,
|
|
18
|
+
textStyle: TextStyle,
|
|
19
|
+
): React.ReactNode {
|
|
20
|
+
return React.Children.map(children, (child) => {
|
|
21
|
+
if (typeof child === 'string' || typeof child === 'number') {
|
|
22
|
+
return <Text style={textStyle}>{child}</Text>;
|
|
23
|
+
}
|
|
24
|
+
return child;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convert user-facing FrameElevation (-2..2) to internal ElevationLevel (0..2).
|
|
30
|
+
*
|
|
31
|
+
* | Frame | Internal | Background |
|
|
32
|
+
* |:-----:|:--------:|:-----------|
|
|
33
|
+
* | -2 | 0 | Sunken |
|
|
34
|
+
* | -1 | 0 | Sunken |
|
|
35
|
+
* | 0 | 1 | Default |
|
|
36
|
+
* | 1 | 2 | Elevated |
|
|
37
|
+
* | 2 | 2 | Elevated |
|
|
38
|
+
*/
|
|
39
|
+
function toElevationLevel(frameElevation: FrameElevation): ElevationLevel {
|
|
40
|
+
if (frameElevation <= -1) return 0;
|
|
41
|
+
if (frameElevation === 0) return 1;
|
|
42
|
+
return 2;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Frame — Foundational layout building block.
|
|
47
|
+
*
|
|
48
|
+
* Frame is a container that provides theme and elevation context to all
|
|
49
|
+
* descendants, plus layout (flex/grid), spacing, sizing, appearance,
|
|
50
|
+
* and interactivity capabilities.
|
|
51
|
+
*
|
|
52
|
+
* Frames can be nested. Inner frames override outer frames for their subtree.
|
|
53
|
+
* Unspecified props inherit from the nearest parent Frame.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```tsx
|
|
57
|
+
* // Basic layout
|
|
58
|
+
* <Frame direction="horizontal" gap="md" padding="lg" align="center">
|
|
59
|
+
* <Button onPress={() => {}}>Save</Button>
|
|
60
|
+
* <Button variant="ghost" onPress={() => {}}>Cancel</Button>
|
|
61
|
+
* </Frame>
|
|
62
|
+
* ```
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```tsx
|
|
66
|
+
* // Card-like frame
|
|
67
|
+
* <Frame
|
|
68
|
+
* theme="primary"
|
|
69
|
+
* elevation={2}
|
|
70
|
+
* radius="lg"
|
|
71
|
+
* padding="xl"
|
|
72
|
+
* bordered
|
|
73
|
+
* onPress={() => navigate('/details')}
|
|
74
|
+
* >
|
|
75
|
+
* <Text>Clickable card</Text>
|
|
76
|
+
* </Frame>
|
|
77
|
+
* ```
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* ```tsx
|
|
81
|
+
* // Grid layout (web)
|
|
82
|
+
* <Frame layout="grid" columns={3} gap="md" padding="lg">
|
|
83
|
+
* <Frame radius="md" padding="md" bordered>Cell 1</Frame>
|
|
84
|
+
* <Frame radius="md" padding="md" bordered>Cell 2</Frame>
|
|
85
|
+
* <Frame radius="md" padding="md" bordered>Cell 3</Frame>
|
|
86
|
+
* </Frame>
|
|
87
|
+
* ```
|
|
88
|
+
*/
|
|
89
|
+
export function Frame({
|
|
90
|
+
children,
|
|
91
|
+
// Theme & elevation
|
|
92
|
+
theme,
|
|
93
|
+
elevation,
|
|
94
|
+
// Layout
|
|
95
|
+
layout,
|
|
96
|
+
direction,
|
|
97
|
+
wrap,
|
|
98
|
+
reverse,
|
|
99
|
+
columns,
|
|
100
|
+
rows,
|
|
101
|
+
// Alignment
|
|
102
|
+
align,
|
|
103
|
+
justify,
|
|
104
|
+
// Spacing
|
|
105
|
+
padding,
|
|
106
|
+
gap,
|
|
107
|
+
// Sizing
|
|
108
|
+
width,
|
|
109
|
+
height,
|
|
110
|
+
minWidth,
|
|
111
|
+
maxWidth,
|
|
112
|
+
minHeight,
|
|
113
|
+
maxHeight,
|
|
114
|
+
// Appearance
|
|
115
|
+
radius,
|
|
116
|
+
bordered,
|
|
117
|
+
// Interactivity
|
|
118
|
+
onPress,
|
|
119
|
+
href,
|
|
120
|
+
disabled = false,
|
|
121
|
+
// Style override
|
|
122
|
+
style,
|
|
123
|
+
}: FrameProps) {
|
|
124
|
+
const { config, mode, theme: providerTheme } = useNewtoneTheme();
|
|
125
|
+
const parentFrameCtx = useFrameContext();
|
|
126
|
+
|
|
127
|
+
// Resolve theme: prop > parent Frame > NewtoneProvider
|
|
128
|
+
const resolvedTheme = theme ?? parentFrameCtx?.theme ?? providerTheme;
|
|
129
|
+
|
|
130
|
+
// Resolve frame elevation (user-facing, for shadow/style decisions)
|
|
131
|
+
const resolvedFrameElevation: FrameElevation = elevation ?? 0;
|
|
132
|
+
|
|
133
|
+
// Map to internal elevation level for token computation
|
|
134
|
+
const resolvedElevation: ElevationLevel = elevation !== undefined
|
|
135
|
+
? toElevationLevel(elevation)
|
|
136
|
+
: parentFrameCtx?.elevation ?? 1;
|
|
137
|
+
|
|
138
|
+
// Compute tokens directly (not useTokens) to avoid reading our own FrameContext
|
|
139
|
+
const tokens = useMemo(() => {
|
|
140
|
+
const themeMapping = config.themes[resolvedTheme];
|
|
141
|
+
return computeTokens(
|
|
142
|
+
config.colorSystem,
|
|
143
|
+
mode,
|
|
144
|
+
themeMapping,
|
|
145
|
+
resolvedElevation,
|
|
146
|
+
config.elevation.offsets,
|
|
147
|
+
config.spacing,
|
|
148
|
+
config.radius,
|
|
149
|
+
config.typography,
|
|
150
|
+
config.icons,
|
|
151
|
+
);
|
|
152
|
+
}, [config, mode, resolvedTheme, resolvedElevation]);
|
|
153
|
+
|
|
154
|
+
const styles = useMemo(
|
|
155
|
+
() => getFrameStyles({
|
|
156
|
+
tokens,
|
|
157
|
+
frameElevation: resolvedFrameElevation,
|
|
158
|
+
layout,
|
|
159
|
+
direction,
|
|
160
|
+
wrap,
|
|
161
|
+
reverse,
|
|
162
|
+
columns,
|
|
163
|
+
rows,
|
|
164
|
+
align,
|
|
165
|
+
justify,
|
|
166
|
+
padding,
|
|
167
|
+
gap,
|
|
168
|
+
width,
|
|
169
|
+
height,
|
|
170
|
+
minWidth,
|
|
171
|
+
maxWidth,
|
|
172
|
+
minHeight,
|
|
173
|
+
maxHeight,
|
|
174
|
+
radius,
|
|
175
|
+
bordered,
|
|
176
|
+
disabled,
|
|
177
|
+
}),
|
|
178
|
+
[
|
|
179
|
+
tokens, resolvedFrameElevation,
|
|
180
|
+
layout, direction, wrap, reverse, columns, rows,
|
|
181
|
+
align, justify, padding, gap,
|
|
182
|
+
width, height, minWidth, maxWidth, minHeight, maxHeight,
|
|
183
|
+
radius, bordered, disabled,
|
|
184
|
+
],
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Memoize context value
|
|
188
|
+
const contextValue = useMemo(
|
|
189
|
+
() => ({ theme: resolvedTheme, elevation: resolvedElevation }),
|
|
190
|
+
[resolvedTheme, resolvedElevation],
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Build the style array with web-specific overrides
|
|
194
|
+
const webOverrides: ViewStyle[] = [];
|
|
195
|
+
if (styles.gridWebStyle) {
|
|
196
|
+
webOverrides.push(styles.gridWebStyle as unknown as ViewStyle);
|
|
197
|
+
}
|
|
198
|
+
if (styles.insetBoxShadow) {
|
|
199
|
+
webOverrides.push({ boxShadow: styles.insetBoxShadow } as unknown as ViewStyle);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const userStyles = Array.isArray(style) ? style : style ? [style] : [];
|
|
203
|
+
|
|
204
|
+
const isInteractive = onPress !== undefined || href !== undefined;
|
|
205
|
+
|
|
206
|
+
// Wrap raw string/number children in <Text> for RN compatibility
|
|
207
|
+
const textStyle = useMemo<TextStyle>(
|
|
208
|
+
() => ({
|
|
209
|
+
color: srgbToHex(tokens.textPrimary.srgb),
|
|
210
|
+
fontSize: tokens.typography.size.base,
|
|
211
|
+
fontFamily: tokens.typography.fonts.default,
|
|
212
|
+
lineHeight: tokens.typography.size.base * tokens.typography.lineHeight.normal,
|
|
213
|
+
}),
|
|
214
|
+
[tokens],
|
|
215
|
+
);
|
|
216
|
+
const wrappedChildren = wrapTextChildren(children, textStyle);
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<FrameContext.Provider value={contextValue}>
|
|
220
|
+
{isInteractive ? (
|
|
221
|
+
<Pressable
|
|
222
|
+
onPress={onPress}
|
|
223
|
+
disabled={disabled}
|
|
224
|
+
// react-native-web renders Pressable with href as <a>
|
|
225
|
+
{...(href ? { href, accessibilityRole: 'link' as const } : { accessibilityRole: 'button' as const })}
|
|
226
|
+
style={({ pressed }) => [
|
|
227
|
+
styles.container,
|
|
228
|
+
pressed && !disabled && styles.pressed,
|
|
229
|
+
...webOverrides,
|
|
230
|
+
...userStyles,
|
|
231
|
+
]}
|
|
232
|
+
>
|
|
233
|
+
{wrappedChildren}
|
|
234
|
+
</Pressable>
|
|
235
|
+
) : (
|
|
236
|
+
<View style={[styles.container, ...webOverrides, ...userStyles]}>
|
|
237
|
+
{wrappedChildren}
|
|
238
|
+
</View>
|
|
239
|
+
)}
|
|
240
|
+
</FrameContext.Provider>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { ViewStyle, GestureResponderEvent } from 'react-native';
|
|
2
|
+
import type { ThemeName, FrameElevation } from '../theme/types';
|
|
3
|
+
|
|
4
|
+
// ── Spacing Types ──────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
/** Design system spacing tokens */
|
|
7
|
+
export type SpacingToken = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
|
|
8
|
+
|
|
9
|
+
/** A spacing value: either a design token name or a pixel number */
|
|
10
|
+
export type SpacingValue = SpacingToken | number;
|
|
11
|
+
|
|
12
|
+
/** Per-side spacing */
|
|
13
|
+
export interface SpacingSides {
|
|
14
|
+
readonly top?: SpacingValue;
|
|
15
|
+
readonly right?: SpacingValue;
|
|
16
|
+
readonly bottom?: SpacingValue;
|
|
17
|
+
readonly left?: SpacingValue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Axis-based spacing shorthand */
|
|
21
|
+
export interface SpacingAxes {
|
|
22
|
+
readonly x?: SpacingValue;
|
|
23
|
+
readonly y?: SpacingValue;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Padding: uniform, axis shorthand, or per-side */
|
|
27
|
+
export type PaddingProp = SpacingValue | SpacingAxes | SpacingSides;
|
|
28
|
+
|
|
29
|
+
/** Gap axis shorthand */
|
|
30
|
+
export interface GapAxes {
|
|
31
|
+
readonly row?: SpacingValue;
|
|
32
|
+
readonly column?: SpacingValue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Gap: uniform or row/column */
|
|
36
|
+
export type GapProp = SpacingValue | GapAxes;
|
|
37
|
+
|
|
38
|
+
// ── Radius Types ───────────────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
/** Design system radius tokens */
|
|
41
|
+
export type RadiusToken = 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'pill';
|
|
42
|
+
|
|
43
|
+
/** A radius value: either a design token name or a pixel number */
|
|
44
|
+
export type RadiusValue = RadiusToken | number;
|
|
45
|
+
|
|
46
|
+
/** Per-corner radius */
|
|
47
|
+
export interface RadiusCorners {
|
|
48
|
+
readonly topLeft?: RadiusValue;
|
|
49
|
+
readonly topRight?: RadiusValue;
|
|
50
|
+
readonly bottomLeft?: RadiusValue;
|
|
51
|
+
readonly bottomRight?: RadiusValue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Radius: uniform or per-corner */
|
|
55
|
+
export type RadiusProp = RadiusValue | RadiusCorners;
|
|
56
|
+
|
|
57
|
+
// ── Sizing Types ───────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Figma-like sizing mode:
|
|
61
|
+
* - 'hug': shrink to content (default)
|
|
62
|
+
* - 'fill': expand to fill available space
|
|
63
|
+
* - number: fixed pixel dimension
|
|
64
|
+
*/
|
|
65
|
+
export type SizingMode = 'hug' | 'fill' | number;
|
|
66
|
+
|
|
67
|
+
// ── Layout Types ───────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/** Layout mode */
|
|
70
|
+
export type LayoutMode = 'flex' | 'grid';
|
|
71
|
+
|
|
72
|
+
/** Flex direction (Figma-like naming) */
|
|
73
|
+
export type Direction = 'horizontal' | 'vertical';
|
|
74
|
+
|
|
75
|
+
/** Cross-axis alignment */
|
|
76
|
+
export type Alignment = 'start' | 'center' | 'end' | 'stretch' | 'baseline';
|
|
77
|
+
|
|
78
|
+
/** Main-axis justification */
|
|
79
|
+
export type Justification = 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
|
|
80
|
+
|
|
81
|
+
// ── Props ──────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
export interface FrameProps {
|
|
84
|
+
/** Child elements that inherit this Frame's theme and elevation context */
|
|
85
|
+
readonly children: React.ReactNode;
|
|
86
|
+
|
|
87
|
+
// ── Theme & Elevation ──
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Theme override for this frame and all descendants.
|
|
91
|
+
* When omitted, inherits from parent Frame or NewtoneProvider.
|
|
92
|
+
*/
|
|
93
|
+
readonly theme?: ThemeName;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Surface elevation: -2 (deeply sunken with inset shadow), -1 (sunken),
|
|
97
|
+
* 0 (default), 1 (elevated), 2 (prominently elevated with drop shadow).
|
|
98
|
+
* When omitted, inherits from parent Frame or defaults to 0.
|
|
99
|
+
*/
|
|
100
|
+
readonly elevation?: FrameElevation;
|
|
101
|
+
|
|
102
|
+
// ── Layout ──
|
|
103
|
+
|
|
104
|
+
/** Layout mode. @default 'flex' */
|
|
105
|
+
readonly layout?: LayoutMode;
|
|
106
|
+
|
|
107
|
+
/** Flex direction. @default 'vertical' */
|
|
108
|
+
readonly direction?: Direction;
|
|
109
|
+
|
|
110
|
+
/** Enable flex wrap. @default false */
|
|
111
|
+
readonly wrap?: boolean;
|
|
112
|
+
|
|
113
|
+
/** Reverse flex direction. @default false */
|
|
114
|
+
readonly reverse?: boolean;
|
|
115
|
+
|
|
116
|
+
/** CSS Grid column count (web-only, requires layout='grid') */
|
|
117
|
+
readonly columns?: number;
|
|
118
|
+
|
|
119
|
+
/** CSS Grid row count (web-only, requires layout='grid') */
|
|
120
|
+
readonly rows?: number;
|
|
121
|
+
|
|
122
|
+
// ── Alignment ──
|
|
123
|
+
|
|
124
|
+
/** Cross-axis alignment of children */
|
|
125
|
+
readonly align?: Alignment;
|
|
126
|
+
|
|
127
|
+
/** Main-axis justification of children */
|
|
128
|
+
readonly justify?: Justification;
|
|
129
|
+
|
|
130
|
+
// ── Spacing ──
|
|
131
|
+
|
|
132
|
+
/** Padding: uniform token/number, axis shorthand, or per-side */
|
|
133
|
+
readonly padding?: PaddingProp;
|
|
134
|
+
|
|
135
|
+
/** Gap between children: uniform or row/column */
|
|
136
|
+
readonly gap?: GapProp;
|
|
137
|
+
|
|
138
|
+
// ── Sizing ──
|
|
139
|
+
|
|
140
|
+
/** Width: 'hug' (content-sized), 'fill' (expand), or fixed number */
|
|
141
|
+
readonly width?: SizingMode;
|
|
142
|
+
|
|
143
|
+
/** Height: 'hug' (content-sized), 'fill' (expand), or fixed number */
|
|
144
|
+
readonly height?: SizingMode;
|
|
145
|
+
|
|
146
|
+
/** Minimum width in pixels */
|
|
147
|
+
readonly minWidth?: number;
|
|
148
|
+
|
|
149
|
+
/** Maximum width in pixels */
|
|
150
|
+
readonly maxWidth?: number;
|
|
151
|
+
|
|
152
|
+
/** Minimum height in pixels */
|
|
153
|
+
readonly minHeight?: number;
|
|
154
|
+
|
|
155
|
+
/** Maximum height in pixels */
|
|
156
|
+
readonly maxHeight?: number;
|
|
157
|
+
|
|
158
|
+
// ── Appearance ──
|
|
159
|
+
|
|
160
|
+
/** Border radius: token, number, or per-corner object */
|
|
161
|
+
readonly radius?: RadiusProp;
|
|
162
|
+
|
|
163
|
+
/** Show 1px border using theme border color. @default false */
|
|
164
|
+
readonly bordered?: boolean;
|
|
165
|
+
|
|
166
|
+
// ── Interactivity ──
|
|
167
|
+
|
|
168
|
+
/** Press handler — switches rendering from View to Pressable */
|
|
169
|
+
readonly onPress?: (event: GestureResponderEvent) => void;
|
|
170
|
+
|
|
171
|
+
/** Navigation URL — switches rendering to Pressable with link role */
|
|
172
|
+
readonly href?: string;
|
|
173
|
+
|
|
174
|
+
/** Disable interaction. @default false */
|
|
175
|
+
readonly disabled?: boolean;
|
|
176
|
+
|
|
177
|
+
// ── Style override ──
|
|
178
|
+
|
|
179
|
+
/** Custom style overrides (applied last) */
|
|
180
|
+
readonly style?: ViewStyle | ViewStyle[];
|
|
181
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import type { ViewStyle } from 'react-native';
|
|
2
|
+
import type { ResolvedTokens } from '../tokens/types';
|
|
3
|
+
import type {
|
|
4
|
+
SpacingValue,
|
|
5
|
+
SpacingAxes,
|
|
6
|
+
SpacingSides,
|
|
7
|
+
PaddingProp,
|
|
8
|
+
GapProp,
|
|
9
|
+
RadiusValue,
|
|
10
|
+
RadiusProp,
|
|
11
|
+
SizingMode,
|
|
12
|
+
Direction,
|
|
13
|
+
Alignment,
|
|
14
|
+
Justification,
|
|
15
|
+
} from './Frame.types';
|
|
16
|
+
|
|
17
|
+
// ── Spacing ──────────────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/** Resolve a spacing token or number to a pixel value */
|
|
20
|
+
export function resolveSpacing(
|
|
21
|
+
value: SpacingValue,
|
|
22
|
+
tokens: ResolvedTokens,
|
|
23
|
+
): number {
|
|
24
|
+
if (typeof value === 'number') return value;
|
|
25
|
+
return tokens.spacing[value];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Resolve padding prop to per-side pixel values */
|
|
29
|
+
export function resolvePadding(
|
|
30
|
+
prop: PaddingProp,
|
|
31
|
+
tokens: ResolvedTokens,
|
|
32
|
+
): { readonly top: number; readonly right: number; readonly bottom: number; readonly left: number } {
|
|
33
|
+
if (typeof prop === 'string' || typeof prop === 'number') {
|
|
34
|
+
const px = resolveSpacing(prop, tokens);
|
|
35
|
+
return { top: px, right: px, bottom: px, left: px };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Axis shorthand: { x?, y? }
|
|
39
|
+
if ('x' in prop || 'y' in prop) {
|
|
40
|
+
const axes = prop as SpacingAxes;
|
|
41
|
+
const x = axes.x !== undefined ? resolveSpacing(axes.x, tokens) : 0;
|
|
42
|
+
const y = axes.y !== undefined ? resolveSpacing(axes.y, tokens) : 0;
|
|
43
|
+
return { top: y, right: x, bottom: y, left: x };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Per-side: { top?, right?, bottom?, left? }
|
|
47
|
+
const sides = prop as SpacingSides;
|
|
48
|
+
return {
|
|
49
|
+
top: sides.top !== undefined ? resolveSpacing(sides.top, tokens) : 0,
|
|
50
|
+
right: sides.right !== undefined ? resolveSpacing(sides.right, tokens) : 0,
|
|
51
|
+
bottom: sides.bottom !== undefined ? resolveSpacing(sides.bottom, tokens) : 0,
|
|
52
|
+
left: sides.left !== undefined ? resolveSpacing(sides.left, tokens) : 0,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Resolve gap prop to rowGap/columnGap pixel values */
|
|
57
|
+
export function resolveGap(
|
|
58
|
+
prop: GapProp,
|
|
59
|
+
tokens: ResolvedTokens,
|
|
60
|
+
): { readonly rowGap: number; readonly columnGap: number } {
|
|
61
|
+
if (typeof prop === 'string' || typeof prop === 'number') {
|
|
62
|
+
const px = resolveSpacing(prop, tokens);
|
|
63
|
+
return { rowGap: px, columnGap: px };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// { row?, column? }
|
|
67
|
+
return {
|
|
68
|
+
rowGap: prop.row !== undefined ? resolveSpacing(prop.row, tokens) : 0,
|
|
69
|
+
columnGap: prop.column !== undefined ? resolveSpacing(prop.column, tokens) : 0,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ── Radius ───────────────────────────────────────────────────────
|
|
74
|
+
|
|
75
|
+
/** Resolve a radius token or number to a pixel value */
|
|
76
|
+
export function resolveRadius(
|
|
77
|
+
value: RadiusValue,
|
|
78
|
+
tokens: ResolvedTokens,
|
|
79
|
+
): number {
|
|
80
|
+
if (typeof value === 'number') return value;
|
|
81
|
+
return tokens.radius[value];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Resolved per-corner radius values */
|
|
85
|
+
export interface ResolvedCorners {
|
|
86
|
+
readonly topLeft: number;
|
|
87
|
+
readonly topRight: number;
|
|
88
|
+
readonly bottomLeft: number;
|
|
89
|
+
readonly bottomRight: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Resolve radius prop to per-corner pixel values */
|
|
93
|
+
export function resolveRadiusCorners(
|
|
94
|
+
prop: RadiusProp,
|
|
95
|
+
tokens: ResolvedTokens,
|
|
96
|
+
): ResolvedCorners {
|
|
97
|
+
if (typeof prop === 'string' || typeof prop === 'number') {
|
|
98
|
+
const px = resolveRadius(prop, tokens);
|
|
99
|
+
return { topLeft: px, topRight: px, bottomLeft: px, bottomRight: px };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Per-corner object
|
|
103
|
+
return {
|
|
104
|
+
topLeft: prop.topLeft !== undefined ? resolveRadius(prop.topLeft, tokens) : 0,
|
|
105
|
+
topRight: prop.topRight !== undefined ? resolveRadius(prop.topRight, tokens) : 0,
|
|
106
|
+
bottomLeft: prop.bottomLeft !== undefined ? resolveRadius(prop.bottomLeft, tokens) : 0,
|
|
107
|
+
bottomRight: prop.bottomRight !== undefined ? resolveRadius(prop.bottomRight, tokens) : 0,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Check if any corner has a positive radius (for auto-clip) */
|
|
112
|
+
export function hasPositiveRadius(corners: ResolvedCorners): boolean {
|
|
113
|
+
return corners.topLeft > 0
|
|
114
|
+
|| corners.topRight > 0
|
|
115
|
+
|| corners.bottomLeft > 0
|
|
116
|
+
|| corners.bottomRight > 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Sizing ───────────────────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
/** Resolve width/height sizing modes to ViewStyle properties */
|
|
122
|
+
export function resolveSizing(
|
|
123
|
+
width: SizingMode | undefined,
|
|
124
|
+
height: SizingMode | undefined,
|
|
125
|
+
): ViewStyle {
|
|
126
|
+
const style: Record<string, unknown> = {};
|
|
127
|
+
|
|
128
|
+
if (width !== undefined) {
|
|
129
|
+
if (width === 'fill') {
|
|
130
|
+
style.flexGrow = 1;
|
|
131
|
+
style.width = '100%';
|
|
132
|
+
} else if (typeof width === 'number') {
|
|
133
|
+
style.width = width;
|
|
134
|
+
}
|
|
135
|
+
// 'hug' = default RN behavior, no explicit dimension
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (height !== undefined) {
|
|
139
|
+
if (height === 'fill') {
|
|
140
|
+
style.flexGrow = style.flexGrow === 1 ? 1 : 1;
|
|
141
|
+
style.height = '100%';
|
|
142
|
+
} else if (typeof height === 'number') {
|
|
143
|
+
style.height = height;
|
|
144
|
+
}
|
|
145
|
+
// 'hug' = default RN behavior
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return style as ViewStyle;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ── Layout ───────────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
const ALIGN_MAP: Record<Alignment, ViewStyle['alignItems']> = {
|
|
154
|
+
start: 'flex-start',
|
|
155
|
+
center: 'center',
|
|
156
|
+
end: 'flex-end',
|
|
157
|
+
stretch: 'stretch',
|
|
158
|
+
baseline: 'baseline',
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const JUSTIFY_MAP: Record<Justification, ViewStyle['justifyContent']> = {
|
|
162
|
+
start: 'flex-start',
|
|
163
|
+
center: 'center',
|
|
164
|
+
end: 'flex-end',
|
|
165
|
+
between: 'space-between',
|
|
166
|
+
around: 'space-around',
|
|
167
|
+
evenly: 'space-evenly',
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/** Resolve alignment prop to ViewStyle alignItems */
|
|
171
|
+
export function resolveAlignment(align: Alignment): ViewStyle['alignItems'] {
|
|
172
|
+
return ALIGN_MAP[align];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Resolve justification prop to ViewStyle justifyContent */
|
|
176
|
+
export function resolveJustification(justify: Justification): ViewStyle['justifyContent'] {
|
|
177
|
+
return JUSTIFY_MAP[justify];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Resolve direction + reverse to ViewStyle flexDirection */
|
|
181
|
+
export function resolveFlexDirection(
|
|
182
|
+
direction: Direction,
|
|
183
|
+
reverse: boolean,
|
|
184
|
+
): ViewStyle['flexDirection'] {
|
|
185
|
+
if (direction === 'horizontal') {
|
|
186
|
+
return reverse ? 'row-reverse' : 'row';
|
|
187
|
+
}
|
|
188
|
+
return reverse ? 'column-reverse' : 'column';
|
|
189
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export { Frame } from './Frame';
|
|
2
|
+
export type {
|
|
3
|
+
FrameProps,
|
|
4
|
+
SpacingToken,
|
|
5
|
+
SpacingValue,
|
|
6
|
+
SpacingSides,
|
|
7
|
+
SpacingAxes,
|
|
8
|
+
PaddingProp,
|
|
9
|
+
GapAxes,
|
|
10
|
+
GapProp,
|
|
11
|
+
RadiusToken,
|
|
12
|
+
RadiusValue,
|
|
13
|
+
RadiusCorners,
|
|
14
|
+
RadiusProp,
|
|
15
|
+
SizingMode,
|
|
16
|
+
LayoutMode,
|
|
17
|
+
Direction,
|
|
18
|
+
Alignment,
|
|
19
|
+
Justification,
|
|
20
|
+
} from './Frame.types';
|
|
21
|
+
export type { ResolvedCorners } from './Frame.utils';
|