@newtonedev/components 0.1.18 → 0.1.19

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 (89) hide show
  1. package/dist/composites/actions/Button/Button.d.ts.map +1 -1
  2. package/dist/composites/actions/Button/Button.styles.d.ts +1 -0
  3. package/dist/composites/actions/Button/Button.styles.d.ts.map +1 -1
  4. package/dist/index.cjs +850 -509
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.ts +11 -4
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +808 -470
  9. package/dist/index.js.map +1 -1
  10. package/dist/layout/Page/Page.d.ts +30 -0
  11. package/dist/layout/Page/Page.d.ts.map +1 -0
  12. package/dist/layout/Page/Page.types.d.ts +60 -0
  13. package/dist/layout/Page/Page.types.d.ts.map +1 -0
  14. package/dist/layout/Page/index.d.ts +3 -0
  15. package/dist/layout/Page/index.d.ts.map +1 -0
  16. package/dist/layout/Section/Section.d.ts +23 -0
  17. package/dist/layout/Section/Section.d.ts.map +1 -0
  18. package/dist/layout/Section/Section.styles.d.ts +20 -0
  19. package/dist/layout/Section/Section.styles.d.ts.map +1 -0
  20. package/dist/layout/Section/Section.types.d.ts +74 -0
  21. package/dist/layout/Section/Section.types.d.ts.map +1 -0
  22. package/dist/layout/Section/index.d.ts +3 -0
  23. package/dist/layout/Section/index.d.ts.map +1 -0
  24. package/dist/layout/Viewport/Viewport.d.ts +22 -0
  25. package/dist/layout/Viewport/Viewport.d.ts.map +1 -0
  26. package/dist/layout/Viewport/Viewport.types.d.ts +34 -0
  27. package/dist/layout/Viewport/Viewport.types.d.ts.map +1 -0
  28. package/dist/layout/Viewport/index.d.ts +3 -0
  29. package/dist/layout/Viewport/index.d.ts.map +1 -0
  30. package/dist/{primitives → layout}/Wrapper/Wrapper.d.ts +1 -16
  31. package/dist/layout/Wrapper/Wrapper.d.ts.map +1 -0
  32. package/dist/{primitives → layout}/Wrapper/Wrapper.styles.d.ts +1 -2
  33. package/dist/layout/Wrapper/Wrapper.styles.d.ts.map +1 -0
  34. package/dist/{primitives → layout}/Wrapper/Wrapper.types.d.ts +1 -3
  35. package/dist/layout/Wrapper/Wrapper.types.d.ts.map +1 -0
  36. package/dist/layout/Wrapper/index.d.ts.map +1 -0
  37. package/dist/layout/index.d.ts +9 -0
  38. package/dist/layout/index.d.ts.map +1 -0
  39. package/dist/primitives/Card/Card.d.ts +15 -0
  40. package/dist/primitives/Card/Card.d.ts.map +1 -0
  41. package/dist/primitives/Card/Card.styles.d.ts +40 -0
  42. package/dist/primitives/Card/Card.styles.d.ts.map +1 -0
  43. package/dist/primitives/Card/Card.types.d.ts +101 -0
  44. package/dist/primitives/Card/Card.types.d.ts.map +1 -0
  45. package/dist/primitives/Card/index.d.ts +3 -0
  46. package/dist/primitives/Card/index.d.ts.map +1 -0
  47. package/dist/primitives/Frame/Frame.d.ts +1 -1
  48. package/dist/primitives/Frame/Frame.d.ts.map +1 -1
  49. package/dist/primitives/Frame/Frame.styles.d.ts +0 -6
  50. package/dist/primitives/Frame/Frame.styles.d.ts.map +1 -1
  51. package/dist/primitives/Frame/Frame.types.d.ts +1 -15
  52. package/dist/primitives/Frame/Frame.types.d.ts.map +1 -1
  53. package/dist/primitives/index.d.ts +2 -2
  54. package/dist/primitives/index.d.ts.map +1 -1
  55. package/package.json +2 -2
  56. package/src/composites/actions/Button/Button.styles.ts +58 -19
  57. package/src/composites/actions/Button/Button.tsx +18 -3
  58. package/src/composites/display/Chip/Chip.tsx +1 -1
  59. package/src/composites/form-controls/TextInput/TextInput.styles.ts +4 -4
  60. package/src/index.ts +18 -5
  61. package/src/layout/Page/Page.tsx +103 -0
  62. package/src/layout/Page/Page.types.ts +69 -0
  63. package/src/layout/Page/index.ts +2 -0
  64. package/src/layout/Section/Section.styles.ts +90 -0
  65. package/src/layout/Section/Section.tsx +57 -0
  66. package/src/layout/Section/Section.types.ts +85 -0
  67. package/src/layout/Section/index.ts +2 -0
  68. package/src/layout/Viewport/Viewport.tsx +52 -0
  69. package/src/layout/Viewport/Viewport.types.ts +40 -0
  70. package/src/layout/Viewport/index.ts +2 -0
  71. package/src/{primitives → layout}/Wrapper/Wrapper.styles.ts +2 -20
  72. package/src/{primitives → layout}/Wrapper/Wrapper.tsx +1 -31
  73. package/src/{primitives → layout}/Wrapper/Wrapper.types.ts +1 -4
  74. package/src/layout/index.ts +15 -0
  75. package/src/primitives/Card/Card.styles.ts +182 -0
  76. package/src/primitives/Card/Card.tsx +197 -0
  77. package/src/primitives/Card/Card.types.ts +155 -0
  78. package/src/primitives/Card/index.ts +2 -0
  79. package/src/primitives/Frame/Frame.styles.ts +0 -32
  80. package/src/primitives/Frame/Frame.tsx +5 -52
  81. package/src/primitives/Frame/Frame.types.ts +1 -22
  82. package/src/primitives/Text/Text.tsx +1 -1
  83. package/src/primitives/index.ts +3 -3
  84. package/dist/primitives/Wrapper/Wrapper.d.ts.map +0 -1
  85. package/dist/primitives/Wrapper/Wrapper.styles.d.ts.map +0 -1
  86. package/dist/primitives/Wrapper/Wrapper.types.d.ts.map +0 -1
  87. package/dist/primitives/Wrapper/index.d.ts.map +0 -1
  88. /package/dist/{primitives → layout}/Wrapper/index.d.ts +0 -0
  89. /package/src/{primitives → layout}/Wrapper/index.ts +0 -0
@@ -0,0 +1,57 @@
1
+ import React, { useMemo } from 'react';
2
+ import { View } from 'react-native';
3
+ import type { SectionProps } from './Section.types';
4
+ import { getSectionStyles } from './Section.styles';
5
+ import { useTokens } from 'newtone-api';
6
+
7
+ /**
8
+ * Section — Full-width layout container that constrains content width.
9
+ *
10
+ * Section fills all available horizontal space and centers its content
11
+ * within a maximum width determined by the `size` preset. It provides
12
+ * horizontal padding so content doesn't touch the viewport edges when
13
+ * the screen is narrower than the max width.
14
+ *
15
+ * Section is invisible — no background, border, or shadow. It is purely
16
+ * a layout tool for structuring page-level content regions.
17
+ *
18
+ * @example
19
+ * ```tsx
20
+ * <Section size="md" gap={24} padding={{ y: 48 }}>
21
+ * <Text role="heading">Documentation</Text>
22
+ * <Text role="body">Content constrained to 768px max width.</Text>
23
+ * </Section>
24
+ * ```
25
+ */
26
+ export function Section({
27
+ children,
28
+ size,
29
+ fill,
30
+ gap,
31
+ padding,
32
+ testID,
33
+ nativeID,
34
+ ref,
35
+ style,
36
+ }: SectionProps) {
37
+ const tokens = useTokens();
38
+
39
+ const containerStyle = useMemo(
40
+ () => getSectionStyles({ tokens, size, fill, gap, padding }),
41
+ [tokens, size, fill, gap, padding],
42
+ );
43
+
44
+ const userStyles = Array.isArray(style) ? style : style ? [style] : [];
45
+
46
+ return (
47
+ <View
48
+ ref={ref}
49
+ testID={testID}
50
+ nativeID={nativeID}
51
+ accessibilityRole="none"
52
+ style={[containerStyle, ...userStyles]}
53
+ >
54
+ {children}
55
+ </View>
56
+ );
57
+ }
@@ -0,0 +1,85 @@
1
+ import type { View, ViewStyle } from 'react-native';
2
+ import type { GapProp, PaddingProp } from '../../primitives/Frame/Frame.types';
3
+
4
+ /**
5
+ * Content width preset for Section.
6
+ *
7
+ * | Size | Max Width | Horizontal Padding |
8
+ * |:------:|:---------:|:------------------:|
9
+ * | `'xs'` | 480px | 24px |
10
+ * | `'sm'` | 640px | 24px |
11
+ * | `'md'` | 768px | 24px |
12
+ * | `'lg'` | 1024px | 32px |
13
+ * | `'xl'` | 1280px | 40px |
14
+ * | `'full'` | none | 0px |
15
+ */
16
+ export type SectionSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
17
+
18
+ /**
19
+ * Props for Section — a full-width layout container that constrains content width.
20
+ *
21
+ * Section fills all available horizontal space and centers its content
22
+ * within a maximum width. It provides horizontal padding so content
23
+ * doesn't touch the edges when the viewport is narrower than the max width.
24
+ *
25
+ * Section is invisible — no background, border, or shadow.
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * <Section size="md" gap={24}>
30
+ * <Text role="heading">Page Title</Text>
31
+ * <Text role="body">Content constrained to 768px.</Text>
32
+ * </Section>
33
+ * ```
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * <Section size="full">
38
+ * <HeroImage />
39
+ * </Section>
40
+ * ```
41
+ */
42
+ export interface SectionProps {
43
+ /** Child elements. */
44
+ readonly children?: React.ReactNode;
45
+
46
+ /**
47
+ * Content width preset. Controls the maximum width and default horizontal padding.
48
+ *
49
+ * @default 'lg'
50
+ */
51
+ readonly size?: SectionSize;
52
+
53
+ /**
54
+ * Gap between children.
55
+ */
56
+ readonly gap?: GapProp;
57
+
58
+ /**
59
+ * When true, the Section expands to fill all available vertical space (`flex: 1`).
60
+ * Useful inside a Viewport when content is shorter than the screen.
61
+ *
62
+ * @default false
63
+ */
64
+ readonly fill?: boolean;
65
+
66
+ /**
67
+ * Vertical padding (top and bottom).
68
+ * Horizontal padding is determined by the size preset.
69
+ */
70
+ readonly padding?: PaddingProp;
71
+
72
+ // ── Testing & Platform ──
73
+
74
+ /** Test identifier — maps to `data-testid` on web. */
75
+ readonly testID?: string;
76
+
77
+ /** DOM id — maps to `id` attribute on web. */
78
+ readonly nativeID?: string;
79
+
80
+ /** Ref to the underlying View element. */
81
+ readonly ref?: React.Ref<View>;
82
+
83
+ /** Custom style overrides (applied last). */
84
+ readonly style?: ViewStyle | ViewStyle[];
85
+ }
@@ -0,0 +1,2 @@
1
+ export { Section } from './Section';
2
+ export type { SectionProps, SectionSize } from './Section.types';
@@ -0,0 +1,52 @@
1
+ import React from 'react';
2
+ import { ScrollView, StyleSheet } from 'react-native';
3
+ import type { ViewportProps } from './Viewport.types';
4
+
5
+ const styles = StyleSheet.create({
6
+ root: {
7
+ flex: 1,
8
+ },
9
+ content: {
10
+ flexGrow: 1,
11
+ },
12
+ });
13
+
14
+ /**
15
+ * Viewport — Full-height scrollable layout root.
16
+ *
17
+ * Viewport fills all available height (`flex: 1`) and scrolls vertically
18
+ * when content overflows. It stacks children in a column.
19
+ *
20
+ * Viewport is invisible — no background, border, or shadow. It is purely
21
+ * a layout tool for structuring the outermost page container.
22
+ *
23
+ * @example
24
+ * ```tsx
25
+ * <Viewport>
26
+ * <Section size="lg" padding={{ y: 48 }}>
27
+ * <Text role="heading">Welcome</Text>
28
+ * </Section>
29
+ * </Viewport>
30
+ * ```
31
+ */
32
+ export function Viewport({
33
+ children,
34
+ testID,
35
+ nativeID,
36
+ ref,
37
+ style,
38
+ }: ViewportProps) {
39
+ const userStyles = Array.isArray(style) ? style : style ? [style] : [];
40
+
41
+ return (
42
+ <ScrollView
43
+ ref={ref}
44
+ testID={testID}
45
+ nativeID={nativeID}
46
+ style={styles.root}
47
+ contentContainerStyle={[styles.content, ...userStyles]}
48
+ >
49
+ {children}
50
+ </ScrollView>
51
+ );
52
+ }
@@ -0,0 +1,40 @@
1
+ import type { ScrollView, ViewStyle } from 'react-native';
2
+
3
+ /**
4
+ * Props for Viewport — a full-height scrollable layout root.
5
+ *
6
+ * Viewport fills all available height and scrolls vertically when its
7
+ * content overflows. It stacks children in a column and provides no
8
+ * visual styling — no background, border, or shadow.
9
+ *
10
+ * Use Viewport as the outermost layout container for a page or screen.
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * <Viewport>
15
+ * <Section size="sm" fill>
16
+ * <Frame elevation={1}>
17
+ * <Text>Centered content</Text>
18
+ * </Frame>
19
+ * </Section>
20
+ * </Viewport>
21
+ * ```
22
+ */
23
+ export interface ViewportProps {
24
+ /** Child elements (typically Sections). */
25
+ readonly children?: React.ReactNode;
26
+
27
+ // ── Testing & Platform ──
28
+
29
+ /** Test identifier — maps to `data-testid` on web. */
30
+ readonly testID?: string;
31
+
32
+ /** DOM id — maps to `id` attribute on web. */
33
+ readonly nativeID?: string;
34
+
35
+ /** Ref to the underlying ScrollView element. */
36
+ readonly ref?: React.Ref<ScrollView>;
37
+
38
+ /** Custom style overrides applied to the scroll content container. */
39
+ readonly style?: ViewStyle | ViewStyle[];
40
+ }
@@ -0,0 +1,2 @@
1
+ export { Viewport } from './Viewport';
2
+ export type { ViewportProps } from './Viewport.types';
@@ -11,7 +11,7 @@ import type {
11
11
  PositionType,
12
12
  OffsetValue,
13
13
  OverflowMode,
14
- } from '../Frame/Frame.types';
14
+ } from '../../primitives/Frame/Frame.types';
15
15
  import {
16
16
  resolvePadding,
17
17
  resolveGap,
@@ -19,7 +19,7 @@ import {
19
19
  resolveFlexDirection,
20
20
  resolveAlignment,
21
21
  resolveJustification,
22
- } from '../Frame/Frame.utils';
22
+ } from '../../primitives/Frame/Frame.utils';
23
23
 
24
24
  export interface WrapperStyleInput {
25
25
  readonly tokens: ResolvedTokens;
@@ -45,7 +45,6 @@ export interface WrapperStyleInput {
45
45
  readonly left?: OffsetValue;
46
46
  readonly zIndex?: number;
47
47
  readonly overflow?: OverflowMode;
48
- readonly opacity?: number;
49
48
  }
50
49
 
51
50
  /**
@@ -78,28 +77,19 @@ export function getWrapperStyles(input: WrapperStyleInput): ViewStyle {
78
77
  left,
79
78
  zIndex,
80
79
  overflow,
81
- opacity,
82
80
  } = input;
83
81
 
84
- // We build styles as a plain object first, then validate it through
85
- // StyleSheet.create() at the end. Using Record<string, unknown> allows us
86
- // to set properties conditionally without TypeScript complaining.
87
82
  const container: Record<string, unknown> = {};
88
83
 
89
84
  // ── Layout ──
90
- // Set whether children are arranged in a row or column (and optionally reversed).
91
85
  container.flexDirection = resolveFlexDirection(direction, reverse);
92
- // Allow children to flow onto multiple lines when they don't fit in one.
93
86
  if (wrap) container.flexWrap = 'wrap';
94
87
 
95
88
  // ── Alignment ──
96
- // Cross-axis: how children are positioned perpendicular to the main direction.
97
89
  if (align) container.alignItems = resolveAlignment(align);
98
- // Main-axis: how children are distributed along the main direction.
99
90
  if (justify) container.justifyContent = resolveJustification(justify);
100
91
 
101
92
  // ── Padding ──
102
- // Convert spacing tokens (like 'md') or pixel values into actual padding.
103
93
  if (padding !== undefined) {
104
94
  const p = resolvePadding(padding, tokens);
105
95
  container.paddingTop = p.top;
@@ -109,7 +99,6 @@ export function getWrapperStyles(input: WrapperStyleInput): ViewStyle {
109
99
  }
110
100
 
111
101
  // ── Gap ──
112
- // Space between children (row gap for vertical stacks, column gap for horizontal).
113
102
  if (gap !== undefined) {
114
103
  const g = resolveGap(gap, tokens);
115
104
  container.rowGap = g.rowGap;
@@ -117,19 +106,15 @@ export function getWrapperStyles(input: WrapperStyleInput): ViewStyle {
117
106
  }
118
107
 
119
108
  // ── Sizing ──
120
- // Merge width/height settings into the container.
121
- // 'hug' = shrink to fit content, 'fill' = expand to fill parent, number = fixed pixels.
122
109
  Object.assign(container, resolveSizing(width, height));
123
110
 
124
111
  // ── Constraints ──
125
- // Set min/max boundaries for width and height.
126
112
  if (minWidth !== undefined) container.minWidth = minWidth;
127
113
  if (maxWidth !== undefined) container.maxWidth = maxWidth;
128
114
  if (minHeight !== undefined) container.minHeight = minHeight;
129
115
  if (maxHeight !== undefined) container.maxHeight = maxHeight;
130
116
 
131
117
  // ── Positioning ──
132
- // Set CSS position mode and offsets for absolute/fixed/sticky positioning.
133
118
  if (position) container.position = position;
134
119
  if (top !== undefined) container.top = top;
135
120
  if (right !== undefined) container.right = right;
@@ -137,9 +122,6 @@ export function getWrapperStyles(input: WrapperStyleInput): ViewStyle {
137
122
  if (left !== undefined) container.left = left;
138
123
  if (zIndex !== undefined) container.zIndex = zIndex;
139
124
  if (overflow) container.overflow = overflow;
140
- if (opacity !== undefined) container.opacity = opacity;
141
125
 
142
- // Pass through StyleSheet.create() to validate and optimize the styles,
143
- // then extract the single style object with `.c`.
144
126
  return StyleSheet.create({ c: container as ViewStyle }).c;
145
127
  }
@@ -13,21 +13,6 @@ import { useTokens } from 'newtone-api';
13
13
  *
14
14
  * Use Wrapper for structural layout. Use Frame when you need visual
15
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="tertiary" 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
16
  */
32
17
  export function Wrapper({
33
18
  children,
@@ -44,7 +29,6 @@ export function Wrapper({
44
29
  maxWidth,
45
30
  minHeight,
46
31
  maxHeight,
47
- // Positioning
48
32
  position,
49
33
  top,
50
34
  right,
@@ -53,20 +37,13 @@ export function Wrapper({
53
37
  zIndex,
54
38
  overflow,
55
39
  pointerEvents,
56
- opacity,
57
40
  style,
58
- // Testing & platform
59
41
  testID,
60
42
  nativeID,
61
43
  ref,
62
44
  }: WrapperProps) {
63
- // Get the theme's design tokens so we can convert spacing names (like 'md')
64
- // into pixel values. Wrapper inherits elevation from FrameContext automatically.
65
45
  const tokens = useTokens();
66
46
 
67
- // Calculate the layout styles (direction, spacing, alignment, sizing).
68
- // Wrapped in useMemo so it only recalculates when one of the props changes,
69
- // instead of running on every render.
70
47
  const containerStyle = useMemo(
71
48
  () => getWrapperStyles({
72
49
  tokens,
@@ -90,30 +67,23 @@ export function Wrapper({
90
67
  left,
91
68
  zIndex,
92
69
  overflow,
93
- opacity,
94
70
  }),
95
71
  [
96
72
  tokens, direction, wrap, reverse,
97
73
  align, justify, padding, gap,
98
74
  width, height, minWidth, maxWidth, minHeight, maxHeight,
99
- position, top, right, bottom, left, zIndex, overflow, opacity,
75
+ position, top, right, bottom, left, zIndex, overflow,
100
76
  ],
101
77
  );
102
78
 
103
- // Normalize user's custom styles into an array so we can merge them.
104
- // The user can pass a single style object or an array of style objects.
105
79
  const userStyles = Array.isArray(style) ? style : style ? [style] : [];
106
80
 
107
- // Render a plain View — no background, no border, no interactivity.
108
- // User's custom styles are applied last so they can override layout defaults.
109
81
  return (
110
82
  <View
111
83
  ref={ref}
112
84
  testID={testID}
113
85
  nativeID={nativeID}
114
86
  pointerEvents={pointerEvents}
115
- // Wrapper is a layout-only container with no semantic meaning.
116
- // "none" tells screen readers to skip this element and read its children directly.
117
87
  accessibilityRole="none"
118
88
  style={[containerStyle, ...userStyles]}
119
89
  >
@@ -10,7 +10,7 @@ import type {
10
10
  OffsetValue,
11
11
  OverflowMode,
12
12
  PointerEventsMode,
13
- } from '../Frame/Frame.types';
13
+ } from '../../primitives/Frame/Frame.types';
14
14
 
15
15
  /**
16
16
  * Props for Wrapper — a lightweight, invisible layout container.
@@ -173,9 +173,6 @@ export interface WrapperProps {
173
173
  */
174
174
  readonly pointerEvents?: PointerEventsMode;
175
175
 
176
- /** Opacity of the element (0 = fully transparent, 1 = fully opaque). */
177
- readonly opacity?: number;
178
-
179
176
  // ── Testing & Platform ──
180
177
 
181
178
  /** Test identifier — maps to `data-testid` on web. Used by testing libraries. */
@@ -0,0 +1,15 @@
1
+ // Page
2
+ export { Page } from './Page';
3
+ export type { PageProps } from './Page';
4
+
5
+ // Viewport
6
+ export { Viewport } from './Viewport';
7
+ export type { ViewportProps } from './Viewport';
8
+
9
+ // Wrapper
10
+ export { Wrapper } from './Wrapper';
11
+ export type { WrapperProps } from './Wrapper';
12
+
13
+ // Section
14
+ export { Section } from './Section';
15
+ export type { SectionProps, SectionSize } from './Section';
@@ -0,0 +1,182 @@
1
+ import type { ViewStyle } from 'react-native';
2
+ import { StyleSheet } from 'react-native';
3
+ import type { ResolvedTokens, AppearanceTokens } from 'newtone-api';
4
+ import type { ThemeName, AppearanceName } from 'newtone-api';
5
+ import type {
6
+ PaddingProp,
7
+ GapProp,
8
+ SizingMode,
9
+ Direction,
10
+ Alignment,
11
+ Justification,
12
+ RadiusProp,
13
+ } from '../Frame/Frame.types';
14
+ import {
15
+ resolvePadding,
16
+ resolveGap,
17
+ resolveSizing,
18
+ resolveFlexDirection,
19
+ resolveAlignment,
20
+ resolveJustification,
21
+ resolveRadiusCorners,
22
+ hasPositiveRadius,
23
+ } from '../Frame/Frame.utils';
24
+
25
+ // ── Input ────────────────────────────────────────────────────────
26
+
27
+ export interface CardStyleInput {
28
+ readonly tokens: ResolvedTokens;
29
+
30
+ // Theme / Appearance (theme is inherited, not user-supplied)
31
+ readonly theme: ThemeName;
32
+ readonly appearance?: AppearanceName;
33
+
34
+ // Layout
35
+ readonly direction?: Direction;
36
+ readonly wrap?: boolean;
37
+ readonly reverse?: boolean;
38
+
39
+ // Alignment
40
+ readonly align?: Alignment;
41
+ readonly justify?: Justification;
42
+
43
+ // Spacing
44
+ readonly padding?: PaddingProp;
45
+ readonly gap?: GapProp;
46
+
47
+ // Sizing
48
+ readonly width?: SizingMode;
49
+ readonly height?: SizingMode;
50
+ readonly minWidth?: number;
51
+ readonly maxWidth?: number;
52
+ readonly minHeight?: number;
53
+ readonly maxHeight?: number;
54
+
55
+ // Appearance
56
+ readonly radius?: RadiusProp;
57
+
58
+ // Interactivity
59
+ readonly disabled?: boolean;
60
+ }
61
+
62
+ // ── Output ───────────────────────────────────────────────────────
63
+
64
+ export interface CardStyles {
65
+ /** Main container style */
66
+ readonly container: ViewStyle;
67
+ /** Style applied when pressed (background shift) */
68
+ readonly pressed: ViewStyle;
69
+ }
70
+
71
+ // ── Builder ──────────────────────────────────────────────────────
72
+
73
+ /**
74
+ * Build all visual styles for a Card.
75
+ *
76
+ * Cards are always elevated with a border and drop shadow.
77
+ * Takes the Card's props + design tokens and produces:
78
+ * - container: the main style (background, border, shadow, layout, spacing, etc.)
79
+ * - pressed: style override applied when the user is pressing
80
+ */
81
+ export function getCardStyles(input: CardStyleInput): CardStyles {
82
+ const {
83
+ tokens,
84
+ theme,
85
+ appearance = 'main',
86
+ direction = 'vertical',
87
+ wrap = false,
88
+ reverse = false,
89
+ align,
90
+ justify,
91
+ padding,
92
+ gap,
93
+ width,
94
+ height,
95
+ minWidth,
96
+ maxWidth,
97
+ minHeight,
98
+ maxHeight,
99
+ radius,
100
+ disabled = false,
101
+ } = input;
102
+
103
+ const container: Record<string, unknown> = {};
104
+
105
+ // ── Background & foreground ──
106
+ const appearanceTokens: AppearanceTokens = tokens.colors[theme][appearance];
107
+ container.backgroundColor = appearanceTokens.background;
108
+ container.color = appearanceTokens.fontPrimary;
109
+
110
+ // ── Layout ──
111
+ container.display = 'flex';
112
+ container.flexDirection = resolveFlexDirection(direction, reverse);
113
+ if (wrap) container.flexWrap = 'wrap';
114
+
115
+ // ── Alignment ──
116
+ if (align) container.alignItems = resolveAlignment(align);
117
+ if (justify) container.justifyContent = resolveJustification(justify);
118
+
119
+ // ── Padding ──
120
+ if (padding !== undefined) {
121
+ const p = resolvePadding(padding, tokens);
122
+ container.paddingTop = p.top;
123
+ container.paddingRight = p.right;
124
+ container.paddingBottom = p.bottom;
125
+ container.paddingLeft = p.left;
126
+ }
127
+
128
+ // ── Gap ──
129
+ if (gap !== undefined) {
130
+ const g = resolveGap(gap, tokens);
131
+ container.rowGap = g.rowGap;
132
+ container.columnGap = g.columnGap;
133
+ }
134
+
135
+ // ── Sizing ──
136
+ Object.assign(container, resolveSizing(width, height));
137
+
138
+ // ── Constraints ──
139
+ if (minWidth !== undefined) container.minWidth = minWidth;
140
+ if (maxWidth !== undefined) container.maxWidth = maxWidth;
141
+ if (minHeight !== undefined) container.minHeight = minHeight;
142
+ if (maxHeight !== undefined) container.maxHeight = maxHeight;
143
+
144
+ // ── Radius ──
145
+ if (radius !== undefined) {
146
+ const corners = resolveRadiusCorners(radius, tokens);
147
+ container.borderTopLeftRadius = corners.topLeft;
148
+ container.borderTopRightRadius = corners.topRight;
149
+ container.borderBottomLeftRadius = corners.bottomLeft;
150
+ container.borderBottomRightRadius = corners.bottomRight;
151
+
152
+ if (hasPositiveRadius(corners)) {
153
+ container.overflow = 'hidden';
154
+ }
155
+ }
156
+
157
+ // ── Border (always on) ──
158
+ container.borderWidth = 1;
159
+ container.borderColor = appearanceTokens.divider;
160
+
161
+ // ── Shadow (always elevated) ──
162
+ container.shadowColor = '#000';
163
+ container.shadowOffset = { width: 0, height: 2 };
164
+ container.shadowOpacity = 0.12;
165
+ container.shadowRadius = 6;
166
+ container.elevation = 4; // Android-specific shadow depth
167
+
168
+ // ── Disabled ──
169
+ if (disabled) {
170
+ container.opacity = 0.5;
171
+ }
172
+
173
+ // ── Pressed state ──
174
+ const pressed = StyleSheet.create({
175
+ s: { backgroundColor: appearanceTokens.fontSecondary },
176
+ }).s;
177
+
178
+ return {
179
+ container: StyleSheet.create({ c: container as ViewStyle }).c,
180
+ pressed,
181
+ };
182
+ }