jfs-components 0.0.71 → 0.0.72
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 +49 -0
- package/lib/commonjs/components/CardAdvisory/CardAdvisory.js +2 -2
- package/lib/commonjs/components/CardFinancialCondition/CardFinancialCondition.js +213 -0
- package/lib/commonjs/components/Carousel/Carousel.js +9 -7
- package/lib/commonjs/components/HoldingsCard/HoldingsCard.js +2 -2
- package/lib/commonjs/components/InstitutionBadge/InstitutionBadge.js +132 -0
- package/lib/commonjs/components/Radio/Radio.js +194 -0
- package/lib/commonjs/components/RadioButton/RadioButton.js +21 -188
- package/lib/commonjs/components/index.js +21 -0
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CardAdvisory/CardAdvisory.js +2 -2
- package/lib/module/components/CardFinancialCondition/CardFinancialCondition.js +207 -0
- package/lib/module/components/Carousel/Carousel.js +9 -7
- package/lib/module/components/HoldingsCard/HoldingsCard.js +2 -2
- package/lib/module/components/InstitutionBadge/InstitutionBadge.js +127 -0
- package/lib/module/components/Radio/Radio.js +188 -0
- package/lib/module/components/RadioButton/RadioButton.js +20 -185
- package/lib/module/components/index.js +7 -0
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/CardFinancialCondition/CardFinancialCondition.d.ts +50 -0
- package/lib/typescript/src/components/InstitutionBadge/InstitutionBadge.d.ts +30 -0
- package/lib/typescript/src/components/Radio/Radio.d.ts +30 -0
- package/lib/typescript/src/components/RadioButton/RadioButton.d.ts +20 -28
- package/lib/typescript/src/components/index.d.ts +7 -0
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/CardAdvisory/CardAdvisory.tsx +2 -2
- package/src/components/CardFinancialCondition/CardFinancialCondition.tsx +366 -0
- package/src/components/Carousel/Carousel.tsx +14 -6
- package/src/components/HoldingsCard/HoldingsCard.tsx +2 -2
- package/src/components/InstitutionBadge/InstitutionBadge.tsx +216 -0
- package/src/components/Radio/Radio.tsx +227 -0
- package/src/components/RadioButton/RadioButton.tsx +23 -225
- package/src/components/index.ts +7 -0
- package/src/icons/registry.ts +1 -1
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import React, { useMemo } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Text,
|
|
4
|
+
View,
|
|
5
|
+
type StyleProp,
|
|
6
|
+
type TextStyle,
|
|
7
|
+
type ViewStyle,
|
|
8
|
+
} from 'react-native'
|
|
9
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
10
|
+
import { useTokens } from '../../design-tokens/JFSThemeProvider'
|
|
11
|
+
import { EMPTY_MODES, cloneChildrenWithModes } from '../../utils/react-utils'
|
|
12
|
+
import MediaSource, { type UnifiedSource } from '../../utils/MediaSource'
|
|
13
|
+
|
|
14
|
+
// Default avatar asset (shared with the Avatar component) so that
|
|
15
|
+
// InstitutionBadge has a sensible visual fallback when no `source` is
|
|
16
|
+
// provided in stories or playgrounds.
|
|
17
|
+
const DEFAULT_AVATAR_IMAGE = require('../Avatar/31595e70c4181263f9971590224b12934b280c9b.png')
|
|
18
|
+
|
|
19
|
+
type InstitutionBadgeBaseProps = Omit<
|
|
20
|
+
React.ComponentProps<typeof View>,
|
|
21
|
+
'children' | 'style'
|
|
22
|
+
>
|
|
23
|
+
|
|
24
|
+
export type InstitutionBadgeProps = InstitutionBadgeBaseProps & {
|
|
25
|
+
/** Visible label for the institution (e.g. bank name). */
|
|
26
|
+
label?: string
|
|
27
|
+
/**
|
|
28
|
+
* Unified avatar source. Accepts a remote URI (raster or `.svg`), an
|
|
29
|
+
* inline SVG XML string, a `require()` asset, an SVG React component,
|
|
30
|
+
* or an already-rendered React element. Smart-detects raster vs SVG so
|
|
31
|
+
* the same prop works on iOS, Android and web. See {@link UnifiedSource}.
|
|
32
|
+
* Ignored when `avatarSlot` is provided.
|
|
33
|
+
*/
|
|
34
|
+
source?: UnifiedSource
|
|
35
|
+
/** Slot replacing the default Avatar (e.g. for monogram avatars). Receives `modes` recursively. */
|
|
36
|
+
avatarSlot?: React.ReactNode
|
|
37
|
+
/** Design token modes forwarded to token lookups and the Avatar slot. */
|
|
38
|
+
modes?: Record<string, any>
|
|
39
|
+
/** Container style override. */
|
|
40
|
+
style?: StyleProp<ViewStyle>
|
|
41
|
+
/** Label style override. */
|
|
42
|
+
labelStyle?: StyleProp<TextStyle>
|
|
43
|
+
/** Accessibility label. Defaults to `label`. */
|
|
44
|
+
accessibilityLabel?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface InstitutionBadgeTokens {
|
|
48
|
+
containerStyle: ViewStyle
|
|
49
|
+
avatarStyle: ViewStyle
|
|
50
|
+
avatarSize: number
|
|
51
|
+
labelStyle: TextStyle
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const toNumber = (value: unknown, fallback: number) => {
|
|
55
|
+
if (typeof value === 'number') {
|
|
56
|
+
return Number.isFinite(value) ? value : fallback
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (typeof value === 'string') {
|
|
60
|
+
const parsed = Number(value)
|
|
61
|
+
return Number.isFinite(parsed) ? parsed : fallback
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return fallback
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const toFontWeight = (value: unknown, fallback: TextStyle['fontWeight']) => {
|
|
68
|
+
if (typeof value === 'number') {
|
|
69
|
+
return String(value) as TextStyle['fontWeight']
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof value === 'string') {
|
|
73
|
+
return value as TextStyle['fontWeight']
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return fallback
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function resolveInstitutionBadgeTokens(
|
|
80
|
+
modes: Record<string, any>
|
|
81
|
+
): InstitutionBadgeTokens {
|
|
82
|
+
const gap = toNumber(getVariableByName('institutionBadge/gap', modes), 8)
|
|
83
|
+
|
|
84
|
+
const foreground =
|
|
85
|
+
(getVariableByName('institutionBadge/foreground', modes) as string) ||
|
|
86
|
+
'#080d1a'
|
|
87
|
+
const fontSize = toNumber(
|
|
88
|
+
getVariableByName('institutionBadge/fontSize', modes),
|
|
89
|
+
14
|
|
90
|
+
)
|
|
91
|
+
const fontFamily =
|
|
92
|
+
(getVariableByName('institutionBadge/fontFamily', modes) as string) ||
|
|
93
|
+
'JioType Var'
|
|
94
|
+
const lineHeight = toNumber(
|
|
95
|
+
getVariableByName('institutionBadge/lineHeight', modes),
|
|
96
|
+
18
|
|
97
|
+
)
|
|
98
|
+
const fontWeight = toFontWeight(
|
|
99
|
+
getVariableByName('institutionBadge/fontWeight', modes),
|
|
100
|
+
'500'
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
// Avatar wrapper styled from shared `avatar/*` tokens so the badge stays
|
|
104
|
+
// visually consistent with other Avatar-bearing components.
|
|
105
|
+
const avatarSize = toNumber(getVariableByName('avatar/size', modes), 36)
|
|
106
|
+
const avatarRadiusRaw = toNumber(
|
|
107
|
+
getVariableByName('avatar/radius', modes),
|
|
108
|
+
9999
|
|
109
|
+
)
|
|
110
|
+
// 9999 is the design-token sentinel for "perfect circle".
|
|
111
|
+
const avatarRadius = avatarRadiusRaw === 9999 ? avatarSize / 2 : avatarRadiusRaw
|
|
112
|
+
const avatarBorderColor =
|
|
113
|
+
(getVariableByName('avatar/border/color', modes) as string) ||
|
|
114
|
+
'rgba(255,255,255,0)'
|
|
115
|
+
const avatarBorderSize = toNumber(
|
|
116
|
+
getVariableByName('avatar/border/size', modes),
|
|
117
|
+
1
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
containerStyle: {
|
|
122
|
+
alignItems: 'center',
|
|
123
|
+
flexDirection: 'row',
|
|
124
|
+
gap,
|
|
125
|
+
},
|
|
126
|
+
avatarStyle: {
|
|
127
|
+
width: avatarSize,
|
|
128
|
+
height: avatarSize,
|
|
129
|
+
borderRadius: avatarRadius,
|
|
130
|
+
borderWidth: avatarBorderSize,
|
|
131
|
+
borderColor: avatarBorderColor,
|
|
132
|
+
overflow: 'hidden',
|
|
133
|
+
},
|
|
134
|
+
avatarSize,
|
|
135
|
+
labelStyle: {
|
|
136
|
+
color: foreground,
|
|
137
|
+
fontFamily,
|
|
138
|
+
fontSize,
|
|
139
|
+
fontWeight,
|
|
140
|
+
lineHeight,
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Default Avatar Size = M (36px in tokens) — matches the Figma badge. Callers
|
|
146
|
+
// can override via `modes` if needed.
|
|
147
|
+
const DEFAULT_AVATAR_MODE = 'M'
|
|
148
|
+
|
|
149
|
+
function InstitutionBadge({
|
|
150
|
+
label = 'State Bank of India',
|
|
151
|
+
source,
|
|
152
|
+
avatarSlot,
|
|
153
|
+
modes: propModes = EMPTY_MODES,
|
|
154
|
+
style,
|
|
155
|
+
labelStyle,
|
|
156
|
+
accessibilityLabel,
|
|
157
|
+
...rest
|
|
158
|
+
}: InstitutionBadgeProps) {
|
|
159
|
+
const { modes: globalModes } = useTokens()
|
|
160
|
+
const modes = useMemo(
|
|
161
|
+
() =>
|
|
162
|
+
globalModes === EMPTY_MODES && propModes === EMPTY_MODES
|
|
163
|
+
? EMPTY_MODES
|
|
164
|
+
: { ...globalModes, ...propModes },
|
|
165
|
+
[globalModes, propModes]
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
const avatarModes = useMemo(
|
|
169
|
+
() => ({ 'Avatar Size': DEFAULT_AVATAR_MODE, ...modes }),
|
|
170
|
+
[modes]
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
const tokens = useMemo(
|
|
174
|
+
() => resolveInstitutionBadgeTokens(avatarModes),
|
|
175
|
+
[avatarModes]
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
const processedAvatarSlot = useMemo(() => {
|
|
179
|
+
if (!avatarSlot) return null
|
|
180
|
+
const processed = cloneChildrenWithModes(
|
|
181
|
+
React.Children.toArray(avatarSlot),
|
|
182
|
+
avatarModes
|
|
183
|
+
)
|
|
184
|
+
return processed.length === 1 ? processed[0] : processed
|
|
185
|
+
}, [avatarSlot, avatarModes])
|
|
186
|
+
|
|
187
|
+
const resolvedSource: UnifiedSource =
|
|
188
|
+
(source as UnifiedSource | undefined) ?? (DEFAULT_AVATAR_IMAGE as UnifiedSource)
|
|
189
|
+
|
|
190
|
+
const defaultAccessibilityLabel = accessibilityLabel ?? label
|
|
191
|
+
|
|
192
|
+
return (
|
|
193
|
+
<View
|
|
194
|
+
accessibilityLabel={defaultAccessibilityLabel}
|
|
195
|
+
style={[tokens.containerStyle, style]}
|
|
196
|
+
{...rest}
|
|
197
|
+
>
|
|
198
|
+
{processedAvatarSlot || (
|
|
199
|
+
<View style={tokens.avatarStyle}>
|
|
200
|
+
<MediaSource
|
|
201
|
+
source={resolvedSource}
|
|
202
|
+
size={tokens.avatarSize}
|
|
203
|
+
resizeMode="cover"
|
|
204
|
+
accessibilityElementsHidden={true}
|
|
205
|
+
importantForAccessibility="no"
|
|
206
|
+
/>
|
|
207
|
+
</View>
|
|
208
|
+
)}
|
|
209
|
+
<Text style={[tokens.labelStyle, labelStyle]} numberOfLines={1}>
|
|
210
|
+
{label}
|
|
211
|
+
</Text>
|
|
212
|
+
</View>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export default React.memo(InstitutionBadge)
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import React, { useMemo, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
Pressable,
|
|
4
|
+
View,
|
|
5
|
+
StyleSheet,
|
|
6
|
+
Platform,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
DimensionValue,
|
|
9
|
+
StyleProp,
|
|
10
|
+
} from 'react-native'
|
|
11
|
+
import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
|
|
12
|
+
import { EMPTY_MODES } from '../../utils/react-utils'
|
|
13
|
+
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Props
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
|
|
18
|
+
export interface RadioProps {
|
|
19
|
+
/**
|
|
20
|
+
* Whether the radio is selected.
|
|
21
|
+
*/
|
|
22
|
+
selected?: boolean
|
|
23
|
+
/**
|
|
24
|
+
* Whether the radio is disabled.
|
|
25
|
+
*/
|
|
26
|
+
disabled?: boolean
|
|
27
|
+
/**
|
|
28
|
+
* Function to call when the radio is pressed.
|
|
29
|
+
*/
|
|
30
|
+
onPress?: () => void
|
|
31
|
+
/**
|
|
32
|
+
* Modes object for design-token resolution.
|
|
33
|
+
*/
|
|
34
|
+
modes?: Record<string, any>
|
|
35
|
+
/**
|
|
36
|
+
* Custom style for the radio container.
|
|
37
|
+
*/
|
|
38
|
+
style?: StyleProp<ViewStyle>
|
|
39
|
+
/**
|
|
40
|
+
* Test ID for testing.
|
|
41
|
+
*/
|
|
42
|
+
testID?: string
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Radio
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
export function Radio({
|
|
50
|
+
selected = false,
|
|
51
|
+
disabled = false,
|
|
52
|
+
onPress,
|
|
53
|
+
modes = EMPTY_MODES,
|
|
54
|
+
style,
|
|
55
|
+
testID,
|
|
56
|
+
}: RadioProps) {
|
|
57
|
+
// ---- Refs & State ----
|
|
58
|
+
const [hovered, setHovered] = useState(false)
|
|
59
|
+
const [focused, setFocused] = useState(false)
|
|
60
|
+
const [pressed, setPressed] = useState(false)
|
|
61
|
+
|
|
62
|
+
// ---- Dimensions ----
|
|
63
|
+
const widthStr = getVariableByName('radio/width', modes) || '18'
|
|
64
|
+
const heightStr = getVariableByName('radio/height', modes) || '18'
|
|
65
|
+
const selectorSizeStr = getVariableByName('radio/selector/size', modes) || '10'
|
|
66
|
+
|
|
67
|
+
const width = parseFloat(widthStr?.toString() || '18')
|
|
68
|
+
const height = parseFloat(heightStr?.toString() || '18')
|
|
69
|
+
const selectorSize = parseFloat(selectorSizeStr?.toString() || '10')
|
|
70
|
+
|
|
71
|
+
// ---- State Logic ----
|
|
72
|
+
// Priority: Disabled -> Focused -> Hover/Pressed -> Idle
|
|
73
|
+
// Note: Design treats Active (Pressed) similar to Selected for some styles,
|
|
74
|
+
// but usually in Radios, Pressed is a transient state.
|
|
75
|
+
// We will map:
|
|
76
|
+
// - Disabled -> 'disabled'
|
|
77
|
+
// - Focused -> 'focus'
|
|
78
|
+
// - Hovered -> 'hover'
|
|
79
|
+
// - Idle -> 'idle'
|
|
80
|
+
|
|
81
|
+
// We handle `selected` as a separate dimension derived from state.
|
|
82
|
+
|
|
83
|
+
let visualState = 'idle'
|
|
84
|
+
if (disabled) {
|
|
85
|
+
visualState = 'disabled'
|
|
86
|
+
} else if (focused) {
|
|
87
|
+
visualState = 'focus'
|
|
88
|
+
} else if (hovered || pressed) {
|
|
89
|
+
visualState = 'hover'
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Construct token paths based on state + selected
|
|
93
|
+
let prefix = `radio/${visualState}`
|
|
94
|
+
if (visualState === 'idle' && selected) {
|
|
95
|
+
prefix = `radio/selected`
|
|
96
|
+
} else if (selected) {
|
|
97
|
+
prefix = `radio/${visualState}Selected`
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---- Colors & Border ----
|
|
101
|
+
|
|
102
|
+
const resolveColor = (path: string, fallback: string) => {
|
|
103
|
+
return getVariableByName(path, modes) || fallback
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Background Color
|
|
107
|
+
let bgColorVar = `${prefix}/background/color`
|
|
108
|
+
// Fix for disabledSelected weirdness if needed
|
|
109
|
+
if (visualState === 'disabled' && selected) {
|
|
110
|
+
// Check specific path from dump: `radio/disabledSelected/background`
|
|
111
|
+
if (!getVariableByName(`${prefix}/background/color`, modes)) {
|
|
112
|
+
bgColorVar = `${prefix}/background`
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Border Color
|
|
117
|
+
let borderColorVar = `${prefix}/border/color`
|
|
118
|
+
|
|
119
|
+
// Border Width
|
|
120
|
+
let borderWidthVar = `${prefix}/border/size`
|
|
121
|
+
// Fix for huge path: `radio/disabled/radio/disabled/border/size`
|
|
122
|
+
if (visualState === 'disabled' && !selected) {
|
|
123
|
+
if (getVariableByName('radio/disabled/radio/disabled/border/size', modes)) {
|
|
124
|
+
borderWidthVar = 'radio/disabled/radio/disabled/border/size'
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Selector Color
|
|
129
|
+
let selectorBgVar = `${prefix}/selector/background/color`
|
|
130
|
+
if (!selected) {
|
|
131
|
+
selectorBgVar = 'transparent'
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Shadows (Glow)
|
|
135
|
+
let shadowSizeVar = `${prefix}/boxShadow/size`
|
|
136
|
+
let shadowColorVar = `${prefix}/shadow/color`
|
|
137
|
+
|
|
138
|
+
// Resolve Values
|
|
139
|
+
const backgroundColor = resolveColor(bgColorVar, 'transparent')
|
|
140
|
+
const borderColor = resolveColor(borderColorVar, 'transparent')
|
|
141
|
+
const borderWidth = parseFloat(getVariableByName(borderWidthVar, modes)?.toString() || '1')
|
|
142
|
+
const selectorColor = resolveColor(selectorBgVar, 'transparent')
|
|
143
|
+
|
|
144
|
+
const shadowSize = parseFloat(getVariableByName(shadowSizeVar, modes)?.toString() || '0')
|
|
145
|
+
const shadowColor = resolveColor(shadowColorVar, 'transparent')
|
|
146
|
+
|
|
147
|
+
// Styles
|
|
148
|
+
const containerStyle: any = {
|
|
149
|
+
width,
|
|
150
|
+
height,
|
|
151
|
+
borderRadius: width / 2, // 9999px -> circle
|
|
152
|
+
borderWidth,
|
|
153
|
+
borderColor,
|
|
154
|
+
backgroundColor,
|
|
155
|
+
justifyContent: 'center',
|
|
156
|
+
alignItems: 'center',
|
|
157
|
+
// Web shadow (ring)
|
|
158
|
+
...(Platform.OS === 'web' && shadowSize > 0 ? {
|
|
159
|
+
boxShadow: `0px 0px 0px ${shadowSize}px ${shadowColor}`,
|
|
160
|
+
} : {}),
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const selectorStyle: ViewStyle = {
|
|
164
|
+
width: selectorSize,
|
|
165
|
+
height: selectorSize,
|
|
166
|
+
borderRadius: selectorSize / 2,
|
|
167
|
+
backgroundColor: selectorColor,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Dummy block for token extraction (static analysis)
|
|
171
|
+
if (false as boolean) {
|
|
172
|
+
getVariableByName('radio/idle/background/color')
|
|
173
|
+
getVariableByName('radio/idle/border/color')
|
|
174
|
+
getVariableByName('radio/selector/size')
|
|
175
|
+
getVariableByName('radio/width')
|
|
176
|
+
getVariableByName('radio/height')
|
|
177
|
+
getVariableByName('radio/background/color')
|
|
178
|
+
getVariableByName('radio/hover/background/color')
|
|
179
|
+
getVariableByName('radio/hover/border/color')
|
|
180
|
+
getVariableByName('radio/hover/boxShadow/size')
|
|
181
|
+
getVariableByName('radio/hover/shadow/color')
|
|
182
|
+
getVariableByName('radio/selected/background/color')
|
|
183
|
+
getVariableByName('radio/selected/border/color')
|
|
184
|
+
getVariableByName('radio/selected/selector/background/color')
|
|
185
|
+
getVariableByName('radio/hoverSelected/background/color')
|
|
186
|
+
getVariableByName('radio/hoverSelected/border/color')
|
|
187
|
+
getVariableByName('radio/hoverSelected/boxShadow/size')
|
|
188
|
+
getVariableByName('radio/hoverSelected/shadow/color')
|
|
189
|
+
getVariableByName('radio/hoverSelected/selector/background/color')
|
|
190
|
+
getVariableByName('radio/focus/background/color')
|
|
191
|
+
getVariableByName('radio/focus/border/color')
|
|
192
|
+
getVariableByName('radio/focus/border/size')
|
|
193
|
+
getVariableByName('radio/focus/boxShadow/size')
|
|
194
|
+
getVariableByName('radio/focus/shadow/color')
|
|
195
|
+
getVariableByName('radio/focusSelected/background/color')
|
|
196
|
+
getVariableByName('radio/focusSelected/selector/background/color')
|
|
197
|
+
getVariableByName('radio/focusSelected/border/size')
|
|
198
|
+
getVariableByName('radio/disabled/radio/disabled/border/size')
|
|
199
|
+
getVariableByName('radio/disabled/background/color')
|
|
200
|
+
getVariableByName('radio/disabled/border/color')
|
|
201
|
+
getVariableByName('radio/disabledSelected/selector/background/color')
|
|
202
|
+
getVariableByName('radio/disabledSelected/background')
|
|
203
|
+
getVariableByName('radio/disabledSelected/border/color')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<Pressable
|
|
208
|
+
testID={testID}
|
|
209
|
+
disabled={disabled}
|
|
210
|
+
onPress={onPress}
|
|
211
|
+
onHoverIn={() => setHovered(true)}
|
|
212
|
+
onHoverOut={() => setHovered(false)}
|
|
213
|
+
onFocus={() => setFocused(true)}
|
|
214
|
+
onBlur={() => setFocused(false)}
|
|
215
|
+
onPressIn={() => setPressed(true)}
|
|
216
|
+
onPressOut={() => setPressed(false)}
|
|
217
|
+
style={({ pressed: isPressed }) => [
|
|
218
|
+
containerStyle,
|
|
219
|
+
style,
|
|
220
|
+
]}
|
|
221
|
+
>
|
|
222
|
+
<View style={selectorStyle} />
|
|
223
|
+
</Pressable>
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export default Radio
|
|
@@ -1,227 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
* Whether the radio button is disabled.
|
|
25
|
-
*/
|
|
26
|
-
disabled?: boolean
|
|
27
|
-
/**
|
|
28
|
-
* Function to call when the radio button is pressed.
|
|
29
|
-
*/
|
|
30
|
-
onPress?: () => void
|
|
31
|
-
/**
|
|
32
|
-
* Modes object for design-token resolution.
|
|
33
|
-
*/
|
|
34
|
-
modes?: Record<string, any>
|
|
35
|
-
/**
|
|
36
|
-
* Custom style for the radio button container.
|
|
37
|
-
*/
|
|
38
|
-
style?: StyleProp<ViewStyle>
|
|
39
|
-
/**
|
|
40
|
-
* Test ID for testing.
|
|
41
|
-
*/
|
|
42
|
-
testID?: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// ---------------------------------------------------------------------------
|
|
46
|
-
// RadioButton
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
|
|
49
|
-
export function RadioButton({
|
|
50
|
-
selected = false,
|
|
51
|
-
disabled = false,
|
|
52
|
-
onPress,
|
|
53
|
-
modes = EMPTY_MODES,
|
|
54
|
-
style,
|
|
55
|
-
testID,
|
|
56
|
-
}: RadioButtonProps) {
|
|
57
|
-
// ---- Refs & State ----
|
|
58
|
-
const [hovered, setHovered] = useState(false)
|
|
59
|
-
const [focused, setFocused] = useState(false)
|
|
60
|
-
const [pressed, setPressed] = useState(false)
|
|
61
|
-
|
|
62
|
-
// ---- Dimensions ----
|
|
63
|
-
const widthStr = getVariableByName('radio/width', modes) || '18'
|
|
64
|
-
const heightStr = getVariableByName('radio/height', modes) || '18'
|
|
65
|
-
const selectorSizeStr = getVariableByName('radio/selector/size', modes) || '10'
|
|
66
|
-
|
|
67
|
-
const width = parseFloat(widthStr?.toString() || '18')
|
|
68
|
-
const height = parseFloat(heightStr?.toString() || '18')
|
|
69
|
-
const selectorSize = parseFloat(selectorSizeStr?.toString() || '10')
|
|
70
|
-
|
|
71
|
-
// ---- State Logic ----
|
|
72
|
-
// Priority: Disabled -> Focused -> Hover/Pressed -> Idle
|
|
73
|
-
// Note: Design treats Active (Pressed) similar to Selected for some styles,
|
|
74
|
-
// but usually in Radio Buttons, Pressed is a transient state.
|
|
75
|
-
// We will map:
|
|
76
|
-
// - Disabled -> 'disabled'
|
|
77
|
-
// - Focused -> 'focus'
|
|
78
|
-
// - Hovered -> 'hover'
|
|
79
|
-
// - Idle -> 'idle'
|
|
80
|
-
|
|
81
|
-
// We handle `selected` as a separate dimension derived from state.
|
|
82
|
-
|
|
83
|
-
let visualState = 'idle'
|
|
84
|
-
if (disabled) {
|
|
85
|
-
visualState = 'disabled'
|
|
86
|
-
} else if (focused) {
|
|
87
|
-
visualState = 'focus'
|
|
88
|
-
} else if (hovered || pressed) {
|
|
89
|
-
visualState = 'hover'
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Construct token paths based on state + selected
|
|
93
|
-
let prefix = `radio/${visualState}`
|
|
94
|
-
if (visualState === 'idle' && selected) {
|
|
95
|
-
prefix = `radio/selected`
|
|
96
|
-
} else if (selected) {
|
|
97
|
-
prefix = `radio/${visualState}Selected`
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// ---- Colors & Border ----
|
|
101
|
-
|
|
102
|
-
const resolveColor = (path: string, fallback: string) => {
|
|
103
|
-
return getVariableByName(path, modes) || fallback
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Background Color
|
|
107
|
-
let bgColorVar = `${prefix}/background/color`
|
|
108
|
-
// Fix for disabledSelected weirdness if needed
|
|
109
|
-
if (visualState === 'disabled' && selected) {
|
|
110
|
-
// Check specific path from dump: `radio/disabledSelected/background`
|
|
111
|
-
if (!getVariableByName(`${prefix}/background/color`, modes)) {
|
|
112
|
-
bgColorVar = `${prefix}/background`
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Border Color
|
|
117
|
-
let borderColorVar = `${prefix}/border/color`
|
|
118
|
-
|
|
119
|
-
// Border Width
|
|
120
|
-
let borderWidthVar = `${prefix}/border/size`
|
|
121
|
-
// Fix for huge path: `radio/disabled/radio/disabled/border/size`
|
|
122
|
-
if (visualState === 'disabled' && !selected) {
|
|
123
|
-
if (getVariableByName('radio/disabled/radio/disabled/border/size', modes)) {
|
|
124
|
-
borderWidthVar = 'radio/disabled/radio/disabled/border/size'
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Selector Color
|
|
129
|
-
let selectorBgVar = `${prefix}/selector/background/color`
|
|
130
|
-
if (!selected) {
|
|
131
|
-
selectorBgVar = 'transparent'
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Shadows (Glow)
|
|
135
|
-
let shadowSizeVar = `${prefix}/boxShadow/size`
|
|
136
|
-
let shadowColorVar = `${prefix}/shadow/color`
|
|
137
|
-
|
|
138
|
-
// Resolve Values
|
|
139
|
-
const backgroundColor = resolveColor(bgColorVar, 'transparent')
|
|
140
|
-
const borderColor = resolveColor(borderColorVar, 'transparent')
|
|
141
|
-
const borderWidth = parseFloat(getVariableByName(borderWidthVar, modes)?.toString() || '1')
|
|
142
|
-
const selectorColor = resolveColor(selectorBgVar, 'transparent')
|
|
143
|
-
|
|
144
|
-
const shadowSize = parseFloat(getVariableByName(shadowSizeVar, modes)?.toString() || '0')
|
|
145
|
-
const shadowColor = resolveColor(shadowColorVar, 'transparent')
|
|
146
|
-
|
|
147
|
-
// Styles
|
|
148
|
-
const containerStyle: any = {
|
|
149
|
-
width,
|
|
150
|
-
height,
|
|
151
|
-
borderRadius: width / 2, // 9999px -> circle
|
|
152
|
-
borderWidth,
|
|
153
|
-
borderColor,
|
|
154
|
-
backgroundColor,
|
|
155
|
-
justifyContent: 'center',
|
|
156
|
-
alignItems: 'center',
|
|
157
|
-
// Web shadow (ring)
|
|
158
|
-
...(Platform.OS === 'web' && shadowSize > 0 ? {
|
|
159
|
-
boxShadow: `0px 0px 0px ${shadowSize}px ${shadowColor}`,
|
|
160
|
-
} : {}),
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const selectorStyle: ViewStyle = {
|
|
164
|
-
width: selectorSize,
|
|
165
|
-
height: selectorSize,
|
|
166
|
-
borderRadius: selectorSize / 2,
|
|
167
|
-
backgroundColor: selectorColor,
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Dummy block for token extraction (static analysis)
|
|
171
|
-
if (false as boolean) {
|
|
172
|
-
getVariableByName('radio/idle/background/color')
|
|
173
|
-
getVariableByName('radio/idle/border/color')
|
|
174
|
-
getVariableByName('radio/selector/size')
|
|
175
|
-
getVariableByName('radio/width')
|
|
176
|
-
getVariableByName('radio/height')
|
|
177
|
-
getVariableByName('radio/background/color')
|
|
178
|
-
getVariableByName('radio/hover/background/color')
|
|
179
|
-
getVariableByName('radio/hover/border/color')
|
|
180
|
-
getVariableByName('radio/hover/boxShadow/size')
|
|
181
|
-
getVariableByName('radio/hover/shadow/color')
|
|
182
|
-
getVariableByName('radio/selected/background/color')
|
|
183
|
-
getVariableByName('radio/selected/border/color')
|
|
184
|
-
getVariableByName('radio/selected/selector/background/color')
|
|
185
|
-
getVariableByName('radio/hoverSelected/background/color')
|
|
186
|
-
getVariableByName('radio/hoverSelected/border/color')
|
|
187
|
-
getVariableByName('radio/hoverSelected/boxShadow/size')
|
|
188
|
-
getVariableByName('radio/hoverSelected/shadow/color')
|
|
189
|
-
getVariableByName('radio/hoverSelected/selector/background/color')
|
|
190
|
-
getVariableByName('radio/focus/background/color')
|
|
191
|
-
getVariableByName('radio/focus/border/color')
|
|
192
|
-
getVariableByName('radio/focus/border/size')
|
|
193
|
-
getVariableByName('radio/focus/boxShadow/size')
|
|
194
|
-
getVariableByName('radio/focus/shadow/color')
|
|
195
|
-
getVariableByName('radio/focusSelected/background/color')
|
|
196
|
-
getVariableByName('radio/focusSelected/selector/background/color')
|
|
197
|
-
getVariableByName('radio/focusSelected/border/size')
|
|
198
|
-
getVariableByName('radio/disabled/radio/disabled/border/size')
|
|
199
|
-
getVariableByName('radio/disabled/background/color')
|
|
200
|
-
getVariableByName('radio/disabled/border/color')
|
|
201
|
-
getVariableByName('radio/disabledSelected/selector/background/color')
|
|
202
|
-
getVariableByName('radio/disabledSelected/background')
|
|
203
|
-
getVariableByName('radio/disabledSelected/border/color')
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return (
|
|
207
|
-
<Pressable
|
|
208
|
-
testID={testID}
|
|
209
|
-
disabled={disabled}
|
|
210
|
-
onPress={onPress}
|
|
211
|
-
onHoverIn={() => setHovered(true)}
|
|
212
|
-
onHoverOut={() => setHovered(false)}
|
|
213
|
-
onFocus={() => setFocused(true)}
|
|
214
|
-
onBlur={() => setFocused(false)}
|
|
215
|
-
onPressIn={() => setPressed(true)}
|
|
216
|
-
onPressOut={() => setPressed(false)}
|
|
217
|
-
style={({ pressed: isPressed }) => [
|
|
218
|
-
containerStyle,
|
|
219
|
-
style,
|
|
220
|
-
]}
|
|
221
|
-
>
|
|
222
|
-
<View style={selectorStyle} />
|
|
223
|
-
</Pressable>
|
|
224
|
-
)
|
|
225
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @deprecated `RadioButton` has been renamed to `Radio`.
|
|
3
|
+
*
|
|
4
|
+
* This file is kept as a backward-compatibility shim for teams that may be
|
|
5
|
+
* importing `RadioButton` directly from this deep path:
|
|
6
|
+
*
|
|
7
|
+
* import RadioButton from 'jfs-components/src/components/RadioButton/RadioButton'
|
|
8
|
+
* import { RadioButton, RadioButtonProps } from '...'
|
|
9
|
+
*
|
|
10
|
+
* The recommended public import is now:
|
|
11
|
+
*
|
|
12
|
+
* import { Radio, type RadioProps } from 'jfs-components'
|
|
13
|
+
*
|
|
14
|
+
* Going forward, this component is called `Radio`. This shim only re-exports
|
|
15
|
+
* the new `Radio` component under the old `RadioButton` names; please migrate
|
|
16
|
+
* existing usages to `Radio` at your earliest convenience.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Radio, type RadioProps } from '../Radio/Radio'
|
|
20
|
+
|
|
21
|
+
export type RadioButtonProps = RadioProps
|
|
22
|
+
|
|
23
|
+
export const RadioButton = Radio
|
|
226
24
|
|
|
227
25
|
export default RadioButton
|