jfs-components 0.0.63 → 0.0.64
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 +23 -0
- package/lib/commonjs/components/Drawer/Drawer.js +107 -47
- package/lib/commonjs/components/Section/Section.js +280 -58
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/module/components/Drawer/Drawer.js +107 -47
- package/lib/module/components/Section/Section.js +280 -58
- package/lib/module/icons/registry.js +1 -1
- package/lib/typescript/src/components/Section/Section.d.ts +42 -1
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/Drawer/Drawer.tsx +122 -57
- package/src/components/Section/Section.tsx +411 -71
- package/src/icons/registry.ts +1 -1
|
@@ -22,6 +22,10 @@ declare function Section({ title, supportText, showSupportText, slot, slotDirect
|
|
|
22
22
|
declare namespace Section {
|
|
23
23
|
var Bento: typeof SectionBento;
|
|
24
24
|
}
|
|
25
|
+
type BentoToggleRenderState = {
|
|
26
|
+
expanded: boolean;
|
|
27
|
+
toggle: () => void;
|
|
28
|
+
};
|
|
25
29
|
type SectionBentoProps = {
|
|
26
30
|
navSlot?: React.ReactNode;
|
|
27
31
|
upiSlot?: React.ReactNode;
|
|
@@ -33,7 +37,44 @@ type SectionBentoProps = {
|
|
|
33
37
|
* Web-specific accessibility props (only used on web platform)
|
|
34
38
|
*/
|
|
35
39
|
webAccessibilityProps?: WebAccessibilityProps;
|
|
40
|
+
/**
|
|
41
|
+
* Total cell count visible when collapsed (real items + the toggle cell).
|
|
42
|
+
* Defaults to {@link SLOT_GRID_MAX_COLUMNS} (4) so the collapsed state fills
|
|
43
|
+
* exactly one row. When `navSlot.length <= collapsedCount`, expansion is
|
|
44
|
+
* disabled (no toggle injected, no animation wrappers — identical to the
|
|
45
|
+
* legacy behavior for back-compat).
|
|
46
|
+
*/
|
|
47
|
+
collapsedCount?: number;
|
|
48
|
+
/**
|
|
49
|
+
* Uncontrolled initial expanded state. Ignored when `expanded` is provided.
|
|
50
|
+
* Defaults to `false`.
|
|
51
|
+
*/
|
|
52
|
+
defaultExpanded?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Controlled expanded state. When provided, `onExpandedChange` should also
|
|
55
|
+
* be provided so the component can request changes.
|
|
56
|
+
*/
|
|
57
|
+
expanded?: boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Called when the user taps the toggle. Required in controlled mode; ignored
|
|
60
|
+
* in uncontrolled mode unless you want to observe the change.
|
|
61
|
+
*/
|
|
62
|
+
onExpandedChange?: (next: boolean) => void;
|
|
63
|
+
/** Label shown on the toggle cell when collapsed. Default `'More'`. */
|
|
64
|
+
toggleMoreLabel?: string;
|
|
65
|
+
/** Label shown on the toggle cell when expanded. Default `'Less'`. */
|
|
66
|
+
toggleLessLabel?: string;
|
|
67
|
+
/** Icon name shown on the toggle when collapsed. Default `'ic_chevron_down'`. */
|
|
68
|
+
toggleMoreIcon?: string;
|
|
69
|
+
/** Icon name shown on the toggle when expanded. Default `'ic_chevron_up'`. */
|
|
70
|
+
toggleLessIcon?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Escape hatch: render a custom toggle cell instead of the default ListItem.
|
|
73
|
+
* The provided node is rendered in the toggle's grid slot. Wire `toggle()` to
|
|
74
|
+
* any tap interaction inside it. Height + per-cell animations still apply.
|
|
75
|
+
*/
|
|
76
|
+
renderToggle?: (state: BentoToggleRenderState) => React.ReactNode;
|
|
36
77
|
} & React.ComponentProps<typeof View>;
|
|
37
|
-
declare function SectionBento({ navSlot, upiSlot, modes, style, accessibilityLabel: _accessibilityLabel, accessibilityHint, ...rest }: SectionBentoProps): import("react/jsx-runtime").JSX.Element;
|
|
78
|
+
declare function SectionBento({ navSlot, upiSlot, modes, style, accessibilityLabel: _accessibilityLabel, accessibilityHint, collapsedCount, defaultExpanded, expanded: controlledExpanded, onExpandedChange, toggleMoreLabel, toggleLessLabel, toggleMoreIcon, toggleLessIcon, renderToggle, ...rest }: SectionBentoProps): import("react/jsx-runtime").JSX.Element;
|
|
38
79
|
export default Section;
|
|
39
80
|
//# sourceMappingURL=Section.d.ts.map
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Auto-generated from SVG files in src/icons/
|
|
5
5
|
* DO NOT EDIT MANUALLY - Run "npm run icons:generate" to regenerate
|
|
6
6
|
*
|
|
7
|
-
* Generated: 2026-04-
|
|
7
|
+
* Generated: 2026-04-20T20:41:14.535Z
|
|
8
8
|
*/
|
|
9
9
|
export declare const iconRegistry: Record<string, {
|
|
10
10
|
path: string;
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useState, useRef } from 'react'
|
|
2
|
-
import { Platform, StyleSheet, Text, useWindowDimensions, View } from 'react-native'
|
|
2
|
+
import { Platform, StyleSheet, Text, useWindowDimensions, View, ViewStyle } from 'react-native'
|
|
3
3
|
import {
|
|
4
4
|
Gesture,
|
|
5
5
|
GestureDetector,
|
|
@@ -167,10 +167,19 @@ function Drawer({
|
|
|
167
167
|
setMode(newMode)
|
|
168
168
|
}, [])
|
|
169
169
|
|
|
170
|
+
// Gesture policy:
|
|
171
|
+
// • activeOffsetY: require a clear *vertical* drag (10px) before this
|
|
172
|
+
// pan claims the gesture. Matches typical iOS scroll activation feel.
|
|
173
|
+
// • failOffsetX: if the finger crosses ~16px horizontally *before* we
|
|
174
|
+
// activate, surrender the gesture entirely so any horizontal child
|
|
175
|
+
// (FlatList horizontal, swiper, slider, etc.) can scroll cleanly
|
|
176
|
+
// without the drawer also translating on Y.
|
|
177
|
+
// • simultaneousWithExternalGesture(scrollRef): cooperate with the
|
|
178
|
+
// drawer's own internal vertical ScrollView for nested scrolling.
|
|
170
179
|
const gesture = Gesture.Pan()
|
|
171
180
|
.simultaneousWithExternalGesture(scrollRef)
|
|
172
|
-
.activeOffsetY([-
|
|
173
|
-
.
|
|
181
|
+
.activeOffsetY([-10, 10])
|
|
182
|
+
.failOffsetX([-16, 16])
|
|
174
183
|
.onStart(() => {
|
|
175
184
|
context.value = { y: translateY.value }
|
|
176
185
|
isDrawerActive.value = true
|
|
@@ -179,6 +188,16 @@ function Drawer({
|
|
|
179
188
|
scrollTopTranslationOffset.value = 0
|
|
180
189
|
})
|
|
181
190
|
.onUpdate((event) => {
|
|
191
|
+
// Defense-in-depth: even after vertical activation, if the *current*
|
|
192
|
+
// motion is dominantly horizontal (e.g., the user activated with a
|
|
193
|
+
// small Y nudge and then curved into a horizontal swipe on a child
|
|
194
|
+
// carousel), don't translate the drawer this frame. failOffsetX
|
|
195
|
+
// already prevents activation in pure-horizontal swipes; this guards
|
|
196
|
+
// the diagonal-then-horizontal case.
|
|
197
|
+
if (Math.abs(event.translationX) > Math.abs(event.translationY) * 1.5) {
|
|
198
|
+
return
|
|
199
|
+
}
|
|
200
|
+
|
|
182
201
|
// Logic for nested scrolling:
|
|
183
202
|
// If we are at the expanded position (minTranslateY) AND content is
|
|
184
203
|
// scrolled down (scrollY > 0), let the ScrollView handle the gesture.
|
|
@@ -299,6 +318,36 @@ function Drawer({
|
|
|
299
318
|
const titleLineHeight = getVariableByName('drawer/title/lineHeight', modes) || 17
|
|
300
319
|
const titlePaddingBottom = getVariableByName('drawer/titleWrap/padding/bottom', modes) || 8
|
|
301
320
|
|
|
321
|
+
// Drop shadow — Figma layers two shadows (primary + secondary) sharing
|
|
322
|
+
// the same offsetY/blur but with their own offsetX and color.
|
|
323
|
+
const shadowPrimaryOffsetX = (getVariableByName('drawer/shadow/primary/offsetX', modes) ?? 0) as number
|
|
324
|
+
const shadowPrimaryOffsetY = (getVariableByName('drawer/shadow/primary/offsetY', modes) ?? 16) as number
|
|
325
|
+
const shadowPrimaryBlur = (getVariableByName('drawer/shadow/primary/blur', modes) ?? 48) as number
|
|
326
|
+
const shadowPrimaryColor = (getVariableByName('drawer/shadow/primary/color', modes) ?? 'rgba(12, 13, 16, 0.16)') as string
|
|
327
|
+
const shadowSecondaryOffsetX = (getVariableByName('drawer/shadow/secondary/offsetX', modes) ?? 0) as number
|
|
328
|
+
const shadowSecondaryColor = (getVariableByName('drawer/shadow/secondary/color', modes) ?? 'rgba(12, 13, 16, 0.12)') as string
|
|
329
|
+
|
|
330
|
+
// Cross-platform shadow style. Web supports stacking two shadows via
|
|
331
|
+
// boxShadow. iOS only supports a single native shadow per view, so we
|
|
332
|
+
// apply the more prominent (primary) one. Android uses elevation.
|
|
333
|
+
const shadowStyle: ViewStyle = Platform.select({
|
|
334
|
+
web: {
|
|
335
|
+
boxShadow:
|
|
336
|
+
`${shadowSecondaryOffsetX}px ${shadowPrimaryOffsetY}px ${shadowPrimaryBlur}px 0px ${shadowSecondaryColor}, ` +
|
|
337
|
+
`${shadowPrimaryOffsetX}px ${shadowPrimaryOffsetY}px ${shadowPrimaryBlur}px 0px ${shadowPrimaryColor}`,
|
|
338
|
+
} as ViewStyle,
|
|
339
|
+
ios: {
|
|
340
|
+
shadowColor: shadowPrimaryColor,
|
|
341
|
+
shadowOffset: { width: shadowPrimaryOffsetX, height: shadowPrimaryOffsetY },
|
|
342
|
+
shadowOpacity: 1,
|
|
343
|
+
shadowRadius: shadowPrimaryBlur / 2,
|
|
344
|
+
},
|
|
345
|
+
android: {
|
|
346
|
+
elevation: 16,
|
|
347
|
+
},
|
|
348
|
+
default: {},
|
|
349
|
+
}) as ViewStyle
|
|
350
|
+
|
|
302
351
|
const defaultAccessibilityLabel = accessibilityLabel || title || 'Drawer'
|
|
303
352
|
|
|
304
353
|
return (
|
|
@@ -314,11 +363,8 @@ function Drawer({
|
|
|
314
363
|
backgroundColor,
|
|
315
364
|
borderTopLeftRadius: radius,
|
|
316
365
|
borderTopRightRadius: radius,
|
|
317
|
-
paddingLeft,
|
|
318
|
-
paddingRight,
|
|
319
|
-
paddingBottom,
|
|
320
|
-
rowGap: drawerGap,
|
|
321
366
|
},
|
|
367
|
+
shadowStyle,
|
|
322
368
|
sheetStyle,
|
|
323
369
|
animatedStyle,
|
|
324
370
|
]}
|
|
@@ -327,57 +373,73 @@ function Drawer({
|
|
|
327
373
|
accessibilityLabel={undefined}
|
|
328
374
|
accessibilityHint={accessibilityHint || 'Swipe up to expand, swipe down to collapse'}
|
|
329
375
|
>
|
|
330
|
-
{/*
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
{/* Custom Header Slot */}
|
|
345
|
-
{header}
|
|
346
|
-
|
|
347
|
-
{/* Title (Legacy/Simple Mode) */}
|
|
348
|
-
{title && (
|
|
349
|
-
<Text
|
|
350
|
-
style={[
|
|
351
|
-
{
|
|
352
|
-
color: titleColor,
|
|
353
|
-
fontSize: titleSize,
|
|
354
|
-
fontWeight: titleWeight as any,
|
|
355
|
-
lineHeight: titleLineHeight,
|
|
356
|
-
marginBottom: titlePaddingBottom,
|
|
357
|
-
}
|
|
358
|
-
]}
|
|
359
|
-
>
|
|
360
|
-
{title}
|
|
361
|
-
</Text>
|
|
362
|
-
)}
|
|
363
|
-
|
|
364
|
-
{/* Scrollable Content */}
|
|
365
|
-
<AnimatedScrollView
|
|
366
|
-
ref={scrollRef}
|
|
367
|
-
style={[styles.content, contentStyle]}
|
|
368
|
-
contentContainerStyle={[{ paddingBottom: paddingBottom + bottomInset, gap: drawerGap, flexDirection: 'column', alignItems: 'stretch' }, contentContainerStyle]}
|
|
369
|
-
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
|
370
|
-
animatedProps={animatedScrollProps}
|
|
371
|
-
alwaysBounceVertical={false}
|
|
372
|
-
overScrollMode="always"
|
|
373
|
-
onScroll={useAnimatedScrollHandler((event) => {
|
|
374
|
-
scrollY.value = event.contentOffset.y
|
|
375
|
-
})}
|
|
376
|
-
scrollEventThrottle={16}
|
|
376
|
+
{/* Inner clip layer — keeps overflow:'hidden' off the shadow-carrying
|
|
377
|
+
outer view so iOS doesn't clip the drop shadow. */}
|
|
378
|
+
<View
|
|
379
|
+
style={[
|
|
380
|
+
styles.sheetInner,
|
|
381
|
+
{
|
|
382
|
+
borderTopLeftRadius: radius,
|
|
383
|
+
borderTopRightRadius: radius,
|
|
384
|
+
paddingLeft,
|
|
385
|
+
paddingRight,
|
|
386
|
+
paddingBottom,
|
|
387
|
+
rowGap: drawerGap,
|
|
388
|
+
},
|
|
389
|
+
]}
|
|
377
390
|
>
|
|
378
|
-
{/*
|
|
379
|
-
{
|
|
380
|
-
|
|
391
|
+
{/* Handle Area */}
|
|
392
|
+
<View style={[styles.handleArea, (!title && !header) && { paddingBottom: 0 }]}>
|
|
393
|
+
<View
|
|
394
|
+
style={[
|
|
395
|
+
{
|
|
396
|
+
backgroundColor: handleColor,
|
|
397
|
+
width: handleWidth,
|
|
398
|
+
height: handleHeight,
|
|
399
|
+
borderRadius: handleRadius
|
|
400
|
+
},
|
|
401
|
+
]}
|
|
402
|
+
/>
|
|
403
|
+
</View>
|
|
404
|
+
|
|
405
|
+
{/* Custom Header Slot */}
|
|
406
|
+
{header}
|
|
407
|
+
|
|
408
|
+
{/* Title (Legacy/Simple Mode) */}
|
|
409
|
+
{title && (
|
|
410
|
+
<Text
|
|
411
|
+
style={[
|
|
412
|
+
{
|
|
413
|
+
color: titleColor,
|
|
414
|
+
fontSize: titleSize,
|
|
415
|
+
fontWeight: titleWeight as any,
|
|
416
|
+
lineHeight: titleLineHeight,
|
|
417
|
+
marginBottom: titlePaddingBottom,
|
|
418
|
+
}
|
|
419
|
+
]}
|
|
420
|
+
>
|
|
421
|
+
{title}
|
|
422
|
+
</Text>
|
|
423
|
+
)}
|
|
424
|
+
|
|
425
|
+
{/* Scrollable Content */}
|
|
426
|
+
<AnimatedScrollView
|
|
427
|
+
ref={scrollRef}
|
|
428
|
+
style={[styles.content, contentStyle]}
|
|
429
|
+
contentContainerStyle={[{ paddingBottom: paddingBottom + bottomInset, gap: drawerGap, flexDirection: 'column', alignItems: 'stretch' }, contentContainerStyle]}
|
|
430
|
+
showsVerticalScrollIndicator={showsVerticalScrollIndicator}
|
|
431
|
+
animatedProps={animatedScrollProps}
|
|
432
|
+
alwaysBounceVertical={false}
|
|
433
|
+
overScrollMode="always"
|
|
434
|
+
onScroll={useAnimatedScrollHandler((event) => {
|
|
435
|
+
scrollY.value = event.contentOffset.y
|
|
436
|
+
})}
|
|
437
|
+
scrollEventThrottle={16}
|
|
438
|
+
>
|
|
439
|
+
{/* Prevent touch propagation for text selection if needed */}
|
|
440
|
+
{children}
|
|
441
|
+
</AnimatedScrollView>
|
|
442
|
+
</View>
|
|
381
443
|
</Animated.View>
|
|
382
444
|
</GestureDetector>
|
|
383
445
|
</GestureHandlerRootView>
|
|
@@ -399,6 +461,9 @@ const styles = StyleSheet.create({
|
|
|
399
461
|
width: '100%',
|
|
400
462
|
position: 'absolute',
|
|
401
463
|
top: 0,
|
|
464
|
+
},
|
|
465
|
+
sheetInner: {
|
|
466
|
+
flex: 1,
|
|
402
467
|
overflow: 'hidden',
|
|
403
468
|
},
|
|
404
469
|
handleArea: {
|