jfs-components 0.1.23 → 0.1.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/lib/commonjs/components/AutoplayControl/AutoplayControl.js +66 -0
  3. package/lib/commonjs/components/CardCTA/CardCTA.js +32 -25
  4. package/lib/commonjs/components/Carousel/Carousel.js +112 -19
  5. package/lib/commonjs/components/ChipSelect/ChipSelect.js +2 -1
  6. package/lib/commonjs/components/ContentSheet/ContentSheet.js +39 -5
  7. package/lib/commonjs/components/FormUpload/Additem.js +168 -0
  8. package/lib/commonjs/components/FormUpload/FormUpload.js +198 -0
  9. package/lib/commonjs/components/Grid/Grid.js +206 -0
  10. package/lib/commonjs/components/ListItem/ListItem.js +11 -4
  11. package/lib/commonjs/components/MetricData/MetricData.js +132 -0
  12. package/lib/commonjs/components/NumberPagination/NumberPagination.js +144 -0
  13. package/lib/commonjs/components/ProfileCard/ProfileCard.js +208 -0
  14. package/lib/commonjs/components/Rating/Rating.js +137 -0
  15. package/lib/commonjs/components/UpiHandle/UpiHandle.js +23 -11
  16. package/lib/commonjs/components/index.js +42 -0
  17. package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
  18. package/lib/commonjs/design-tokens/figma-modes.generated.js +23 -10
  19. package/lib/commonjs/icons/components/IconBrokenimage.js +19 -0
  20. package/lib/commonjs/icons/components/IconImage.js +19 -0
  21. package/lib/commonjs/icons/components/IconMic.js +19 -0
  22. package/lib/commonjs/icons/components/IconMicoff.js +19 -0
  23. package/lib/commonjs/icons/components/IconPause.js +19 -0
  24. package/lib/commonjs/icons/components/IconPlay.js +19 -0
  25. package/lib/commonjs/icons/components/index.js +66 -0
  26. package/lib/commonjs/icons/registry.js +2 -2
  27. package/lib/module/components/AutoplayControl/AutoplayControl.js +60 -0
  28. package/lib/module/components/CardCTA/CardCTA.js +32 -25
  29. package/lib/module/components/Carousel/Carousel.js +111 -19
  30. package/lib/module/components/ChipSelect/ChipSelect.js +2 -1
  31. package/lib/module/components/ContentSheet/ContentSheet.js +41 -7
  32. package/lib/module/components/FormUpload/Additem.js +162 -0
  33. package/lib/module/components/FormUpload/FormUpload.js +192 -0
  34. package/lib/module/components/Grid/Grid.js +201 -0
  35. package/lib/module/components/ListItem/ListItem.js +11 -4
  36. package/lib/module/components/MetricData/MetricData.js +127 -0
  37. package/lib/module/components/NumberPagination/NumberPagination.js +138 -0
  38. package/lib/module/components/ProfileCard/ProfileCard.js +202 -0
  39. package/lib/module/components/Rating/Rating.js +132 -0
  40. package/lib/module/components/UpiHandle/UpiHandle.js +23 -11
  41. package/lib/module/components/index.js +7 -1
  42. package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
  43. package/lib/module/design-tokens/figma-modes.generated.js +23 -10
  44. package/lib/module/icons/components/IconBrokenimage.js +12 -0
  45. package/lib/module/icons/components/IconImage.js +12 -0
  46. package/lib/module/icons/components/IconMic.js +12 -0
  47. package/lib/module/icons/components/IconMicoff.js +12 -0
  48. package/lib/module/icons/components/IconPause.js +12 -0
  49. package/lib/module/icons/components/IconPlay.js +12 -0
  50. package/lib/module/icons/components/index.js +6 -0
  51. package/lib/module/icons/registry.js +2 -2
  52. package/lib/typescript/src/components/AutoplayControl/AutoplayControl.d.ts +15 -0
  53. package/lib/typescript/src/components/Carousel/Carousel.d.ts +8 -1
  54. package/lib/typescript/src/components/ChipSelect/ChipSelect.d.ts +6 -1
  55. package/lib/typescript/src/components/ContentSheet/ContentSheet.d.ts +7 -1
  56. package/lib/typescript/src/components/FormUpload/Additem.d.ts +96 -0
  57. package/lib/typescript/src/components/FormUpload/FormUpload.d.ts +85 -0
  58. package/lib/typescript/src/components/Grid/Grid.d.ts +87 -0
  59. package/lib/typescript/src/components/MetricData/MetricData.d.ts +53 -0
  60. package/lib/typescript/src/components/NumberPagination/NumberPagination.d.ts +21 -0
  61. package/lib/typescript/src/components/ProfileCard/ProfileCard.d.ts +89 -0
  62. package/lib/typescript/src/components/Rating/Rating.d.ts +30 -0
  63. package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +7 -1
  64. package/lib/typescript/src/components/index.d.ts +34 -28
  65. package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +10 -4
  66. package/lib/typescript/src/icons/components/IconBrokenimage.d.ts +3 -0
  67. package/lib/typescript/src/icons/components/IconImage.d.ts +3 -0
  68. package/lib/typescript/src/icons/components/IconMic.d.ts +3 -0
  69. package/lib/typescript/src/icons/components/IconMicoff.d.ts +3 -0
  70. package/lib/typescript/src/icons/components/IconPause.d.ts +3 -0
  71. package/lib/typescript/src/icons/components/IconPlay.d.ts +3 -0
  72. package/lib/typescript/src/icons/components/index.d.ts +6 -0
  73. package/lib/typescript/src/icons/registry.d.ts +1 -1
  74. package/package.json +1 -1
  75. package/src/components/AutoplayControl/AutoplayControl.tsx +77 -0
  76. package/src/components/CardCTA/CardCTA.tsx +30 -15
  77. package/src/components/Carousel/Carousel.tsx +133 -23
  78. package/src/components/ChipSelect/ChipSelect.tsx +7 -1
  79. package/src/components/ContentSheet/ContentSheet.tsx +50 -0
  80. package/src/components/FormUpload/Additem.tsx +262 -0
  81. package/src/components/FormUpload/FormUpload.tsx +313 -0
  82. package/src/components/Grid/Grid.tsx +368 -0
  83. package/src/components/ListItem/ListItem.tsx +11 -4
  84. package/src/components/MetricData/MetricData.tsx +185 -0
  85. package/src/components/NumberPagination/NumberPagination.tsx +170 -0
  86. package/src/components/ProfileCard/ProfileCard.tsx +268 -0
  87. package/src/components/Rating/Rating.tsx +174 -0
  88. package/src/components/UpiHandle/UpiHandle.tsx +35 -24
  89. package/src/components/index.ts +329 -182
  90. package/src/design-tokens/Coin Variables-variables-full.json +1 -1
  91. package/src/design-tokens/figma-modes.generated.ts +23 -10
  92. package/src/icons/components/IconBrokenimage.tsx +11 -0
  93. package/src/icons/components/IconImage.tsx +11 -0
  94. package/src/icons/components/IconMic.tsx +11 -0
  95. package/src/icons/components/IconMicoff.tsx +11 -0
  96. package/src/icons/components/IconPause.tsx +11 -0
  97. package/src/icons/components/IconPlay.tsx +11 -0
  98. package/src/icons/components/index.ts +6 -0
  99. package/src/icons/registry.ts +25 -1
@@ -0,0 +1,174 @@
1
+ import React, { useMemo } from 'react'
2
+ import {
3
+ Pressable,
4
+ View,
5
+ type StyleProp,
6
+ type ViewStyle,
7
+ } from 'react-native'
8
+ import Svg, { Path } from 'react-native-svg'
9
+ import { getVariableByName } from '../../design-tokens/figma-variables-resolver'
10
+ import { useTokens } from '../../design-tokens/JFSThemeProvider'
11
+ import { EMPTY_MODES } from '../../utils/react-utils'
12
+ import type { Modes } from '../../design-tokens'
13
+
14
+ /**
15
+ * Star glyph taken directly from the Figma `Rating` component. Filled and empty
16
+ * stars share this shape and differ only by fill color, so a single path keeps
17
+ * the row perfectly aligned.
18
+ */
19
+ const STAR_PATH =
20
+ 'M40.7187 15.3334C40.4728 14.6159 40.0426 13.9805 39.4688 13.4885C38.8951 12.9965 38.1985 12.689 37.4403 12.566L28.3018 11.1721L24.2038 2.35741C23.8759 1.66044 23.3432 1.06596 22.6875 0.635476C22.0318 0.225491 21.2737 0 20.4951 0C19.7165 0 18.9583 0.225491 18.3027 0.635476C17.647 1.04546 17.1142 1.63994 16.7864 2.35741L12.6884 11.1721L3.4679 12.566C2.73026 12.689 2.01311 12.9965 1.43939 13.4885C0.865675 13.9805 0.435385 14.6159 0.189505 15.3334C-0.0358854 16.0304 -0.0563753 16.7889 0.107545 17.5063C0.271465 18.2238 0.640285 18.9003 1.15254 19.4333L7.89374 26.362L6.29552 36.1811C6.17258 36.9396 6.27503 37.7186 6.56189 38.4156C6.84875 39.1125 7.34051 39.7275 7.9757 40.1785C8.65187 40.691 9.49196 40.978 10.3525 40.9985C11.0697 40.9985 11.7664 40.8345 12.4015 40.486L20.5771 35.9761L28.7526 40.486C29.3673 40.8345 30.0844 41.019 30.8016 40.9985C31.6621 40.9985 32.4817 40.732 33.1784 40.24C33.7931 39.789 34.2849 39.1945 34.5922 38.4771C34.8791 37.7801 34.9815 37.0011 34.8586 36.2426L33.2604 26.4235L40.0016 19.4948C40.4933 18.9413 40.8007 18.2443 40.9441 17.5063C41.0671 16.7684 40.9851 16.0099 40.7187 15.3334Z'
21
+ const STAR_VIEWBOX = '0 0 41 41'
22
+
23
+ // Fallbacks mirror the Figma `Rating Star / Output` + `Rating Star State` tokens.
24
+ const DEFAULT_STAR_SIZE = 41
25
+ const DEFAULT_GAP = 12
26
+ const SELECTED_FALLBACK = '#5D00B5'
27
+ const IDLE_FALLBACK = '#F6F3FF'
28
+
29
+ /**
30
+ * `ratingStar/background` is state-driven: the same token resolves to the filled
31
+ * color when `Rating Star State = selected` and to the empty color when `idle`.
32
+ */
33
+ const RATING_STATE_COLLECTION = 'Rating Star State'
34
+
35
+ export type RatingProps = {
36
+ /** Number of filled stars. Rounded to the nearest whole star for display. */
37
+ value?: number
38
+ /** Total number of stars to render. */
39
+ max?: number
40
+ /**
41
+ * Called with the new rating (1..max) when a star is pressed. Providing this
42
+ * makes the control interactive; omit it (or set `readOnly`) for a display.
43
+ */
44
+ onChange?: (value: number) => void
45
+ /** Force a read-only display even when `onChange` is provided. */
46
+ readOnly?: boolean
47
+ /** Override the star edge length. Defaults to the `ratingStar/width` token. */
48
+ starSize?: number
49
+ /** Override the spacing between stars. Defaults to the `rating/gap` token. */
50
+ gap?: number
51
+ /** Design token modes forwarded to token lookups. */
52
+ modes?: Modes
53
+ /** Optional container style overrides. */
54
+ style?: StyleProp<ViewStyle>
55
+ /** Accessibility label for the whole rating. Defaults to "X of Y stars". */
56
+ accessibilityLabel?: string
57
+ } & Omit<React.ComponentProps<typeof View>, 'style'>
58
+
59
+ const toNumber = (value: unknown, fallback: number) => {
60
+ const n = typeof value === 'string' ? Number(value) : value
61
+ return typeof n === 'number' && Number.isFinite(n) ? n : fallback
62
+ }
63
+
64
+ function Star({
65
+ width,
66
+ height,
67
+ color,
68
+ }: {
69
+ width: number
70
+ height: number
71
+ color: string
72
+ }) {
73
+ return (
74
+ <Svg width={width} height={height} viewBox={STAR_VIEWBOX}>
75
+ <Path d={STAR_PATH} fill={color} />
76
+ </Svg>
77
+ )
78
+ }
79
+
80
+ function Rating({
81
+ value = 0,
82
+ max = 5,
83
+ onChange,
84
+ readOnly = false,
85
+ starSize,
86
+ gap,
87
+ modes: propModes = EMPTY_MODES,
88
+ style,
89
+ accessibilityLabel,
90
+ ...rest
91
+ }: RatingProps) {
92
+ const { modes: globalModes } = useTokens()
93
+ const modes = useMemo(
94
+ () =>
95
+ globalModes === EMPTY_MODES && propModes === EMPTY_MODES
96
+ ? EMPTY_MODES
97
+ : { ...globalModes, ...propModes },
98
+ [globalModes, propModes]
99
+ )
100
+
101
+ const { selectedColor, idleColor, starWidth, starHeight, resolvedGap } =
102
+ useMemo(() => {
103
+ const selected = getVariableByName('ratingStar/background', {
104
+ ...modes,
105
+ [RATING_STATE_COLLECTION]: 'selected',
106
+ }) as string | undefined
107
+ const idle = getVariableByName('ratingStar/background', {
108
+ ...modes,
109
+ [RATING_STATE_COLLECTION]: 'idle',
110
+ }) as string | undefined
111
+
112
+ return {
113
+ selectedColor: selected ?? SELECTED_FALLBACK,
114
+ idleColor: idle ?? IDLE_FALLBACK,
115
+ starWidth:
116
+ starSize ??
117
+ toNumber(
118
+ getVariableByName('ratingStar/width', modes),
119
+ DEFAULT_STAR_SIZE
120
+ ),
121
+ starHeight:
122
+ starSize ??
123
+ toNumber(
124
+ getVariableByName('ratingStar/height', modes),
125
+ DEFAULT_STAR_SIZE
126
+ ),
127
+ resolvedGap:
128
+ gap ?? toNumber(getVariableByName('rating/gap', modes), DEFAULT_GAP),
129
+ }
130
+ }, [modes, starSize, gap])
131
+
132
+ const total = Math.max(0, Math.round(max))
133
+ const filledCount = Math.max(0, Math.min(total, Math.round(value)))
134
+ const interactive = !readOnly && typeof onChange === 'function'
135
+ const label = accessibilityLabel ?? `${filledCount} of ${total} stars`
136
+
137
+ return (
138
+ <View
139
+ {...rest}
140
+ style={[
141
+ { flexDirection: 'row', alignItems: 'center', gap: resolvedGap },
142
+ style,
143
+ ]}
144
+ accessibilityRole={interactive ? 'adjustable' : 'image'}
145
+ accessibilityLabel={label}
146
+ accessibilityValue={
147
+ interactive ? { min: 0, max: total, now: filledCount } : undefined
148
+ }
149
+ >
150
+ {Array.from({ length: total }, (_, index) => {
151
+ const color = index < filledCount ? selectedColor : idleColor
152
+ const star = <Star width={starWidth} height={starHeight} color={color} />
153
+
154
+ if (!interactive) {
155
+ return <React.Fragment key={index}>{star}</React.Fragment>
156
+ }
157
+
158
+ return (
159
+ <Pressable
160
+ key={index}
161
+ onPress={() => onChange?.(index + 1)}
162
+ accessibilityRole="button"
163
+ accessibilityLabel={`Rate ${index + 1} of ${total} stars`}
164
+ hitSlop={8}
165
+ >
166
+ {star}
167
+ </Pressable>
168
+ )
169
+ })}
170
+ </View>
171
+ )
172
+ }
173
+
174
+ export default React.memo(Rating)
@@ -18,10 +18,6 @@ import MediaSource, { type UnifiedSource } from '../../utils/MediaSource'
18
18
  import Icon from '../../icons/Icon'
19
19
  import type { Modes } from '../../design-tokens'
20
20
 
21
- // Default static asset from the component folder.
22
- // Consumers can override the image via the `source` prop if needed.
23
- const DEFAULT_AVATAR_IMAGE = require('./Image.png')
24
-
25
21
  const IS_WEB = Platform.OS === 'web'
26
22
  const IS_IOS = Platform.OS === 'ios'
27
23
  const PRESS_DELAY = IS_IOS ? 130 : 0
@@ -39,6 +35,12 @@ type UpiHandleProps = {
39
35
  * inline SVG XML string, a `require()` asset, an SVG React component, or
40
36
  * an already-rendered element. See {@link UnifiedSource}. Avatars are
41
37
  * intentionally **not** tinted — the source renders as-is.
38
+ *
39
+ * The leading avatar is driven entirely by this prop: providing a `source`
40
+ * (or the deprecated `avatarSource`) force-sets the Figma "UPI Handle Image"
41
+ * collection to `True` and renders the avatar; omitting it force-sets the
42
+ * mode to `False` and hides it. This override wins over any `UPI Handle
43
+ * Image` value passed via `modes`.
42
44
  */
43
45
  source?: UnifiedSource;
44
46
  /**
@@ -134,7 +136,7 @@ function resolveUpiHandleTokens(modes: Modes): UpiHandleTokens {
134
136
  * @param {Object} [props.modes={}] - Modes object passed directly to `getVariableByName`.
135
137
  * @param {boolean} [props.showIcon=true] - Toggles the trailing icon visibility.
136
138
  * @param {string} [props.iconName='ic_scan_qr_code'] - Icon name from the actions set.
137
- * @param {UnifiedSource} [props.source] - Unified avatar source (URI, inline SVG XML, `require()` asset, SVG React component, or React element). Smart-detects raster vs SVG so the same prop works on iOS, Android and web.
139
+ * @param {UnifiedSource} [props.source] - Unified avatar source (URI, inline SVG XML, `require()` asset, SVG React component, or React element). Smart-detects raster vs SVG so the same prop works on iOS, Android and web. Providing a source force-sets `UPI Handle Image: True` (avatar shown); omitting it force-sets `False` (avatar hidden), overriding any `UPI Handle Image` value passed via `modes`.
138
140
  * @param {ImageSourcePropType|UnifiedSource} [props.avatarSource] - Deprecated alias for `source`; kept for back-compat.
139
141
  * @param {Function} [props.onClick] - Click/tap handler. Works as an alias for `onPress`.
140
142
  * @param {string} [props.accessibilityLabel] - Accessibility label for screen readers
@@ -168,12 +170,21 @@ function UpiHandle({
168
170
  }: UpiHandleProps) {
169
171
  const { modes: globalModes } = useTokens()
170
172
 
171
- const modes = useMemo(
172
- () => (globalModes === EMPTY_MODES && propModes === EMPTY_MODES
173
+ // The avatar is fully driven by the presence of a `source`. It force-sets the
174
+ // Figma "UPI Handle Image" collection to `True` when a source is provided and
175
+ // `False` otherwise. This is applied last so it overrides any consumer-passed
176
+ // `UPI Handle Image` mode, keeping the avatar visibility and the token layout
177
+ // (symmetric padding in the hidden-image state) in lockstep with the source.
178
+ const hasSource = source != null || avatarSource != null
179
+
180
+ const modes = useMemo(() => {
181
+ const base = globalModes === EMPTY_MODES && propModes === EMPTY_MODES
173
182
  ? EMPTY_MODES
174
- : { ...globalModes, ...propModes }),
175
- [globalModes, propModes]
176
- )
183
+ : { ...globalModes, ...propModes }
184
+ return { ...base, 'UPI Handle Image': hasSource ? 'True' : 'False' }
185
+ }, [globalModes, propModes, hasSource])
186
+
187
+ const showImageResolved = hasSource
177
188
 
178
189
  const tokens = useMemo(() => resolveUpiHandleTokens(modes), [modes])
179
190
 
@@ -226,25 +237,25 @@ function UpiHandle({
226
237
 
227
238
  // `source` wins; `avatarSource` is the legacy fallback. Both are accepted
228
239
  // as a UnifiedSource (string / number / {uri} / component / element), and
229
- // the legacy `ImageSourcePropType` shapes naturally fit that union too.
230
- const resolvedAvatarSource: UnifiedSource =
231
- (source as UnifiedSource | undefined) ??
232
- (avatarSource as UnifiedSource | undefined) ??
233
- (DEFAULT_AVATAR_IMAGE as UnifiedSource)
240
+ // the legacy `ImageSourcePropType` shapes naturally fit that union too. This
241
+ // is only read when `showImageResolved` is true, which requires a source.
242
+ const resolvedAvatarSource = (source ?? avatarSource) as UnifiedSource
234
243
 
235
244
  const avatarSize = (tokens.avatarStyle.width as number | undefined) ?? 23
236
245
 
237
246
  const innerContent = (
238
247
  <>
239
- <View style={tokens.avatarStyle}>
240
- <MediaSource
241
- source={resolvedAvatarSource}
242
- size={avatarSize}
243
- resizeMode="cover"
244
- accessibilityElementsHidden={true}
245
- importantForAccessibility="no"
246
- />
247
- </View>
248
+ {showImageResolved && (
249
+ <View style={tokens.avatarStyle}>
250
+ <MediaSource
251
+ source={resolvedAvatarSource}
252
+ size={avatarSize}
253
+ resizeMode="cover"
254
+ accessibilityElementsHidden={true}
255
+ importantForAccessibility="no"
256
+ />
257
+ </View>
258
+ )}
248
259
  <Text
249
260
  style={tokens.labelStyle}
250
261
  {...resolveTruncation(disableTruncation, 1, 'tail')}