@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.
Files changed (113) hide show
  1. package/dist/Popover/Popover.d.ts.map +1 -1
  2. package/dist/Popover/Popover.styles.d.ts +64 -1
  3. package/dist/Popover/Popover.styles.d.ts.map +1 -1
  4. package/dist/Select/Select.d.ts.map +1 -1
  5. package/dist/_COMPONENT_TEMPLATE/ComponentName.d.ts +70 -0
  6. package/dist/_COMPONENT_TEMPLATE/ComponentName.d.ts.map +1 -0
  7. package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts +22 -0
  8. package/dist/_COMPONENT_TEMPLATE/ComponentName.styles.d.ts.map +1 -0
  9. package/dist/_COMPONENT_TEMPLATE/ComponentName.types.d.ts +45 -0
  10. package/dist/_COMPONENT_TEMPLATE/ComponentName.types.d.ts.map +1 -0
  11. package/dist/_COMPONENT_TEMPLATE/index.d.ts +3 -0
  12. package/dist/_COMPONENT_TEMPLATE/index.d.ts.map +1 -0
  13. package/dist/fonts/GoogleFontLoader.d.ts.map +1 -1
  14. package/dist/fonts/IconFontLoader.d.ts.map +1 -1
  15. package/dist/index.cjs +371 -74
  16. package/dist/index.cjs.map +1 -1
  17. package/dist/index.d.ts +11 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +370 -76
  20. package/dist/index.js.map +1 -1
  21. package/dist/{Frame → primitives/Frame}/Frame.d.ts +1 -1
  22. package/dist/primitives/Frame/Frame.d.ts.map +1 -0
  23. package/dist/{Frame → primitives/Frame}/Frame.styles.d.ts +11 -2
  24. package/dist/primitives/Frame/Frame.styles.d.ts.map +1 -0
  25. package/dist/primitives/Frame/Frame.types.d.ts +240 -0
  26. package/dist/primitives/Frame/Frame.types.d.ts.map +1 -0
  27. package/dist/{Frame → primitives/Frame}/Frame.utils.d.ts +12 -12
  28. package/dist/primitives/Frame/Frame.utils.d.ts.map +1 -0
  29. package/dist/primitives/Frame/index.d.ts.map +1 -0
  30. package/dist/primitives/Icon/Icon.d.ts +17 -0
  31. package/dist/primitives/Icon/Icon.d.ts.map +1 -0
  32. package/dist/primitives/Icon/Icon.types.d.ts +55 -0
  33. package/dist/primitives/Icon/Icon.types.d.ts.map +1 -0
  34. package/dist/primitives/Icon/index.d.ts +3 -0
  35. package/dist/primitives/Icon/index.d.ts.map +1 -0
  36. package/dist/primitives/Text/Text.d.ts +17 -0
  37. package/dist/primitives/Text/Text.d.ts.map +1 -0
  38. package/dist/primitives/Text/Text.types.d.ts +85 -0
  39. package/dist/primitives/Text/Text.types.d.ts.map +1 -0
  40. package/dist/primitives/Text/index.d.ts +3 -0
  41. package/dist/primitives/Text/index.d.ts.map +1 -0
  42. package/dist/primitives/Wrapper/Wrapper.d.ts +29 -0
  43. package/dist/primitives/Wrapper/Wrapper.d.ts.map +1 -0
  44. package/dist/primitives/Wrapper/Wrapper.styles.d.ts +28 -0
  45. package/dist/primitives/Wrapper/Wrapper.styles.d.ts.map +1 -0
  46. package/dist/primitives/Wrapper/Wrapper.types.d.ts +113 -0
  47. package/dist/primitives/Wrapper/Wrapper.types.d.ts.map +1 -0
  48. package/dist/primitives/Wrapper/index.d.ts +3 -0
  49. package/dist/primitives/Wrapper/index.d.ts.map +1 -0
  50. package/dist/primitives/index.d.ts +12 -0
  51. package/dist/primitives/index.d.ts.map +1 -0
  52. package/dist/primitives/useFocusVisible.d.ts +29 -0
  53. package/dist/primitives/useFocusVisible.d.ts.map +1 -0
  54. package/dist/theme/defaults.d.ts.map +1 -1
  55. package/dist/theme/types.d.ts +13 -6
  56. package/dist/theme/types.d.ts.map +1 -1
  57. package/dist/tokens/computeTokens.d.ts +13 -6
  58. package/dist/tokens/computeTokens.d.ts.map +1 -1
  59. package/dist/tokens/types.d.ts +16 -7
  60. package/dist/tokens/types.d.ts.map +1 -1
  61. package/package.json +1 -1
  62. package/src/Button/Button.styles.ts +9 -9
  63. package/src/Button/Button.tsx +1 -1
  64. package/src/Card/Card.styles.ts +1 -1
  65. package/src/ColorScaleSlider/ColorScaleSlider.styles.ts +1 -1
  66. package/src/HueSlider/HueSlider.styles.ts +1 -1
  67. package/src/Popover/Popover.styles.ts +5 -1
  68. package/src/Popover/Popover.tsx +3 -1
  69. package/src/Select/Select.styles.ts +9 -9
  70. package/src/Select/Select.tsx +2 -3
  71. package/src/Select/SelectOption.tsx +6 -6
  72. package/src/Slider/Slider.styles.ts +1 -1
  73. package/src/TextInput/TextInput.styles.ts +3 -3
  74. package/src/Toggle/Toggle.styles.ts +1 -1
  75. package/src/_COMPONENT_TEMPLATE/ComponentName.styles.ts +29 -0
  76. package/src/_COMPONENT_TEMPLATE/ComponentName.tsx +106 -0
  77. package/src/_COMPONENT_TEMPLATE/ComponentName.types.ts +86 -0
  78. package/src/_COMPONENT_TEMPLATE/index.ts +2 -0
  79. package/src/fonts/GoogleFontLoader.tsx +2 -0
  80. package/src/fonts/IconFontLoader.tsx +2 -0
  81. package/src/index.ts +22 -5
  82. package/src/{Frame → primitives/Frame}/Frame.styles.ts +46 -9
  83. package/src/{Frame → primitives/Frame}/Frame.tsx +90 -16
  84. package/src/primitives/Frame/Frame.types.ts +315 -0
  85. package/src/{Frame → primitives/Frame}/Frame.utils.ts +56 -20
  86. package/src/primitives/Icon/Icon.tsx +89 -0
  87. package/src/primitives/Icon/Icon.types.ts +70 -0
  88. package/src/primitives/Icon/index.ts +2 -0
  89. package/src/primitives/Text/Text.tsx +90 -0
  90. package/src/primitives/Text/Text.types.ts +108 -0
  91. package/src/primitives/Text/index.ts +10 -0
  92. package/src/primitives/Wrapper/Wrapper.styles.ts +113 -0
  93. package/src/primitives/Wrapper/Wrapper.tsx +104 -0
  94. package/src/primitives/Wrapper/Wrapper.types.ts +149 -0
  95. package/src/primitives/Wrapper/index.ts +2 -0
  96. package/src/primitives/index.ts +46 -0
  97. package/src/primitives/useFocusVisible.ts +102 -0
  98. package/src/theme/defaults.ts +13 -6
  99. package/src/theme/types.ts +13 -6
  100. package/src/tokens/computeTokens.ts +1 -1
  101. package/src/tokens/types.ts +16 -7
  102. package/dist/Frame/Frame.d.ts.map +0 -1
  103. package/dist/Frame/Frame.styles.d.ts.map +0 -1
  104. package/dist/Frame/Frame.types.d.ts +0 -115
  105. package/dist/Frame/Frame.types.d.ts.map +0 -1
  106. package/dist/Frame/Frame.utils.d.ts.map +0 -1
  107. package/dist/Frame/index.d.ts.map +0 -1
  108. package/dist/Icon/Icon.d.ts +0 -36
  109. package/dist/Icon/Icon.d.ts.map +0 -1
  110. package/src/Frame/Frame.types.ts +0 -181
  111. package/src/Icon/Icon.tsx +0 -76
  112. /package/dist/{Frame → primitives/Frame}/index.d.ts +0 -0
  113. /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,2 @@
1
+ export { Icon } from './Icon';
2
+ export type { IconProps } from './Icon.types';
@@ -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,10 @@
1
+ export { Text } from './Text';
2
+ export type {
3
+ TextProps,
4
+ TextSize,
5
+ TextWeight,
6
+ TextColor,
7
+ TextFont,
8
+ TextLineHeight,
9
+ TextAlign,
10
+ } from './Text.types';
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ export { Wrapper } from './Wrapper';
2
+ export type { WrapperProps } from './Wrapper.types';