jfs-components 0.0.69 → 0.0.71
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/CHANGELOG.md +20 -0
- package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +203 -0
- package/lib/commonjs/components/CardCTA/CardCTA.js +198 -16
- package/lib/commonjs/components/CircularProgressBar/CircularProgressBar.js +147 -0
- package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +258 -0
- package/lib/commonjs/components/CircularRating/CircularRating.js +161 -0
- package/lib/commonjs/components/Gauge/Gauge.js +223 -0
- package/lib/commonjs/components/ListGroup/ListGroup.js +3 -1
- package/lib/commonjs/components/MediaCard/GlassFill.js +62 -0
- package/lib/commonjs/components/MediaCard/GlassFill.web.js +48 -0
- package/lib/commonjs/components/MediaCard/MediaCard.js +28 -31
- package/lib/commonjs/components/Nudge/Nudge.js +179 -87
- package/lib/commonjs/components/index.js +35 -0
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CardAdvisory/CardAdvisory.js +197 -0
- package/lib/module/components/CardCTA/CardCTA.js +199 -17
- package/lib/module/components/CircularProgressBar/CircularProgressBar.js +141 -0
- package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +253 -0
- package/lib/module/components/CircularRating/CircularRating.js +155 -0
- package/lib/module/components/Gauge/Gauge.js +217 -0
- package/lib/module/components/ListGroup/ListGroup.js +3 -1
- package/lib/module/components/MediaCard/GlassFill.js +57 -0
- package/lib/module/components/MediaCard/GlassFill.web.js +43 -0
- package/lib/module/components/MediaCard/MediaCard.js +29 -32
- package/lib/module/components/Nudge/Nudge.js +178 -87
- package/lib/module/components/index.js +5 -0
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/CardAdvisory/CardAdvisory.d.ts +49 -0
- package/lib/typescript/src/components/CardCTA/CardCTA.d.ts +16 -1
- package/lib/typescript/src/components/CircularProgressBar/CircularProgressBar.d.ts +27 -0
- package/lib/typescript/src/components/CircularProgressBarDoted/CircularProgressBarDoted.d.ts +48 -0
- package/lib/typescript/src/components/CircularRating/CircularRating.d.ts +49 -0
- package/lib/typescript/src/components/Gauge/Gauge.d.ts +53 -0
- package/lib/typescript/src/components/MediaCard/GlassFill.d.ts +47 -0
- package/lib/typescript/src/components/MediaCard/GlassFill.web.d.ts +20 -0
- package/lib/typescript/src/components/MediaCard/MediaCard.d.ts +17 -13
- package/lib/typescript/src/components/Nudge/Nudge.d.ts +14 -11
- package/lib/typescript/src/components/index.d.ts +6 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +3 -2
- package/src/components/CardAdvisory/CardAdvisory.tsx +283 -0
- package/src/components/CardCTA/CardCTA.tsx +236 -13
- package/src/components/CircularProgressBar/CircularProgressBar.tsx +190 -0
- package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +357 -0
- package/src/components/CircularRating/CircularRating.tsx +241 -0
- package/src/components/Gauge/Gauge.tsx +303 -0
- package/src/components/ListGroup/ListGroup.tsx +3 -1
- package/src/components/MediaCard/GlassFill.tsx +89 -0
- package/src/components/MediaCard/GlassFill.web.tsx +53 -0
- package/src/components/MediaCard/MediaCard.tsx +29 -48
- package/src/components/Nudge/Nudge.tsx +222 -82
- package/src/components/index.ts +6 -1
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/icons/registry.ts +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { createContext, useContext } from 'react'
|
|
2
|
-
import { View, Text, StyleSheet, type ViewStyle, type TextStyle, type StyleProp, type ImageSourcePropType
|
|
3
|
-
import { BlurView, type BlurTint } from 'expo-blur'
|
|
2
|
+
import { View, Text, StyleSheet, type ViewStyle, type TextStyle, type StyleProp, type ImageSourcePropType } from 'react-native'
|
|
4
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
5
4
|
import Image from '../Image/Image'
|
|
5
|
+
import GlassFill, { type GlassTint } from './GlassFill'
|
|
6
6
|
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
7
7
|
|
|
8
8
|
const MediaCardContext = createContext<{ modes?: Record<string, any> }>({})
|
|
@@ -162,20 +162,24 @@ export function Title({ children, style, modes: propModes }: { children?: React.
|
|
|
162
162
|
* Glass Footer — pinned to the bottom of the card, **always** on top of the
|
|
163
163
|
* Header (`zIndex: 2`).
|
|
164
164
|
*
|
|
165
|
-
* Glass implementation
|
|
166
|
-
* - **iOS
|
|
167
|
-
*
|
|
168
|
-
* `
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* - **Web:** `BlurView` on web is implemented as `backdrop-filter: blur()`,
|
|
176
|
-
* which already worked in the previous version. Same component, same API.
|
|
165
|
+
* Glass implementation:
|
|
166
|
+
* - **iOS / Android:** `<GlassFill>` (this folder) wraps
|
|
167
|
+
* `@react-native-community/blur`'s `BlurView`. iOS gets a real
|
|
168
|
+
* `UIVisualEffectView` (live OS blur); Android gets the community
|
|
169
|
+
* `RealtimeBlurView` with a token-driven tinted scrim fallback for
|
|
170
|
+
* devices where realtime blur is unavailable.
|
|
171
|
+
* - **Web:** the platform-extension file `GlassFill.web.tsx` renders a
|
|
172
|
+
* translucent View with `backdrop-filter: blur()` — Metro picks the
|
|
173
|
+
* correct file automatically, so the web bundle never imports the
|
|
174
|
+
* native-only blur module.
|
|
177
175
|
*
|
|
178
|
-
*
|
|
176
|
+
* Why we don't use `expo-blur`: it requires Expo Modules autolinking on the
|
|
177
|
+
* consumer side (`use_expo_modules!` / `ExpoModulesPackage`), which silently
|
|
178
|
+
* breaks bare React Native apps that just install this library and run
|
|
179
|
+
* `pod install`. `@react-native-community/blur` is a regular RN native
|
|
180
|
+
* module — autolinking handles it with no additional setup.
|
|
181
|
+
*
|
|
182
|
+
* Tokens still drive the tint color, blur intensity and inner spacing.
|
|
179
183
|
*/
|
|
180
184
|
export function Footer({ children, style, modes: propModes }: { children?: React.ReactNode; style?: StyleProp<ViewStyle>; modes?: Record<string, any> }) {
|
|
181
185
|
const context = useContext(MediaCardContext)
|
|
@@ -186,10 +190,14 @@ export function Footer({ children, style, modes: propModes }: { children?: React
|
|
|
186
190
|
const paddingVertical = parseFloat(getVariableByName('cardMedia/footer/padding/vertical', modes) || '12')
|
|
187
191
|
|
|
188
192
|
// Figma tokens:
|
|
189
|
-
// blur/minimal/background -> tint laid over the
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
+
// blur/minimal/background -> tint laid over the live blur, also used
|
|
194
|
+
// as the iOS reduced-transparency fallback.
|
|
195
|
+
// blur/minimal -> blur radius (px). The community BlurView
|
|
196
|
+
// uses `blurAmount` (~0-32). `GlassFill`
|
|
197
|
+
// accepts a 0-100 "intensity" (kept compat
|
|
198
|
+
// with the previous expo-blur scale) and
|
|
199
|
+
// maps it internally — here we convert the
|
|
200
|
+
// token's radius to that intensity scale.
|
|
193
201
|
const glassBgColor = getVariableByName('blur/minimal/background', modes) || '#1414174a'
|
|
194
202
|
const blurRadius = parseFloat(getVariableByName('blur/minimal', modes) || '29')
|
|
195
203
|
const intensity = Math.max(0, Math.min(100, Math.round(blurRadius * 1.7)))
|
|
@@ -197,7 +205,7 @@ export function Footer({ children, style, modes: propModes }: { children?: React
|
|
|
197
205
|
// Pick the iOS/Android material tint from "Contrast Context" mode so the
|
|
198
206
|
// glass adapts to dark/light backgrounds the same way the Figma tokens do.
|
|
199
207
|
const contrast = (modes['Contrast Context'] || 'on dark') as string
|
|
200
|
-
const tint:
|
|
208
|
+
const tint: GlassTint = contrast === 'on light' ? 'light' : 'dark'
|
|
201
209
|
|
|
202
210
|
return (
|
|
203
211
|
<View
|
|
@@ -216,34 +224,7 @@ export function Footer({ children, style, modes: propModes }: { children?: React
|
|
|
216
224
|
]}
|
|
217
225
|
pointerEvents="box-none"
|
|
218
226
|
>
|
|
219
|
-
{
|
|
220
|
-
tinted scrim automatically; on web it's a backdrop-filter. */}
|
|
221
|
-
<BlurView
|
|
222
|
-
style={StyleSheet.absoluteFill}
|
|
223
|
-
tint={tint}
|
|
224
|
-
intensity={intensity}
|
|
225
|
-
experimentalBlurMethod="dimezisBlurView"
|
|
226
|
-
/>
|
|
227
|
-
|
|
228
|
-
{/* Token-driven tint laid on top of the live blur — keeps the
|
|
229
|
-
Figma color signature regardless of platform blur quality. */}
|
|
230
|
-
<View style={[StyleSheet.absoluteFill, { backgroundColor: glassBgColor }]} />
|
|
231
|
-
|
|
232
|
-
{/* Subtle noise/grain on Android only, to compensate for the
|
|
233
|
-
lower-fidelity blur — purely additive, no behavior change.
|
|
234
|
-
On iOS/web the native blur already has natural texture. */}
|
|
235
|
-
{Platform.OS === 'android' ? (
|
|
236
|
-
<View
|
|
237
|
-
style={[
|
|
238
|
-
StyleSheet.absoluteFill,
|
|
239
|
-
{
|
|
240
|
-
backgroundColor: 'rgba(255,255,255,0.03)',
|
|
241
|
-
opacity: 0.6,
|
|
242
|
-
},
|
|
243
|
-
]}
|
|
244
|
-
pointerEvents="none"
|
|
245
|
-
/>
|
|
246
|
-
) : null}
|
|
227
|
+
<GlassFill tint={tint} intensity={intensity} overlayColor={glassBgColor} />
|
|
247
228
|
|
|
248
229
|
<View
|
|
249
230
|
style={{
|
|
@@ -1,30 +1,34 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
2
|
import { View, Text, type ViewStyle, type TextStyle, type StyleProp } from 'react-native'
|
|
3
3
|
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
4
4
|
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
5
5
|
import { cloneChildrenWithModes, EMPTY_MODES } from '../../utils/react-utils'
|
|
6
6
|
import Button from '../Button/Button'
|
|
7
|
+
import Icon from '../../icons/Icon'
|
|
8
|
+
|
|
9
|
+
export type NudgeType = 'stacked-prominent' | 'stacked-detailed' | 'inline-compact'
|
|
7
10
|
|
|
8
11
|
export type NudgeProps = {
|
|
9
12
|
/**
|
|
10
|
-
* Controls the layout
|
|
11
|
-
* - "
|
|
12
|
-
* - "
|
|
13
|
+
* Controls the layout type.
|
|
14
|
+
* - "stacked-prominent": icon + title/body/button content
|
|
15
|
+
* - "inline-compact": icon + body/button in one row
|
|
16
|
+
* - "stacked-detailed": header + children detail slot
|
|
13
17
|
*/
|
|
14
|
-
|
|
18
|
+
type?: NudgeType;
|
|
15
19
|
/** Title text displayed in the nudge */
|
|
16
20
|
title?: string;
|
|
17
|
-
/** Body text displayed
|
|
21
|
+
/** Body text displayed in prominent and compact types when no children are provided */
|
|
18
22
|
body?: string;
|
|
19
|
-
/** Label for the default button
|
|
23
|
+
/** Label for the default button when no buttonSlot is provided */
|
|
20
24
|
buttonLabel?: string;
|
|
21
25
|
/** Callback for the default button press */
|
|
22
26
|
onPressButton?: () => void;
|
|
23
|
-
/** Custom button slot
|
|
27
|
+
/** Custom button slot, overrides buttonLabel/onPressButton */
|
|
24
28
|
buttonSlot?: React.ReactNode;
|
|
25
|
-
/** Optional leading slot for
|
|
26
|
-
startSlot?: React.ReactNode;
|
|
27
|
-
/** Content slot — overrides
|
|
29
|
+
/** Optional leading slot. Omit for the token-driven sparkle icon, pass null/false to hide. */
|
|
30
|
+
startSlot?: React.ReactNode | false;
|
|
31
|
+
/** Content slot — overrides default content, or provides detailed list content */
|
|
28
32
|
children?: React.ReactNode;
|
|
29
33
|
/** Mode configuration for design token resolution */
|
|
30
34
|
modes?: Record<string, any>;
|
|
@@ -32,21 +36,32 @@ export type NudgeProps = {
|
|
|
32
36
|
style?: StyleProp<ViewStyle>;
|
|
33
37
|
};
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
interface NudgeTokens {
|
|
40
|
+
containerBaseStyle: ViewStyle;
|
|
41
|
+
prominentContainerStyle: ViewStyle;
|
|
42
|
+
compactContainerStyle: ViewStyle;
|
|
43
|
+
detailedContainerStyle: ViewStyle;
|
|
44
|
+
contentStyle: ViewStyle;
|
|
45
|
+
compactOuterContentStyle: ViewStyle;
|
|
46
|
+
compactContentWrapStyle: ViewStyle;
|
|
47
|
+
textWrapStyle: ViewStyle;
|
|
48
|
+
compactTextWrapStyle: ViewStyle;
|
|
49
|
+
headerStyle: ViewStyle;
|
|
50
|
+
detailSlotStyle: ViewStyle;
|
|
51
|
+
titleTextStyle: TextStyle;
|
|
52
|
+
bodyTextStyle: TextStyle;
|
|
53
|
+
iconColor: string;
|
|
54
|
+
iconSize: number;
|
|
55
|
+
startSlotGap: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function toFontWeight(value: unknown, fallback: TextStyle['fontWeight']): TextStyle['fontWeight'] {
|
|
59
|
+
if (typeof value === 'number') return value.toString() as TextStyle['fontWeight']
|
|
60
|
+
if (typeof value === 'string') return value as TextStyle['fontWeight']
|
|
61
|
+
return fallback
|
|
62
|
+
}
|
|
49
63
|
|
|
64
|
+
function resolveNudgeTokens(modes: Record<string, any>): NudgeTokens {
|
|
50
65
|
const background = getVariableByName('nudge/background', modes) || '#f5f5f5'
|
|
51
66
|
const radius = getVariableByName('nudge/radius', modes) || 12
|
|
52
67
|
const paddingH = getVariableByName('nudge/padding/horizontal', modes) || 12
|
|
@@ -55,96 +70,221 @@ function Nudge({
|
|
|
55
70
|
|
|
56
71
|
const titleColor = getVariableByName('nudge/title/color', modes) || '#0d0d0f'
|
|
57
72
|
const titleFontSize = getVariableByName('nudge/title/fontSize', modes) || 14
|
|
58
|
-
const titleFontFamily = getVariableByName('nudge/title/fontFamily', modes) || '
|
|
73
|
+
const titleFontFamily = getVariableByName('nudge/title/fontFamily', modes) || 'System'
|
|
59
74
|
const titleLineHeight = getVariableByName('nudge/title/lineHeight', modes) || 15
|
|
60
|
-
const
|
|
61
|
-
const titleFontWeight = typeof titleFontWeightRaw === 'number' ? titleFontWeightRaw.toString() : titleFontWeightRaw
|
|
75
|
+
const titleFontWeight = toFontWeight(getVariableByName('nudge/title/fontWeight', modes), '700')
|
|
62
76
|
|
|
63
77
|
const bodyColor = getVariableByName('nudge/body/color', modes) || '#1a1c1f'
|
|
64
78
|
const bodyFontSize = getVariableByName('nudge/body/fontSize', modes) || 12
|
|
65
|
-
const bodyFontFamily = getVariableByName('nudge/body/fontFamily', modes) || '
|
|
79
|
+
const bodyFontFamily = getVariableByName('nudge/body/fontFamily', modes) || 'System'
|
|
66
80
|
const bodyLineHeight = getVariableByName('nudge/body/lineHeight', modes) || 16
|
|
67
|
-
const
|
|
68
|
-
const bodyFontWeight = typeof bodyFontWeightRaw === 'number' ? bodyFontWeightRaw.toString() : bodyFontWeightRaw
|
|
81
|
+
const bodyFontWeight = toFontWeight(getVariableByName('nudge/body/fontWeight', modes), '500')
|
|
69
82
|
|
|
70
83
|
const textGap = getVariableByName('nudge/text/gap', modes) || 4
|
|
71
84
|
const contentGap = getVariableByName('nudge/content/gap', modes) || 8
|
|
72
85
|
const contentMinHeight = getVariableByName('nudge/content/minHeight', modes) || 20
|
|
86
|
+
const startSlotGap = getVariableByName('nudge/startSlot/gap', modes) || 4
|
|
73
87
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
88
|
+
return {
|
|
89
|
+
containerBaseStyle: {
|
|
90
|
+
backgroundColor: background as string,
|
|
91
|
+
borderRadius: radius as number,
|
|
92
|
+
paddingHorizontal: paddingH as number,
|
|
93
|
+
paddingVertical: paddingV as number,
|
|
94
|
+
gap: gap as number,
|
|
95
|
+
overflow: 'hidden',
|
|
96
|
+
},
|
|
97
|
+
prominentContainerStyle: {
|
|
98
|
+
flexDirection: 'row',
|
|
99
|
+
alignItems: 'flex-start',
|
|
100
|
+
},
|
|
101
|
+
compactContainerStyle: {
|
|
102
|
+
flexDirection: 'row',
|
|
103
|
+
alignItems: 'center',
|
|
104
|
+
},
|
|
105
|
+
detailedContainerStyle: {
|
|
106
|
+
flexDirection: 'column',
|
|
107
|
+
alignItems: 'flex-start',
|
|
108
|
+
},
|
|
109
|
+
contentStyle: {
|
|
110
|
+
flex: 1,
|
|
111
|
+
minWidth: 1,
|
|
112
|
+
minHeight: contentMinHeight as number,
|
|
113
|
+
justifyContent: 'center',
|
|
114
|
+
overflow: 'hidden',
|
|
115
|
+
},
|
|
116
|
+
compactOuterContentStyle: {
|
|
117
|
+
flex: 1,
|
|
118
|
+
minWidth: 1,
|
|
119
|
+
alignSelf: 'stretch',
|
|
120
|
+
justifyContent: 'center',
|
|
121
|
+
},
|
|
122
|
+
compactContentWrapStyle: {
|
|
123
|
+
flexDirection: 'row',
|
|
124
|
+
alignItems: 'center',
|
|
125
|
+
gap: contentGap as number,
|
|
126
|
+
width: '100%',
|
|
127
|
+
},
|
|
128
|
+
textWrapStyle: {
|
|
129
|
+
gap: textGap as number,
|
|
130
|
+
alignItems: 'flex-start',
|
|
131
|
+
width: '100%',
|
|
132
|
+
},
|
|
133
|
+
compactTextWrapStyle: {
|
|
134
|
+
flex: 1,
|
|
135
|
+
minWidth: 1,
|
|
136
|
+
alignItems: 'flex-start',
|
|
137
|
+
},
|
|
138
|
+
headerStyle: {
|
|
139
|
+
flexDirection: 'row',
|
|
140
|
+
alignItems: 'center',
|
|
141
|
+
gap: gap as number,
|
|
142
|
+
width: '100%',
|
|
143
|
+
},
|
|
144
|
+
detailSlotStyle: {
|
|
145
|
+
gap: getVariableByName('slot/gap', modes) || 8,
|
|
146
|
+
width: '100%',
|
|
147
|
+
},
|
|
148
|
+
titleTextStyle: {
|
|
149
|
+
color: titleColor as string,
|
|
150
|
+
fontSize: titleFontSize as number,
|
|
151
|
+
fontFamily: titleFontFamily as string,
|
|
152
|
+
lineHeight: titleLineHeight as number,
|
|
153
|
+
fontWeight: titleFontWeight,
|
|
154
|
+
},
|
|
155
|
+
bodyTextStyle: {
|
|
156
|
+
color: bodyColor as string,
|
|
157
|
+
fontSize: bodyFontSize as number,
|
|
158
|
+
fontFamily: bodyFontFamily as string,
|
|
159
|
+
lineHeight: bodyLineHeight as number,
|
|
160
|
+
fontWeight: bodyFontWeight,
|
|
161
|
+
},
|
|
162
|
+
iconColor: (getVariableByName('appearance/nudge/icon/color', modes) || '#5d00b5') as string,
|
|
163
|
+
iconSize: (getVariableByName('nudge/icon/size', modes) || 20) as number,
|
|
164
|
+
startSlotGap: startSlotGap as number,
|
|
84
165
|
}
|
|
166
|
+
}
|
|
85
167
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
168
|
+
function NudgeImpl({
|
|
169
|
+
type = 'stacked-prominent',
|
|
170
|
+
title = 'Split payment',
|
|
171
|
+
body = 'Split this transaction into installments',
|
|
172
|
+
buttonLabel = 'Button',
|
|
173
|
+
onPressButton,
|
|
174
|
+
buttonSlot,
|
|
175
|
+
startSlot,
|
|
176
|
+
children,
|
|
177
|
+
modes: propModes = EMPTY_MODES,
|
|
178
|
+
style,
|
|
179
|
+
}: NudgeProps) {
|
|
180
|
+
const { modes: globalModes } = useTokens()
|
|
181
|
+
const modes = useMemo(
|
|
182
|
+
() => (globalModes === EMPTY_MODES && propModes === EMPTY_MODES
|
|
183
|
+
? EMPTY_MODES
|
|
184
|
+
: { ...globalModes, ...propModes }),
|
|
185
|
+
[globalModes, propModes]
|
|
186
|
+
)
|
|
93
187
|
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
188
|
+
const tokens = useMemo(() => resolveNudgeTokens(modes), [modes])
|
|
189
|
+
|
|
190
|
+
const startSlotElement = useMemo(() => {
|
|
191
|
+
if (startSlot === null || startSlot === false) return null
|
|
192
|
+
|
|
193
|
+
if (startSlot !== undefined) {
|
|
194
|
+
const processed = cloneChildrenWithModes(React.Children.toArray(startSlot), modes)
|
|
195
|
+
return processed.length === 1 ? processed[0] : processed
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return (
|
|
199
|
+
<Icon
|
|
200
|
+
name="ic_ai_sparkle"
|
|
201
|
+
size={tokens.iconSize}
|
|
202
|
+
color={tokens.iconColor}
|
|
203
|
+
accessibilityElementsHidden={true}
|
|
204
|
+
importantForAccessibility="no"
|
|
205
|
+
/>
|
|
206
|
+
)
|
|
207
|
+
}, [startSlot, modes, tokens.iconColor, tokens.iconSize])
|
|
208
|
+
|
|
209
|
+
const startSlotWrapper = startSlotElement ? (
|
|
210
|
+
<View style={{ gap: tokens.startSlotGap, alignItems: 'center' }}>
|
|
211
|
+
{startSlotElement}
|
|
212
|
+
</View>
|
|
213
|
+
) : null
|
|
101
214
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
215
|
+
const processedChildren = useMemo(() => {
|
|
216
|
+
if (!children) return null
|
|
217
|
+
const processed = cloneChildrenWithModes(React.Children.toArray(children), modes)
|
|
218
|
+
return processed.length === 1 ? processed[0] : processed
|
|
219
|
+
}, [children, modes])
|
|
105
220
|
|
|
106
|
-
const
|
|
107
|
-
? (
|
|
108
|
-
:
|
|
221
|
+
const buttonElement = buttonSlot
|
|
222
|
+
? cloneChildrenWithModes(React.Children.toArray(buttonSlot), modes)
|
|
223
|
+
: (
|
|
224
|
+
<Button
|
|
225
|
+
label={buttonLabel}
|
|
226
|
+
modes={modes}
|
|
227
|
+
{...(onPressButton ? { onPress: onPressButton } : {})}
|
|
228
|
+
/>
|
|
229
|
+
)
|
|
109
230
|
|
|
110
|
-
if (
|
|
231
|
+
if (type === 'stacked-detailed') {
|
|
111
232
|
return (
|
|
112
|
-
<View style={[
|
|
113
|
-
<View style={
|
|
114
|
-
{
|
|
115
|
-
<Text style={[
|
|
233
|
+
<View style={[tokens.containerBaseStyle, tokens.detailedContainerStyle, style]}>
|
|
234
|
+
<View style={tokens.headerStyle}>
|
|
235
|
+
{startSlotWrapper}
|
|
236
|
+
<Text style={[tokens.titleTextStyle, { flex: 1 }]}>{title}</Text>
|
|
116
237
|
</View>
|
|
117
238
|
|
|
118
|
-
{
|
|
119
|
-
|
|
239
|
+
{processedChildren ? (
|
|
240
|
+
<View style={tokens.detailSlotStyle}>
|
|
241
|
+
{processedChildren}
|
|
242
|
+
</View>
|
|
120
243
|
) : null}
|
|
121
244
|
</View>
|
|
122
245
|
)
|
|
123
246
|
}
|
|
124
247
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
<View style={
|
|
128
|
-
|
|
129
|
-
|
|
248
|
+
if (type === 'inline-compact') {
|
|
249
|
+
return (
|
|
250
|
+
<View style={[tokens.containerBaseStyle, tokens.compactContainerStyle, style]}>
|
|
251
|
+
{startSlotWrapper}
|
|
252
|
+
|
|
253
|
+
<View style={tokens.compactOuterContentStyle}>
|
|
254
|
+
{processedChildren || (
|
|
255
|
+
<View style={tokens.compactContentWrapStyle}>
|
|
256
|
+
<View style={tokens.compactTextWrapStyle}>
|
|
257
|
+
<Text style={tokens.bodyTextStyle}>{body}</Text>
|
|
258
|
+
</View>
|
|
259
|
+
{buttonElement}
|
|
260
|
+
</View>
|
|
261
|
+
)}
|
|
262
|
+
</View>
|
|
263
|
+
</View>
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const prominentContent = (
|
|
268
|
+
<View style={{ gap: tokens.compactContentWrapStyle.gap as number, alignItems: 'flex-start', width: '100%' }}>
|
|
269
|
+
<View style={tokens.textWrapStyle}>
|
|
270
|
+
<Text style={tokens.titleTextStyle}>{title}</Text>
|
|
271
|
+
<Text style={tokens.bodyTextStyle}>{body}</Text>
|
|
130
272
|
</View>
|
|
131
|
-
{
|
|
132
|
-
? cloneChildrenWithModes(React.Children.toArray(buttonSlot), modes)
|
|
133
|
-
: <Button label={buttonLabel} onPress={onPressButton} modes={modes} />}
|
|
273
|
+
{buttonElement}
|
|
134
274
|
</View>
|
|
135
275
|
)
|
|
136
276
|
|
|
137
277
|
return (
|
|
138
|
-
<View style={[
|
|
139
|
-
{
|
|
278
|
+
<View style={[tokens.containerBaseStyle, tokens.prominentContainerStyle, style]}>
|
|
279
|
+
{startSlotWrapper}
|
|
140
280
|
|
|
141
|
-
<View style={
|
|
142
|
-
{
|
|
143
|
-
? cloneChildrenWithModes(React.Children.toArray(children), modes)
|
|
144
|
-
: defaultContent}
|
|
281
|
+
<View style={tokens.contentStyle}>
|
|
282
|
+
{processedChildren || prominentContent}
|
|
145
283
|
</View>
|
|
146
284
|
</View>
|
|
147
285
|
)
|
|
148
286
|
}
|
|
149
287
|
|
|
288
|
+
const Nudge = React.memo(NudgeImpl)
|
|
289
|
+
|
|
150
290
|
export default Nudge
|
package/src/components/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ export { default as BottomNav } from './BottomNav/BottomNav';
|
|
|
7
7
|
export { default as BottomNavItem } from './BottomNavItem/BottomNavItem';
|
|
8
8
|
export { default as Button, type ButtonProps } from './Button/Button';
|
|
9
9
|
export { default as Card } from './Card/Card';
|
|
10
|
+
export { default as CardAdvisory, type CardAdvisoryProps } from './CardAdvisory/CardAdvisory'
|
|
10
11
|
export { default as Carousel } from './Carousel/Carousel';
|
|
11
12
|
export type { CarouselProps, CarouselItemProps, PaginationProps } from './Carousel/Carousel';
|
|
12
13
|
export { default as Checkbox, type CheckboxProps } from './Checkbox/Checkbox';
|
|
@@ -14,12 +15,16 @@ export { default as CardFeedback, type CardFeedbackProps } from './CardFeedback/
|
|
|
14
15
|
export { default as Disclaimer } from './Disclaimer/Disclaimer';
|
|
15
16
|
export { default as Divider, type DividerProps, type DividerDirection } from './Divider/Divider';
|
|
16
17
|
export { default as Drawer } from './Drawer/Drawer';
|
|
17
|
-
export { default as CardCTA, type CardCTAProps } from './CardCTA/CardCTA'
|
|
18
|
+
export { default as CardCTA, type CardCTAProps, type CardCTAType } from './CardCTA/CardCTA'
|
|
18
19
|
export { default as DebitCard, type DebitCardProps } from './DebitCard/DebitCard';
|
|
19
20
|
export { default as FilterBar } from './FilterBar/FilterBar';
|
|
20
21
|
export { default as Form, type FormProps } from './Form/Form';
|
|
21
22
|
export { useFormContext } from './Form/Form';
|
|
22
23
|
export { default as FormField, type FormFieldProps, type FormFieldType } from './FormField/FormField';
|
|
24
|
+
export { default as CircularProgressBar, type CircularProgressBarProps } from './CircularProgressBar/CircularProgressBar'
|
|
25
|
+
export { default as CircularProgressBarDoted, type CircularProgressBarDotedProps } from './CircularProgressBarDoted/CircularProgressBarDoted'
|
|
26
|
+
export { default as CircularRating, type CircularRatingProps } from './CircularRating/CircularRating'
|
|
27
|
+
export { default as Gauge, type GaugeProps } from './Gauge/Gauge';
|
|
23
28
|
export { default as HoldingsCard, type HoldingsCardProps } from './HoldingsCard/HoldingsCard';
|
|
24
29
|
export { default as HStack, type HStackProps } from './HStack/HStack';
|
|
25
30
|
export { default as IconButton } from './IconButton/IconButton';
|