@newtonedev/components 0.1.2 → 0.1.3
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/Popover/Popover.d.ts.map +1 -1
- package/dist/Popover/Popover.styles.d.ts +64 -1
- package/dist/Popover/Popover.styles.d.ts.map +1 -1
- package/dist/Select/Select.d.ts.map +1 -1
- package/dist/_COMPONENT_TEMPLATE/ComponentName.d.ts +70 -0
- package/dist/_COMPONENT_TEMPLATE/ComponentName.d.ts.map +1 -0
- package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts +22 -0
- package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts.map +1 -0
- package/dist/_COMPONENT_TEMPLATE/ComponentName.types.d.ts +45 -0
- package/dist/_COMPONENT_TEMPLATE/ComponentName.types.d.ts.map +1 -0
- package/dist/_COMPONENT_TEMPLATE/index.d.ts +3 -0
- package/dist/_COMPONENT_TEMPLATE/index.d.ts.map +1 -0
- package/dist/fonts/GoogleFontLoader.d.ts.map +1 -1
- package/dist/fonts/IconFontLoader.d.ts.map +1 -1
- package/dist/index.cjs +371 -74
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +370 -76
- package/dist/index.js.map +1 -1
- package/dist/{Frame → primitives/Frame}/Frame.d.ts +1 -1
- package/dist/primitives/Frame/Frame.d.ts.map +1 -0
- package/dist/{Frame → primitives/Frame}/Frame.styles.d.ts +11 -2
- package/dist/primitives/Frame/Frame.styles.d.ts.map +1 -0
- package/dist/primitives/Frame/Frame.types.d.ts +240 -0
- package/dist/primitives/Frame/Frame.types.d.ts.map +1 -0
- package/dist/{Frame → primitives/Frame}/Frame.utils.d.ts +12 -12
- package/dist/primitives/Frame/Frame.utils.d.ts.map +1 -0
- package/dist/primitives/Frame/index.d.ts.map +1 -0
- package/dist/primitives/Icon/Icon.d.ts +17 -0
- package/dist/primitives/Icon/Icon.d.ts.map +1 -0
- package/dist/primitives/Icon/Icon.types.d.ts +55 -0
- package/dist/primitives/Icon/Icon.types.d.ts.map +1 -0
- package/dist/primitives/Icon/index.d.ts +3 -0
- package/dist/primitives/Icon/index.d.ts.map +1 -0
- package/dist/primitives/Text/Text.d.ts +17 -0
- package/dist/primitives/Text/Text.d.ts.map +1 -0
- package/dist/primitives/Text/Text.types.d.ts +85 -0
- package/dist/primitives/Text/Text.types.d.ts.map +1 -0
- package/dist/primitives/Text/index.d.ts +3 -0
- package/dist/primitives/Text/index.d.ts.map +1 -0
- package/dist/primitives/Wrapper/Wrapper.d.ts +29 -0
- package/dist/primitives/Wrapper/Wrapper.d.ts.map +1 -0
- package/dist/primitives/Wrapper/Wrapper.styles.d.ts +28 -0
- package/dist/primitives/Wrapper/Wrapper.styles.d.ts.map +1 -0
- package/dist/primitives/Wrapper/Wrapper.types.d.ts +113 -0
- package/dist/primitives/Wrapper/Wrapper.types.d.ts.map +1 -0
- package/dist/primitives/Wrapper/index.d.ts +3 -0
- package/dist/primitives/Wrapper/index.d.ts.map +1 -0
- package/dist/primitives/index.d.ts +12 -0
- package/dist/primitives/index.d.ts.map +1 -0
- package/dist/primitives/useFocusVisible.d.ts +29 -0
- package/dist/primitives/useFocusVisible.d.ts.map +1 -0
- package/dist/theme/defaults.d.ts.map +1 -1
- package/dist/theme/types.d.ts +13 -6
- package/dist/theme/types.d.ts.map +1 -1
- package/dist/tokens/computeTokens.d.ts +13 -6
- package/dist/tokens/computeTokens.d.ts.map +1 -1
- package/dist/tokens/types.d.ts +16 -7
- package/dist/tokens/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/Button/Button.styles.ts +9 -9
- package/src/Button/Button.tsx +1 -1
- package/src/Card/Card.styles.ts +1 -1
- package/src/ColorScaleSlider/ColorScaleSlider.styles.ts +1 -1
- package/src/HueSlider/HueSlider.styles.ts +1 -1
- package/src/Popover/Popover.styles.ts +5 -1
- package/src/Popover/Popover.tsx +3 -1
- package/src/Select/Select.styles.ts +9 -9
- package/src/Select/Select.tsx +2 -3
- package/src/Select/SelectOption.tsx +6 -6
- package/src/Slider/Slider.styles.ts +1 -1
- package/src/TextInput/TextInput.styles.ts +3 -3
- package/src/Toggle/Toggle.styles.ts +1 -1
- package/src/_COMPONENT_TEMPLATE/ComponentName.styles.ts +29 -0
- package/src/_COMPONENT_TEMPLATE/ComponentName.tsx +106 -0
- package/src/_COMPONENT_TEMPLATE/ComponentName.types.ts +86 -0
- package/src/_COMPONENT_TEMPLATE/index.ts +2 -0
- package/src/fonts/GoogleFontLoader.tsx +2 -0
- package/src/fonts/IconFontLoader.tsx +2 -0
- package/src/index.ts +22 -5
- package/src/{Frame → primitives/Frame}/Frame.styles.ts +46 -9
- package/src/{Frame → primitives/Frame}/Frame.tsx +90 -16
- package/src/primitives/Frame/Frame.types.ts +315 -0
- package/src/{Frame → primitives/Frame}/Frame.utils.ts +56 -20
- package/src/primitives/Icon/Icon.tsx +89 -0
- package/src/primitives/Icon/Icon.types.ts +70 -0
- package/src/primitives/Icon/index.ts +2 -0
- package/src/primitives/Text/Text.tsx +90 -0
- package/src/primitives/Text/Text.types.ts +108 -0
- package/src/primitives/Text/index.ts +10 -0
- package/src/primitives/Wrapper/Wrapper.styles.ts +113 -0
- package/src/primitives/Wrapper/Wrapper.tsx +104 -0
- package/src/primitives/Wrapper/Wrapper.types.ts +149 -0
- package/src/primitives/Wrapper/index.ts +2 -0
- package/src/primitives/index.ts +46 -0
- package/src/primitives/useFocusVisible.ts +102 -0
- package/src/theme/defaults.ts +13 -6
- package/src/theme/types.ts +13 -6
- package/src/tokens/computeTokens.ts +1 -1
- package/src/tokens/types.ts +16 -7
- package/dist/Frame/Frame.d.ts.map +0 -1
- package/dist/Frame/Frame.styles.d.ts.map +0 -1
- package/dist/Frame/Frame.types.d.ts +0 -115
- package/dist/Frame/Frame.types.d.ts.map +0 -1
- package/dist/Frame/Frame.utils.d.ts.map +0 -1
- package/dist/Frame/index.d.ts.map +0 -1
- package/dist/Icon/Icon.d.ts +0 -36
- package/dist/Icon/Icon.d.ts.map +0 -1
- package/src/Frame/Frame.types.ts +0 -181
- package/src/Icon/Icon.tsx +0 -76
- /package/dist/{Frame → primitives/Frame}/index.d.ts +0 -0
- /package/src/{Frame → primitives/Frame}/index.ts +0 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { Text as RNText, TextStyle } from 'react-native';
|
|
2
|
+
import type { ElevationLevel } from '../../theme/types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Props for Icon — Material Symbols icon with variable font support.
|
|
6
|
+
*
|
|
7
|
+
* Uses the icon variant, weight, and grade from the theme config.
|
|
8
|
+
* Per-instance control over size, fill, and color.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <Icon name="home" />
|
|
13
|
+
* <Icon name="settings" size={24} fill={1} />
|
|
14
|
+
* <Icon name="check" color="#00ff00" />
|
|
15
|
+
* <Icon name="delete" size={20} onPress={() => handleDelete()} />
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* @see {@link https://fonts.google.com/icons Material Symbols icon reference}
|
|
19
|
+
*/
|
|
20
|
+
export interface IconProps {
|
|
21
|
+
/**
|
|
22
|
+
* Material Symbols ligature name.
|
|
23
|
+
*
|
|
24
|
+
* @example `'home'`, `'settings'`, `'check'`, `'expand_more'`, `'delete'`, `'add'`, `'search'`
|
|
25
|
+
* @see {@link https://fonts.google.com/icons Browse all icons}
|
|
26
|
+
*/
|
|
27
|
+
readonly name: string;
|
|
28
|
+
|
|
29
|
+
/** Font size in pixels. @default tokens.typography.size.base (~16px) */
|
|
30
|
+
readonly size?: number;
|
|
31
|
+
|
|
32
|
+
/** Optical size for variable font axis. Adjusts stroke weight for readability at small sizes. @default same as size */
|
|
33
|
+
readonly opticalSize?: number;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fill state for the icon.
|
|
37
|
+
*
|
|
38
|
+
* - `0` — Outlined (default)
|
|
39
|
+
* - `1` — Filled (solid)
|
|
40
|
+
*/
|
|
41
|
+
readonly fill?: 0 | 1;
|
|
42
|
+
|
|
43
|
+
/** Icon color as hex string. @default tokens.textPrimary */
|
|
44
|
+
readonly color?: string;
|
|
45
|
+
|
|
46
|
+
/** Elevation level for token computation. @default 1 */
|
|
47
|
+
readonly elevation?: ElevationLevel;
|
|
48
|
+
|
|
49
|
+
/** Additional styles applied to the icon Text element. */
|
|
50
|
+
readonly style?: TextStyle;
|
|
51
|
+
|
|
52
|
+
/** Press handler — makes the icon tappable. */
|
|
53
|
+
readonly onPress?: () => void;
|
|
54
|
+
|
|
55
|
+
// ── Accessibility ──
|
|
56
|
+
|
|
57
|
+
/** Label read by screen readers. When omitted, the icon is treated as decorative (hidden from assistive technology). */
|
|
58
|
+
readonly accessibilityLabel?: string;
|
|
59
|
+
|
|
60
|
+
// ── Testing & Platform ──
|
|
61
|
+
|
|
62
|
+
/** Test identifier — maps to `data-testid` on web. Used by testing libraries. */
|
|
63
|
+
readonly testID?: string;
|
|
64
|
+
|
|
65
|
+
/** DOM id — maps to `id` attribute on web. Used for anchors and scroll targets. */
|
|
66
|
+
readonly nativeID?: string;
|
|
67
|
+
|
|
68
|
+
/** Ref to the underlying Text element. */
|
|
69
|
+
readonly ref?: React.Ref<RNText>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Text as RNText } from 'react-native';
|
|
3
|
+
import type { TextStyle } from 'react-native';
|
|
4
|
+
import { srgbToHex } from 'newtone';
|
|
5
|
+
import { useTokens } from '../../tokens/useTokens';
|
|
6
|
+
import type { TextProps, TextColor } from './Text.types';
|
|
7
|
+
|
|
8
|
+
// Maps user-friendly color names to the actual token keys in the theme.
|
|
9
|
+
// Example: color="primary" → looks up tokens.textPrimary to get the hex color.
|
|
10
|
+
const COLOR_MAP: Record<TextColor, 'textPrimary' | 'textSecondary' | 'interactive'> = {
|
|
11
|
+
primary: 'textPrimary',
|
|
12
|
+
secondary: 'textSecondary',
|
|
13
|
+
interactive: 'interactive',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Token-aware text primitive.
|
|
18
|
+
*
|
|
19
|
+
* Reads typography tokens from the nearest NewtoneProvider and maps
|
|
20
|
+
* semantic props (size, weight, color, font, lineHeight) to resolved values.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```tsx
|
|
24
|
+
* <Text>Body text</Text>
|
|
25
|
+
* <Text size="sm" weight="semibold" color="secondary">Label</Text>
|
|
26
|
+
* <Text size="xl" font="display">Heading</Text>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function Text({
|
|
30
|
+
children,
|
|
31
|
+
size = 'base',
|
|
32
|
+
weight = 'regular',
|
|
33
|
+
color = 'primary',
|
|
34
|
+
font = 'default',
|
|
35
|
+
lineHeight = 'normal',
|
|
36
|
+
align,
|
|
37
|
+
numberOfLines,
|
|
38
|
+
elevation = 1,
|
|
39
|
+
style,
|
|
40
|
+
// Accessibility
|
|
41
|
+
accessibilityRole,
|
|
42
|
+
// Testing & platform
|
|
43
|
+
testID,
|
|
44
|
+
nativeID,
|
|
45
|
+
ref,
|
|
46
|
+
}: TextProps) {
|
|
47
|
+
// Get the current theme's design tokens (colors, fonts, spacing).
|
|
48
|
+
// The elevation parameter affects which background shade the tokens reference.
|
|
49
|
+
const tokens = useTokens(elevation);
|
|
50
|
+
|
|
51
|
+
// Build the text style from the theme tokens.
|
|
52
|
+
// Wrapped in useMemo so it only recalculates when the inputs actually change,
|
|
53
|
+
// instead of recalculating on every render (which would be wasteful).
|
|
54
|
+
const resolvedStyle = useMemo<TextStyle>(() => {
|
|
55
|
+
// Look up the pixel size for the chosen size token (e.g. 'base' → 16px).
|
|
56
|
+
const fontSize = tokens.typography.size[size];
|
|
57
|
+
return {
|
|
58
|
+
// Font family from the theme (e.g. 'default' → 'Inter', 'mono' → 'JetBrains Mono').
|
|
59
|
+
fontFamily: tokens.typography.fonts[font],
|
|
60
|
+
fontSize,
|
|
61
|
+
// Font weight is stored as a number (e.g. 400, 600) but React Native expects a string.
|
|
62
|
+
fontWeight: String(tokens.typography.weight[weight]) as TextStyle['fontWeight'],
|
|
63
|
+
// Convert the theme color from internal sRGB format to a hex string (e.g. '#1a1a1a').
|
|
64
|
+
color: srgbToHex(tokens[COLOR_MAP[color]].srgb),
|
|
65
|
+
// Line height = font size × multiplier (e.g. 16px × 1.5 = 24px line height).
|
|
66
|
+
lineHeight: fontSize * tokens.typography.lineHeight[lineHeight],
|
|
67
|
+
textAlign: align,
|
|
68
|
+
};
|
|
69
|
+
}, [tokens, size, weight, color, font, lineHeight, align]);
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<RNText
|
|
73
|
+
ref={ref}
|
|
74
|
+
testID={testID}
|
|
75
|
+
nativeID={nativeID}
|
|
76
|
+
accessibilityRole={accessibilityRole}
|
|
77
|
+
// If the user passed custom styles, merge them after the theme styles.
|
|
78
|
+
// The last style in the array wins, so user styles can override theme defaults.
|
|
79
|
+
// Normalize: the user can pass a single style or an array of styles.
|
|
80
|
+
style={style
|
|
81
|
+
? [resolvedStyle, ...(Array.isArray(style) ? style : [style])]
|
|
82
|
+
: resolvedStyle
|
|
83
|
+
}
|
|
84
|
+
// When set, text is cut off after this many lines with "..." at the end.
|
|
85
|
+
numberOfLines={numberOfLines}
|
|
86
|
+
>
|
|
87
|
+
{children}
|
|
88
|
+
</RNText>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { Text as RNText, TextStyle } from 'react-native';
|
|
2
|
+
import type { ElevationLevel } from '../../theme/types';
|
|
3
|
+
|
|
4
|
+
/** Typography size tokens — maps to `tokens.typography.size.*` pixel values. */
|
|
5
|
+
export type TextSize = 'xs' | 'sm' | 'base' | 'md' | 'lg' | 'xl' | 'xxl';
|
|
6
|
+
|
|
7
|
+
/** Typography weight tokens — maps to `tokens.typography.weight.*` numeric values. */
|
|
8
|
+
export type TextWeight = 'regular' | 'medium' | 'semibold' | 'bold';
|
|
9
|
+
|
|
10
|
+
/** Semantic text color tokens — resolved from the current theme's token palette. */
|
|
11
|
+
export type TextColor = 'primary' | 'secondary' | 'interactive';
|
|
12
|
+
|
|
13
|
+
/** Font family tokens — maps to `tokens.typography.fonts.*` font stacks. */
|
|
14
|
+
export type TextFont = 'default' | 'display' | 'mono';
|
|
15
|
+
|
|
16
|
+
/** Line height multiplier tokens — maps to `tokens.typography.lineHeight.*` values. */
|
|
17
|
+
export type TextLineHeight = 'tight' | 'normal' | 'relaxed';
|
|
18
|
+
|
|
19
|
+
/** Text alignment. */
|
|
20
|
+
export type TextAlign = 'left' | 'center' | 'right';
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Props for Text — themed typography component.
|
|
24
|
+
*
|
|
25
|
+
* All visual properties are token-driven: size, weight, color, font,
|
|
26
|
+
* and line height resolve from the current theme context. Use semantic
|
|
27
|
+
* tokens rather than raw values for consistency.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <Text>Default body text</Text>
|
|
32
|
+
* <Text size="lg" weight="bold">Heading</Text>
|
|
33
|
+
* <Text color="secondary" size="sm">Caption text</Text>
|
|
34
|
+
* <Text font="mono" size="sm">const x = 42;</Text>
|
|
35
|
+
* <Text numberOfLines={2}>Long text that truncates after two lines...</Text>
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export interface TextProps {
|
|
39
|
+
/**
|
|
40
|
+
* Text content. Accepts strings, numbers, and nested `<Text>` elements
|
|
41
|
+
* for inline formatting (e.g., bold spans within a paragraph).
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* <Text>Plain string</Text>
|
|
46
|
+
* <Text>Count: {42}</Text>
|
|
47
|
+
* <Text>Normal <Text weight="bold">bold</Text> normal</Text>
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
readonly children: React.ReactNode;
|
|
51
|
+
|
|
52
|
+
/** Typography size token. @default 'base' */
|
|
53
|
+
readonly size?: TextSize;
|
|
54
|
+
|
|
55
|
+
/** Font weight token. @default 'regular' */
|
|
56
|
+
readonly weight?: TextWeight;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Semantic color token.
|
|
60
|
+
*
|
|
61
|
+
* - `'primary'` — Main text color (default)
|
|
62
|
+
* - `'secondary'` — Subdued/muted text
|
|
63
|
+
* - `'interactive'` — Accent color for links/actions
|
|
64
|
+
*
|
|
65
|
+
* @default 'primary'
|
|
66
|
+
*/
|
|
67
|
+
readonly color?: TextColor;
|
|
68
|
+
|
|
69
|
+
/** Font family token. @default 'default' */
|
|
70
|
+
readonly font?: TextFont;
|
|
71
|
+
|
|
72
|
+
/** Line height multiplier token. @default 'normal' */
|
|
73
|
+
readonly lineHeight?: TextLineHeight;
|
|
74
|
+
|
|
75
|
+
/** Text alignment. */
|
|
76
|
+
readonly align?: TextAlign;
|
|
77
|
+
|
|
78
|
+
/** Maximum number of lines before truncation with ellipsis. When omitted, text wraps freely. */
|
|
79
|
+
readonly numberOfLines?: number;
|
|
80
|
+
|
|
81
|
+
/** Elevation level for token computation. @default 1 */
|
|
82
|
+
readonly elevation?: ElevationLevel;
|
|
83
|
+
|
|
84
|
+
/** Style overrides (applied last). Supports a single style or an array of styles. */
|
|
85
|
+
readonly style?: TextStyle | readonly TextStyle[];
|
|
86
|
+
|
|
87
|
+
// ── Accessibility ──
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Semantic role for screen readers.
|
|
91
|
+
*
|
|
92
|
+
* - `'header'` — Marks this as a heading (e.g. page title, section header)
|
|
93
|
+
* - `'text'` — Default text role (usually omitted)
|
|
94
|
+
* - `'link'` — Marks this as a navigable link
|
|
95
|
+
*/
|
|
96
|
+
readonly accessibilityRole?: 'header' | 'text' | 'link';
|
|
97
|
+
|
|
98
|
+
// ── Testing & Platform ──
|
|
99
|
+
|
|
100
|
+
/** Test identifier — maps to `data-testid` on web. Used by testing libraries. */
|
|
101
|
+
readonly testID?: string;
|
|
102
|
+
|
|
103
|
+
/** DOM id — maps to `id` attribute on web. Used for anchors and scroll targets. */
|
|
104
|
+
readonly nativeID?: string;
|
|
105
|
+
|
|
106
|
+
/** Ref to the underlying React Native Text element. */
|
|
107
|
+
readonly ref?: React.Ref<RNText>;
|
|
108
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { ViewStyle } from 'react-native';
|
|
2
|
+
import { StyleSheet } from 'react-native';
|
|
3
|
+
import type { ResolvedTokens } from '../../tokens/types';
|
|
4
|
+
import type {
|
|
5
|
+
Direction,
|
|
6
|
+
Alignment,
|
|
7
|
+
Justification,
|
|
8
|
+
PaddingProp,
|
|
9
|
+
GapProp,
|
|
10
|
+
SizingMode,
|
|
11
|
+
} from '../Frame/Frame.types';
|
|
12
|
+
import {
|
|
13
|
+
resolvePadding,
|
|
14
|
+
resolveGap,
|
|
15
|
+
resolveSizing,
|
|
16
|
+
resolveFlexDirection,
|
|
17
|
+
resolveAlignment,
|
|
18
|
+
resolveJustification,
|
|
19
|
+
} from '../Frame/Frame.utils';
|
|
20
|
+
|
|
21
|
+
export interface WrapperStyleInput {
|
|
22
|
+
readonly tokens: ResolvedTokens;
|
|
23
|
+
readonly direction?: Direction;
|
|
24
|
+
readonly wrap?: boolean;
|
|
25
|
+
readonly reverse?: boolean;
|
|
26
|
+
readonly align?: Alignment;
|
|
27
|
+
readonly justify?: Justification;
|
|
28
|
+
readonly padding?: PaddingProp;
|
|
29
|
+
readonly gap?: GapProp;
|
|
30
|
+
readonly width?: SizingMode;
|
|
31
|
+
readonly height?: SizingMode;
|
|
32
|
+
readonly minWidth?: number;
|
|
33
|
+
readonly maxWidth?: number;
|
|
34
|
+
readonly minHeight?: number;
|
|
35
|
+
readonly maxHeight?: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Build the layout styles for a Wrapper.
|
|
40
|
+
*
|
|
41
|
+
* Unlike Frame, this only handles structure (direction, spacing, sizing).
|
|
42
|
+
* No background, border, shadow, or radius — Wrapper is always invisible.
|
|
43
|
+
* Reuses the same helper functions that Frame uses for layout.
|
|
44
|
+
*/
|
|
45
|
+
export function getWrapperStyles(input: WrapperStyleInput): ViewStyle {
|
|
46
|
+
const {
|
|
47
|
+
tokens,
|
|
48
|
+
direction = 'vertical',
|
|
49
|
+
wrap = false,
|
|
50
|
+
reverse = false,
|
|
51
|
+
align,
|
|
52
|
+
justify,
|
|
53
|
+
padding,
|
|
54
|
+
gap,
|
|
55
|
+
width,
|
|
56
|
+
height,
|
|
57
|
+
minWidth,
|
|
58
|
+
maxWidth,
|
|
59
|
+
minHeight,
|
|
60
|
+
maxHeight,
|
|
61
|
+
} = input;
|
|
62
|
+
|
|
63
|
+
// We build styles as a plain object first, then validate it through
|
|
64
|
+
// StyleSheet.create() at the end. Using Record<string, unknown> allows us
|
|
65
|
+
// to set properties conditionally without TypeScript complaining.
|
|
66
|
+
const container: Record<string, unknown> = {};
|
|
67
|
+
|
|
68
|
+
// ── Layout ──
|
|
69
|
+
// Set whether children are arranged in a row or column (and optionally reversed).
|
|
70
|
+
container.flexDirection = resolveFlexDirection(direction, reverse);
|
|
71
|
+
// Allow children to flow onto multiple lines when they don't fit in one.
|
|
72
|
+
if (wrap) container.flexWrap = 'wrap';
|
|
73
|
+
|
|
74
|
+
// ── Alignment ──
|
|
75
|
+
// Cross-axis: how children are positioned perpendicular to the main direction.
|
|
76
|
+
if (align) container.alignItems = resolveAlignment(align);
|
|
77
|
+
// Main-axis: how children are distributed along the main direction.
|
|
78
|
+
if (justify) container.justifyContent = resolveJustification(justify);
|
|
79
|
+
|
|
80
|
+
// ── Padding ──
|
|
81
|
+
// Convert spacing tokens (like 'md') or pixel values into actual padding.
|
|
82
|
+
if (padding !== undefined) {
|
|
83
|
+
const p = resolvePadding(padding, tokens);
|
|
84
|
+
container.paddingTop = p.top;
|
|
85
|
+
container.paddingRight = p.right;
|
|
86
|
+
container.paddingBottom = p.bottom;
|
|
87
|
+
container.paddingLeft = p.left;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── Gap ──
|
|
91
|
+
// Space between children (row gap for vertical stacks, column gap for horizontal).
|
|
92
|
+
if (gap !== undefined) {
|
|
93
|
+
const g = resolveGap(gap, tokens);
|
|
94
|
+
container.rowGap = g.rowGap;
|
|
95
|
+
container.columnGap = g.columnGap;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// ── Sizing ──
|
|
99
|
+
// Merge width/height settings into the container.
|
|
100
|
+
// 'hug' = shrink to fit content, 'fill' = expand to fill parent, number = fixed pixels.
|
|
101
|
+
Object.assign(container, resolveSizing(width, height));
|
|
102
|
+
|
|
103
|
+
// ── Constraints ──
|
|
104
|
+
// Set min/max boundaries for width and height.
|
|
105
|
+
if (minWidth !== undefined) container.minWidth = minWidth;
|
|
106
|
+
if (maxWidth !== undefined) container.maxWidth = maxWidth;
|
|
107
|
+
if (minHeight !== undefined) container.minHeight = minHeight;
|
|
108
|
+
if (maxHeight !== undefined) container.maxHeight = maxHeight;
|
|
109
|
+
|
|
110
|
+
// Pass through StyleSheet.create() to validate and optimize the styles,
|
|
111
|
+
// then extract the single style object with `.c`.
|
|
112
|
+
return StyleSheet.create({ c: container as ViewStyle }).c;
|
|
113
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import type { WrapperProps } from './Wrapper.types';
|
|
4
|
+
import { getWrapperStyles } from './Wrapper.styles';
|
|
5
|
+
import { useTokens } from '../../tokens/useTokens';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Wrapper — Lightweight, invisible layout container.
|
|
9
|
+
*
|
|
10
|
+
* Same layout API as Frame (direction, spacing, alignment, sizing)
|
|
11
|
+
* but with zero appearance overhead. No background, border, shadow,
|
|
12
|
+
* theme context, or interactivity. Always renders a plain View.
|
|
13
|
+
*
|
|
14
|
+
* Use Wrapper for structural layout. Use Frame when you need visual
|
|
15
|
+
* appearance or theme/elevation context.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```tsx
|
|
19
|
+
* <Wrapper direction="horizontal" gap="md" align="center">
|
|
20
|
+
* <Button onPress={() => {}}>Save</Button>
|
|
21
|
+
* <Button variant="ghost" onPress={() => {}}>Cancel</Button>
|
|
22
|
+
* </Wrapper>
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```tsx
|
|
27
|
+
* <Wrapper padding={{ x: 'lg', y: 'md' }} gap="sm">
|
|
28
|
+
* <Text>Spaced content with no background</Text>
|
|
29
|
+
* </Wrapper>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export function Wrapper({
|
|
33
|
+
children,
|
|
34
|
+
direction,
|
|
35
|
+
wrap,
|
|
36
|
+
reverse,
|
|
37
|
+
align,
|
|
38
|
+
justify,
|
|
39
|
+
padding,
|
|
40
|
+
gap,
|
|
41
|
+
width,
|
|
42
|
+
height,
|
|
43
|
+
minWidth,
|
|
44
|
+
maxWidth,
|
|
45
|
+
minHeight,
|
|
46
|
+
maxHeight,
|
|
47
|
+
style,
|
|
48
|
+
// Testing & platform
|
|
49
|
+
testID,
|
|
50
|
+
nativeID,
|
|
51
|
+
ref,
|
|
52
|
+
}: WrapperProps) {
|
|
53
|
+
// Get the theme's design tokens so we can convert spacing names (like 'md')
|
|
54
|
+
// into pixel values. The "1" is the default elevation level — Wrapper doesn't
|
|
55
|
+
// have its own elevation, but tokens need one for the spacing lookups.
|
|
56
|
+
const tokens = useTokens(1);
|
|
57
|
+
|
|
58
|
+
// Calculate the layout styles (direction, spacing, alignment, sizing).
|
|
59
|
+
// Wrapped in useMemo so it only recalculates when one of the props changes,
|
|
60
|
+
// instead of running on every render.
|
|
61
|
+
const containerStyle = useMemo(
|
|
62
|
+
() => getWrapperStyles({
|
|
63
|
+
tokens,
|
|
64
|
+
direction,
|
|
65
|
+
wrap,
|
|
66
|
+
reverse,
|
|
67
|
+
align,
|
|
68
|
+
justify,
|
|
69
|
+
padding,
|
|
70
|
+
gap,
|
|
71
|
+
width,
|
|
72
|
+
height,
|
|
73
|
+
minWidth,
|
|
74
|
+
maxWidth,
|
|
75
|
+
minHeight,
|
|
76
|
+
maxHeight,
|
|
77
|
+
}),
|
|
78
|
+
[
|
|
79
|
+
tokens, direction, wrap, reverse,
|
|
80
|
+
align, justify, padding, gap,
|
|
81
|
+
width, height, minWidth, maxWidth, minHeight, maxHeight,
|
|
82
|
+
],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Normalize user's custom styles into an array so we can merge them.
|
|
86
|
+
// The user can pass a single style object or an array of style objects.
|
|
87
|
+
const userStyles = Array.isArray(style) ? style : style ? [style] : [];
|
|
88
|
+
|
|
89
|
+
// Render a plain View — no background, no border, no interactivity.
|
|
90
|
+
// User's custom styles are applied last so they can override layout defaults.
|
|
91
|
+
return (
|
|
92
|
+
<View
|
|
93
|
+
ref={ref}
|
|
94
|
+
testID={testID}
|
|
95
|
+
nativeID={nativeID}
|
|
96
|
+
// Wrapper is a layout-only container with no semantic meaning.
|
|
97
|
+
// "none" tells screen readers to skip this element and read its children directly.
|
|
98
|
+
accessibilityRole="none"
|
|
99
|
+
style={[containerStyle, ...userStyles]}
|
|
100
|
+
>
|
|
101
|
+
{children}
|
|
102
|
+
</View>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { View, ViewStyle } from 'react-native';
|
|
2
|
+
import type {
|
|
3
|
+
Direction,
|
|
4
|
+
Alignment,
|
|
5
|
+
Justification,
|
|
6
|
+
PaddingProp,
|
|
7
|
+
GapProp,
|
|
8
|
+
SizingMode,
|
|
9
|
+
} from '../Frame/Frame.types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Props for Wrapper — a lightweight, invisible layout container.
|
|
13
|
+
*
|
|
14
|
+
* Same layout API as Frame (direction, spacing, alignment, sizing) but
|
|
15
|
+
* with zero appearance: no background, border, shadow, radius, theme
|
|
16
|
+
* context, or interactivity. Always renders a plain `<View>`.
|
|
17
|
+
*
|
|
18
|
+
* Use Wrapper for structural layout. Use Frame when you need visual
|
|
19
|
+
* appearance or theme/elevation context.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* // Horizontal row with spacing
|
|
24
|
+
* <Wrapper direction="horizontal" gap="md" align="center">
|
|
25
|
+
* <Button onPress={() => {}}>Save</Button>
|
|
26
|
+
* <Button variant="ghost" onPress={() => {}}>Cancel</Button>
|
|
27
|
+
* </Wrapper>
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* // Padded vertical stack
|
|
33
|
+
* <Wrapper padding={{ x: 'lg', y: 'md' }} gap="sm">
|
|
34
|
+
* <Text>First item</Text>
|
|
35
|
+
* <Text>Second item</Text>
|
|
36
|
+
* </Wrapper>
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* // Full-width centered container
|
|
42
|
+
* <Wrapper width="fill" align="center" justify="center">
|
|
43
|
+
* <Text>Centered content</Text>
|
|
44
|
+
* </Wrapper>
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export interface WrapperProps {
|
|
48
|
+
/**
|
|
49
|
+
* Child elements. Must be React Native elements (View, Text, etc.).
|
|
50
|
+
* Unlike Frame, raw strings are NOT auto-wrapped in `<Text>`.
|
|
51
|
+
*/
|
|
52
|
+
readonly children: React.ReactNode;
|
|
53
|
+
|
|
54
|
+
// ── Layout ──
|
|
55
|
+
|
|
56
|
+
/** Flex direction. @default 'vertical' */
|
|
57
|
+
readonly direction?: Direction;
|
|
58
|
+
|
|
59
|
+
/** Enable flex wrap. @default false */
|
|
60
|
+
readonly wrap?: boolean;
|
|
61
|
+
|
|
62
|
+
/** Reverse flex direction. @default false */
|
|
63
|
+
readonly reverse?: boolean;
|
|
64
|
+
|
|
65
|
+
// ── Alignment ──
|
|
66
|
+
|
|
67
|
+
/** Cross-axis alignment of children. */
|
|
68
|
+
readonly align?: Alignment;
|
|
69
|
+
|
|
70
|
+
/** Main-axis justification of children. */
|
|
71
|
+
readonly justify?: Justification;
|
|
72
|
+
|
|
73
|
+
// ── Spacing ──
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Padding inside the wrapper.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```tsx
|
|
80
|
+
* <Wrapper padding="md" /> // uniform token
|
|
81
|
+
* <Wrapper padding={16} /> // uniform px
|
|
82
|
+
* <Wrapper padding={{ x: 'lg', y: 'sm' }} /> // axis shorthand
|
|
83
|
+
* <Wrapper padding={{ top: 8, bottom: 16 }} /> // per-side
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
readonly padding?: PaddingProp;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Gap between children.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```tsx
|
|
93
|
+
* <Wrapper gap="md" /> // uniform token
|
|
94
|
+
* <Wrapper gap={12} /> // uniform px
|
|
95
|
+
* <Wrapper gap={{ row: 'sm', column: 'lg' }} /> // per-axis
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
readonly gap?: GapProp;
|
|
99
|
+
|
|
100
|
+
// ── Sizing ──
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Width sizing: `'hug'` (shrink to content), `'fill'` (expand), or fixed px.
|
|
104
|
+
* @example
|
|
105
|
+
* ```tsx
|
|
106
|
+
* <Wrapper width="fill" /> // expand to fill parent
|
|
107
|
+
* <Wrapper width={300} /> // fixed 300px
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
readonly width?: SizingMode;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Height sizing: `'hug'` (shrink to content), `'fill'` (expand), or fixed px.
|
|
114
|
+
* @example
|
|
115
|
+
* ```tsx
|
|
116
|
+
* <Wrapper height="fill" /> // expand to fill parent
|
|
117
|
+
* <Wrapper height={200} /> // fixed 200px
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
readonly height?: SizingMode;
|
|
121
|
+
|
|
122
|
+
/** Minimum width in pixels. */
|
|
123
|
+
readonly minWidth?: number;
|
|
124
|
+
|
|
125
|
+
/** Maximum width in pixels. */
|
|
126
|
+
readonly maxWidth?: number;
|
|
127
|
+
|
|
128
|
+
/** Minimum height in pixels. */
|
|
129
|
+
readonly minHeight?: number;
|
|
130
|
+
|
|
131
|
+
/** Maximum height in pixels. */
|
|
132
|
+
readonly maxHeight?: number;
|
|
133
|
+
|
|
134
|
+
// ── Testing & Platform ──
|
|
135
|
+
|
|
136
|
+
/** Test identifier — maps to `data-testid` on web. Used by testing libraries. */
|
|
137
|
+
readonly testID?: string;
|
|
138
|
+
|
|
139
|
+
/** DOM id — maps to `id` attribute on web. Used for anchors and scroll targets. */
|
|
140
|
+
readonly nativeID?: string;
|
|
141
|
+
|
|
142
|
+
/** Ref to the underlying View element. */
|
|
143
|
+
readonly ref?: React.Ref<View>;
|
|
144
|
+
|
|
145
|
+
// ── Style override ──
|
|
146
|
+
|
|
147
|
+
/** Custom style overrides (applied last). */
|
|
148
|
+
readonly style?: ViewStyle | ViewStyle[];
|
|
149
|
+
}
|