jfs-components 0.1.48 → 0.1.50
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 +8 -1
- package/lib/commonjs/components/CardCTA/CardCTA.js +7 -4
- package/lib/commonjs/components/CardFeedback/CardFeedback.js +1 -1
- package/lib/commonjs/components/CategoryCard/CategoryCard.js +4 -4
- package/lib/commonjs/components/Checkbox/Checkbox.js +4 -6
- package/lib/commonjs/components/CircularProgressBarDoted/CircularProgressBarDoted.js +12 -9
- package/lib/commonjs/components/DonutChartSummary/DonutChartSummary.js +7 -2
- package/lib/commonjs/components/ProductMerchandisingCard/ProductMerchandisingCard.js +3 -1
- package/lib/commonjs/components/ProductOverview/ProductOverview.js +2 -2
- package/lib/commonjs/components/Radio/Radio.js +5 -3
- package/lib/commonjs/components/SavingsGoalSummary/SavingsGoalSummary.js +2 -2
- package/lib/commonjs/components/Section/Section.js +3 -1
- package/lib/commonjs/components/SegmentedControl/SegmentedControl.js +1 -1
- package/lib/commonjs/components/SegmentedTrack/SegmentedTrack.js +1 -1
- package/lib/commonjs/components/Slider/Slider.js +3 -2
- package/lib/commonjs/components/StatusHero/StatusHero.js +1 -1
- package/lib/commonjs/components/Tooltip/Tooltip.js +4 -2
- package/lib/commonjs/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/commonjs/design-tokens/figma-modes.generated.js +38 -34
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/CardCTA/CardCTA.js +7 -4
- package/lib/module/components/CardFeedback/CardFeedback.js +1 -1
- package/lib/module/components/CategoryCard/CategoryCard.js +4 -4
- package/lib/module/components/Checkbox/Checkbox.js +4 -6
- package/lib/module/components/CircularProgressBarDoted/CircularProgressBarDoted.js +12 -9
- package/lib/module/components/DonutChartSummary/DonutChartSummary.js +7 -2
- package/lib/module/components/ProductMerchandisingCard/ProductMerchandisingCard.js +3 -1
- package/lib/module/components/ProductOverview/ProductOverview.js +2 -2
- package/lib/module/components/Radio/Radio.js +5 -3
- package/lib/module/components/SavingsGoalSummary/SavingsGoalSummary.js +2 -2
- package/lib/module/components/Section/Section.js +3 -1
- package/lib/module/components/SegmentedControl/SegmentedControl.js +1 -1
- package/lib/module/components/SegmentedTrack/SegmentedTrack.js +1 -1
- package/lib/module/components/Slider/Slider.js +3 -2
- package/lib/module/components/StatusHero/StatusHero.js +1 -1
- package/lib/module/components/Tooltip/Tooltip.js +4 -2
- package/lib/module/design-tokens/Coin Variables-variables-full.json +1 -1
- package/lib/module/design-tokens/figma-modes.generated.js +38 -34
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/Section/Section.d.ts +2 -1
- package/lib/typescript/src/design-tokens/figma-modes.generated.d.ts +6 -2
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/CardCTA/CardCTA.tsx +7 -4
- package/src/components/CardFeedback/CardFeedback.tsx +1 -1
- package/src/components/CategoryCard/CategoryCard.tsx +4 -4
- package/src/components/Checkbox/Checkbox.tsx +4 -6
- package/src/components/CircularProgressBarDoted/CircularProgressBarDoted.tsx +12 -9
- package/src/components/DonutChartSummary/DonutChartSummary.tsx +6 -6
- package/src/components/ProductMerchandisingCard/ProductMerchandisingCard.tsx +3 -1
- package/src/components/ProductOverview/ProductOverview.tsx +2 -2
- package/src/components/Radio/Radio.tsx +8 -3
- package/src/components/SavingsGoalSummary/SavingsGoalSummary.tsx +3 -3
- package/src/components/Section/Section.tsx +121 -156
- package/src/components/SegmentedControl/SegmentedControl.tsx +1 -1
- package/src/components/SegmentedTrack/SegmentedTrack.tsx +1 -2
- package/src/components/Slider/Slider.tsx +3 -2
- package/src/components/StatusHero/StatusHero.tsx +1 -1
- package/src/components/Tooltip/Tooltip.tsx +4 -2
- package/src/design-tokens/Coin Variables-variables-full.json +1 -1
- package/src/design-tokens/figma-modes.generated.ts +38 -34
- package/src/icons/registry.ts +1 -1
- package/D2C.md +0 -113
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import React, { useState, useMemo, useRef, useCallback } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
View,
|
|
4
|
+
Text,
|
|
5
|
+
Pressable,
|
|
6
|
+
Platform,
|
|
7
|
+
type StyleProp,
|
|
8
|
+
type ViewStyle,
|
|
9
|
+
type PressableStateCallbackType,
|
|
10
|
+
} from 'react-native'
|
|
3
11
|
import Animated, {
|
|
4
12
|
Easing,
|
|
5
13
|
FadeInUp,
|
|
@@ -94,16 +102,16 @@ const SLOT_GRID_HEIGHT_DURATION_MS = 280
|
|
|
94
102
|
const SLOT_GRID_EASING = Easing.out(Easing.cubic)
|
|
95
103
|
|
|
96
104
|
type SlotGridProps = {
|
|
97
|
-
items: React.ReactNode[]
|
|
98
|
-
gap: number
|
|
99
|
-
maxColumns?: number
|
|
105
|
+
items: React.ReactNode[]
|
|
106
|
+
gap: number
|
|
107
|
+
maxColumns?: number
|
|
100
108
|
/**
|
|
101
109
|
* If set, cells whose index is `>= animateExtrasFromIndex` are wrapped in
|
|
102
110
|
* `Animated.View` with staggered FadeInUp/FadeOutUp builders so that they
|
|
103
111
|
* fade in (and reverse-fade out) when they mount or unmount. Cells below
|
|
104
112
|
* this threshold render as plain `<View>` and are unaffected.
|
|
105
113
|
*/
|
|
106
|
-
animateExtrasFromIndex?: number
|
|
114
|
+
animateExtrasFromIndex?: number
|
|
107
115
|
/**
|
|
108
116
|
* If true, the rows container animates its height via an explicit
|
|
109
117
|
* `useSharedValue` + `withTiming` (ease-out cubic, no overshoot) driven by
|
|
@@ -111,7 +119,7 @@ type SlotGridProps = {
|
|
|
111
119
|
* to clip mid-animation. Cells inside always render at their natural size
|
|
112
120
|
* — they are *never* resized during the transition. Default false.
|
|
113
121
|
*/
|
|
114
|
-
animateContainerLayout?: boolean
|
|
122
|
+
animateContainerLayout?: boolean
|
|
115
123
|
}
|
|
116
124
|
|
|
117
125
|
const slotGridRowFlowStyle: ViewStyle = {
|
|
@@ -150,12 +158,10 @@ const SlotGrid = React.memo(function SlotGrid({
|
|
|
150
158
|
for (const w of widths.values()) {
|
|
151
159
|
if (w > newMax) newMax = w
|
|
152
160
|
}
|
|
153
|
-
setFirstRowMaxWidth((prev) =>
|
|
154
|
-
prev !== null && Math.abs(prev - newMax) < 0.5 ? prev : newMax
|
|
155
|
-
)
|
|
161
|
+
setFirstRowMaxWidth((prev) => (prev !== null && Math.abs(prev - newMax) < 0.5 ? prev : newMax))
|
|
156
162
|
}
|
|
157
163
|
},
|
|
158
|
-
[firstRowSize]
|
|
164
|
+
[firstRowSize],
|
|
159
165
|
)
|
|
160
166
|
|
|
161
167
|
const cellWidth = firstRowMaxWidth
|
|
@@ -175,7 +181,7 @@ const SlotGrid = React.memo(function SlotGrid({
|
|
|
175
181
|
// animated-height clip springs to the new total).
|
|
176
182
|
const cellStyle = useMemo<ViewStyle | undefined>(
|
|
177
183
|
() => (cellWidth !== null ? { width: cellWidth } : undefined),
|
|
178
|
-
[cellWidth]
|
|
184
|
+
[cellWidth],
|
|
179
185
|
)
|
|
180
186
|
|
|
181
187
|
// `space-between` distributes any leftover row space as inter-cell gap, so
|
|
@@ -188,15 +194,11 @@ const SlotGrid = React.memo(function SlotGrid({
|
|
|
188
194
|
// Resolve the threshold once. When undefined we treat it as
|
|
189
195
|
// Number.POSITIVE_INFINITY so the per-cell branch always picks the plain
|
|
190
196
|
// `<View>` path.
|
|
191
|
-
const extrasThreshold = animationsEnabled
|
|
192
|
-
? (animateExtrasFromIndex as number)
|
|
193
|
-
: Number.POSITIVE_INFINITY
|
|
197
|
+
const extrasThreshold = animationsEnabled ? (animateExtrasFromIndex as number) : Number.POSITIVE_INFINITY
|
|
194
198
|
// Total count of "extra" cells currently rendered. Used to compute the
|
|
195
199
|
// reverse-stagger delay for the exiting animation so that the last cell
|
|
196
200
|
// leaves first.
|
|
197
|
-
const extrasCount = animationsEnabled
|
|
198
|
-
? Math.max(0, totalItems - extrasThreshold)
|
|
199
|
-
: 0
|
|
201
|
+
const extrasCount = animationsEnabled ? Math.max(0, totalItems - extrasThreshold) : 0
|
|
200
202
|
|
|
201
203
|
const useAnimatedContainer = animateContainerLayout === true
|
|
202
204
|
|
|
@@ -236,12 +238,10 @@ const SlotGrid = React.memo(function SlotGrid({
|
|
|
236
238
|
reduceMotion: ReduceMotion.System,
|
|
237
239
|
})
|
|
238
240
|
},
|
|
239
|
-
[animatedHeight]
|
|
241
|
+
[animatedHeight],
|
|
240
242
|
)
|
|
241
243
|
const animatedHeightStyle = useAnimatedStyle(() =>
|
|
242
|
-
animatedHeight.value < 0
|
|
243
|
-
? {}
|
|
244
|
-
: { height: animatedHeight.value, overflow: 'hidden' as const }
|
|
244
|
+
animatedHeight.value < 0 ? {} : { height: animatedHeight.value, overflow: 'hidden' as const },
|
|
245
245
|
)
|
|
246
246
|
|
|
247
247
|
const rowsChildren = (
|
|
@@ -258,26 +258,14 @@ const SlotGrid = React.memo(function SlotGrid({
|
|
|
258
258
|
const onLayoutHandler =
|
|
259
259
|
firstRowMaxWidth === null && itemIndex < firstRowSize
|
|
260
260
|
? (e: { nativeEvent: { layout: { width: number } } }) =>
|
|
261
|
-
handleFirstRowItemLayout(
|
|
262
|
-
itemIndex,
|
|
263
|
-
e.nativeEvent.layout.width
|
|
264
|
-
)
|
|
261
|
+
handleFirstRowItemLayout(itemIndex, e.nativeEvent.layout.width)
|
|
265
262
|
: undefined
|
|
266
263
|
|
|
267
264
|
if (itemIndex >= extrasThreshold) {
|
|
268
265
|
const extraOrdinal = itemIndex - extrasThreshold
|
|
269
|
-
const enterStaggerSteps = Math.min(
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
)
|
|
273
|
-
const reverseOrdinal = Math.max(
|
|
274
|
-
0,
|
|
275
|
-
extrasCount - 1 - extraOrdinal
|
|
276
|
-
)
|
|
277
|
-
const exitStaggerSteps = Math.min(
|
|
278
|
-
reverseOrdinal,
|
|
279
|
-
SLOT_GRID_STAGGER_CAP
|
|
280
|
-
)
|
|
266
|
+
const enterStaggerSteps = Math.min(extraOrdinal, SLOT_GRID_STAGGER_CAP)
|
|
267
|
+
const reverseOrdinal = Math.max(0, extrasCount - 1 - extraOrdinal)
|
|
268
|
+
const exitStaggerSteps = Math.min(reverseOrdinal, SLOT_GRID_STAGGER_CAP)
|
|
281
269
|
const entering = FadeInUp.duration(SLOT_GRID_ENTER_DURATION_MS)
|
|
282
270
|
.easing(SLOT_GRID_EASING)
|
|
283
271
|
.delay(enterStaggerSteps * SLOT_GRID_ENTER_STAGGER_MS)
|
|
@@ -300,20 +288,14 @@ const SlotGrid = React.memo(function SlotGrid({
|
|
|
300
288
|
}
|
|
301
289
|
|
|
302
290
|
return (
|
|
303
|
-
<View
|
|
304
|
-
key={itemIndex}
|
|
305
|
-
{...(onLayoutHandler ? { onLayout: onLayoutHandler } : null)}
|
|
306
|
-
style={cellStyle}
|
|
307
|
-
>
|
|
291
|
+
<View key={itemIndex} {...(onLayoutHandler ? { onLayout: onLayoutHandler } : null)} style={cellStyle}>
|
|
308
292
|
{child}
|
|
309
293
|
</View>
|
|
310
294
|
)
|
|
311
295
|
})}
|
|
312
296
|
{cellWidth !== null &&
|
|
313
297
|
spacersNeeded > 0 &&
|
|
314
|
-
Array.from({ length: spacersNeeded }, (_, i) =>
|
|
315
|
-
<View key={`spacer-${i}`} style={cellStyle} />
|
|
316
|
-
))}
|
|
298
|
+
Array.from({ length: spacersNeeded }, (_, i) => <View key={`spacer-${i}`} style={cellStyle} />)}
|
|
317
299
|
</View>
|
|
318
300
|
)
|
|
319
301
|
})}
|
|
@@ -351,28 +333,28 @@ function slotGridPropsAreEqual(prev: SlotGridProps, next: SlotGridProps) {
|
|
|
351
333
|
}
|
|
352
334
|
|
|
353
335
|
type SectionProps = {
|
|
354
|
-
title?: string
|
|
355
|
-
supportText?: string
|
|
356
|
-
showSupportText?: boolean
|
|
357
|
-
slot?: React.ReactNode
|
|
336
|
+
title?: string
|
|
337
|
+
supportText?: string
|
|
338
|
+
showSupportText?: boolean
|
|
339
|
+
slot?: React.ReactNode
|
|
358
340
|
/** Layout direction of the slot's children. Defaults to 'row' (horizontal). */
|
|
359
|
-
slotDirection?: 'row' | 'column'
|
|
360
|
-
modes?: Modes
|
|
361
|
-
onPress?: () => void
|
|
362
|
-
style?: StyleProp<ViewStyle
|
|
363
|
-
accessibilityLabel?: string
|
|
364
|
-
accessibilityHint?: string
|
|
341
|
+
slotDirection?: 'row' | 'column'
|
|
342
|
+
modes?: Modes
|
|
343
|
+
onPress?: () => void
|
|
344
|
+
style?: StyleProp<ViewStyle>
|
|
345
|
+
accessibilityLabel?: string
|
|
346
|
+
accessibilityHint?: string
|
|
365
347
|
/**
|
|
366
348
|
* Web-specific accessibility props (only used on web platform)
|
|
367
349
|
*/
|
|
368
|
-
webAccessibilityProps?: WebAccessibilityProps
|
|
350
|
+
webAccessibilityProps?: WebAccessibilityProps
|
|
369
351
|
/**
|
|
370
352
|
* When `true`, disables text truncation: the trailing ellipsis (`…`) is never
|
|
371
353
|
* shown and any `numberOfLines` clamp is ignored, so the text wraps to as many
|
|
372
354
|
* lines as it needs even when horizontal space is tight.
|
|
373
355
|
*/
|
|
374
|
-
disableTruncation?: boolean
|
|
375
|
-
} & React.ComponentProps<typeof View
|
|
356
|
+
disableTruncation?: boolean
|
|
357
|
+
} & React.ComponentProps<typeof View>
|
|
376
358
|
|
|
377
359
|
/**
|
|
378
360
|
* Section component that mirrors the Figma "Section" component.
|
|
@@ -399,18 +381,18 @@ type SectionProps = {
|
|
|
399
381
|
* @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
|
|
400
382
|
*/
|
|
401
383
|
interface SectionTokens {
|
|
402
|
-
containerStyle: ViewStyle
|
|
403
|
-
headerStyle: ViewStyle
|
|
404
|
-
titleStyle: ViewStyle
|
|
405
|
-
supportTextStyle: ViewStyle
|
|
406
|
-
sectionGap: number
|
|
407
|
-
slotGap: number
|
|
384
|
+
containerStyle: ViewStyle
|
|
385
|
+
headerStyle: ViewStyle
|
|
386
|
+
titleStyle: ViewStyle
|
|
387
|
+
supportTextStyle: ViewStyle
|
|
388
|
+
sectionGap: number
|
|
389
|
+
slotGap: number
|
|
408
390
|
}
|
|
409
391
|
|
|
410
392
|
function resolveSectionTokens(modes: Modes): SectionTokens {
|
|
411
393
|
const backgroundColor = getVariableByName('section/background/color', modes)
|
|
412
|
-
const sectionGap =
|
|
413
|
-
const slotGap =
|
|
394
|
+
const sectionGap = getVariableByName('section/gap', modes) as number
|
|
395
|
+
const slotGap = getVariableByName('slot/gap', modes) as number
|
|
414
396
|
const paddingHorizontal = getVariableByName('section/padding/horizontal', modes)
|
|
415
397
|
const paddingVertical = getVariableByName('section/padding/vertical', modes)
|
|
416
398
|
const radius = getVariableByName('section/radius', modes)
|
|
@@ -424,10 +406,7 @@ function resolveSectionTokens(modes: Modes): SectionTokens {
|
|
|
424
406
|
const titleLineHeight = getVariableByName('section/title/lineHeight', modes)
|
|
425
407
|
const titleFontFamily = getVariableByName('section/title/fontFamily', modes)
|
|
426
408
|
const titleFontWeightRaw = getVariableByName('section/title/fontWeight', modes)
|
|
427
|
-
const titleFontWeight =
|
|
428
|
-
typeof titleFontWeightRaw === 'number'
|
|
429
|
-
? titleFontWeightRaw.toString()
|
|
430
|
-
: titleFontWeightRaw
|
|
409
|
+
const titleFontWeight = typeof titleFontWeightRaw === 'number' ? titleFontWeightRaw.toString() : titleFontWeightRaw
|
|
431
410
|
|
|
432
411
|
const supportTextColor = getVariableByName('section/supportText/color', modes)
|
|
433
412
|
const supportTextFontSize = getVariableByName('section/supportText/fontSize', modes)
|
|
@@ -435,9 +414,7 @@ function resolveSectionTokens(modes: Modes): SectionTokens {
|
|
|
435
414
|
const supportTextFontFamily = getVariableByName('section/supportText/fontFamily', modes)
|
|
436
415
|
const supportTextFontWeightRaw = getVariableByName('section/supportText/fontWeight', modes)
|
|
437
416
|
const supportTextFontWeight =
|
|
438
|
-
typeof supportTextFontWeightRaw === 'number'
|
|
439
|
-
? supportTextFontWeightRaw.toString()
|
|
440
|
-
: supportTextFontWeightRaw
|
|
417
|
+
typeof supportTextFontWeightRaw === 'number' ? supportTextFontWeightRaw.toString() : supportTextFontWeightRaw
|
|
441
418
|
|
|
442
419
|
return {
|
|
443
420
|
containerStyle: {
|
|
@@ -540,9 +517,7 @@ function Section({
|
|
|
540
517
|
>
|
|
541
518
|
{title}
|
|
542
519
|
</Text>
|
|
543
|
-
{onPress &&
|
|
544
|
-
<NavArrow direction="Forward" modes={modes} />
|
|
545
|
-
)}
|
|
520
|
+
{onPress && <NavArrow direction="Forward" modes={modes} />}
|
|
546
521
|
</View>
|
|
547
522
|
{showSupportText && (
|
|
548
523
|
<Text
|
|
@@ -592,13 +567,10 @@ function Section({
|
|
|
592
567
|
isHeaderHovered ? headerHoverStyle : null,
|
|
593
568
|
isHeaderFocused ? headerFocusStyle : null,
|
|
594
569
|
],
|
|
595
|
-
[tokens.headerStyle, isHeaderHovered, isHeaderFocused]
|
|
570
|
+
[tokens.headerStyle, isHeaderHovered, isHeaderFocused],
|
|
596
571
|
)
|
|
597
572
|
|
|
598
|
-
const containerStyleArray = useMemo(
|
|
599
|
-
() => [tokens.containerStyle, style],
|
|
600
|
-
[tokens.containerStyle, style]
|
|
601
|
-
)
|
|
573
|
+
const containerStyleArray = useMemo(() => [tokens.containerStyle, style], [tokens.containerStyle, style])
|
|
602
574
|
|
|
603
575
|
return (
|
|
604
576
|
<View
|
|
@@ -627,21 +599,27 @@ function Section({
|
|
|
627
599
|
{headerContent}
|
|
628
600
|
</Pressable>
|
|
629
601
|
) : (
|
|
630
|
-
<View style={tokens.headerStyle}>
|
|
631
|
-
|
|
632
|
-
|
|
602
|
+
<View style={tokens.headerStyle}>{headerContent}</View>
|
|
603
|
+
)}
|
|
604
|
+
{slot && (
|
|
605
|
+
<SectionSlot
|
|
606
|
+
slot={slot}
|
|
607
|
+
modes={modes}
|
|
608
|
+
direction={slotDirection}
|
|
609
|
+
rowGap={tokens.sectionGap}
|
|
610
|
+
columnGap={tokens.slotGap}
|
|
611
|
+
/>
|
|
633
612
|
)}
|
|
634
|
-
{slot && <SectionSlot slot={slot} modes={modes} direction={slotDirection} rowGap={tokens.sectionGap} columnGap={tokens.slotGap} />}
|
|
635
613
|
</View>
|
|
636
614
|
)
|
|
637
615
|
}
|
|
638
616
|
|
|
639
617
|
type SectionSlotProps = {
|
|
640
|
-
slot: React.ReactNode
|
|
641
|
-
modes: Modes
|
|
642
|
-
direction: 'row' | 'column'
|
|
643
|
-
rowGap: number
|
|
644
|
-
columnGap: number
|
|
618
|
+
slot: React.ReactNode
|
|
619
|
+
modes: Modes
|
|
620
|
+
direction: 'row' | 'column'
|
|
621
|
+
rowGap: number
|
|
622
|
+
columnGap: number
|
|
645
623
|
}
|
|
646
624
|
|
|
647
625
|
/**
|
|
@@ -651,15 +629,9 @@ type SectionSlotProps = {
|
|
|
651
629
|
* without re-walking the slot tree via `cloneChildrenWithModes`.
|
|
652
630
|
*/
|
|
653
631
|
function SectionSlot({ slot, modes, direction, rowGap, columnGap }: SectionSlotProps) {
|
|
654
|
-
const processed = useMemo(
|
|
655
|
-
() => cloneChildrenWithModes(flattenChildren(slot), modes),
|
|
656
|
-
[slot, modes]
|
|
657
|
-
)
|
|
632
|
+
const processed = useMemo(() => cloneChildrenWithModes(flattenChildren(slot), modes), [slot, modes])
|
|
658
633
|
|
|
659
|
-
const columnContainerStyle = useMemo<ViewStyle>(
|
|
660
|
-
() => ({ flexDirection: 'column', gap: columnGap }),
|
|
661
|
-
[columnGap]
|
|
662
|
-
)
|
|
634
|
+
const columnContainerStyle = useMemo<ViewStyle>(() => ({ flexDirection: 'column', gap: columnGap }), [columnGap])
|
|
663
635
|
|
|
664
636
|
if (direction === 'row') {
|
|
665
637
|
return <SlotGrid items={processed} gap={rowGap} />
|
|
@@ -668,21 +640,22 @@ function SectionSlot({ slot, modes, direction, rowGap, columnGap }: SectionSlotP
|
|
|
668
640
|
}
|
|
669
641
|
|
|
670
642
|
type BentoToggleRenderState = {
|
|
671
|
-
expanded: boolean
|
|
672
|
-
toggle: () => void
|
|
643
|
+
expanded: boolean
|
|
644
|
+
toggle: () => void
|
|
673
645
|
}
|
|
674
646
|
|
|
675
647
|
type SectionBentoProps = {
|
|
676
|
-
navSlot?: React.ReactNode
|
|
677
|
-
upiSlot?: React.ReactNode
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
648
|
+
navSlot?: React.ReactNode
|
|
649
|
+
upiSlot?: React.ReactNode
|
|
650
|
+
upi?: boolean
|
|
651
|
+
modes?: Modes
|
|
652
|
+
style?: StyleProp<ViewStyle>
|
|
653
|
+
accessibilityLabel?: string
|
|
654
|
+
accessibilityHint?: string
|
|
682
655
|
/**
|
|
683
656
|
* Web-specific accessibility props (only used on web platform)
|
|
684
657
|
*/
|
|
685
|
-
webAccessibilityProps?: WebAccessibilityProps
|
|
658
|
+
webAccessibilityProps?: WebAccessibilityProps
|
|
686
659
|
/**
|
|
687
660
|
* Total cell count visible when collapsed (real items + the toggle cell).
|
|
688
661
|
* Defaults to {@link SLOT_GRID_MAX_COLUMNS} (4) so the collapsed state fills
|
|
@@ -690,37 +663,37 @@ type SectionBentoProps = {
|
|
|
690
663
|
* disabled (no toggle injected, no animation wrappers — identical to the
|
|
691
664
|
* legacy behavior for back-compat).
|
|
692
665
|
*/
|
|
693
|
-
collapsedCount?: number
|
|
666
|
+
collapsedCount?: number
|
|
694
667
|
/**
|
|
695
668
|
* Uncontrolled initial expanded state. Ignored when `expanded` is provided.
|
|
696
669
|
* Defaults to `false`.
|
|
697
670
|
*/
|
|
698
|
-
defaultExpanded?: boolean
|
|
671
|
+
defaultExpanded?: boolean
|
|
699
672
|
/**
|
|
700
673
|
* Controlled expanded state. When provided, `onExpandedChange` should also
|
|
701
674
|
* be provided so the component can request changes.
|
|
702
675
|
*/
|
|
703
|
-
expanded?: boolean
|
|
676
|
+
expanded?: boolean
|
|
704
677
|
/**
|
|
705
678
|
* Called when the user taps the toggle. Required in controlled mode; ignored
|
|
706
679
|
* in uncontrolled mode unless you want to observe the change.
|
|
707
680
|
*/
|
|
708
|
-
onExpandedChange?: (next: boolean) => void
|
|
681
|
+
onExpandedChange?: (next: boolean) => void
|
|
709
682
|
/** Label shown on the toggle cell when collapsed. Default `'More'`. */
|
|
710
|
-
toggleMoreLabel?: string
|
|
683
|
+
toggleMoreLabel?: string
|
|
711
684
|
/** Label shown on the toggle cell when expanded. Default `'Less'`. */
|
|
712
|
-
toggleLessLabel?: string
|
|
685
|
+
toggleLessLabel?: string
|
|
713
686
|
/** Icon name shown on the toggle when collapsed. Default `'ic_chevron_down'`. */
|
|
714
|
-
toggleMoreIcon?: string
|
|
687
|
+
toggleMoreIcon?: string
|
|
715
688
|
/** Icon name shown on the toggle when expanded. Default `'ic_chevron_up'`. */
|
|
716
|
-
toggleLessIcon?: string
|
|
689
|
+
toggleLessIcon?: string
|
|
717
690
|
/**
|
|
718
691
|
* Escape hatch: render a custom toggle cell instead of the default ListItem.
|
|
719
692
|
* The provided node is rendered in the toggle's grid slot. Wire `toggle()` to
|
|
720
693
|
* any tap interaction inside it. Height + per-cell animations still apply.
|
|
721
694
|
*/
|
|
722
|
-
renderToggle?: (state: BentoToggleRenderState) => React.ReactNode
|
|
723
|
-
} & React.ComponentProps<typeof View
|
|
695
|
+
renderToggle?: (state: BentoToggleRenderState) => React.ReactNode
|
|
696
|
+
} & React.ComponentProps<typeof View>
|
|
724
697
|
|
|
725
698
|
/**
|
|
726
699
|
* Section.Bento component that mirrors the Figma "Section.Bento" component.
|
|
@@ -739,6 +712,7 @@ type SectionBentoProps = {
|
|
|
739
712
|
* @param {Object} props
|
|
740
713
|
* @param {React.ReactNode} [props.navSlot] - Optional custom slot for navigation items (Figma Slot "Nav wrap")
|
|
741
714
|
* @param {React.ReactNode} [props.upiSlot] - Optional custom slot for UPI handles (Figma Slot "UPI wrap")
|
|
715
|
+
* @param {boolean} [props.upi=true] - Toggles the UPI wrap (Figma boolean property "upi")
|
|
742
716
|
* @param {Object} [props.modes={}] - Modes object passed to `getVariableByName` for all design tokens
|
|
743
717
|
* @param {Object} [props.style] - Optional container style overrides
|
|
744
718
|
* @param {string} [props.accessibilityLabel] - Accessibility label for the section
|
|
@@ -749,6 +723,7 @@ const sectionBentoUpiRowStyle: ViewStyle = { flexDirection: 'row', gap: 8 }
|
|
|
749
723
|
function SectionBento({
|
|
750
724
|
navSlot,
|
|
751
725
|
upiSlot,
|
|
726
|
+
upi = true,
|
|
752
727
|
modes = EMPTY_MODES,
|
|
753
728
|
style,
|
|
754
729
|
// Same rationale as Section: accepted on the type but unused internally.
|
|
@@ -779,17 +754,17 @@ function SectionBento({
|
|
|
779
754
|
borderRadius: radius,
|
|
780
755
|
gap,
|
|
781
756
|
}),
|
|
782
|
-
[backgroundColor, paddingHorizontal, paddingVertical, radius, gap]
|
|
757
|
+
[backgroundColor, paddingHorizontal, paddingVertical, radius, gap],
|
|
783
758
|
)
|
|
784
759
|
|
|
785
760
|
const processedNavSlot = useMemo(
|
|
786
761
|
() => (navSlot ? cloneChildrenWithModes(flattenChildren(navSlot), modes) : null),
|
|
787
|
-
[navSlot, modes]
|
|
762
|
+
[navSlot, modes],
|
|
788
763
|
)
|
|
789
764
|
|
|
790
765
|
const processedUpiSlot = useMemo(
|
|
791
766
|
() => (upiSlot ? cloneChildrenWithModes(flattenChildren(upiSlot), modes) : null),
|
|
792
|
-
[upiSlot, modes]
|
|
767
|
+
[upiSlot, modes],
|
|
793
768
|
)
|
|
794
769
|
|
|
795
770
|
// `canExpand` is true iff there are strictly more real items than fit into
|
|
@@ -823,24 +798,22 @@ function SectionBento({
|
|
|
823
798
|
return allRealItems
|
|
824
799
|
}
|
|
825
800
|
// Leave the last collapsed slot for the toggle cell.
|
|
826
|
-
const visibleRealItems = expanded
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
/>
|
|
843
|
-
)
|
|
801
|
+
const visibleRealItems = expanded ? allRealItems : allRealItems.slice(0, collapsedCount - 1)
|
|
802
|
+
|
|
803
|
+
const toggleNode = renderToggle ? (
|
|
804
|
+
renderToggle({ expanded, toggle })
|
|
805
|
+
) : (
|
|
806
|
+
<DefaultBentoToggle
|
|
807
|
+
expanded={expanded}
|
|
808
|
+
onPress={toggle}
|
|
809
|
+
modes={modes}
|
|
810
|
+
moreLabel={toggleMoreLabel}
|
|
811
|
+
lessLabel={toggleLessLabel}
|
|
812
|
+
moreIcon={toggleMoreIcon}
|
|
813
|
+
lessIcon={toggleLessIcon}
|
|
814
|
+
extraCount={allRealItems.length - (collapsedCount - 1)}
|
|
815
|
+
/>
|
|
816
|
+
)
|
|
844
817
|
|
|
845
818
|
return [...visibleRealItems, toggleNode]
|
|
846
819
|
}, [
|
|
@@ -877,11 +850,7 @@ function SectionBento({
|
|
|
877
850
|
: null)}
|
|
878
851
|
/>
|
|
879
852
|
)}
|
|
880
|
-
{processedUpiSlot &&
|
|
881
|
-
<View style={sectionBentoUpiRowStyle}>
|
|
882
|
-
{processedUpiSlot}
|
|
883
|
-
</View>
|
|
884
|
-
)}
|
|
853
|
+
{upi && processedUpiSlot && <View style={sectionBentoUpiRowStyle}>{processedUpiSlot}</View>}
|
|
885
854
|
</View>
|
|
886
855
|
)
|
|
887
856
|
}
|
|
@@ -897,15 +866,15 @@ function SectionBento({
|
|
|
897
866
|
// shared-value-driven rotation would lose its anchor across toggles.
|
|
898
867
|
// ---------------------------------------------------------------------------
|
|
899
868
|
type DefaultBentoToggleProps = {
|
|
900
|
-
expanded: boolean
|
|
901
|
-
onPress: () => void
|
|
902
|
-
modes: Modes
|
|
903
|
-
moreLabel: string
|
|
904
|
-
lessLabel: string
|
|
905
|
-
moreIcon: string
|
|
906
|
-
lessIcon: string
|
|
869
|
+
expanded: boolean
|
|
870
|
+
onPress: () => void
|
|
871
|
+
modes: Modes
|
|
872
|
+
moreLabel: string
|
|
873
|
+
lessLabel: string
|
|
874
|
+
moreIcon: string
|
|
875
|
+
lessIcon: string
|
|
907
876
|
/** How many additional actions become visible when expanding. Used in the a11y hint. */
|
|
908
|
-
extraCount: number
|
|
877
|
+
extraCount: number
|
|
909
878
|
}
|
|
910
879
|
|
|
911
880
|
function DefaultBentoToggle({
|
|
@@ -921,10 +890,7 @@ function DefaultBentoToggle({
|
|
|
921
890
|
const label = expanded ? lessLabel : moreLabel
|
|
922
891
|
const iconName = expanded ? lessIcon : moreIcon
|
|
923
892
|
const accessibilityState = useMemo(() => ({ expanded }), [expanded])
|
|
924
|
-
const webAccessibilityProps = useMemo(
|
|
925
|
-
() => ({ ariaExpanded: expanded }),
|
|
926
|
-
[expanded]
|
|
927
|
-
)
|
|
893
|
+
const webAccessibilityProps = useMemo(() => ({ ariaExpanded: expanded }), [expanded])
|
|
928
894
|
const accessibilityHint = expanded
|
|
929
895
|
? `Hides ${extraCount} additional ${extraCount === 1 ? 'action' : 'actions'}`
|
|
930
896
|
: `Shows ${extraCount} additional ${extraCount === 1 ? 'action' : 'actions'}`
|
|
@@ -948,4 +914,3 @@ function DefaultBentoToggle({
|
|
|
948
914
|
Section.Bento = SectionBento
|
|
949
915
|
|
|
950
916
|
export default Section
|
|
951
|
-
|
|
@@ -141,7 +141,7 @@ function SegmentedControl({
|
|
|
141
141
|
|
|
142
142
|
const gap = (getVariableByName('segmentedControl/gap', modes) ?? 8) as number
|
|
143
143
|
const background = (getVariableByName('segmentedControl/background', modes) ?? '#f6f3ff') as string
|
|
144
|
-
const radius = (getVariableByName('
|
|
144
|
+
const radius = (getVariableByName('segment/radius', modes) ?? 20) as number
|
|
145
145
|
|
|
146
146
|
const containerStyle: ViewStyle = {
|
|
147
147
|
flexDirection: 'row',
|
|
@@ -171,8 +171,7 @@ function SegmentedTrack({
|
|
|
171
171
|
|
|
172
172
|
const trackHeight =
|
|
173
173
|
(getVariableByName('segmentedTrack/height', modes) as number | null) ?? 24
|
|
174
|
-
const trackRadius =
|
|
175
|
-
(getVariableByName('segmentedTrack/radius', modes) as number | null) ?? 999
|
|
174
|
+
const trackRadius = trackHeight / 2
|
|
176
175
|
|
|
177
176
|
const renderedSegments = renderSegments({
|
|
178
177
|
segments,
|
|
@@ -547,8 +547,9 @@ function resolveTokens(modes: Modes): ResolvedTokens {
|
|
|
547
547
|
const tooltipBackground = asStr(getVariableByName('tooltip/background', modes), '#0f0d0a')
|
|
548
548
|
const tooltipPaddingH = asNum(getVariableByName('tooltip/padding/horizontal', modes), 12)
|
|
549
549
|
const tooltipPaddingV = asNum(getVariableByName('tooltip/padding/vertical', modes), 8)
|
|
550
|
-
|
|
551
|
-
const
|
|
550
|
+
// Not yet in Coin Variables export (Figma still binds bare local radius/maxWidth).
|
|
551
|
+
const tooltipRadius = asNum(getVariableByName('tooltip/radius', modes), 8)
|
|
552
|
+
const tooltipMaxWidth = asNum(getVariableByName('tooltip/maxWidth', modes), 280)
|
|
552
553
|
const tipWidth = asNum(getVariableByName('tooltip/tipItem/width', modes), 16)
|
|
553
554
|
const tipHeight = asNum(getVariableByName('tooltip/tipItem/height', modes), 8)
|
|
554
555
|
|
|
@@ -266,8 +266,10 @@ export function TooltipContent({
|
|
|
266
266
|
const bgColor = (getVariableByName('tooltip/background', modes) as string)
|
|
267
267
|
const paddingH = Number(getVariableByName('tooltip/padding/horizontal', modes))
|
|
268
268
|
const paddingV = Number(getVariableByName('tooltip/padding/vertical', modes))
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
// Figma still binds bare local radius/maxWidth; published names are tooltip/*.
|
|
270
|
+
// Not yet in Coin Variables export — fall back to Figma values (8 / 280).
|
|
271
|
+
const radius = Number(getVariableByName('tooltip/radius', modes) ?? 8)
|
|
272
|
+
const maxWidthToken = Number(getVariableByName('tooltip/maxWidth', modes) ?? 280)
|
|
271
273
|
const arrowWidth = Number(getVariableByName('tooltip/tipItem/width', modes))
|
|
272
274
|
const arrowHeight = Number(getVariableByName('tooltip/tipItem/height', modes))
|
|
273
275
|
const labelColor = (getVariableByName('tooltip/label/color', modes) as string)
|