@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,315 @@
|
|
|
1
|
+
import type { View, ViewStyle, GestureResponderEvent } from 'react-native';
|
|
2
|
+
import type { ThemeName, FrameElevation } from '../../theme/types';
|
|
3
|
+
|
|
4
|
+
// ── Spacing Types ──────────────────────────────────────────────
|
|
5
|
+
|
|
6
|
+
/** Design system spacing tokens (represent px values at Medium preset) */
|
|
7
|
+
export type SpacingToken = '00' | '02' | '04' | '06' | '08' | '10' | '12' | '16' | '20' | '24' | '32' | '40' | '48';
|
|
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
|
+
/**
|
|
27
|
+
* Padding value — supports three forms:
|
|
28
|
+
*
|
|
29
|
+
* - **Uniform**: `'12'` or `16` — same padding on all sides
|
|
30
|
+
* - **Axis shorthand**: `{ x: '16', y: '08' }` — horizontal/vertical
|
|
31
|
+
* - **Per-side**: `{ top: 8, bottom: 16, left: '12', right: '12' }` — individual sides
|
|
32
|
+
*
|
|
33
|
+
* Token names (`'00'` through `'48'`) resolve to pixel values from the theme.
|
|
34
|
+
*/
|
|
35
|
+
export type PaddingProp = SpacingValue | SpacingAxes | SpacingSides;
|
|
36
|
+
|
|
37
|
+
/** Gap axis shorthand */
|
|
38
|
+
export interface GapAxes {
|
|
39
|
+
readonly row?: SpacingValue;
|
|
40
|
+
readonly column?: SpacingValue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Gap value — supports two forms:
|
|
45
|
+
*
|
|
46
|
+
* - **Uniform**: `'12'` or `12` — same gap between all children
|
|
47
|
+
* - **Row/column**: `{ row: '08', column: '16' }` — different gaps per axis
|
|
48
|
+
*
|
|
49
|
+
* Token names (`'00'` through `'48'`) resolve to pixel values from the theme.
|
|
50
|
+
*/
|
|
51
|
+
export type GapProp = SpacingValue | GapAxes;
|
|
52
|
+
|
|
53
|
+
// ── Radius Types ───────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
/** Design system radius tokens */
|
|
56
|
+
export type RadiusToken = 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'pill';
|
|
57
|
+
|
|
58
|
+
/** A radius value: either a design token name or a pixel number */
|
|
59
|
+
export type RadiusValue = RadiusToken | number;
|
|
60
|
+
|
|
61
|
+
/** Per-corner radius */
|
|
62
|
+
export interface RadiusCorners {
|
|
63
|
+
readonly topLeft?: RadiusValue;
|
|
64
|
+
readonly topRight?: RadiusValue;
|
|
65
|
+
readonly bottomLeft?: RadiusValue;
|
|
66
|
+
readonly bottomRight?: RadiusValue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Border radius — supports two forms:
|
|
71
|
+
*
|
|
72
|
+
* - **Uniform**: `'md'` or `8` — same radius on all corners
|
|
73
|
+
* - **Per-corner**: `{ topLeft: 'lg', topRight: 'lg', bottomLeft: 0, bottomRight: 0 }`
|
|
74
|
+
*
|
|
75
|
+
* Token names (`'none'` through `'pill'`) resolve to pixel values from the theme.
|
|
76
|
+
*/
|
|
77
|
+
export type RadiusProp = RadiusValue | RadiusCorners;
|
|
78
|
+
|
|
79
|
+
// ── Sizing Types ───────────────────────────────────────────────
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Figma-like sizing mode:
|
|
83
|
+
*
|
|
84
|
+
* - `'hug'` — shrink to fit content (default, no explicit dimension set)
|
|
85
|
+
* - `'fill'` — expand to fill available space (`flexGrow: 1` + `width/height: '100%'`)
|
|
86
|
+
* - `number` — fixed pixel dimension (e.g., `200`)
|
|
87
|
+
*/
|
|
88
|
+
export type SizingMode = 'hug' | 'fill' | number;
|
|
89
|
+
|
|
90
|
+
// ── Layout Types ───────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
/** Layout mode */
|
|
93
|
+
export type LayoutMode = 'flex' | 'grid';
|
|
94
|
+
|
|
95
|
+
/** Flex direction (Figma-like naming) */
|
|
96
|
+
export type Direction = 'horizontal' | 'vertical';
|
|
97
|
+
|
|
98
|
+
/** Cross-axis alignment */
|
|
99
|
+
export type Alignment = 'start' | 'center' | 'end' | 'stretch' | 'baseline';
|
|
100
|
+
|
|
101
|
+
/** Main-axis justification */
|
|
102
|
+
export type Justification = 'start' | 'center' | 'end' | 'between' | 'around' | 'evenly';
|
|
103
|
+
|
|
104
|
+
// ── Props ──────────────────────────────────────────────────────
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Props for Frame — the foundational layout building block.
|
|
108
|
+
*
|
|
109
|
+
* Frame provides theme/elevation context, layout (flex/grid), spacing,
|
|
110
|
+
* sizing, appearance, and interactivity. Frames can be nested — inner
|
|
111
|
+
* frames override outer frames for their subtree.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```tsx
|
|
115
|
+
* // Horizontal toolbar with consistent spacing
|
|
116
|
+
* <Frame direction="horizontal" gap="md" padding="lg" align="center">
|
|
117
|
+
* <Button onPress={() => {}}>Save</Button>
|
|
118
|
+
* <Button variant="ghost" onPress={() => {}}>Cancel</Button>
|
|
119
|
+
* </Frame>
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```tsx
|
|
124
|
+
* // Elevated card with primary theme
|
|
125
|
+
* <Frame theme="primary" elevation={2} radius="lg" padding="xl" bordered>
|
|
126
|
+
* <Text>Elevated card content</Text>
|
|
127
|
+
* </Frame>
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```tsx
|
|
132
|
+
* // Clickable card with link
|
|
133
|
+
* <Frame radius="md" padding="md" bordered onPress={() => navigate('/details')}>
|
|
134
|
+
* <Text>Tap to navigate</Text>
|
|
135
|
+
* </Frame>
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export interface FrameProps {
|
|
139
|
+
/**
|
|
140
|
+
* Child elements. Inherits this Frame's theme and elevation context.
|
|
141
|
+
* Raw strings/numbers are auto-wrapped in `<Text>` with theme-appropriate styling.
|
|
142
|
+
*/
|
|
143
|
+
readonly children: React.ReactNode;
|
|
144
|
+
|
|
145
|
+
// ── Theme & Elevation ──
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Theme override for this frame and all descendants.
|
|
149
|
+
* When omitted, inherits from parent Frame or NewtoneProvider.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```tsx
|
|
153
|
+
* <Frame theme="primary"> // Blue-ish surface
|
|
154
|
+
* <Frame theme="strong"> // High-contrast surface
|
|
155
|
+
* ```
|
|
156
|
+
*/
|
|
157
|
+
readonly theme?: ThemeName;
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Surface elevation: controls background lightness and shadow.
|
|
161
|
+
*
|
|
162
|
+
* | Value | Effect |
|
|
163
|
+
* |:-----:|:-------|
|
|
164
|
+
* | -2 | Deeply sunken (inset shadow) |
|
|
165
|
+
* | -1 | Sunken |
|
|
166
|
+
* | 0 | Default surface |
|
|
167
|
+
* | 1 | Elevated |
|
|
168
|
+
* | 2 | Prominently elevated (drop shadow) |
|
|
169
|
+
*
|
|
170
|
+
* When omitted, inherits from parent Frame or defaults to 0.
|
|
171
|
+
*/
|
|
172
|
+
readonly elevation?: FrameElevation;
|
|
173
|
+
|
|
174
|
+
// ── Layout ──
|
|
175
|
+
|
|
176
|
+
/** Layout mode. @default 'flex' */
|
|
177
|
+
readonly layout?: LayoutMode;
|
|
178
|
+
|
|
179
|
+
/** Flex direction. @default 'vertical' */
|
|
180
|
+
readonly direction?: Direction;
|
|
181
|
+
|
|
182
|
+
/** Enable flex wrap. @default false */
|
|
183
|
+
readonly wrap?: boolean;
|
|
184
|
+
|
|
185
|
+
/** Reverse flex direction. @default false */
|
|
186
|
+
readonly reverse?: boolean;
|
|
187
|
+
|
|
188
|
+
/** CSS Grid column count (web-only, requires layout='grid'). */
|
|
189
|
+
readonly columns?: number;
|
|
190
|
+
|
|
191
|
+
/** CSS Grid row count (web-only, requires layout='grid'). */
|
|
192
|
+
readonly rows?: number;
|
|
193
|
+
|
|
194
|
+
// ── Alignment ──
|
|
195
|
+
|
|
196
|
+
/** Cross-axis alignment of children. */
|
|
197
|
+
readonly align?: Alignment;
|
|
198
|
+
|
|
199
|
+
/** Main-axis justification of children. */
|
|
200
|
+
readonly justify?: Justification;
|
|
201
|
+
|
|
202
|
+
// ── Spacing ──
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Padding inside the frame.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```tsx
|
|
209
|
+
* <Frame padding="md" /> // uniform
|
|
210
|
+
* <Frame padding={16} /> // uniform px
|
|
211
|
+
* <Frame padding={{ x: 'lg', y: 'sm' }} /> // axis shorthand
|
|
212
|
+
* <Frame padding={{ top: 8, bottom: 16 }} /> // per-side
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
readonly padding?: PaddingProp;
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Gap between children.
|
|
219
|
+
*
|
|
220
|
+
* @example
|
|
221
|
+
* ```tsx
|
|
222
|
+
* <Frame gap="md" /> // uniform
|
|
223
|
+
* <Frame gap={12} /> // uniform px
|
|
224
|
+
* <Frame gap={{ row: 'sm', column: 'lg' }} /> // per-axis
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
readonly gap?: GapProp;
|
|
228
|
+
|
|
229
|
+
// ── Sizing ──
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Width sizing: `'hug'` (shrink to content), `'fill'` (expand), or fixed px.
|
|
233
|
+
* @example
|
|
234
|
+
* ```tsx
|
|
235
|
+
* <Frame width="fill" /> // expand to fill parent
|
|
236
|
+
* <Frame width={300} /> // fixed 300px
|
|
237
|
+
* ```
|
|
238
|
+
*/
|
|
239
|
+
readonly width?: SizingMode;
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Height sizing: `'hug'` (shrink to content), `'fill'` (expand), or fixed px.
|
|
243
|
+
* @example
|
|
244
|
+
* ```tsx
|
|
245
|
+
* <Frame height="fill" /> // expand to fill parent
|
|
246
|
+
* <Frame height={200} /> // fixed 200px
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
readonly height?: SizingMode;
|
|
250
|
+
|
|
251
|
+
/** Minimum width in pixels. */
|
|
252
|
+
readonly minWidth?: number;
|
|
253
|
+
|
|
254
|
+
/** Maximum width in pixels. */
|
|
255
|
+
readonly maxWidth?: number;
|
|
256
|
+
|
|
257
|
+
/** Minimum height in pixels. */
|
|
258
|
+
readonly minHeight?: number;
|
|
259
|
+
|
|
260
|
+
/** Maximum height in pixels. */
|
|
261
|
+
readonly maxHeight?: number;
|
|
262
|
+
|
|
263
|
+
// ── Appearance ──
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Border radius.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```tsx
|
|
270
|
+
* <Frame radius="md" /> // uniform token
|
|
271
|
+
* <Frame radius={8} /> // uniform px
|
|
272
|
+
* <Frame radius={{ topLeft: 'lg', topRight: 'lg' }} /> // per-corner
|
|
273
|
+
* <Frame radius="pill" /> // fully rounded
|
|
274
|
+
* ```
|
|
275
|
+
*/
|
|
276
|
+
readonly radius?: RadiusProp;
|
|
277
|
+
|
|
278
|
+
/** Show 1px border using theme border color. @default false */
|
|
279
|
+
readonly bordered?: boolean;
|
|
280
|
+
|
|
281
|
+
// ── Interactivity ──
|
|
282
|
+
|
|
283
|
+
/** Press handler — switches rendering from View to Pressable. */
|
|
284
|
+
readonly onPress?: (event: GestureResponderEvent) => void;
|
|
285
|
+
|
|
286
|
+
/** Navigation URL — switches rendering to Pressable with link role. */
|
|
287
|
+
readonly href?: string;
|
|
288
|
+
|
|
289
|
+
/** Disable interaction. @default false */
|
|
290
|
+
readonly disabled?: boolean;
|
|
291
|
+
|
|
292
|
+
// ── Accessibility ──
|
|
293
|
+
|
|
294
|
+
/** Accessible label read by screen readers. Use for icon-only or non-textual frames. */
|
|
295
|
+
readonly accessibilityLabel?: string;
|
|
296
|
+
|
|
297
|
+
/** Additional hint read after the label (e.g. "Opens settings page"). */
|
|
298
|
+
readonly accessibilityHint?: string;
|
|
299
|
+
|
|
300
|
+
// ── Testing & Platform ──
|
|
301
|
+
|
|
302
|
+
/** Test identifier — maps to `data-testid` on web. Used by testing libraries. */
|
|
303
|
+
readonly testID?: string;
|
|
304
|
+
|
|
305
|
+
/** DOM id — maps to `id` attribute on web. Used for anchors and scroll targets. */
|
|
306
|
+
readonly nativeID?: string;
|
|
307
|
+
|
|
308
|
+
/** Ref to the underlying View or Pressable element. */
|
|
309
|
+
readonly ref?: React.Ref<View>;
|
|
310
|
+
|
|
311
|
+
// ── Style override ──
|
|
312
|
+
|
|
313
|
+
/** Custom style overrides (applied last). */
|
|
314
|
+
readonly style?: ViewStyle | ViewStyle[];
|
|
315
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ViewStyle } from 'react-native';
|
|
2
|
-
import type { ResolvedTokens } from '
|
|
2
|
+
import type { ResolvedTokens } from '../../tokens/types';
|
|
3
3
|
import type {
|
|
4
4
|
SpacingValue,
|
|
5
5
|
SpacingAxes,
|
|
@@ -15,27 +15,33 @@ import type {
|
|
|
15
15
|
} from './Frame.types';
|
|
16
16
|
|
|
17
17
|
// ── Spacing ──────────────────────────────────────────────────────
|
|
18
|
+
// These functions convert spacing props (token names like 'md' or pixel numbers)
|
|
19
|
+
// into actual pixel values that React Native can use for padding and gap.
|
|
18
20
|
|
|
19
|
-
/**
|
|
21
|
+
/** Convert a spacing token name (like 'md') or raw number into pixels. */
|
|
20
22
|
export function resolveSpacing(
|
|
21
23
|
value: SpacingValue,
|
|
22
24
|
tokens: ResolvedTokens,
|
|
23
25
|
): number {
|
|
26
|
+
// If already a number, use it directly as pixels.
|
|
24
27
|
if (typeof value === 'number') return value;
|
|
28
|
+
// Otherwise look up the token name in the theme's spacing scale.
|
|
25
29
|
return tokens.spacing[value];
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
/**
|
|
32
|
+
/** Convert a padding prop into pixel values for all four sides. */
|
|
29
33
|
export function resolvePadding(
|
|
30
34
|
prop: PaddingProp,
|
|
31
35
|
tokens: ResolvedTokens,
|
|
32
36
|
): { readonly top: number; readonly right: number; readonly bottom: number; readonly left: number } {
|
|
37
|
+
// Uniform: same padding on all sides (e.g. padding="md" or padding={16}).
|
|
33
38
|
if (typeof prop === 'string' || typeof prop === 'number') {
|
|
34
39
|
const px = resolveSpacing(prop, tokens);
|
|
35
40
|
return { top: px, right: px, bottom: px, left: px };
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
// Axis shorthand:
|
|
43
|
+
// Axis shorthand: separate horizontal (x) and vertical (y) padding.
|
|
44
|
+
// Example: padding={{ x: 'lg', y: 'sm' }}
|
|
39
45
|
if ('x' in prop || 'y' in prop) {
|
|
40
46
|
const axes = prop as SpacingAxes;
|
|
41
47
|
const x = axes.x !== undefined ? resolveSpacing(axes.x, tokens) : 0;
|
|
@@ -43,7 +49,8 @@ export function resolvePadding(
|
|
|
43
49
|
return { top: y, right: x, bottom: y, left: x };
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
// Per-side:
|
|
52
|
+
// Per-side: different padding on each side.
|
|
53
|
+
// Example: padding={{ top: 8, bottom: 16, left: 'md', right: 'md' }}
|
|
47
54
|
const sides = prop as SpacingSides;
|
|
48
55
|
return {
|
|
49
56
|
top: sides.top !== undefined ? resolveSpacing(sides.top, tokens) : 0,
|
|
@@ -53,17 +60,19 @@ export function resolvePadding(
|
|
|
53
60
|
};
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
/**
|
|
63
|
+
/** Convert a gap prop into row and column gap pixel values. */
|
|
57
64
|
export function resolveGap(
|
|
58
65
|
prop: GapProp,
|
|
59
66
|
tokens: ResolvedTokens,
|
|
60
67
|
): { readonly rowGap: number; readonly columnGap: number } {
|
|
68
|
+
// Uniform: same gap in both directions (e.g. gap="md" or gap={12}).
|
|
61
69
|
if (typeof prop === 'string' || typeof prop === 'number') {
|
|
62
70
|
const px = resolveSpacing(prop, tokens);
|
|
63
71
|
return { rowGap: px, columnGap: px };
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
//
|
|
74
|
+
// Per-axis: different gap for rows vs columns.
|
|
75
|
+
// Example: gap={{ row: 'sm', column: 'lg' }}
|
|
67
76
|
return {
|
|
68
77
|
rowGap: prop.row !== undefined ? resolveSpacing(prop.row, tokens) : 0,
|
|
69
78
|
columnGap: prop.column !== undefined ? resolveSpacing(prop.column, tokens) : 0,
|
|
@@ -71,8 +80,10 @@ export function resolveGap(
|
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
// ── Radius ───────────────────────────────────────────────────────
|
|
83
|
+
// These functions convert radius props (token names like 'lg' or pixel numbers)
|
|
84
|
+
// into actual pixel values for rounding corners.
|
|
74
85
|
|
|
75
|
-
/**
|
|
86
|
+
/** Convert a radius token name (like 'lg') or raw number into pixels. */
|
|
76
87
|
export function resolveRadius(
|
|
77
88
|
value: RadiusValue,
|
|
78
89
|
tokens: ResolvedTokens,
|
|
@@ -81,7 +92,7 @@ export function resolveRadius(
|
|
|
81
92
|
return tokens.radius[value];
|
|
82
93
|
}
|
|
83
94
|
|
|
84
|
-
/**
|
|
95
|
+
/** Pixel values for each corner of a rounded rectangle. */
|
|
85
96
|
export interface ResolvedCorners {
|
|
86
97
|
readonly topLeft: number;
|
|
87
98
|
readonly topRight: number;
|
|
@@ -89,17 +100,19 @@ export interface ResolvedCorners {
|
|
|
89
100
|
readonly bottomRight: number;
|
|
90
101
|
}
|
|
91
102
|
|
|
92
|
-
/**
|
|
103
|
+
/** Convert a radius prop into pixel values for all four corners. */
|
|
93
104
|
export function resolveRadiusCorners(
|
|
94
105
|
prop: RadiusProp,
|
|
95
106
|
tokens: ResolvedTokens,
|
|
96
107
|
): ResolvedCorners {
|
|
108
|
+
// Uniform: same radius on all corners (e.g. radius="lg" or radius={8}).
|
|
97
109
|
if (typeof prop === 'string' || typeof prop === 'number') {
|
|
98
110
|
const px = resolveRadius(prop, tokens);
|
|
99
111
|
return { topLeft: px, topRight: px, bottomLeft: px, bottomRight: px };
|
|
100
112
|
}
|
|
101
113
|
|
|
102
|
-
// Per-corner
|
|
114
|
+
// Per-corner: different radius on each corner.
|
|
115
|
+
// Example: radius={{ topLeft: 'lg', topRight: 'lg', bottomLeft: 0, bottomRight: 0 }}
|
|
103
116
|
return {
|
|
104
117
|
topLeft: prop.topLeft !== undefined ? resolveRadius(prop.topLeft, tokens) : 0,
|
|
105
118
|
topRight: prop.topRight !== undefined ? resolveRadius(prop.topRight, tokens) : 0,
|
|
@@ -108,7 +121,7 @@ export function resolveRadiusCorners(
|
|
|
108
121
|
};
|
|
109
122
|
}
|
|
110
123
|
|
|
111
|
-
/** Check if any corner
|
|
124
|
+
/** Check if any corner is rounded (used to decide whether to clip overflowing content). */
|
|
112
125
|
export function hasPositiveRadius(corners: ResolvedCorners): boolean {
|
|
113
126
|
return corners.topLeft > 0
|
|
114
127
|
|| corners.topRight > 0
|
|
@@ -117,8 +130,9 @@ export function hasPositiveRadius(corners: ResolvedCorners): boolean {
|
|
|
117
130
|
}
|
|
118
131
|
|
|
119
132
|
// ── Sizing ───────────────────────────────────────────────────────
|
|
133
|
+
// Converts Figma-like sizing modes into React Native style properties.
|
|
120
134
|
|
|
121
|
-
/**
|
|
135
|
+
/** Convert width/height sizing modes to style properties. */
|
|
122
136
|
export function resolveSizing(
|
|
123
137
|
width: SizingMode | undefined,
|
|
124
138
|
height: SizingMode | undefined,
|
|
@@ -127,29 +141,43 @@ export function resolveSizing(
|
|
|
127
141
|
|
|
128
142
|
if (width !== undefined) {
|
|
129
143
|
if (width === 'fill') {
|
|
144
|
+
// 'fill' = expand to take all available space in the parent.
|
|
145
|
+
// flexGrow tells the layout engine to give this element extra space.
|
|
146
|
+
// width: 100% ensures it spans the full width.
|
|
130
147
|
style.flexGrow = 1;
|
|
131
148
|
style.width = '100%';
|
|
132
149
|
} else if (typeof width === 'number') {
|
|
150
|
+
// Fixed pixel width (e.g. 300).
|
|
133
151
|
style.width = width;
|
|
134
152
|
}
|
|
135
|
-
// 'hug' =
|
|
153
|
+
// 'hug' = shrink to fit the content (React Native's default behavior).
|
|
136
154
|
}
|
|
137
155
|
|
|
138
156
|
if (height !== undefined) {
|
|
139
157
|
if (height === 'fill') {
|
|
140
|
-
|
|
158
|
+
// Same as width 'fill' — expand to fill all available vertical space.
|
|
159
|
+
style.flexGrow = 1;
|
|
141
160
|
style.height = '100%';
|
|
142
161
|
} else if (typeof height === 'number') {
|
|
162
|
+
// Fixed pixel height (e.g. 200).
|
|
143
163
|
style.height = height;
|
|
144
164
|
}
|
|
145
|
-
// 'hug' = default
|
|
165
|
+
// 'hug' = shrink to fit the content (React Native's default behavior).
|
|
146
166
|
}
|
|
147
167
|
|
|
148
168
|
return style as ViewStyle;
|
|
149
169
|
}
|
|
150
170
|
|
|
151
171
|
// ── Layout ───────────────────────────────────────────────────────
|
|
152
|
-
|
|
172
|
+
// These functions convert our friendly prop names (like 'start', 'between')
|
|
173
|
+
// into the actual CSS flexbox values that React Native uses internally.
|
|
174
|
+
|
|
175
|
+
// Maps our alignment names to flexbox cross-axis values:
|
|
176
|
+
// 'start' → push children to the top/left edge
|
|
177
|
+
// 'center' → center children in the cross direction
|
|
178
|
+
// 'end' → push children to the bottom/right edge
|
|
179
|
+
// 'stretch' → stretch children to fill the cross dimension
|
|
180
|
+
// 'baseline' → align children by their text baseline
|
|
153
181
|
const ALIGN_MAP: Record<Alignment, ViewStyle['alignItems']> = {
|
|
154
182
|
start: 'flex-start',
|
|
155
183
|
center: 'center',
|
|
@@ -158,6 +186,13 @@ const ALIGN_MAP: Record<Alignment, ViewStyle['alignItems']> = {
|
|
|
158
186
|
baseline: 'baseline',
|
|
159
187
|
};
|
|
160
188
|
|
|
189
|
+
// Maps our justification names to flexbox main-axis values:
|
|
190
|
+
// 'start' → pack children at the beginning
|
|
191
|
+
// 'center' → pack children in the middle
|
|
192
|
+
// 'end' → pack children at the end
|
|
193
|
+
// 'between' → spread children with equal space between them
|
|
194
|
+
// 'around' → spread children with equal space around each
|
|
195
|
+
// 'evenly' → spread children with equal space everywhere
|
|
161
196
|
const JUSTIFY_MAP: Record<Justification, ViewStyle['justifyContent']> = {
|
|
162
197
|
start: 'flex-start',
|
|
163
198
|
center: 'center',
|
|
@@ -167,21 +202,22 @@ const JUSTIFY_MAP: Record<Justification, ViewStyle['justifyContent']> = {
|
|
|
167
202
|
evenly: 'space-evenly',
|
|
168
203
|
};
|
|
169
204
|
|
|
170
|
-
/**
|
|
205
|
+
/** Convert our alignment name to the flexbox value React Native expects. */
|
|
171
206
|
export function resolveAlignment(align: Alignment): ViewStyle['alignItems'] {
|
|
172
207
|
return ALIGN_MAP[align];
|
|
173
208
|
}
|
|
174
209
|
|
|
175
|
-
/**
|
|
210
|
+
/** Convert our justification name to the flexbox value React Native expects. */
|
|
176
211
|
export function resolveJustification(justify: Justification): ViewStyle['justifyContent'] {
|
|
177
212
|
return JUSTIFY_MAP[justify];
|
|
178
213
|
}
|
|
179
214
|
|
|
180
|
-
/**
|
|
215
|
+
/** Convert direction ('horizontal'/'vertical') + reverse flag to flexbox direction. */
|
|
181
216
|
export function resolveFlexDirection(
|
|
182
217
|
direction: Direction,
|
|
183
218
|
reverse: boolean,
|
|
184
219
|
): ViewStyle['flexDirection'] {
|
|
220
|
+
// 'horizontal' → children in a row, 'vertical' → children in a column.
|
|
185
221
|
if (direction === 'horizontal') {
|
|
186
222
|
return reverse ? 'row-reverse' : 'row';
|
|
187
223
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { Text, type TextStyle } from 'react-native';
|
|
3
|
+
import { srgbToHex } from 'newtone';
|
|
4
|
+
import { useTokens } from '../../tokens/useTokens';
|
|
5
|
+
import type { IconProps } from './Icon.types';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Material Symbols icon component with variable font support.
|
|
9
|
+
*
|
|
10
|
+
* Uses global icon configuration (variant, weight, auto-grade) from theme config,
|
|
11
|
+
* with per-instance control over size, fill, and color.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <Icon name="home" />
|
|
16
|
+
* <Icon name="settings" size={24} fill={1} />
|
|
17
|
+
* <Icon name="check" color="#00ff00" />
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function Icon({
|
|
21
|
+
name,
|
|
22
|
+
size,
|
|
23
|
+
opticalSize,
|
|
24
|
+
fill = 0,
|
|
25
|
+
color,
|
|
26
|
+
elevation = 1,
|
|
27
|
+
style,
|
|
28
|
+
onPress,
|
|
29
|
+
// Accessibility
|
|
30
|
+
accessibilityLabel,
|
|
31
|
+
// Testing & platform
|
|
32
|
+
testID,
|
|
33
|
+
nativeID,
|
|
34
|
+
ref,
|
|
35
|
+
}: IconProps) {
|
|
36
|
+
// Get the current theme's design tokens for colors, typography, and icon settings.
|
|
37
|
+
const tokens = useTokens(elevation);
|
|
38
|
+
|
|
39
|
+
// Build the icon's style from the theme tokens and props.
|
|
40
|
+
// Wrapped in useMemo so it only recalculates when the inputs change,
|
|
41
|
+
// instead of rebuilding the style object on every render.
|
|
42
|
+
const iconStyle = useMemo<TextStyle>(() => {
|
|
43
|
+
// Use the provided size, or fall back to the theme's default text size.
|
|
44
|
+
const fontSize = size ?? tokens.typography.size.base;
|
|
45
|
+
// Optical size adjusts stroke weight for readability at small sizes.
|
|
46
|
+
const opsz = opticalSize ?? fontSize;
|
|
47
|
+
// Use the provided color, or fall back to the theme's primary text color.
|
|
48
|
+
const iconColor = color ?? srgbToHex(tokens.textPrimary.srgb);
|
|
49
|
+
|
|
50
|
+
// Build the font family name from the theme's icon variant setting.
|
|
51
|
+
// Example: variant 'rounded' → 'Material Symbols Rounded'
|
|
52
|
+
// The charAt(0).toUpperCase() + slice(1) capitalizes the first letter.
|
|
53
|
+
const fontFamily = `Material Symbols ${tokens.icons.variant.charAt(0).toUpperCase() + tokens.icons.variant.slice(1)}`;
|
|
54
|
+
|
|
55
|
+
// Material Symbols is a variable font with 4 adjustable axes:
|
|
56
|
+
// FILL: 0 = outlined (hollow), 1 = filled (solid)
|
|
57
|
+
// wght: font weight (thinner or bolder strokes)
|
|
58
|
+
// GRAD: grade (fine-tune weight without changing overall size)
|
|
59
|
+
// opsz: optical size (adjusts detail level for the display size)
|
|
60
|
+
const fontVariationSettings = `'FILL' ${fill}, 'wght' ${tokens.icons.weight}, 'GRAD' ${tokens.icons.grade}, 'opsz' ${opsz}`;
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
fontFamily,
|
|
64
|
+
fontSize,
|
|
65
|
+
color: iconColor,
|
|
66
|
+
userSelect: 'none', // web-only: prevents users from selecting the icon as text
|
|
67
|
+
fontVariationSettings, // web-only: controls the variable font axes listed above
|
|
68
|
+
...style,
|
|
69
|
+
} as TextStyle; // Cast needed because web-only properties aren't in RN's type definitions
|
|
70
|
+
}, [tokens, size, opticalSize, fill, color, style]);
|
|
71
|
+
|
|
72
|
+
// Material Symbols renders icons as text ligatures — the icon name (like "home")
|
|
73
|
+
// is passed as text content, and the font renders it as the icon glyph.
|
|
74
|
+
return (
|
|
75
|
+
<Text
|
|
76
|
+
ref={ref}
|
|
77
|
+
testID={testID}
|
|
78
|
+
nativeID={nativeID}
|
|
79
|
+
accessibilityLabel={accessibilityLabel}
|
|
80
|
+
// When no label is provided, the icon is decorative — hide it from screen readers
|
|
81
|
+
// so assistive technology doesn't try to read the ligature text (e.g. "home").
|
|
82
|
+
importantForAccessibility={accessibilityLabel ? 'yes' : 'no-hide-descendants'}
|
|
83
|
+
style={iconStyle}
|
|
84
|
+
onPress={onPress} // When provided, makes the icon tappable
|
|
85
|
+
>
|
|
86
|
+
{name}
|
|
87
|
+
</Text>
|
|
88
|
+
);
|
|
89
|
+
}
|