jfs-components 0.0.64 → 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 +8 -0
- package/lib/commonjs/components/Carousel/Carousel.js +12 -9
- package/lib/commonjs/components/Drawer/Drawer.js +9 -3
- 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/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 +9 -3
- 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/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/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 +13 -2
- 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/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
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
6
6
|
|
|
7
|
+
## [0.0.65] - 2026-04-21
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`Drawer` state callback:** Optional `onStateChange?: (state: 'collapsed' | 'expanded') => void` runs when the drawer settles into a new snap state (after gestures), so parents can react programmatically.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
7
15
|
## [0.0.64] - 2026-04-20
|
|
8
16
|
|
|
9
17
|
### Added
|
|
@@ -51,7 +51,8 @@ function Carousel({
|
|
|
51
51
|
const gap = gapProp ?? tokenGap;
|
|
52
52
|
const containerPaddingH = parseFloat((0, _figmaVariablesResolver.getVariableByName)('carousel/padding/horizontal', modes) || '0');
|
|
53
53
|
const containerPaddingV = parseFloat((0, _figmaVariablesResolver.getVariableByName)('carousel/padding/vertical', modes) || '0');
|
|
54
|
-
|
|
54
|
+
// Spacing between the cards row and the pagination dots uses `carousel/gap`.
|
|
55
|
+
const paginationOffset = gap;
|
|
55
56
|
|
|
56
57
|
// ---- Refs & state ----
|
|
57
58
|
const scrollRef = (0, _react.useRef)(null);
|
|
@@ -208,7 +209,7 @@ function Carousel({
|
|
|
208
209
|
}), showPagination && totalItems > 1 && /*#__PURE__*/(0, _jsxRuntime.jsx)(Pagination, {
|
|
209
210
|
modes: modes,
|
|
210
211
|
style: {
|
|
211
|
-
marginTop:
|
|
212
|
+
marginTop: paginationOffset
|
|
212
213
|
}
|
|
213
214
|
})]
|
|
214
215
|
})
|
|
@@ -250,13 +251,15 @@ function Pagination({
|
|
|
250
251
|
} = (0, _react.useContext)(CarouselContext);
|
|
251
252
|
const modes = propModes || ctxModes || {};
|
|
252
253
|
|
|
253
|
-
// Token resolution for dots
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
const
|
|
258
|
-
const
|
|
259
|
-
const
|
|
254
|
+
// Token resolution for dots — matches Figma tokens
|
|
255
|
+
// (carousel/pagination/gap, carousel/pagination/indicator/{activecolor,inactivecolor,radius}).
|
|
256
|
+
// Dot dimensions are fixed per Figma spec: inactive 6x6, active 16x6.
|
|
257
|
+
const dotSize = 6;
|
|
258
|
+
const dotActiveWidth = 16;
|
|
259
|
+
const dotGap = parseFloat((0, _figmaVariablesResolver.getVariableByName)('carousel/pagination/gap', modes) || '4');
|
|
260
|
+
const dotColor = (0, _figmaVariablesResolver.getVariableByName)('carousel/pagination/indicator/inactivecolor', modes) || 'rgba(0,0,0,0.3)';
|
|
261
|
+
const dotActiveColor = (0, _figmaVariablesResolver.getVariableByName)('carousel/pagination/indicator/activecolor', modes) || '#170d0a';
|
|
262
|
+
const dotRadius = parseFloat((0, _figmaVariablesResolver.getVariableByName)('carousel/pagination/indicator/radius', modes) || '9999');
|
|
260
263
|
const containerStyle = {
|
|
261
264
|
flexDirection: 'row',
|
|
262
265
|
justifyContent: 'center',
|
|
@@ -64,7 +64,8 @@ function Drawer({
|
|
|
64
64
|
accessibilityHint,
|
|
65
65
|
contentContainerStyle,
|
|
66
66
|
showsVerticalScrollIndicator = false,
|
|
67
|
-
bottomInset = 80
|
|
67
|
+
bottomInset = 80,
|
|
68
|
+
onStateChange
|
|
68
69
|
}) {
|
|
69
70
|
const {
|
|
70
71
|
height: screenHeight
|
|
@@ -129,8 +130,13 @@ function Drawer({
|
|
|
129
130
|
|
|
130
131
|
// Update JS state for accessibility/logic if needed
|
|
131
132
|
const updateMode = (0, _react.useCallback)(newMode => {
|
|
132
|
-
setMode(
|
|
133
|
-
|
|
133
|
+
setMode(prev => {
|
|
134
|
+
if (prev !== newMode) {
|
|
135
|
+
onStateChange?.(newMode);
|
|
136
|
+
}
|
|
137
|
+
return newMode;
|
|
138
|
+
});
|
|
139
|
+
}, [onStateChange]);
|
|
134
140
|
|
|
135
141
|
// Gesture policy:
|
|
136
142
|
// • activeOffsetY: require a clear *vertical* drag (10px) before this
|
|
@@ -78,8 +78,13 @@ function resolveIconButtonTokens(modes, disabled) {
|
|
|
78
78
|
* pressed transform mirrored via React state) — removed.
|
|
79
79
|
* - Wrapped in `React.memo`.
|
|
80
80
|
*/
|
|
81
|
+
// Legacy default icon used when neither a `name` nor a `source` is supplied
|
|
82
|
+
// for the resolved slot. Kept as a constant rather than a destructuring
|
|
83
|
+
// default so source-only call sites don't accidentally render `'ic_card'`.
|
|
84
|
+
const LEGACY_DEFAULT_ICON_NAME = 'ic_card';
|
|
81
85
|
function IconButton({
|
|
82
|
-
iconName
|
|
86
|
+
iconName,
|
|
87
|
+
source,
|
|
83
88
|
modes = _reactUtils.EMPTY_MODES,
|
|
84
89
|
onPress,
|
|
85
90
|
disabled = false,
|
|
@@ -90,7 +95,9 @@ function IconButton({
|
|
|
90
95
|
webAccessibilityProps,
|
|
91
96
|
isToggle = false,
|
|
92
97
|
activeIcon,
|
|
98
|
+
activeSource,
|
|
93
99
|
inactiveIcon,
|
|
100
|
+
inactiveSource,
|
|
94
101
|
isActive = false,
|
|
95
102
|
...rest
|
|
96
103
|
}) {
|
|
@@ -113,11 +120,35 @@ function IconButton({
|
|
|
113
120
|
userHandlersRef.current.onHoverIn = rest?.onHoverIn;
|
|
114
121
|
userHandlersRef.current.onHoverOut = rest?.onHoverOut;
|
|
115
122
|
|
|
116
|
-
//
|
|
117
|
-
|
|
123
|
+
// Resolve the active (name + source) pair for the current slot. Toggle
|
|
124
|
+
// mode picks active/inactive based on `isActive`; per-state overrides
|
|
125
|
+
// fall back to the default `iconName` / `source` when omitted. We then
|
|
126
|
+
// apply the legacy default icon only as a last resort, so a source-only
|
|
127
|
+
// call site (`<IconButton source="…" />`) renders the source instead of
|
|
128
|
+
// bleeding through to `'ic_card'`.
|
|
129
|
+
let resolvedIconName;
|
|
130
|
+
let resolvedSource;
|
|
131
|
+
if (isToggle) {
|
|
132
|
+
if (isActive) {
|
|
133
|
+
resolvedIconName = activeIcon ?? iconName;
|
|
134
|
+
resolvedSource = activeSource ?? source;
|
|
135
|
+
} else {
|
|
136
|
+
resolvedIconName = inactiveIcon ?? iconName;
|
|
137
|
+
resolvedSource = inactiveSource ?? source;
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
resolvedIconName = iconName;
|
|
141
|
+
resolvedSource = source;
|
|
142
|
+
}
|
|
143
|
+
if (!resolvedIconName && resolvedSource === undefined) {
|
|
144
|
+
resolvedIconName = LEGACY_DEFAULT_ICON_NAME;
|
|
145
|
+
}
|
|
118
146
|
|
|
119
|
-
// Generate default accessibility label from icon name
|
|
120
|
-
|
|
147
|
+
// Generate default accessibility label from the resolved icon name when
|
|
148
|
+
// possible. Source-only call sites should provide an explicit
|
|
149
|
+
// `accessibilityLabel`; we fall back to a generic 'Icon button' so we
|
|
150
|
+
// never crash on `iconName.replace(...)` when only a `source` is supplied.
|
|
151
|
+
const defaultAccessibilityLabel = accessibilityLabel || (resolvedIconName ? resolvedIconName.replace(/^ic_/, '').replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) : 'Icon button');
|
|
121
152
|
const webProps = (0, _webPlatformUtils.usePressableWebSupport)({
|
|
122
153
|
restProps: rest,
|
|
123
154
|
onPress: disabled ? undefined : onPress,
|
|
@@ -170,7 +201,12 @@ function IconButton({
|
|
|
170
201
|
style: styleCallback,
|
|
171
202
|
...webProps,
|
|
172
203
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
|
|
173
|
-
|
|
204
|
+
...(resolvedIconName !== undefined ? {
|
|
205
|
+
name: resolvedIconName
|
|
206
|
+
} : {}),
|
|
207
|
+
...(resolvedSource !== undefined ? {
|
|
208
|
+
source: resolvedSource
|
|
209
|
+
} : {}),
|
|
174
210
|
size: tokens.iconSize,
|
|
175
211
|
color: tokens.iconColor,
|
|
176
212
|
accessibilityElementsHidden: true,
|
|
@@ -49,6 +49,7 @@ function resolveIconCapsuleTokens(modes) {
|
|
|
49
49
|
* @component
|
|
50
50
|
* @param {Object} props - Component props
|
|
51
51
|
* @param {string} [props.iconName="ic_card"] - The name of the icon to display from the icon registry
|
|
52
|
+
* @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.
|
|
52
53
|
* @param {Object} [props.modes={}] - Mode configuration for design tokens (e.g., {"Appearance": "Primary"})
|
|
53
54
|
* @param {string} [props.accessibilityLabel] - Accessibility label for screen readers
|
|
54
55
|
* @param {string} [props.accessibilityRole] - Accessibility role (defaults to "image" for decorative icons)
|
|
@@ -62,6 +63,7 @@ function resolveIconCapsuleTokens(modes) {
|
|
|
62
63
|
*/
|
|
63
64
|
function IconCapsule({
|
|
64
65
|
iconName = 'ic_card',
|
|
66
|
+
source,
|
|
65
67
|
modes: propModes = _reactUtils.EMPTY_MODES,
|
|
66
68
|
// accessibilityLabel is accepted on the type for API back-compat but the
|
|
67
69
|
// component intentionally renders `accessibilityLabel={undefined}` (icons
|
|
@@ -91,6 +93,9 @@ function IconCapsule({
|
|
|
91
93
|
...rest,
|
|
92
94
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_Icon.default, {
|
|
93
95
|
name: iconName,
|
|
96
|
+
...(source !== undefined ? {
|
|
97
|
+
source
|
|
98
|
+
} : {}),
|
|
94
99
|
size: tokens.iconSize,
|
|
95
100
|
color: tokens.iconColor,
|
|
96
101
|
accessibilityElementsHidden: true,
|
|
@@ -112,12 +112,12 @@ const Popup = /*#__PURE__*/(0, _react.forwardRef)(function Popup({
|
|
|
112
112
|
children: /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
113
113
|
style: styles.overlay,
|
|
114
114
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Animated.View, {
|
|
115
|
-
style: [_reactNative.StyleSheet.
|
|
115
|
+
style: [_reactNative.StyleSheet.absoluteFill, {
|
|
116
116
|
backgroundColor: backdropColor,
|
|
117
117
|
opacity: backdropAnim
|
|
118
118
|
}],
|
|
119
119
|
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Pressable, {
|
|
120
|
-
style: _reactNative.StyleSheet.
|
|
120
|
+
style: _reactNative.StyleSheet.absoluteFill,
|
|
121
121
|
onPress: closeOnBackdropPress ? handleClose : undefined,
|
|
122
122
|
accessibilityRole: "button",
|
|
123
123
|
accessibilityLabel: "Close popup"
|
|
@@ -9,12 +9,13 @@ var _reactNative = require("react-native");
|
|
|
9
9
|
var _figmaVariablesResolver = require("../../design-tokens/figma-variables-resolver");
|
|
10
10
|
var _JFSThemeProvider = require("../../design-tokens/JFSThemeProvider");
|
|
11
11
|
var _reactUtils = require("../../utils/react-utils");
|
|
12
|
+
var _MediaSource = _interopRequireDefault(require("../../utils/MediaSource"));
|
|
12
13
|
var _Icon = _interopRequireDefault(require("../../icons/Icon"));
|
|
13
14
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
14
15
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
16
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
16
17
|
// Default static asset from the component folder.
|
|
17
|
-
// Consumers can override the image via the `
|
|
18
|
+
// Consumers can override the image via the `source` prop if needed.
|
|
18
19
|
const DEFAULT_AVATAR_IMAGE = require('./Image.png');
|
|
19
20
|
const IS_WEB = _reactNative.Platform.OS === 'web';
|
|
20
21
|
const IS_IOS = _reactNative.Platform.OS === 'ios';
|
|
@@ -88,7 +89,8 @@ function resolveUpiHandleTokens(modes) {
|
|
|
88
89
|
* @param {Object} [props.modes={}] - Modes object passed directly to `getVariableByName`.
|
|
89
90
|
* @param {boolean} [props.showIcon=true] - Toggles the trailing icon visibility.
|
|
90
91
|
* @param {string} [props.iconName='ic_scan_qr_code'] - Icon name from the actions set.
|
|
91
|
-
* @param {
|
|
92
|
+
* @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.
|
|
93
|
+
* @param {ImageSourcePropType|UnifiedSource} [props.avatarSource] - Deprecated alias for `source`; kept for back-compat.
|
|
92
94
|
* @param {Function} [props.onClick] - Click/tap handler. Works as an alias for `onPress`.
|
|
93
95
|
* @param {string} [props.accessibilityLabel] - Accessibility label for screen readers
|
|
94
96
|
* @param {string} [props.accessibilityHint] - Additional accessibility hint for screen readers
|
|
@@ -106,6 +108,7 @@ function UpiHandle({
|
|
|
106
108
|
modes: propModes = _reactUtils.EMPTY_MODES,
|
|
107
109
|
showIcon = true,
|
|
108
110
|
iconName = 'ic_scan_qr_code',
|
|
111
|
+
source,
|
|
109
112
|
avatarSource,
|
|
110
113
|
onPress,
|
|
111
114
|
onClick,
|
|
@@ -154,13 +157,22 @@ function UpiHandle({
|
|
|
154
157
|
pressed
|
|
155
158
|
}) => [tokens.containerStyle, pressed ? pressedOverlayStyle : null, isFocused ? focusOverlayStyle : null], [tokens.containerStyle, isFocused]);
|
|
156
159
|
const staticContainerStyle = (0, _react.useMemo)(() => [tokens.containerStyle, isFocused ? focusOverlayStyle : null], [tokens.containerStyle, isFocused]);
|
|
160
|
+
|
|
161
|
+
// `source` wins; `avatarSource` is the legacy fallback. Both are accepted
|
|
162
|
+
// as a UnifiedSource (string / number / {uri} / component / element), and
|
|
163
|
+
// the legacy `ImageSourcePropType` shapes naturally fit that union too.
|
|
164
|
+
const resolvedAvatarSource = source ?? avatarSource ?? DEFAULT_AVATAR_IMAGE;
|
|
165
|
+
const avatarSize = tokens.avatarStyle.width ?? 23;
|
|
157
166
|
const innerContent = /*#__PURE__*/(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
|
|
158
|
-
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.
|
|
159
|
-
source: avatarSource || DEFAULT_AVATAR_IMAGE,
|
|
167
|
+
children: [/*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
160
168
|
style: tokens.avatarStyle,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
169
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_MediaSource.default, {
|
|
170
|
+
source: resolvedAvatarSource,
|
|
171
|
+
size: avatarSize,
|
|
172
|
+
resizeMode: "cover",
|
|
173
|
+
accessibilityElementsHidden: true,
|
|
174
|
+
importantForAccessibility: "no"
|
|
175
|
+
})
|
|
164
176
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, {
|
|
165
177
|
style: tokens.labelStyle,
|
|
166
178
|
numberOfLines: 1,
|
|
@@ -8,99 +8,96 @@ var _react = _interopRequireDefault(require("react"));
|
|
|
8
8
|
var _reactNative = require("react-native");
|
|
9
9
|
var _reactNativeSvg = _interopRequireWildcard(require("react-native-svg"));
|
|
10
10
|
var _registry = require("./registry");
|
|
11
|
+
var _MediaSource = _interopRequireDefault(require("../utils/MediaSource"));
|
|
11
12
|
var _jsxRuntime = require("react/jsx-runtime");
|
|
12
13
|
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
|
|
13
14
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
14
15
|
/**
|
|
15
|
-
* Generic Icon
|
|
16
|
-
*
|
|
17
|
-
* Renders an icon from the registry by name
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* @param {number} [props.size=24] - Icon size in pixels (width and height)
|
|
23
|
-
* @param {string} [props.color='#141414'] - Icon color (hex, rgb, or named color)
|
|
24
|
-
* @param {Object} [props.style] - Additional styles for the container View
|
|
25
|
-
*
|
|
16
|
+
* Generic Icon component.
|
|
17
|
+
*
|
|
18
|
+
* Renders an icon from the registry by `name`, or falls back to a
|
|
19
|
+
* smart-detected `source` (SVG / PNG / JPG / require / SVG component /
|
|
20
|
+
* remote URI). External sources are tinted with `color` so they participate
|
|
21
|
+
* in the design-token modes just like built-in icons.
|
|
22
|
+
*
|
|
26
23
|
* @example
|
|
27
|
-
* ```
|
|
24
|
+
* ```tsx
|
|
25
|
+
* // Built-in icon from the registry.
|
|
28
26
|
* <Icon name="ic_ccv" size={24} color="#141414" />
|
|
29
|
-
*
|
|
30
|
-
*
|
|
27
|
+
*
|
|
28
|
+
* // Fallback to a remote SVG (auto-detected by the .svg extension).
|
|
29
|
+
* <Icon source="https://cdn.example.com/avatar.svg" size={24} color="#5c00b5" />
|
|
30
|
+
*
|
|
31
|
+
* // Fallback to a local raster asset.
|
|
32
|
+
* <Icon source={require('./brand.png')} size={32} />
|
|
33
|
+
*
|
|
34
|
+
* // Fallback to an SVG React component (e.g. via react-native-svg-transformer).
|
|
35
|
+
* import BrandLogo from './brand.svg';
|
|
36
|
+
* <Icon source={BrandLogo} size={24} color="red" />
|
|
31
37
|
* ```
|
|
32
38
|
*/
|
|
33
39
|
function Icon({
|
|
34
40
|
name,
|
|
41
|
+
source,
|
|
35
42
|
size = 24,
|
|
36
43
|
color = '#141414',
|
|
37
44
|
style,
|
|
38
45
|
...rest
|
|
39
46
|
}) {
|
|
40
|
-
|
|
41
|
-
if (!name) {
|
|
42
|
-
console.warn('Icon: name prop is required');
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
if (!(0, _registry.hasIcon)(name)) {
|
|
46
|
-
const {
|
|
47
|
-
getIconNames
|
|
48
|
-
} = require('./registry');
|
|
49
|
-
console.warn(`Icon: "${name}" not found in registry. Available icons: ${getIconNames().join(', ')}`);
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Get icon data from registry
|
|
54
|
-
const iconData = (0, _registry.getIcon)(name);
|
|
55
|
-
if (!iconData) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Parse viewBox to get width and height for aspect ratio
|
|
60
|
-
const viewBoxParts = iconData.viewBox.split(' ');
|
|
61
|
-
// @ts-ignore
|
|
62
|
-
const viewBoxWidth = parseFloat(viewBoxParts[2]) || size;
|
|
63
|
-
// @ts-ignore
|
|
64
|
-
const viewBoxHeight = parseFloat(viewBoxParts[3]) || size;
|
|
65
|
-
|
|
66
|
-
// Calculate aspect ratio to maintain proper scaling
|
|
67
|
-
const aspectRatio = viewBoxWidth / viewBoxHeight;
|
|
68
|
-
|
|
69
|
-
// Determine actual width and height based on size and aspect ratio
|
|
70
|
-
let width = size;
|
|
71
|
-
let height = size;
|
|
72
|
-
|
|
73
|
-
// If viewBox is not square, adjust dimensions to maintain aspect ratio
|
|
74
|
-
if (Math.abs(aspectRatio - 1) > 0.01) {
|
|
75
|
-
if (aspectRatio > 1) {
|
|
76
|
-
// Wider than tall
|
|
77
|
-
height = size / aspectRatio;
|
|
78
|
-
} else {
|
|
79
|
-
// Taller than wide
|
|
80
|
-
width = size * aspectRatio;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
const containerStyle = {
|
|
47
|
+
const containerStyle = [{
|
|
84
48
|
width: size,
|
|
85
49
|
height: size,
|
|
86
50
|
alignItems: 'center',
|
|
87
|
-
justifyContent: 'center'
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
51
|
+
justifyContent: 'center'
|
|
52
|
+
}, style];
|
|
53
|
+
const iconData = name && (0, _registry.hasIcon)(name) ? (0, _registry.getIcon)(name) : null;
|
|
54
|
+
if (iconData) {
|
|
55
|
+
const viewBoxParts = iconData.viewBox.split(' ');
|
|
56
|
+
const viewBoxWidth = parseFloat(viewBoxParts[2] ?? `${size}`) || size;
|
|
57
|
+
const viewBoxHeight = parseFloat(viewBoxParts[3] ?? `${size}`) || size;
|
|
58
|
+
const aspectRatio = viewBoxWidth / viewBoxHeight;
|
|
59
|
+
let width = size;
|
|
60
|
+
let height = size;
|
|
61
|
+
if (Math.abs(aspectRatio - 1) > 0.01) {
|
|
62
|
+
if (aspectRatio > 1) {
|
|
63
|
+
height = size / aspectRatio;
|
|
64
|
+
} else {
|
|
65
|
+
width = size * aspectRatio;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
69
|
+
style: containerStyle,
|
|
70
|
+
...rest,
|
|
71
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.default, {
|
|
72
|
+
width: width,
|
|
73
|
+
height: height,
|
|
74
|
+
viewBox: iconData.viewBox,
|
|
75
|
+
preserveAspectRatio: "xMidYMid meet",
|
|
76
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNativeSvg.Path, {
|
|
77
|
+
d: iconData.path,
|
|
78
|
+
fill: color,
|
|
79
|
+
fillRule: iconData.fillRule || 'nonzero'
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
if (source !== undefined) {
|
|
85
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, {
|
|
86
|
+
style: containerStyle,
|
|
87
|
+
...rest,
|
|
88
|
+
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_MediaSource.default, {
|
|
89
|
+
source: source,
|
|
90
|
+
size: size,
|
|
91
|
+
tintColor: color,
|
|
92
|
+
resizeMode: "contain"
|
|
102
93
|
})
|
|
103
|
-
})
|
|
104
|
-
}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
if (!name) {
|
|
97
|
+
console.warn('Icon: either `name` or `source` is required');
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
console.warn(`Icon: "${name}" not found in registry and no \`source\` fallback was provided.`);
|
|
101
|
+
return null;
|
|
105
102
|
}
|
|
106
103
|
var _default = exports.default = Icon;
|