@idealyst/components 1.1.2 → 1.1.4
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/package.json +8 -3
- package/src/Button/Button.styles.tsx +1 -0
- package/src/Input/Input.styles.tsx +1 -0
- package/src/Slider/Slider.native.tsx +3 -3
- package/src/TabBar/TabBar.native.tsx +46 -5
- package/src/TabBar/TabBar.styles.tsx +88 -1
- package/src/TabBar/TabBar.web.tsx +48 -2
- package/src/TabBar/types.ts +10 -0
- package/src/Text/Text.native.tsx +5 -7
- package/src/Text/Text.styles.tsx +23 -30
- package/src/Text/Text.web.tsx +6 -8
- package/src/Text/types.ts +1 -9
- package/src/examples/TabBarExamples.tsx +122 -1
- package/src/utils/index.ts +20 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/components",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Shared component library for React and React Native",
|
|
5
5
|
"documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/components#readme",
|
|
6
6
|
"readme": "README.md",
|
|
@@ -25,6 +25,11 @@
|
|
|
25
25
|
"require": "./src/index.ts",
|
|
26
26
|
"types": "./src/index.ts"
|
|
27
27
|
},
|
|
28
|
+
"./utils": {
|
|
29
|
+
"import": "./src/utils/index.ts",
|
|
30
|
+
"require": "./src/utils/index.ts",
|
|
31
|
+
"types": "./src/utils/index.ts"
|
|
32
|
+
},
|
|
28
33
|
"./plugin/web": {
|
|
29
34
|
"import": "./plugin/web.js",
|
|
30
35
|
"require": "./plugin/web.js",
|
|
@@ -41,7 +46,7 @@
|
|
|
41
46
|
"publish:npm": "npm publish"
|
|
42
47
|
},
|
|
43
48
|
"peerDependencies": {
|
|
44
|
-
"@idealyst/theme": "^1.1.
|
|
49
|
+
"@idealyst/theme": "^1.1.4",
|
|
45
50
|
"@mdi/js": ">=7.0.0",
|
|
46
51
|
"@mdi/react": ">=1.0.0",
|
|
47
52
|
"@react-native-vector-icons/common": ">=12.0.0",
|
|
@@ -91,7 +96,7 @@
|
|
|
91
96
|
}
|
|
92
97
|
},
|
|
93
98
|
"devDependencies": {
|
|
94
|
-
"@idealyst/theme": "^1.1.
|
|
99
|
+
"@idealyst/theme": "^1.1.4",
|
|
95
100
|
"@mdi/react": "^1.6.1",
|
|
96
101
|
"@types/react": "^19.1.0",
|
|
97
102
|
"react": "^19.1.0",
|
|
@@ -210,7 +210,7 @@ const Slider = forwardRef<View, SliderProps>(({
|
|
|
210
210
|
{ left: markPosition },
|
|
211
211
|
]}
|
|
212
212
|
>
|
|
213
|
-
<Text
|
|
213
|
+
<Text typography="caption">{mark.label}</Text>
|
|
214
214
|
</View>
|
|
215
215
|
)}
|
|
216
216
|
</View>
|
|
@@ -243,8 +243,8 @@ const Slider = forwardRef<View, SliderProps>(({
|
|
|
243
243
|
|
|
244
244
|
{showMinMax && (
|
|
245
245
|
<View style={sliderStyles.minMaxLabels}>
|
|
246
|
-
<Text style={sliderStyles.minMaxLabel}
|
|
247
|
-
<Text style={sliderStyles.minMaxLabel}
|
|
246
|
+
<Text style={sliderStyles.minMaxLabel} typography="caption">{min}</Text>
|
|
247
|
+
<Text style={sliderStyles.minMaxLabel} typography="caption">{max}</Text>
|
|
248
248
|
</View>
|
|
249
249
|
)}
|
|
250
250
|
</View>
|
|
@@ -1,22 +1,47 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect, forwardRef } from 'react';
|
|
1
|
+
import React, { useState, useRef, useEffect, forwardRef, ReactNode } from 'react';
|
|
2
2
|
import { View, TouchableOpacity, Text, ScrollView } from 'react-native';
|
|
3
3
|
import Animated, { useSharedValue, useAnimatedStyle, withSpring } from 'react-native-reanimated';
|
|
4
4
|
import {
|
|
5
5
|
tabBarContainerStyles,
|
|
6
6
|
tabBarTabStyles,
|
|
7
7
|
tabBarLabelStyles,
|
|
8
|
-
tabBarIndicatorStyles
|
|
8
|
+
tabBarIndicatorStyles,
|
|
9
|
+
tabBarIconStyles
|
|
9
10
|
} from './TabBar.styles';
|
|
10
|
-
import type { TabBarProps } from './types';
|
|
11
|
+
import type { TabBarProps, TabBarItem } from './types';
|
|
12
|
+
|
|
13
|
+
// Icon size mapping based on size variant
|
|
14
|
+
const ICON_SIZES: Record<string, number> = {
|
|
15
|
+
xs: 14,
|
|
16
|
+
sm: 16,
|
|
17
|
+
md: 18,
|
|
18
|
+
lg: 20,
|
|
19
|
+
xl: 24,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Helper to render icon
|
|
23
|
+
function renderIcon(
|
|
24
|
+
icon: TabBarItem['icon'],
|
|
25
|
+
active: boolean,
|
|
26
|
+
size: number
|
|
27
|
+
): ReactNode {
|
|
28
|
+
if (!icon) return null;
|
|
29
|
+
if (typeof icon === 'function') {
|
|
30
|
+
return icon({ active, size });
|
|
31
|
+
}
|
|
32
|
+
return icon;
|
|
33
|
+
}
|
|
11
34
|
|
|
12
35
|
const TabBar = forwardRef<View, TabBarProps>(({
|
|
13
36
|
items,
|
|
14
37
|
value: controlledValue,
|
|
15
38
|
defaultValue,
|
|
16
39
|
onChange,
|
|
17
|
-
type = '
|
|
40
|
+
type = 'standard',
|
|
18
41
|
size = 'md',
|
|
19
42
|
pillMode = 'light',
|
|
43
|
+
iconPosition = 'left',
|
|
44
|
+
justify = 'start',
|
|
20
45
|
// Spacing variants from ContainerStyleProps
|
|
21
46
|
gap,
|
|
22
47
|
padding,
|
|
@@ -87,8 +112,10 @@ const TabBar = forwardRef<View, TabBarProps>(({
|
|
|
87
112
|
|
|
88
113
|
// Apply container and indicator types right before rendering
|
|
89
114
|
tabBarContainerStyles.useVariants({
|
|
115
|
+
type,
|
|
90
116
|
size,
|
|
91
117
|
pillMode,
|
|
118
|
+
justify,
|
|
92
119
|
gap,
|
|
93
120
|
padding,
|
|
94
121
|
paddingVertical,
|
|
@@ -97,7 +124,7 @@ const TabBar = forwardRef<View, TabBarProps>(({
|
|
|
97
124
|
marginVertical,
|
|
98
125
|
marginHorizontal,
|
|
99
126
|
});
|
|
100
|
-
tabBarIndicatorStyles.useVariants({ pillMode });
|
|
127
|
+
tabBarIndicatorStyles.useVariants({ type, pillMode });
|
|
101
128
|
|
|
102
129
|
return (
|
|
103
130
|
<ScrollView
|
|
@@ -118,20 +145,33 @@ const TabBar = forwardRef<View, TabBarProps>(({
|
|
|
118
145
|
<View style={{ flexDirection: 'row' }}>
|
|
119
146
|
{items.map((item) => {
|
|
120
147
|
const isActive = value === item.value;
|
|
148
|
+
const iconSize = ICON_SIZES[size] || 18;
|
|
121
149
|
|
|
122
150
|
// Apply tab and label types for this specific tab
|
|
123
151
|
tabBarTabStyles.useVariants({
|
|
124
152
|
size,
|
|
153
|
+
type,
|
|
125
154
|
active: isActive,
|
|
126
155
|
disabled: Boolean(item.disabled),
|
|
127
156
|
pillMode,
|
|
157
|
+
iconPosition,
|
|
158
|
+
justify,
|
|
128
159
|
});
|
|
129
160
|
tabBarLabelStyles.useVariants({
|
|
130
161
|
size,
|
|
162
|
+
type,
|
|
131
163
|
pillMode,
|
|
132
164
|
active: isActive,
|
|
133
165
|
disabled: Boolean(item.disabled),
|
|
134
166
|
});
|
|
167
|
+
tabBarIconStyles.useVariants({
|
|
168
|
+
size,
|
|
169
|
+
active: isActive,
|
|
170
|
+
disabled: Boolean(item.disabled),
|
|
171
|
+
iconPosition,
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const icon = renderIcon(item.icon, isActive, iconSize);
|
|
135
175
|
|
|
136
176
|
return (
|
|
137
177
|
<TouchableOpacity
|
|
@@ -146,6 +186,7 @@ const TabBar = forwardRef<View, TabBarProps>(({
|
|
|
146
186
|
activeOpacity={0.7}
|
|
147
187
|
testID={`${testID}-tab-${item.value}`}
|
|
148
188
|
>
|
|
189
|
+
{icon && <View style={tabBarIconStyles.tabIcon}>{icon}</View>}
|
|
149
190
|
<Text style={tabBarLabelStyles.tabLabel}>{item.label}</Text>
|
|
150
191
|
</TouchableOpacity>
|
|
151
192
|
);
|
|
@@ -10,12 +10,13 @@ import {
|
|
|
10
10
|
buildMarginVerticalVariants,
|
|
11
11
|
buildMarginHorizontalVariants,
|
|
12
12
|
} from '../utils/buildViewStyleVariants';
|
|
13
|
-
import { TabBarPillMode, TabBarSizeVariant, TabBarType } from './types';
|
|
13
|
+
import { TabBarPillMode, TabBarSizeVariant, TabBarType, TabBarIconPosition, TabBarJustify } from './types';
|
|
14
14
|
|
|
15
15
|
type TabBarContainerVariants = {
|
|
16
16
|
type: TabBarType;
|
|
17
17
|
size: TabBarSizeVariant;
|
|
18
18
|
pillMode: TabBarPillMode;
|
|
19
|
+
justify: TabBarJustify;
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
type TabBarTabVariants = {
|
|
@@ -24,6 +25,15 @@ type TabBarTabVariants = {
|
|
|
24
25
|
active: boolean;
|
|
25
26
|
disabled: boolean;
|
|
26
27
|
pillMode: TabBarPillMode;
|
|
28
|
+
iconPosition: TabBarIconPosition;
|
|
29
|
+
justify: TabBarJustify;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type TabBarIconVariants = {
|
|
33
|
+
size: TabBarSizeVariant;
|
|
34
|
+
active: boolean;
|
|
35
|
+
disabled: boolean;
|
|
36
|
+
iconPosition: TabBarIconPosition;
|
|
27
37
|
}
|
|
28
38
|
|
|
29
39
|
type TabBarLabelVariants = {
|
|
@@ -238,6 +248,22 @@ const createContainerStyles = (theme: Theme) => {
|
|
|
238
248
|
light: {},
|
|
239
249
|
dark: {},
|
|
240
250
|
},
|
|
251
|
+
justify: {
|
|
252
|
+
start: {
|
|
253
|
+
justifyContent: 'flex-start',
|
|
254
|
+
},
|
|
255
|
+
center: {
|
|
256
|
+
justifyContent: 'center',
|
|
257
|
+
},
|
|
258
|
+
equal: {
|
|
259
|
+
justifyContent: 'stretch',
|
|
260
|
+
width: '100%',
|
|
261
|
+
},
|
|
262
|
+
'space-between': {
|
|
263
|
+
justifyContent: 'space-between',
|
|
264
|
+
width: '100%',
|
|
265
|
+
},
|
|
266
|
+
},
|
|
241
267
|
// Spacing variants from ContainerStyleProps
|
|
242
268
|
gap: buildGapVariants(theme),
|
|
243
269
|
padding: buildPaddingVariants(theme),
|
|
@@ -262,6 +288,7 @@ const createTabStyles = (theme: Theme) => {
|
|
|
262
288
|
position: 'relative',
|
|
263
289
|
zIndex: 2,
|
|
264
290
|
backgroundColor: 'transparent',
|
|
291
|
+
gap: 6,
|
|
265
292
|
variants: {
|
|
266
293
|
size: createTabSizeVariants(theme),
|
|
267
294
|
type: {
|
|
@@ -298,6 +325,22 @@ const createTabStyles = (theme: Theme) => {
|
|
|
298
325
|
light: {},
|
|
299
326
|
dark: {},
|
|
300
327
|
},
|
|
328
|
+
iconPosition: {
|
|
329
|
+
left: {
|
|
330
|
+
flexDirection: 'row',
|
|
331
|
+
},
|
|
332
|
+
top: {
|
|
333
|
+
flexDirection: 'column',
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
justify: {
|
|
337
|
+
start: {},
|
|
338
|
+
center: {},
|
|
339
|
+
equal: {
|
|
340
|
+
flex: 1,
|
|
341
|
+
},
|
|
342
|
+
'space-between': {},
|
|
343
|
+
},
|
|
301
344
|
} as const,
|
|
302
345
|
compoundVariants: createTabCompoundVariants(theme),
|
|
303
346
|
_web: {
|
|
@@ -379,6 +422,43 @@ const createIndicatorStyles = (theme: Theme) => {
|
|
|
379
422
|
} as const;
|
|
380
423
|
}
|
|
381
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Create size variants for icon
|
|
427
|
+
*/
|
|
428
|
+
function createIconSizeVariants(theme: Theme) {
|
|
429
|
+
return buildSizeVariants(theme, 'tabBar', (size) => ({
|
|
430
|
+
width: size.iconSize || size.fontSize,
|
|
431
|
+
height: size.iconSize || size.fontSize,
|
|
432
|
+
}));
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const createIconStyles = (theme: Theme) => {
|
|
436
|
+
return {
|
|
437
|
+
display: 'flex',
|
|
438
|
+
alignItems: 'center',
|
|
439
|
+
justifyContent: 'center',
|
|
440
|
+
variants: {
|
|
441
|
+
size: createIconSizeVariants(theme),
|
|
442
|
+
active: {
|
|
443
|
+
true: {},
|
|
444
|
+
false: {},
|
|
445
|
+
},
|
|
446
|
+
disabled: {
|
|
447
|
+
true: {
|
|
448
|
+
opacity: 0.5,
|
|
449
|
+
},
|
|
450
|
+
false: {},
|
|
451
|
+
},
|
|
452
|
+
iconPosition: {
|
|
453
|
+
left: {},
|
|
454
|
+
top: {
|
|
455
|
+
marginBottom: 2,
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
} as const,
|
|
459
|
+
} as const;
|
|
460
|
+
}
|
|
461
|
+
|
|
382
462
|
// Styles are inlined here instead of in @idealyst/theme because Unistyles' Babel transform on native cannot resolve function calls to extract variant structures.
|
|
383
463
|
// @ts-ignore - TS language server needs restart to pick up theme structure changes
|
|
384
464
|
export const tabBarStyles = StyleSheet.create((theme: Theme) => {
|
|
@@ -386,6 +466,7 @@ export const tabBarStyles = StyleSheet.create((theme: Theme) => {
|
|
|
386
466
|
container: createContainerStyles(theme),
|
|
387
467
|
tab: createTabStyles(theme),
|
|
388
468
|
tabLabel: createTabLabelStyles(theme),
|
|
469
|
+
tabIcon: createIconStyles(theme),
|
|
389
470
|
indicator: createIndicatorStyles(theme),
|
|
390
471
|
};
|
|
391
472
|
});
|
|
@@ -414,3 +495,9 @@ export const tabBarIndicatorStyles = StyleSheet.create((theme: Theme) => {
|
|
|
414
495
|
indicator: createIndicatorStyles(theme),
|
|
415
496
|
} as const;
|
|
416
497
|
});
|
|
498
|
+
|
|
499
|
+
export const tabBarIconStyles = StyleSheet.create((theme: Theme) => {
|
|
500
|
+
return {
|
|
501
|
+
tabIcon: createIconStyles(theme),
|
|
502
|
+
} as const;
|
|
503
|
+
});
|
|
@@ -1,14 +1,37 @@
|
|
|
1
|
-
import React, { useState, useRef, useEffect } from 'react';
|
|
1
|
+
import React, { useState, useRef, useEffect, ReactNode } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
3
|
import {
|
|
4
4
|
tabBarContainerStyles,
|
|
5
5
|
tabBarTabStyles,
|
|
6
6
|
tabBarLabelStyles,
|
|
7
|
-
tabBarIndicatorStyles
|
|
7
|
+
tabBarIndicatorStyles,
|
|
8
|
+
tabBarIconStyles
|
|
8
9
|
} from './TabBar.styles';
|
|
9
10
|
import type { TabBarProps, TabBarItem } from './types';
|
|
10
11
|
import useMergeRefs from '../hooks/useMergeRefs';
|
|
11
12
|
|
|
13
|
+
// Icon size mapping based on size variant
|
|
14
|
+
const ICON_SIZES: Record<string, number> = {
|
|
15
|
+
xs: 14,
|
|
16
|
+
sm: 16,
|
|
17
|
+
md: 18,
|
|
18
|
+
lg: 20,
|
|
19
|
+
xl: 24,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Helper to render icon
|
|
23
|
+
function renderIcon(
|
|
24
|
+
icon: TabBarItem['icon'],
|
|
25
|
+
active: boolean,
|
|
26
|
+
size: number
|
|
27
|
+
): ReactNode {
|
|
28
|
+
if (!icon) return null;
|
|
29
|
+
if (typeof icon === 'function') {
|
|
30
|
+
return icon({ active, size });
|
|
31
|
+
}
|
|
32
|
+
return icon;
|
|
33
|
+
}
|
|
34
|
+
|
|
12
35
|
interface TabProps {
|
|
13
36
|
item: TabBarItem;
|
|
14
37
|
isActive: boolean;
|
|
@@ -16,6 +39,8 @@ interface TabProps {
|
|
|
16
39
|
size: TabBarProps['size'];
|
|
17
40
|
type: TabBarProps['type'];
|
|
18
41
|
pillMode: TabBarProps['pillMode'];
|
|
42
|
+
iconPosition: TabBarProps['iconPosition'];
|
|
43
|
+
justify: TabBarProps['justify'];
|
|
19
44
|
testID?: string;
|
|
20
45
|
tabRef: (el: HTMLButtonElement | null) => void;
|
|
21
46
|
}
|
|
@@ -27,9 +52,13 @@ const Tab: React.FC<TabProps> = ({
|
|
|
27
52
|
size,
|
|
28
53
|
type,
|
|
29
54
|
pillMode,
|
|
55
|
+
iconPosition,
|
|
56
|
+
justify,
|
|
30
57
|
testID,
|
|
31
58
|
tabRef,
|
|
32
59
|
}) => {
|
|
60
|
+
const iconSize = ICON_SIZES[size || 'md'] || 18;
|
|
61
|
+
|
|
33
62
|
// Apply tab and label types for this specific tab
|
|
34
63
|
tabBarTabStyles.useVariants({
|
|
35
64
|
size,
|
|
@@ -37,6 +66,8 @@ const Tab: React.FC<TabProps> = ({
|
|
|
37
66
|
active: isActive,
|
|
38
67
|
disabled: Boolean(item.disabled),
|
|
39
68
|
pillMode,
|
|
69
|
+
iconPosition,
|
|
70
|
+
justify,
|
|
40
71
|
});
|
|
41
72
|
tabBarLabelStyles.useVariants({
|
|
42
73
|
size,
|
|
@@ -45,9 +76,16 @@ const Tab: React.FC<TabProps> = ({
|
|
|
45
76
|
active: isActive,
|
|
46
77
|
disabled: Boolean(item.disabled),
|
|
47
78
|
});
|
|
79
|
+
tabBarIconStyles.useVariants({
|
|
80
|
+
size,
|
|
81
|
+
active: isActive,
|
|
82
|
+
disabled: Boolean(item.disabled),
|
|
83
|
+
iconPosition,
|
|
84
|
+
});
|
|
48
85
|
|
|
49
86
|
const tabProps = getWebProps([tabBarTabStyles.tab]);
|
|
50
87
|
const labelProps = getWebProps([tabBarLabelStyles.tabLabel]);
|
|
88
|
+
const iconProps = getWebProps([tabBarIconStyles.tabIcon]);
|
|
51
89
|
|
|
52
90
|
// Merge refs from getWebProps with our tracking ref
|
|
53
91
|
const mergedRef = useMergeRefs<HTMLButtonElement>(
|
|
@@ -55,6 +93,8 @@ const Tab: React.FC<TabProps> = ({
|
|
|
55
93
|
tabRef
|
|
56
94
|
);
|
|
57
95
|
|
|
96
|
+
const icon = renderIcon(item.icon, isActive, iconSize);
|
|
97
|
+
|
|
58
98
|
return (
|
|
59
99
|
<button
|
|
60
100
|
{...tabProps}
|
|
@@ -66,6 +106,7 @@ const Tab: React.FC<TabProps> = ({
|
|
|
66
106
|
aria-disabled={item.disabled}
|
|
67
107
|
data-testid={`${testID}-tab-${item.value}`}
|
|
68
108
|
>
|
|
109
|
+
{icon && <span {...iconProps}>{icon}</span>}
|
|
69
110
|
<span {...labelProps}>{item.label}</span>
|
|
70
111
|
</button>
|
|
71
112
|
);
|
|
@@ -79,6 +120,8 @@ const TabBar: React.FC<TabBarProps> = ({
|
|
|
79
120
|
type = 'standard',
|
|
80
121
|
size = 'md',
|
|
81
122
|
pillMode = 'light',
|
|
123
|
+
iconPosition = 'left',
|
|
124
|
+
justify = 'start',
|
|
82
125
|
// Spacing variants from ContainerStyleProps
|
|
83
126
|
gap,
|
|
84
127
|
padding,
|
|
@@ -152,6 +195,7 @@ const TabBar: React.FC<TabBarProps> = ({
|
|
|
152
195
|
type,
|
|
153
196
|
size,
|
|
154
197
|
pillMode,
|
|
198
|
+
justify,
|
|
155
199
|
gap,
|
|
156
200
|
padding,
|
|
157
201
|
paddingVertical,
|
|
@@ -208,6 +252,8 @@ const TabBar: React.FC<TabBarProps> = ({
|
|
|
208
252
|
size={size}
|
|
209
253
|
type={type}
|
|
210
254
|
pillMode={pillMode}
|
|
255
|
+
iconPosition={iconPosition}
|
|
256
|
+
justify={justify}
|
|
211
257
|
testID={testID}
|
|
212
258
|
tabRef={(el) => {
|
|
213
259
|
tabRefs.current[item.value] = el;
|
package/src/TabBar/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Size } from '@idealyst/theme';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
2
3
|
import type { StyleProp, ViewStyle } from 'react-native';
|
|
3
4
|
import { ContainerStyleProps } from '../utils/viewStyleProps';
|
|
4
5
|
|
|
@@ -6,10 +7,15 @@ import { ContainerStyleProps } from '../utils/viewStyleProps';
|
|
|
6
7
|
export type TabBarSizeVariant = Size;
|
|
7
8
|
export type TabBarType = 'standard' | 'pills' | 'underline';
|
|
8
9
|
export type TabBarPillMode = 'light' | 'dark';
|
|
10
|
+
export type TabBarIconPosition = 'left' | 'top';
|
|
11
|
+
/** Layout justification for tabs */
|
|
12
|
+
export type TabBarJustify = 'start' | 'center' | 'equal' | 'space-between';
|
|
9
13
|
|
|
10
14
|
export interface TabBarItem {
|
|
11
15
|
value: string;
|
|
12
16
|
label: string;
|
|
17
|
+
/** Icon to display - can be a React node or a render function receiving active state */
|
|
18
|
+
icon?: ReactNode | ((props: { active: boolean; size: number }) => ReactNode);
|
|
13
19
|
disabled?: boolean;
|
|
14
20
|
}
|
|
15
21
|
|
|
@@ -22,6 +28,10 @@ export interface TabBarProps extends ContainerStyleProps {
|
|
|
22
28
|
size?: TabBarSizeVariant;
|
|
23
29
|
/** Mode for pills variant: 'light' for light backgrounds (dark pill), 'dark' for dark backgrounds (light pill) */
|
|
24
30
|
pillMode?: TabBarPillMode;
|
|
31
|
+
/** Position of icon relative to label: 'left' (horizontal) or 'top' (stacked) */
|
|
32
|
+
iconPosition?: TabBarIconPosition;
|
|
33
|
+
/** Layout justification: 'start' (left), 'center', 'equal' (full width equal tabs), 'space-between' */
|
|
34
|
+
justify?: TabBarJustify;
|
|
25
35
|
style?: StyleProp<ViewStyle>;
|
|
26
36
|
testID?: string;
|
|
27
37
|
}
|
package/src/Text/Text.native.tsx
CHANGED
|
@@ -5,9 +5,8 @@ import { textStyles } from './Text.styles';
|
|
|
5
5
|
|
|
6
6
|
const Text = forwardRef<RNText, TextProps>(({
|
|
7
7
|
children,
|
|
8
|
-
typography,
|
|
9
|
-
|
|
10
|
-
weight = 'normal',
|
|
8
|
+
typography = 'body1',
|
|
9
|
+
weight,
|
|
11
10
|
color,
|
|
12
11
|
align = 'left',
|
|
13
12
|
// Spacing variants from TextSpacingStyleProps
|
|
@@ -19,10 +18,9 @@ const Text = forwardRef<RNText, TextProps>(({
|
|
|
19
18
|
testID,
|
|
20
19
|
id,
|
|
21
20
|
}, ref) => {
|
|
22
|
-
// When typography is set, it overrides size and weight (handled in styles)
|
|
23
21
|
textStyles.useVariants({
|
|
24
|
-
|
|
25
|
-
weight
|
|
22
|
+
typography,
|
|
23
|
+
weight,
|
|
26
24
|
align,
|
|
27
25
|
gap,
|
|
28
26
|
padding,
|
|
@@ -35,7 +33,7 @@ const Text = forwardRef<RNText, TextProps>(({
|
|
|
35
33
|
ref={ref}
|
|
36
34
|
nativeID={id}
|
|
37
35
|
style={[
|
|
38
|
-
textStyles.text({ color
|
|
36
|
+
textStyles.text({ color }),
|
|
39
37
|
style,
|
|
40
38
|
]}
|
|
41
39
|
testID={testID}
|
package/src/Text/Text.styles.tsx
CHANGED
|
@@ -1,47 +1,51 @@
|
|
|
1
1
|
import { StyleSheet } from "react-native-unistyles";
|
|
2
|
-
import { Theme } from '@idealyst/theme';
|
|
3
|
-
import { buildSizeVariants } from '../utils/buildSizeVariants';
|
|
2
|
+
import { Theme, Typography } from '@idealyst/theme';
|
|
4
3
|
import {
|
|
5
4
|
buildGapVariants,
|
|
6
5
|
buildPaddingVariants,
|
|
7
6
|
buildPaddingVerticalVariants,
|
|
8
7
|
buildPaddingHorizontalVariants,
|
|
9
8
|
} from '../utils/buildViewStyleVariants';
|
|
10
|
-
import { TextAlignVariant, TextColorVariant,
|
|
9
|
+
import { TextAlignVariant, TextColorVariant, TextWeightVariant, TextTypographyVariant } from "./types";
|
|
11
10
|
|
|
12
|
-
type TextVariants = {
|
|
13
|
-
|
|
11
|
+
export type TextVariants = {
|
|
12
|
+
typography: TextTypographyVariant;
|
|
14
13
|
weight: TextWeightVariant;
|
|
15
14
|
align: TextAlignVariant;
|
|
16
15
|
}
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
|
-
* Create
|
|
20
|
-
* @deprecated Use typography prop instead
|
|
18
|
+
* Create typography variants from theme
|
|
21
19
|
*/
|
|
22
|
-
function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
function createTypographyVariants(theme: Theme) {
|
|
21
|
+
const variants: Record<string, object> = {};
|
|
22
|
+
|
|
23
|
+
for (const key in theme.sizes.typography) {
|
|
24
|
+
const typo = theme.sizes.typography[key as Typography];
|
|
25
|
+
variants[key] = {
|
|
26
|
+
fontSize: typo.fontSize,
|
|
27
|
+
lineHeight: typo.lineHeight,
|
|
28
|
+
fontWeight: typo.fontWeight,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return variants;
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
type TextStyleParams = {
|
|
30
36
|
color?: TextColorVariant;
|
|
31
|
-
typography?: TextTypographyVariant;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
function createTextStyles(theme: Theme) {
|
|
35
|
-
return ({ color
|
|
36
|
-
const colorValue = theme.colors.text[color] || theme.colors.text.primary;
|
|
40
|
+
return ({ color }: TextStyleParams) => {
|
|
41
|
+
const colorValue = theme.colors.text[color ?? 'primary'] || theme.colors.text.primary;
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
const baseStyles: any = {
|
|
43
|
+
return {
|
|
40
44
|
margin: 0,
|
|
41
45
|
padding: 0,
|
|
42
46
|
color: colorValue,
|
|
43
47
|
variants: {
|
|
44
|
-
|
|
48
|
+
typography: createTypographyVariants(theme),
|
|
45
49
|
weight: {
|
|
46
50
|
light: {
|
|
47
51
|
fontWeight: '300',
|
|
@@ -78,19 +82,8 @@ function createTextStyles(theme: Theme) {
|
|
|
78
82
|
} as const,
|
|
79
83
|
_web: {
|
|
80
84
|
fontFamily: 'inherit',
|
|
81
|
-
lineHeight: 'inherit',
|
|
82
85
|
},
|
|
83
86
|
};
|
|
84
|
-
|
|
85
|
-
// If typography is set, apply typography styles (overrides size/weight variants)
|
|
86
|
-
if (typography && theme.sizes.typography[typography]) {
|
|
87
|
-
const typo = theme.sizes.typography[typography];
|
|
88
|
-
baseStyles.fontSize = typo.fontSize;
|
|
89
|
-
baseStyles.lineHeight = typo.lineHeight;
|
|
90
|
-
baseStyles.fontWeight = typo.fontWeight;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return baseStyles;
|
|
94
87
|
}
|
|
95
88
|
}
|
|
96
89
|
|
|
@@ -100,4 +93,4 @@ export const textStyles = StyleSheet.create((theme: Theme) => {
|
|
|
100
93
|
return {
|
|
101
94
|
text: createTextStyles(theme),
|
|
102
95
|
};
|
|
103
|
-
});
|
|
96
|
+
});
|
package/src/Text/Text.web.tsx
CHANGED
|
@@ -6,9 +6,8 @@ import useMergeRefs from '../hooks/useMergeRefs';
|
|
|
6
6
|
|
|
7
7
|
const Text = forwardRef<HTMLSpanElement, TextProps>(({
|
|
8
8
|
children,
|
|
9
|
-
typography,
|
|
10
|
-
|
|
11
|
-
weight = 'normal',
|
|
9
|
+
typography = 'body1',
|
|
10
|
+
weight,
|
|
12
11
|
color = 'primary',
|
|
13
12
|
align = 'left',
|
|
14
13
|
// Spacing variants from TextSpacingStyleProps
|
|
@@ -20,10 +19,9 @@ const Text = forwardRef<HTMLSpanElement, TextProps>(({
|
|
|
20
19
|
testID,
|
|
21
20
|
id,
|
|
22
21
|
}, ref) => {
|
|
23
|
-
// When typography is set, it overrides size and weight (handled in styles)
|
|
24
22
|
textStyles.useVariants({
|
|
25
|
-
|
|
26
|
-
weight
|
|
23
|
+
typography,
|
|
24
|
+
weight,
|
|
27
25
|
align,
|
|
28
26
|
gap,
|
|
29
27
|
padding,
|
|
@@ -31,9 +29,9 @@ const Text = forwardRef<HTMLSpanElement, TextProps>(({
|
|
|
31
29
|
paddingHorizontal,
|
|
32
30
|
});
|
|
33
31
|
|
|
34
|
-
// Create the style array
|
|
32
|
+
// Create the style array
|
|
35
33
|
const textStyleArray = [
|
|
36
|
-
textStyles.text({ color
|
|
34
|
+
textStyles.text({ color }),
|
|
37
35
|
style,
|
|
38
36
|
];
|
|
39
37
|
|
package/src/Text/types.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { TextSpacingStyleProps } from '../utils/viewStyleProps';
|
|
|
5
5
|
|
|
6
6
|
// Component-specific type aliases for future extensibility
|
|
7
7
|
export type TextColorVariant = Text;
|
|
8
|
-
export type TextSizeVariant = 'sm' | 'md' | 'lg' | 'xl'; // Using sm/md/lg/xl for consistency
|
|
9
8
|
export type TextWeightVariant = 'light' | 'normal' | 'medium' | 'semibold' | 'bold';
|
|
10
9
|
export type TextAlignVariant = 'left' | 'center' | 'right';
|
|
11
10
|
export type TextTypographyVariant = Typography;
|
|
@@ -19,19 +18,12 @@ export interface TextProps extends TextSpacingStyleProps {
|
|
|
19
18
|
/**
|
|
20
19
|
* Typography variant for semantic text styling.
|
|
21
20
|
* Automatically sets fontSize, lineHeight, and fontWeight.
|
|
22
|
-
* When set, overrides size and weight props.
|
|
23
21
|
*/
|
|
24
22
|
typography?: TextTypographyVariant;
|
|
25
23
|
|
|
26
|
-
/**
|
|
27
|
-
* The size variant of the text
|
|
28
|
-
* @deprecated Use `typography` prop instead for semantic text styling (e.g., 'h1', 'body1', 'caption')
|
|
29
|
-
*/
|
|
30
|
-
size?: TextSizeVariant;
|
|
31
|
-
|
|
32
24
|
/**
|
|
33
25
|
* The weight of the text.
|
|
34
|
-
*
|
|
26
|
+
* Overrides the weight from typography if both are set.
|
|
35
27
|
*/
|
|
36
28
|
weight?: TextWeightVariant;
|
|
37
29
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
|
-
import { Screen, View, Text } from '@idealyst/components';
|
|
2
|
+
import { Screen, View, Text, Icon } from '@idealyst/components';
|
|
3
3
|
import TabBar from '../TabBar';
|
|
4
4
|
import type { TabBarItem } from '../TabBar/types';
|
|
5
5
|
|
|
@@ -7,6 +7,8 @@ export const TabBarExamples: React.FC = () => {
|
|
|
7
7
|
const [activeTab, setActiveTab] = useState('tab1');
|
|
8
8
|
const [variantTab, setVariantTab] = useState('home');
|
|
9
9
|
const [pillTab, setPillTab] = useState('tab1');
|
|
10
|
+
const [iconTab, setIconTab] = useState('home');
|
|
11
|
+
const [justifyTab, setJustifyTab] = useState('tab1');
|
|
10
12
|
|
|
11
13
|
const basicTabs: TabBarItem[] = [
|
|
12
14
|
{ value: 'tab1', label: 'Tab 1' },
|
|
@@ -26,6 +28,38 @@ export const TabBarExamples: React.FC = () => {
|
|
|
26
28
|
{ value: 'tab3', label: 'Enabled' },
|
|
27
29
|
];
|
|
28
30
|
|
|
31
|
+
// Tabs with icons using render function
|
|
32
|
+
const iconTabs: TabBarItem[] = [
|
|
33
|
+
{
|
|
34
|
+
value: 'home',
|
|
35
|
+
label: 'Home',
|
|
36
|
+
icon: ({ active, size }) => (
|
|
37
|
+
<Icon name="home" size={size} color={active ? 'primary' : 'secondary'} />
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
value: 'search',
|
|
42
|
+
label: 'Search',
|
|
43
|
+
icon: ({ active, size }) => (
|
|
44
|
+
<Icon name="magnify" size={size} color={active ? 'primary' : 'secondary'} />
|
|
45
|
+
),
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
value: 'profile',
|
|
49
|
+
label: 'Profile',
|
|
50
|
+
icon: ({ active, size }) => (
|
|
51
|
+
<Icon name="account" size={size} color={active ? 'primary' : 'secondary'} />
|
|
52
|
+
),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
value: 'settings',
|
|
56
|
+
label: 'Settings',
|
|
57
|
+
icon: ({ active, size }) => (
|
|
58
|
+
<Icon name="cog" size={size} color={active ? 'primary' : 'secondary'} />
|
|
59
|
+
),
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
29
63
|
return (
|
|
30
64
|
<Screen background="primary" padding="lg">
|
|
31
65
|
<View gap="lg">
|
|
@@ -135,6 +169,93 @@ export const TabBarExamples: React.FC = () => {
|
|
|
135
169
|
onChange={setActiveTab}
|
|
136
170
|
/>
|
|
137
171
|
</View>
|
|
172
|
+
|
|
173
|
+
<View gap="md">
|
|
174
|
+
<Text typography="h5">With Icons</Text>
|
|
175
|
+
<View gap="sm">
|
|
176
|
+
<View gap="xs">
|
|
177
|
+
<Text typography="body2">Icons Left (default)</Text>
|
|
178
|
+
<TabBar
|
|
179
|
+
items={iconTabs}
|
|
180
|
+
value={iconTab}
|
|
181
|
+
onChange={setIconTab}
|
|
182
|
+
iconPosition="left"
|
|
183
|
+
/>
|
|
184
|
+
</View>
|
|
185
|
+
<View gap="xs">
|
|
186
|
+
<Text typography="body2">Icons Top (stacked)</Text>
|
|
187
|
+
<TabBar
|
|
188
|
+
items={iconTabs}
|
|
189
|
+
value={iconTab}
|
|
190
|
+
onChange={setIconTab}
|
|
191
|
+
iconPosition="top"
|
|
192
|
+
/>
|
|
193
|
+
</View>
|
|
194
|
+
<View gap="xs">
|
|
195
|
+
<Text typography="body2">Pills with Icons</Text>
|
|
196
|
+
<TabBar
|
|
197
|
+
items={iconTabs}
|
|
198
|
+
value={iconTab}
|
|
199
|
+
onChange={setIconTab}
|
|
200
|
+
type="pills"
|
|
201
|
+
iconPosition="left"
|
|
202
|
+
/>
|
|
203
|
+
</View>
|
|
204
|
+
</View>
|
|
205
|
+
</View>
|
|
206
|
+
|
|
207
|
+
<View gap="md">
|
|
208
|
+
<Text typography="h5">Layout Justification</Text>
|
|
209
|
+
<View gap="sm">
|
|
210
|
+
<View gap="xs">
|
|
211
|
+
<Text typography="body2">Start (default)</Text>
|
|
212
|
+
<TabBar
|
|
213
|
+
items={basicTabs}
|
|
214
|
+
value={justifyTab}
|
|
215
|
+
onChange={setJustifyTab}
|
|
216
|
+
justify="start"
|
|
217
|
+
/>
|
|
218
|
+
</View>
|
|
219
|
+
<View gap="xs">
|
|
220
|
+
<Text typography="body2">Center</Text>
|
|
221
|
+
<TabBar
|
|
222
|
+
items={basicTabs}
|
|
223
|
+
value={justifyTab}
|
|
224
|
+
onChange={setJustifyTab}
|
|
225
|
+
justify="center"
|
|
226
|
+
/>
|
|
227
|
+
</View>
|
|
228
|
+
<View gap="xs">
|
|
229
|
+
<Text typography="body2">Equal (full width, equal tabs)</Text>
|
|
230
|
+
<TabBar
|
|
231
|
+
items={basicTabs}
|
|
232
|
+
value={justifyTab}
|
|
233
|
+
onChange={setJustifyTab}
|
|
234
|
+
justify="equal"
|
|
235
|
+
/>
|
|
236
|
+
</View>
|
|
237
|
+
<View gap="xs">
|
|
238
|
+
<Text typography="body2">Space Between</Text>
|
|
239
|
+
<TabBar
|
|
240
|
+
items={basicTabs}
|
|
241
|
+
value={justifyTab}
|
|
242
|
+
onChange={setJustifyTab}
|
|
243
|
+
justify="space-between"
|
|
244
|
+
/>
|
|
245
|
+
</View>
|
|
246
|
+
</View>
|
|
247
|
+
</View>
|
|
248
|
+
|
|
249
|
+
<View gap="md">
|
|
250
|
+
<Text typography="h5">Full Width with Icons</Text>
|
|
251
|
+
<TabBar
|
|
252
|
+
items={iconTabs}
|
|
253
|
+
value={iconTab}
|
|
254
|
+
onChange={setIconTab}
|
|
255
|
+
justify="equal"
|
|
256
|
+
iconPosition="top"
|
|
257
|
+
/>
|
|
258
|
+
</View>
|
|
138
259
|
</View>
|
|
139
260
|
</Screen>
|
|
140
261
|
);
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Size variant builder
|
|
2
|
+
export { buildSizeVariants } from './buildSizeVariants';
|
|
3
|
+
|
|
4
|
+
// View/container style variant builders
|
|
5
|
+
export {
|
|
6
|
+
buildGapVariants,
|
|
7
|
+
buildPaddingVariants,
|
|
8
|
+
buildPaddingVerticalVariants,
|
|
9
|
+
buildPaddingHorizontalVariants,
|
|
10
|
+
buildMarginVariants,
|
|
11
|
+
buildMarginVerticalVariants,
|
|
12
|
+
buildMarginHorizontalVariants,
|
|
13
|
+
buildContainerStyleVariants,
|
|
14
|
+
buildPaddingStyleVariants,
|
|
15
|
+
buildMarginStyleVariants,
|
|
16
|
+
buildTextSpacingVariants,
|
|
17
|
+
} from './buildViewStyleVariants';
|
|
18
|
+
|
|
19
|
+
// General style helpers
|
|
20
|
+
export { deepMerge, isPlainObject } from './styleHelpers';
|