jfs-components 0.0.63 → 0.0.65
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 +31 -0
- package/lib/commonjs/components/Carousel/Carousel.js +12 -9
- package/lib/commonjs/components/Drawer/Drawer.js +116 -50
- package/lib/commonjs/components/IconButton/IconButton.js +42 -6
- package/lib/commonjs/components/IconCapsule/IconCapsule.js +5 -0
- package/lib/commonjs/components/Popup/Popup.js +2 -2
- package/lib/commonjs/components/Section/Section.js +280 -58
- package/lib/commonjs/components/UpiHandle/UpiHandle.js +19 -7
- package/lib/commonjs/icons/Icon.js +72 -75
- package/lib/commonjs/icons/registry.js +1 -1
- package/lib/commonjs/utils/MediaSource.js +181 -0
- package/lib/commonjs/utils/index.js +9 -1
- package/lib/module/components/Carousel/Carousel.js +12 -9
- package/lib/module/components/Drawer/Drawer.js +116 -50
- package/lib/module/components/IconButton/IconButton.js +42 -6
- package/lib/module/components/IconCapsule/IconCapsule.js +5 -0
- package/lib/module/components/Popup/Popup.js +2 -2
- package/lib/module/components/Section/Section.js +280 -58
- package/lib/module/components/UpiHandle/UpiHandle.js +20 -8
- package/lib/module/icons/Icon.js +72 -75
- package/lib/module/icons/registry.js +1 -1
- package/lib/module/utils/MediaSource.js +176 -0
- package/lib/module/utils/index.js +2 -1
- package/lib/typescript/src/components/Drawer/Drawer.d.ts +6 -1
- package/lib/typescript/src/components/IconButton/IconButton.d.ts +25 -14
- package/lib/typescript/src/components/IconCapsule/IconCapsule.d.ts +12 -1
- package/lib/typescript/src/components/Section/Section.d.ts +42 -1
- package/lib/typescript/src/components/UpiHandle/UpiHandle.d.ts +17 -3
- package/lib/typescript/src/icons/Icon.d.ts +35 -16
- package/lib/typescript/src/icons/registry.d.ts +1 -1
- package/lib/typescript/src/utils/MediaSource.d.ts +63 -0
- package/lib/typescript/src/utils/index.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/Carousel/Carousel.tsx +16 -17
- package/src/components/Drawer/Drawer.tsx +136 -60
- package/src/components/IconButton/IconButton.tsx +70 -11
- package/src/components/IconCapsule/IconCapsule.tsx +13 -0
- package/src/components/Popup/Popup.tsx +2 -2
- package/src/components/Section/Section.tsx +411 -71
- package/src/components/UpiHandle/UpiHandle.tsx +37 -11
- package/src/icons/Icon.tsx +91 -76
- package/src/icons/registry.ts +1 -1
- package/src/utils/MediaSource.tsx +220 -0
- package/src/utils/index.ts +2 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = _interopRequireDefault(require("react"));
|
|
8
|
+
var _reactNative = require("react-native");
|
|
9
|
+
var _reactNativeSvg = require("react-native-svg");
|
|
10
|
+
var _jsxRuntime = require("react/jsx-runtime");
|
|
11
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
12
|
+
/**
|
|
13
|
+
* A unified, "do-the-right-thing" image source accepted by `MediaSource` and
|
|
14
|
+
* by the `source` prop on `Icon`, `IconCapsule` and `UpiHandle`.
|
|
15
|
+
*
|
|
16
|
+
* Accepts any of:
|
|
17
|
+
* - `string` — a URI (raster or `.svg`) **or** an inline SVG XML document
|
|
18
|
+
* (`'<svg …>…</svg>'`).
|
|
19
|
+
* - `number` — a Metro asset id from `require('./foo.png')`.
|
|
20
|
+
* - `{ uri, … }` — the standard RN `ImageURISource` object (works for both
|
|
21
|
+
* raster and `.svg` URIs).
|
|
22
|
+
* - `React.ComponentType` — an SVG React component (e.g. produced by
|
|
23
|
+
* `react-native-svg-transformer`, by `@svgr/*`,
|
|
24
|
+
* or hand-written). It is rendered with
|
|
25
|
+
* `{ width, height, color, fill }` so it can be
|
|
26
|
+
* tinted just like a built-in icon.
|
|
27
|
+
* - `React.ReactElement` — an already-rendered node, passed through verbatim.
|
|
28
|
+
*
|
|
29
|
+
* The helper sniffs the input shape (no extension hint required from the
|
|
30
|
+
* caller) and picks the correct renderer for the platform.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const SVG_XML_RE = /<svg[\s>]/i;
|
|
34
|
+
const SVG_URI_RE = /\.svg(\?|#|$)/i;
|
|
35
|
+
function isSvgXml(s) {
|
|
36
|
+
return /^\s*</.test(s) && SVG_XML_RE.test(s);
|
|
37
|
+
}
|
|
38
|
+
function isSvgUri(s) {
|
|
39
|
+
return SVG_URI_RE.test(s);
|
|
40
|
+
}
|
|
41
|
+
function isUriObject(v) {
|
|
42
|
+
return typeof v === 'object' && v !== null && 'uri' in v && typeof v.uri === 'string';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Smart renderer that picks the right primitive for the source shape. See
|
|
47
|
+
* {@link UnifiedSource}.
|
|
48
|
+
*
|
|
49
|
+
* Designed to be used internally by `Icon`, `IconCapsule`, and `UpiHandle`,
|
|
50
|
+
* but also exported for ad-hoc consumer use.
|
|
51
|
+
*/
|
|
52
|
+
function MediaSource(props) {
|
|
53
|
+
const {
|
|
54
|
+
source,
|
|
55
|
+
size,
|
|
56
|
+
width,
|
|
57
|
+
height,
|
|
58
|
+
tintColor,
|
|
59
|
+
style,
|
|
60
|
+
resizeMode = 'cover',
|
|
61
|
+
accessibilityElementsHidden,
|
|
62
|
+
importantForAccessibility
|
|
63
|
+
} = props;
|
|
64
|
+
const w = width ?? size;
|
|
65
|
+
const h = height ?? size;
|
|
66
|
+
|
|
67
|
+
// Pre-rendered element — pass through.
|
|
68
|
+
if (/*#__PURE__*/_react.default.isValidElement(source)) {
|
|
69
|
+
return source;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// SVG / icon component.
|
|
73
|
+
if (typeof source === 'function') {
|
|
74
|
+
const Comp = source;
|
|
75
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(Comp, {
|
|
76
|
+
...(w !== undefined ? {
|
|
77
|
+
width: w
|
|
78
|
+
} : {}),
|
|
79
|
+
...(h !== undefined ? {
|
|
80
|
+
height: h
|
|
81
|
+
} : {}),
|
|
82
|
+
...(tintColor !== undefined ? {
|
|
83
|
+
color: tintColor,
|
|
84
|
+
fill: tintColor
|
|
85
|
+
} : {})
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
const sizeStyle = w !== undefined || h !== undefined ? {
|
|
89
|
+
width: w,
|
|
90
|
+
height: h
|
|
91
|
+
} : null;
|
|
92
|
+
const tintStyle = tintColor ? {
|
|
93
|
+
tintColor
|
|
94
|
+
} : null;
|
|
95
|
+
const composedStyle = [sizeStyle, tintStyle, style];
|
|
96
|
+
if (typeof source === 'string') {
|
|
97
|
+
if (isSvgXml(source)) {
|
|
98
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.SvgXml, {
|
|
99
|
+
xml: source,
|
|
100
|
+
...(w !== undefined ? {
|
|
101
|
+
width: w
|
|
102
|
+
} : {}),
|
|
103
|
+
...(h !== undefined ? {
|
|
104
|
+
height: h
|
|
105
|
+
} : {}),
|
|
106
|
+
...(tintColor !== undefined ? {
|
|
107
|
+
color: tintColor
|
|
108
|
+
} : {})
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (isSvgUri(source)) {
|
|
112
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.SvgUri, {
|
|
113
|
+
uri: source,
|
|
114
|
+
...(w !== undefined ? {
|
|
115
|
+
width: w
|
|
116
|
+
} : {}),
|
|
117
|
+
...(h !== undefined ? {
|
|
118
|
+
height: h
|
|
119
|
+
} : {}),
|
|
120
|
+
...(tintColor !== undefined ? {
|
|
121
|
+
color: tintColor
|
|
122
|
+
} : {})
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
|
|
126
|
+
source: {
|
|
127
|
+
uri: source
|
|
128
|
+
},
|
|
129
|
+
style: composedStyle,
|
|
130
|
+
resizeMode: resizeMode,
|
|
131
|
+
...(accessibilityElementsHidden !== undefined ? {
|
|
132
|
+
accessibilityElementsHidden
|
|
133
|
+
} : {}),
|
|
134
|
+
...(importantForAccessibility !== undefined ? {
|
|
135
|
+
importantForAccessibility
|
|
136
|
+
} : {})
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
if (isUriObject(source)) {
|
|
140
|
+
if (isSvgUri(source.uri)) {
|
|
141
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.SvgUri, {
|
|
142
|
+
uri: source.uri,
|
|
143
|
+
...(w !== undefined ? {
|
|
144
|
+
width: w
|
|
145
|
+
} : {}),
|
|
146
|
+
...(h !== undefined ? {
|
|
147
|
+
height: h
|
|
148
|
+
} : {}),
|
|
149
|
+
...(tintColor !== undefined ? {
|
|
150
|
+
color: tintColor
|
|
151
|
+
} : {})
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
|
|
155
|
+
source: source,
|
|
156
|
+
style: composedStyle,
|
|
157
|
+
resizeMode: resizeMode,
|
|
158
|
+
...(accessibilityElementsHidden !== undefined ? {
|
|
159
|
+
accessibilityElementsHidden
|
|
160
|
+
} : {}),
|
|
161
|
+
...(importantForAccessibility !== undefined ? {
|
|
162
|
+
importantForAccessibility
|
|
163
|
+
} : {})
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (typeof source === 'number') {
|
|
167
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Image, {
|
|
168
|
+
source: source,
|
|
169
|
+
style: composedStyle,
|
|
170
|
+
resizeMode: resizeMode,
|
|
171
|
+
...(accessibilityElementsHidden !== undefined ? {
|
|
172
|
+
accessibilityElementsHidden
|
|
173
|
+
} : {}),
|
|
174
|
+
...(importantForAccessibility !== undefined ? {
|
|
175
|
+
importantForAccessibility
|
|
176
|
+
} : {})
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
var _default = exports.default = /*#__PURE__*/_react.default.memo(MediaSource);
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
Object.defineProperty(exports, "MediaSource", {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: function () {
|
|
9
|
+
return _MediaSource.default;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
6
12
|
Object.defineProperty(exports, "cloneChildrenWithModes", {
|
|
7
13
|
enumerable: true,
|
|
8
14
|
get: function () {
|
|
@@ -15,4 +21,6 @@ Object.defineProperty(exports, "flattenChildren", {
|
|
|
15
21
|
return _reactUtils.flattenChildren;
|
|
16
22
|
}
|
|
17
23
|
});
|
|
18
|
-
var _reactUtils = require("./react-utils");
|
|
24
|
+
var _reactUtils = require("./react-utils");
|
|
25
|
+
var _MediaSource = _interopRequireDefault(require("./MediaSource"));
|
|
26
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
@@ -43,7 +43,8 @@ export function Carousel({
|
|
|
43
43
|
const gap = gapProp ?? tokenGap;
|
|
44
44
|
const containerPaddingH = parseFloat(getVariableByName('carousel/padding/horizontal', modes) || '0');
|
|
45
45
|
const containerPaddingV = parseFloat(getVariableByName('carousel/padding/vertical', modes) || '0');
|
|
46
|
-
|
|
46
|
+
// Spacing between the cards row and the pagination dots uses `carousel/gap`.
|
|
47
|
+
const paginationOffset = gap;
|
|
47
48
|
|
|
48
49
|
// ---- Refs & state ----
|
|
49
50
|
const scrollRef = useRef(null);
|
|
@@ -200,7 +201,7 @@ export function Carousel({
|
|
|
200
201
|
}), showPagination && totalItems > 1 && /*#__PURE__*/_jsx(Pagination, {
|
|
201
202
|
modes: modes,
|
|
202
203
|
style: {
|
|
203
|
-
marginTop:
|
|
204
|
+
marginTop: paginationOffset
|
|
204
205
|
}
|
|
205
206
|
})]
|
|
206
207
|
})
|
|
@@ -242,13 +243,15 @@ export function Pagination({
|
|
|
242
243
|
} = useContext(CarouselContext);
|
|
243
244
|
const modes = propModes || ctxModes || {};
|
|
244
245
|
|
|
245
|
-
// Token resolution for dots
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
246
|
+
// Token resolution for dots — matches Figma tokens
|
|
247
|
+
// (carousel/pagination/gap, carousel/pagination/indicator/{activecolor,inactivecolor,radius}).
|
|
248
|
+
// Dot dimensions are fixed per Figma spec: inactive 6x6, active 16x6.
|
|
249
|
+
const dotSize = 6;
|
|
250
|
+
const dotActiveWidth = 16;
|
|
251
|
+
const dotGap = parseFloat(getVariableByName('carousel/pagination/gap', modes) || '4');
|
|
252
|
+
const dotColor = getVariableByName('carousel/pagination/indicator/inactivecolor', modes) || 'rgba(0,0,0,0.3)';
|
|
253
|
+
const dotActiveColor = getVariableByName('carousel/pagination/indicator/activecolor', modes) || '#170d0a';
|
|
254
|
+
const dotRadius = parseFloat(getVariableByName('carousel/pagination/indicator/radius', modes) || '9999');
|
|
252
255
|
const containerStyle = {
|
|
253
256
|
flexDirection: 'row',
|
|
254
257
|
justifyContent: 'center',
|
|
@@ -59,7 +59,8 @@ function Drawer({
|
|
|
59
59
|
accessibilityHint,
|
|
60
60
|
contentContainerStyle,
|
|
61
61
|
showsVerticalScrollIndicator = false,
|
|
62
|
-
bottomInset = 80
|
|
62
|
+
bottomInset = 80,
|
|
63
|
+
onStateChange
|
|
63
64
|
}) {
|
|
64
65
|
const {
|
|
65
66
|
height: screenHeight
|
|
@@ -124,9 +125,24 @@ function Drawer({
|
|
|
124
125
|
|
|
125
126
|
// Update JS state for accessibility/logic if needed
|
|
126
127
|
const updateMode = useCallback(newMode => {
|
|
127
|
-
setMode(
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
setMode(prev => {
|
|
129
|
+
if (prev !== newMode) {
|
|
130
|
+
onStateChange?.(newMode);
|
|
131
|
+
}
|
|
132
|
+
return newMode;
|
|
133
|
+
});
|
|
134
|
+
}, [onStateChange]);
|
|
135
|
+
|
|
136
|
+
// Gesture policy:
|
|
137
|
+
// • activeOffsetY: require a clear *vertical* drag (10px) before this
|
|
138
|
+
// pan claims the gesture. Matches typical iOS scroll activation feel.
|
|
139
|
+
// • failOffsetX: if the finger crosses ~16px horizontally *before* we
|
|
140
|
+
// activate, surrender the gesture entirely so any horizontal child
|
|
141
|
+
// (FlatList horizontal, swiper, slider, etc.) can scroll cleanly
|
|
142
|
+
// without the drawer also translating on Y.
|
|
143
|
+
// • simultaneousWithExternalGesture(scrollRef): cooperate with the
|
|
144
|
+
// drawer's own internal vertical ScrollView for nested scrolling.
|
|
145
|
+
const gesture = Gesture.Pan().simultaneousWithExternalGesture(scrollRef).activeOffsetY([-10, 10]).failOffsetX([-16, 16]).onStart(() => {
|
|
130
146
|
context.value = {
|
|
131
147
|
y: translateY.value
|
|
132
148
|
};
|
|
@@ -135,6 +151,16 @@ function Drawer({
|
|
|
135
151
|
prevAtTop.value = scrollY.value <= 1;
|
|
136
152
|
scrollTopTranslationOffset.value = 0;
|
|
137
153
|
}).onUpdate(event => {
|
|
154
|
+
// Defense-in-depth: even after vertical activation, if the *current*
|
|
155
|
+
// motion is dominantly horizontal (e.g., the user activated with a
|
|
156
|
+
// small Y nudge and then curved into a horizontal swipe on a child
|
|
157
|
+
// carousel), don't translate the drawer this frame. failOffsetX
|
|
158
|
+
// already prevents activation in pure-horizontal swipes; this guards
|
|
159
|
+
// the diagonal-then-horizontal case.
|
|
160
|
+
if (Math.abs(event.translationX) > Math.abs(event.translationY) * 1.5) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
138
164
|
// Logic for nested scrolling:
|
|
139
165
|
// If we are at the expanded position (minTranslateY) AND content is
|
|
140
166
|
// scrolled down (scrollY > 0), let the ScrollView handle the gesture.
|
|
@@ -246,71 +272,108 @@ function Drawer({
|
|
|
246
272
|
const titleWeight = getVariableByName('drawer/title/fontWeight', modes) || '700';
|
|
247
273
|
const titleLineHeight = getVariableByName('drawer/title/lineHeight', modes) || 17;
|
|
248
274
|
const titlePaddingBottom = getVariableByName('drawer/titleWrap/padding/bottom', modes) || 8;
|
|
275
|
+
|
|
276
|
+
// Drop shadow — Figma layers two shadows (primary + secondary) sharing
|
|
277
|
+
// the same offsetY/blur but with their own offsetX and color.
|
|
278
|
+
const shadowPrimaryOffsetX = getVariableByName('drawer/shadow/primary/offsetX', modes) ?? 0;
|
|
279
|
+
const shadowPrimaryOffsetY = getVariableByName('drawer/shadow/primary/offsetY', modes) ?? 16;
|
|
280
|
+
const shadowPrimaryBlur = getVariableByName('drawer/shadow/primary/blur', modes) ?? 48;
|
|
281
|
+
const shadowPrimaryColor = getVariableByName('drawer/shadow/primary/color', modes) ?? 'rgba(12, 13, 16, 0.16)';
|
|
282
|
+
const shadowSecondaryOffsetX = getVariableByName('drawer/shadow/secondary/offsetX', modes) ?? 0;
|
|
283
|
+
const shadowSecondaryColor = getVariableByName('drawer/shadow/secondary/color', modes) ?? 'rgba(12, 13, 16, 0.12)';
|
|
284
|
+
|
|
285
|
+
// Cross-platform shadow style. Web supports stacking two shadows via
|
|
286
|
+
// boxShadow. iOS only supports a single native shadow per view, so we
|
|
287
|
+
// apply the more prominent (primary) one. Android uses elevation.
|
|
288
|
+
const shadowStyle = Platform.select({
|
|
289
|
+
web: {
|
|
290
|
+
boxShadow: `${shadowSecondaryOffsetX}px ${shadowPrimaryOffsetY}px ${shadowPrimaryBlur}px 0px ${shadowSecondaryColor}, ` + `${shadowPrimaryOffsetX}px ${shadowPrimaryOffsetY}px ${shadowPrimaryBlur}px 0px ${shadowPrimaryColor}`
|
|
291
|
+
},
|
|
292
|
+
ios: {
|
|
293
|
+
shadowColor: shadowPrimaryColor,
|
|
294
|
+
shadowOffset: {
|
|
295
|
+
width: shadowPrimaryOffsetX,
|
|
296
|
+
height: shadowPrimaryOffsetY
|
|
297
|
+
},
|
|
298
|
+
shadowOpacity: 1,
|
|
299
|
+
shadowRadius: shadowPrimaryBlur / 2
|
|
300
|
+
},
|
|
301
|
+
android: {
|
|
302
|
+
elevation: 16
|
|
303
|
+
},
|
|
304
|
+
default: {}
|
|
305
|
+
});
|
|
249
306
|
const defaultAccessibilityLabel = accessibilityLabel || title || 'Drawer';
|
|
250
307
|
return /*#__PURE__*/_jsx(GestureHandlerRootView, {
|
|
251
308
|
style: [styles.host, style],
|
|
252
309
|
pointerEvents: "box-none",
|
|
253
310
|
children: /*#__PURE__*/_jsx(GestureDetector, {
|
|
254
311
|
gesture: gesture,
|
|
255
|
-
children: /*#__PURE__*/
|
|
312
|
+
children: /*#__PURE__*/_jsx(Animated.View, {
|
|
256
313
|
style: [styles.sheet, {
|
|
257
314
|
// Constraint the height strictly to the expanded height
|
|
258
315
|
// This ensures the ScrollView has a finite frame to scroll within
|
|
259
316
|
height: expandedHeight,
|
|
260
317
|
backgroundColor,
|
|
261
318
|
borderTopLeftRadius: radius,
|
|
262
|
-
borderTopRightRadius: radius
|
|
263
|
-
|
|
264
|
-
paddingRight,
|
|
265
|
-
paddingBottom,
|
|
266
|
-
rowGap: drawerGap
|
|
267
|
-
}, sheetStyle, animatedStyle],
|
|
319
|
+
borderTopRightRadius: radius
|
|
320
|
+
}, shadowStyle, sheetStyle, animatedStyle],
|
|
268
321
|
accessible: true,
|
|
269
322
|
...(Platform.OS === 'web' ? {
|
|
270
323
|
accessibilityRole: 'dialog'
|
|
271
324
|
} : undefined),
|
|
272
325
|
accessibilityLabel: undefined,
|
|
273
326
|
accessibilityHint: accessibilityHint || 'Swipe up to expand, swipe down to collapse',
|
|
274
|
-
children:
|
|
275
|
-
style: [styles.
|
|
276
|
-
|
|
327
|
+
children: /*#__PURE__*/_jsxs(View, {
|
|
328
|
+
style: [styles.sheetInner, {
|
|
329
|
+
borderTopLeftRadius: radius,
|
|
330
|
+
borderTopRightRadius: radius,
|
|
331
|
+
paddingLeft,
|
|
332
|
+
paddingRight,
|
|
333
|
+
paddingBottom,
|
|
334
|
+
rowGap: drawerGap
|
|
277
335
|
}],
|
|
278
|
-
children: /*#__PURE__*/_jsx(View, {
|
|
336
|
+
children: [/*#__PURE__*/_jsx(View, {
|
|
337
|
+
style: [styles.handleArea, !title && !header && {
|
|
338
|
+
paddingBottom: 0
|
|
339
|
+
}],
|
|
340
|
+
children: /*#__PURE__*/_jsx(View, {
|
|
341
|
+
style: [{
|
|
342
|
+
backgroundColor: handleColor,
|
|
343
|
+
width: handleWidth,
|
|
344
|
+
height: handleHeight,
|
|
345
|
+
borderRadius: handleRadius
|
|
346
|
+
}]
|
|
347
|
+
})
|
|
348
|
+
}), header, title && /*#__PURE__*/_jsx(Text, {
|
|
279
349
|
style: [{
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
overScrollMode: "always",
|
|
308
|
-
onScroll: useAnimatedScrollHandler(event => {
|
|
309
|
-
scrollY.value = event.contentOffset.y;
|
|
310
|
-
}),
|
|
311
|
-
scrollEventThrottle: 16,
|
|
312
|
-
children: children
|
|
313
|
-
})]
|
|
350
|
+
color: titleColor,
|
|
351
|
+
fontSize: titleSize,
|
|
352
|
+
fontWeight: titleWeight,
|
|
353
|
+
lineHeight: titleLineHeight,
|
|
354
|
+
marginBottom: titlePaddingBottom
|
|
355
|
+
}],
|
|
356
|
+
children: title
|
|
357
|
+
}), /*#__PURE__*/_jsx(AnimatedScrollView, {
|
|
358
|
+
ref: scrollRef,
|
|
359
|
+
style: [styles.content, contentStyle],
|
|
360
|
+
contentContainerStyle: [{
|
|
361
|
+
paddingBottom: paddingBottom + bottomInset,
|
|
362
|
+
gap: drawerGap,
|
|
363
|
+
flexDirection: 'column',
|
|
364
|
+
alignItems: 'stretch'
|
|
365
|
+
}, contentContainerStyle],
|
|
366
|
+
showsVerticalScrollIndicator: showsVerticalScrollIndicator,
|
|
367
|
+
animatedProps: animatedScrollProps,
|
|
368
|
+
alwaysBounceVertical: false,
|
|
369
|
+
overScrollMode: "always",
|
|
370
|
+
onScroll: useAnimatedScrollHandler(event => {
|
|
371
|
+
scrollY.value = event.contentOffset.y;
|
|
372
|
+
}),
|
|
373
|
+
scrollEventThrottle: 16,
|
|
374
|
+
children: children
|
|
375
|
+
})]
|
|
376
|
+
})
|
|
314
377
|
})
|
|
315
378
|
})
|
|
316
379
|
});
|
|
@@ -328,7 +391,10 @@ const styles = StyleSheet.create({
|
|
|
328
391
|
sheet: {
|
|
329
392
|
width: '100%',
|
|
330
393
|
position: 'absolute',
|
|
331
|
-
top: 0
|
|
394
|
+
top: 0
|
|
395
|
+
},
|
|
396
|
+
sheetInner: {
|
|
397
|
+
flex: 1,
|
|
332
398
|
overflow: 'hidden'
|
|
333
399
|
},
|
|
334
400
|
handleArea: {
|
|
@@ -72,8 +72,13 @@ function resolveIconButtonTokens(modes, disabled) {
|
|
|
72
72
|
* pressed transform mirrored via React state) — removed.
|
|
73
73
|
* - Wrapped in `React.memo`.
|
|
74
74
|
*/
|
|
75
|
+
// Legacy default icon used when neither a `name` nor a `source` is supplied
|
|
76
|
+
// for the resolved slot. Kept as a constant rather than a destructuring
|
|
77
|
+
// default so source-only call sites don't accidentally render `'ic_card'`.
|
|
78
|
+
const LEGACY_DEFAULT_ICON_NAME = 'ic_card';
|
|
75
79
|
function IconButton({
|
|
76
|
-
iconName
|
|
80
|
+
iconName,
|
|
81
|
+
source,
|
|
77
82
|
modes = EMPTY_MODES,
|
|
78
83
|
onPress,
|
|
79
84
|
disabled = false,
|
|
@@ -84,7 +89,9 @@ function IconButton({
|
|
|
84
89
|
webAccessibilityProps,
|
|
85
90
|
isToggle = false,
|
|
86
91
|
activeIcon,
|
|
92
|
+
activeSource,
|
|
87
93
|
inactiveIcon,
|
|
94
|
+
inactiveSource,
|
|
88
95
|
isActive = false,
|
|
89
96
|
...rest
|
|
90
97
|
}) {
|
|
@@ -107,11 +114,35 @@ function IconButton({
|
|
|
107
114
|
userHandlersRef.current.onHoverIn = rest?.onHoverIn;
|
|
108
115
|
userHandlersRef.current.onHoverOut = rest?.onHoverOut;
|
|
109
116
|
|
|
110
|
-
//
|
|
111
|
-
|
|
117
|
+
// Resolve the active (name + source) pair for the current slot. Toggle
|
|
118
|
+
// mode picks active/inactive based on `isActive`; per-state overrides
|
|
119
|
+
// fall back to the default `iconName` / `source` when omitted. We then
|
|
120
|
+
// apply the legacy default icon only as a last resort, so a source-only
|
|
121
|
+
// call site (`<IconButton source="…" />`) renders the source instead of
|
|
122
|
+
// bleeding through to `'ic_card'`.
|
|
123
|
+
let resolvedIconName;
|
|
124
|
+
let resolvedSource;
|
|
125
|
+
if (isToggle) {
|
|
126
|
+
if (isActive) {
|
|
127
|
+
resolvedIconName = activeIcon ?? iconName;
|
|
128
|
+
resolvedSource = activeSource ?? source;
|
|
129
|
+
} else {
|
|
130
|
+
resolvedIconName = inactiveIcon ?? iconName;
|
|
131
|
+
resolvedSource = inactiveSource ?? source;
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
resolvedIconName = iconName;
|
|
135
|
+
resolvedSource = source;
|
|
136
|
+
}
|
|
137
|
+
if (!resolvedIconName && resolvedSource === undefined) {
|
|
138
|
+
resolvedIconName = LEGACY_DEFAULT_ICON_NAME;
|
|
139
|
+
}
|
|
112
140
|
|
|
113
|
-
// Generate default accessibility label from icon name
|
|
114
|
-
|
|
141
|
+
// Generate default accessibility label from the resolved icon name when
|
|
142
|
+
// possible. Source-only call sites should provide an explicit
|
|
143
|
+
// `accessibilityLabel`; we fall back to a generic 'Icon button' so we
|
|
144
|
+
// never crash on `iconName.replace(...)` when only a `source` is supplied.
|
|
145
|
+
const defaultAccessibilityLabel = accessibilityLabel || (resolvedIconName ? resolvedIconName.replace(/^ic_/, '').replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) : 'Icon button');
|
|
115
146
|
const webProps = usePressableWebSupport({
|
|
116
147
|
restProps: rest,
|
|
117
148
|
onPress: disabled ? undefined : onPress,
|
|
@@ -164,7 +195,12 @@ function IconButton({
|
|
|
164
195
|
style: styleCallback,
|
|
165
196
|
...webProps,
|
|
166
197
|
children: /*#__PURE__*/_jsx(Icon, {
|
|
167
|
-
|
|
198
|
+
...(resolvedIconName !== undefined ? {
|
|
199
|
+
name: resolvedIconName
|
|
200
|
+
} : {}),
|
|
201
|
+
...(resolvedSource !== undefined ? {
|
|
202
|
+
source: resolvedSource
|
|
203
|
+
} : {}),
|
|
168
204
|
size: tokens.iconSize,
|
|
169
205
|
color: tokens.iconColor,
|
|
170
206
|
accessibilityElementsHidden: true,
|
|
@@ -43,6 +43,7 @@ function resolveIconCapsuleTokens(modes) {
|
|
|
43
43
|
* @component
|
|
44
44
|
* @param {Object} props - Component props
|
|
45
45
|
* @param {string} [props.iconName="ic_card"] - The name of the icon to display from the icon registry
|
|
46
|
+
* @param {UnifiedSource} [props.source] - Fallback source (remote URI, inline SVG XML, `require()` asset, SVG React component, or React element). Used when `iconName` is missing or unknown. Tinted with the mode-resolved icon color so it follows design tokens just like a built-in icon.
|
|
46
47
|
* @param {Object} [props.modes={}] - Mode configuration for design tokens (e.g., {"Appearance": "Primary"})
|
|
47
48
|
* @param {string} [props.accessibilityLabel] - Accessibility label for screen readers
|
|
48
49
|
* @param {string} [props.accessibilityRole] - Accessibility role (defaults to "image" for decorative icons)
|
|
@@ -56,6 +57,7 @@ function resolveIconCapsuleTokens(modes) {
|
|
|
56
57
|
*/
|
|
57
58
|
function IconCapsule({
|
|
58
59
|
iconName = 'ic_card',
|
|
60
|
+
source,
|
|
59
61
|
modes: propModes = EMPTY_MODES,
|
|
60
62
|
// accessibilityLabel is accepted on the type for API back-compat but the
|
|
61
63
|
// component intentionally renders `accessibilityLabel={undefined}` (icons
|
|
@@ -85,6 +87,9 @@ function IconCapsule({
|
|
|
85
87
|
...rest,
|
|
86
88
|
children: /*#__PURE__*/_jsx(Icon, {
|
|
87
89
|
name: iconName,
|
|
90
|
+
...(source !== undefined ? {
|
|
91
|
+
source
|
|
92
|
+
} : {}),
|
|
88
93
|
size: tokens.iconSize,
|
|
89
94
|
color: tokens.iconColor,
|
|
90
95
|
accessibilityElementsHidden: true,
|
|
@@ -106,12 +106,12 @@ const Popup = /*#__PURE__*/forwardRef(function Popup({
|
|
|
106
106
|
children: /*#__PURE__*/_jsxs(View, {
|
|
107
107
|
style: styles.overlay,
|
|
108
108
|
children: [/*#__PURE__*/_jsx(Animated.View, {
|
|
109
|
-
style: [StyleSheet.
|
|
109
|
+
style: [StyleSheet.absoluteFill, {
|
|
110
110
|
backgroundColor: backdropColor,
|
|
111
111
|
opacity: backdropAnim
|
|
112
112
|
}],
|
|
113
113
|
children: /*#__PURE__*/_jsx(Pressable, {
|
|
114
|
-
style: StyleSheet.
|
|
114
|
+
style: StyleSheet.absoluteFill,
|
|
115
115
|
onPress: closeOnBackdropPress ? handleClose : undefined,
|
|
116
116
|
accessibilityRole: "button",
|
|
117
117
|
accessibilityLabel: "Close popup"
|