diff --git a/demo/src/screens/__tests__/__snapshots__/AvatarScreen.spec.js.snap b/demo/src/screens/__tests__/__snapshots__/AvatarScreen.spec.js.snap index 3ca334b2fc..1337485f47 100644 --- a/demo/src/screens/__tests__/__snapshots__/AvatarScreen.spec.js.snap +++ b/demo/src/screens/__tests__/__snapshots__/AvatarScreen.spec.js.snap @@ -937,6 +937,7 @@ exports[`AvatarScreen renders screen 1`] = ` } style={ [ + false, undefined, undefined, { @@ -1194,6 +1195,7 @@ exports[`AvatarScreen renders screen 1`] = ` } style={ [ + false, undefined, undefined, { @@ -1465,6 +1467,7 @@ exports[`AvatarScreen renders screen 1`] = ` } style={ [ + false, undefined, undefined, { @@ -1659,6 +1662,7 @@ exports[`AvatarScreen renders screen 1`] = ` } style={ [ + false, undefined, undefined, { @@ -1916,6 +1920,7 @@ exports[`AvatarScreen renders screen 1`] = ` } style={ [ + false, undefined, undefined, { @@ -2046,6 +2051,7 @@ exports[`AvatarScreen renders screen 1`] = ` } style={ [ + false, null, undefined, undefined, @@ -2218,6 +2224,7 @@ exports[`AvatarScreen renders screen 1`] = ` } style={ [ + false, undefined, undefined, { @@ -2448,6 +2455,7 @@ exports[`AvatarScreen renders screen 1`] = ` } style={ [ + false, undefined, undefined, { @@ -2678,6 +2686,7 @@ exports[`AvatarScreen renders screen 1`] = ` } style={ [ + false, undefined, undefined, { @@ -2866,6 +2875,7 @@ exports[`AvatarScreen renders screen 1`] = ` } style={ [ + false, undefined, undefined, { diff --git a/package.json b/package.json index 16620d94d9..433514f173 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "url": "https://github.com/wix/react-native-ui-lib" }, "scripts": { + "update-web-assets": "node scripts/assets/extract-dimensions.js", "start": "watchman watch-del-all && react-native start", "start:web": "npm --prefix webDemo run start", "ios": "react-native run-ios", @@ -83,6 +84,7 @@ "@types/hoist-non-react-statics": "^3.3.1", "@types/jest": "^29.2.1", "@types/lodash": "^4.0.0", + "@types/mime-types": "^2.1.4", "@types/prop-types": "^15.5.3", "@types/react": "18.3.7", "@types/react-native": "0.73.0", @@ -100,9 +102,11 @@ "eslint-plugin-react": "^7.24.0", "eslint-plugin-react-hooks": "^4.0.4", "eslint-plugin-react-native": "^4.0.0", + "image-size": "^2.0.1", "jest": "^29.6.3", "light-date": "^1.2.0", "metro-react-native-babel-preset": "0.73.10", + "mime-types": "^2.1.35", "moment": "^2.24.0", "object-hash": "^3.0.0", "postcss": "^8.4.21", diff --git a/scripts/assets/extract-dimensions.js b/scripts/assets/extract-dimensions.js new file mode 100644 index 0000000000..432f381d7d --- /dev/null +++ b/scripts/assets/extract-dimensions.js @@ -0,0 +1,85 @@ +const fs = require('fs'); +const path = require('path'); +const mime = require('mime-types'); +const {imageSize: sizeOf} = require('image-size'); + +// Base paths +const ICONS_PATH = path.resolve(__dirname, '../../src/assets/internal/icons'); +const IMAGES_PATH = path.resolve(__dirname, '../../src/assets/internal/images'); + +// Function to check if file is an image +function isImageFile(filePath) { + const mimeType = mime.lookup(filePath); + return !!mimeType && mimeType.includes('image'); +} + +// Function to get dimensions of an image +function getDimensions(imagePath) { + try { + if (!isImageFile(imagePath)) { + console.warn(`File is not an image: ${imagePath}`); + return {width: 0, height: 0}; + } + + try { + const buffer = fs.readFileSync(imagePath); + const dimensions = sizeOf(buffer); + return { + width: dimensions.width, + height: dimensions.height + }; + } catch (sizeError) { + console.error(`Error getting dimensions for ${imagePath}:`, sizeError); + // Default dimensions if sizeOf fails + return {width: 24, height: 24}; + } + } catch (error) { + console.error(`Error getting dimensions for ${imagePath}:`, error); + return {width: 0, height: 0}; + } +} + +// Function to create index files with dimensions +function createIndexFile(sourcePath, targetPath, fileType) { + const files = fs.readdirSync(sourcePath).filter(file => !file.includes('@') && !file.startsWith('.')); + + let content = ''; + + if (fileType === 'icons') { + content = 'export const icons = {\n'; + } else if (fileType === 'images') { + content = 'export const images = {\n'; + } + + files.forEach(file => { + const filePath = path.join(sourcePath, file); + const mimeType = mime.lookup(filePath); + const isImage = !!mimeType && mimeType.includes('image'); + + if (!isImage) { + console.warn(`Skipping non-image file: ${filePath}`); + return; + } + + const name = path.basename(file, path.extname(file)); + const dimensions = getDimensions(filePath); + + // Handle hyphenated filenames by converting to camelCase + const propertyName = name.replace(/-([a-z])/g, (match, letter) => letter.toUpperCase()); + + content += ` get ${propertyName}() {\n`; + content += ` return {uri: require('./${file}'), dimensions: {width: ${dimensions.width}, height: ${dimensions.height}}};\n`; + content += ` },\n`; + }); + + content += '};\n'; + + fs.writeFileSync(targetPath, content); + console.log(`Created ${targetPath}`); +} + +// Create index files +createIndexFile(ICONS_PATH, path.join(ICONS_PATH, 'index.ts'), 'icons'); +createIndexFile(IMAGES_PATH, path.join(IMAGES_PATH, 'index.ts'), 'images'); + +console.log('Index files created successfully!'); diff --git a/src/assets/internal/icons/index.ts b/src/assets/internal/icons/index.ts new file mode 100644 index 0000000000..35c01aca77 --- /dev/null +++ b/src/assets/internal/icons/index.ts @@ -0,0 +1,38 @@ +export const icons = { + get check() { + return {uri: require('./check.png'), dimensions: {width: 21, height: 15}}; + }, + get checkMarkSmall() { + return {uri: require('./checkMarkSmall.png'), dimensions: {width: 16, height: 16}}; + }, + get checkSmall() { + return {uri: require('./checkSmall.png'), dimensions: {width: 24, height: 24}}; + }, + get chevronDown() { + return {uri: require('./chevronDown.png'), dimensions: {width: 16, height: 16}}; + }, + get exclamationSmall() { + return {uri: require('./exclamationSmall.png'), dimensions: {width: 16, height: 16}}; + }, + get minusSmall() { + return {uri: require('./minusSmall.png'), dimensions: {width: 16, height: 16}}; + }, + get plusSmall() { + return {uri: require('./plusSmall.png'), dimensions: {width: 16, height: 16}}; + }, + get search() { + return {uri: require('./search.png'), dimensions: {width: 24, height: 24}}; + }, + get x() { + return {uri: require('./x.png'), dimensions: {width: 17, height: 16}}; + }, + get xFlat() { + return {uri: require('./xFlat.png'), dimensions: {width: 16, height: 16}}; + }, + get xMedium() { + return {uri: require('./xMedium.png'), dimensions: {width: 24, height: 24}}; + }, + get xSmall() { + return {uri: require('./xSmall.png'), dimensions: {width: 16, height: 16}}; + }, +}; diff --git a/src/assets/internal/images/index.ts b/src/assets/internal/images/index.ts new file mode 100644 index 0000000000..d48d3154b4 --- /dev/null +++ b/src/assets/internal/images/index.ts @@ -0,0 +1,26 @@ +export const images = { + get gradient() { + return {uri: require('./gradient.png'), dimensions: {width: 56, height: 2}}; + }, + get gradientOverlay() { + return {uri: require('./gradientOverlay.png'), dimensions: {width: 76, height: 48}}; + }, + get gradientOverlayHigh() { + return {uri: require('./gradientOverlayHigh.png'), dimensions: {width: 1, height: 297}}; + }, + get gradientOverlayLow() { + return {uri: require('./gradientOverlayLow.png'), dimensions: {width: 1, height: 297}}; + }, + get gradientOverlayMedium() { + return {uri: require('./gradientOverlayMedium.png'), dimensions: {width: 1, height: 297}}; + }, + get hintTipMiddle() { + return {uri: require('./hintTipMiddle.png'), dimensions: {width: 20, height: 7}}; + }, + get hintTipSide() { + return {uri: require('./hintTipSide.png'), dimensions: {width: 20, height: 24}}; + }, + get transparentSwatch() { + return {uri: require('./transparentSwatch.png'), dimensions: {width: 100, height: 100}}; + }, +}; diff --git a/src/components/animatedImage/index.tsx b/src/components/animatedImage/index.tsx index a29e98a20a..defc4ea8a0 100644 --- a/src/components/animatedImage/index.tsx +++ b/src/components/animatedImage/index.tsx @@ -51,18 +51,16 @@ const AnimatedImage = (props: AnimatedImageProps) => { } }, [loader]); - const onLoad = useCallback( - (event: NativeSyntheticEvent) => { - setIsLoading(false); - propsOnLoad?.(event); - // did not start the animation already - if (opacity.value === 0) { - opacity.value = withTiming(1, {duration: animationDuration}); - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [setIsLoading, propsOnLoad, animationDuration] - ); + const onLoad = useCallback((event: NativeSyntheticEvent) => { + setIsLoading(false); + propsOnLoad?.(event); + // did not start the animation already + if (opacity.value === 0) { + opacity.value = withTiming(1, {duration: animationDuration}); + } + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [setIsLoading, propsOnLoad, animationDuration]); const onLoadStart = useCallback(() => { setIsLoading(true); diff --git a/src/components/checkbox/__tests__/index.spec.tsx b/src/components/checkbox/__tests__/index.spec.tsx index 411e137771..55cae8275e 100644 --- a/src/components/checkbox/__tests__/index.spec.tsx +++ b/src/components/checkbox/__tests__/index.spec.tsx @@ -42,8 +42,7 @@ describe('Checkbox renderer test', () => { checkboxInitialValue | checkboxExpectedValue ${false} | ${true} ${true} | ${false} - `( - 'Send value ($checkboxInitialValue)', + `('Send value ($checkboxInitialValue)', async ({ checkboxInitialValue, checkboxExpectedValue @@ -59,8 +58,7 @@ describe('Checkbox renderer test', () => { expect(onValueChange).toHaveBeenCalledTimes(1); expect(onValueChange).toHaveBeenCalledWith(checkboxExpectedValue); - } - ); + }); }); describe('Press', () => { diff --git a/src/components/featureHighlight/index.tsx b/src/components/featureHighlight/index.tsx index cdb38afe25..e4b00889b6 100644 --- a/src/components/featureHighlight/index.tsx +++ b/src/components/featureHighlight/index.tsx @@ -222,8 +222,7 @@ class FeatureHighlight extends Component { toValue, // Animate to value duration: toValue ? 100 : 0, // Make it take a while useNativeDriver: true - } - ).start(); // Starts the animation + }).start(); // Starts the animation } setTargetPosition(props = this.props) { @@ -259,9 +258,7 @@ class FeatureHighlight extends Component { topPosition = isUnderMin ? topPosition + innerPadding : targetCenter + minRectHeight / 2 + innerPadding / 2; } if (topPosition < 0 || topPosition + this.contentHeight > Constants.screenHeight) { - console.warn( - `Content is too long and might appear off screen. Please adjust the message length for better results.` - ); + console.warn(`Content is too long and might appear off screen. Please adjust the message length for better results.`); } return topPosition; } diff --git a/src/components/hint/__tests__/__snapshots__/index.spec.tsx.snap b/src/components/hint/__tests__/__snapshots__/index.spec.tsx.snap index 437ef75730..bcf6fb5dc4 100644 --- a/src/components/hint/__tests__/__snapshots__/index.spec.tsx.snap +++ b/src/components/hint/__tests__/__snapshots__/index.spec.tsx.snap @@ -240,6 +240,7 @@ exports[`Hint Screen component test Different positions and scenarios center pos } style={ [ + false, { "tintColor": "#5A48F5", }, @@ -518,6 +519,7 @@ exports[`Hint Screen component test Different positions and scenarios center pos } style={ [ + false, { "tintColor": "#5A48F5", }, @@ -797,6 +799,7 @@ exports[`Hint Screen component test Different positions and scenarios left posit } style={ [ + false, { "tintColor": "#5A48F5", }, @@ -1075,6 +1078,7 @@ exports[`Hint Screen component test Different positions and scenarios left posit } style={ [ + false, { "tintColor": "#5A48F5", }, @@ -1354,6 +1358,7 @@ exports[`Hint Screen component test Different positions and scenarios right posi } style={ [ + false, { "tintColor": "#5A48F5", }, @@ -1632,6 +1637,7 @@ exports[`Hint Screen component test Different positions and scenarios right posi } style={ [ + false, { "tintColor": "#5A48F5", }, diff --git a/src/components/image/index.tsx b/src/components/image/index.tsx index b3498c1380..d2c66ecca7 100644 --- a/src/components/image/index.tsx +++ b/src/components/image/index.tsx @@ -244,6 +244,7 @@ class Image extends PureComponent { // @ts-ignore { if (Array.isArray(props.size)) { if (props.size[0] >= props.size[1] || props.size[1] >= props.size[2]) { - console.warn( - 'It is recommended that largeSize > mediumSize > smallSize, currently: smallSize=', + console.warn('It is recommended that largeSize > mediumSize > smallSize, currently: smallSize=', props.size[0], 'mediumSize=', props.size[1], 'largeSize=', - props.size[2] - ); + props.size[2]); } } } diff --git a/src/components/panningViews/panListenerView.tsx b/src/components/panningViews/panListenerView.tsx index 961a684b50..de1dd72347 100644 --- a/src/components/panningViews/panListenerView.tsx +++ b/src/components/panningViews/panListenerView.tsx @@ -134,13 +134,11 @@ class PanListenerView extends PureComponent { const {dy, dx} = gestureState; const {directions, panSensitivity = DEFAULT_PAN_SENSITIVITY} = this.props; - return Boolean( - directions && + return Boolean(directions && ((directions.includes(PanningProvider.Directions.UP) && dy < -panSensitivity) || (directions.includes(PanningProvider.Directions.DOWN) && dy > panSensitivity) || (directions.includes(PanningProvider.Directions.LEFT) && dx < -panSensitivity) || - (directions.includes(PanningProvider.Directions.RIGHT) && dx > panSensitivity)) - ); + (directions.includes(PanningProvider.Directions.RIGHT) && dx > panSensitivity))); }; handlePanStart = () => { diff --git a/src/components/textField/__tests__/index.driver.spec.tsx b/src/components/textField/__tests__/index.driver.spec.tsx index c39ffbde94..2dae120180 100644 --- a/src/components/textField/__tests__/index.driver.spec.tsx +++ b/src/components/textField/__tests__/index.driver.spec.tsx @@ -225,17 +225,15 @@ describe('TextField', () => { }); it('should remove validation error message after entering a valid input', () => { - const renderTree = render( - - ); + const renderTree = render(); const textFieldDriver = TextFieldDriver({renderTree, testID: TEXT_FIELD_TEST_ID}); expect(textFieldDriver.getValidationMessage().getText()).toEqual('email is invalid'); @@ -272,16 +270,14 @@ describe('TextField', () => { describe('validationIcon', () => { it('should display validationIcon', () => { - const renderTree = render( - - ); + const renderTree = render(); const textFieldDriver = TextFieldDriver({renderTree, testID: TEXT_FIELD_TEST_ID}); expect(textFieldDriver.getValidationMessage().exists()).toBe(true); diff --git a/src/incubator/slider/SliderPresenter.ts b/src/incubator/slider/SliderPresenter.ts index bda2c18e03..cee42bd864 100644 --- a/src/incubator/slider/SliderPresenter.ts +++ b/src/incubator/slider/SliderPresenter.ts @@ -62,12 +62,10 @@ export function validateValues(props: SliderProps) { } } -export function getStepInterpolated( - trackWidth: number, +export function getStepInterpolated(trackWidth: number, minimumValue: number, maximumValue: number, - stepXValue: SharedValue -) { + stepXValue: SharedValue) { 'worklet'; const outputRange = [0, trackWidth]; const inputRange = diff --git a/yarn.lock b/yarn.lock index 5b295c1de7..8df2a1fdbd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3532,6 +3532,13 @@ __metadata: languageName: node linkType: hard +"@types/mime-types@npm:^2.1.4": + version: 2.1.4 + resolution: "@types/mime-types@npm:2.1.4" + checksum: f8c521c54ee0c0b9f90a65356a80b1413ed27ccdc94f5c7ebb3de5d63cedb559cd2610ea55b4100805c7349606a920d96e54f2d16b2f0afa6b7cd5253967ccc9 + languageName: node + linkType: hard + "@types/node-forge@npm:^1.3.0": version: 1.3.11 resolution: "@types/node-forge@npm:1.3.11" @@ -7093,6 +7100,15 @@ __metadata: languageName: node linkType: hard +"image-size@npm:^2.0.1": + version: 2.0.1 + resolution: "image-size@npm:2.0.1" + bin: + image-size: bin/image-size.js + checksum: 1057c58f5b17fddb17c4b1d43e28a9574dfc231411c42b9eca2dcb2b96fa2c19e3ef933b281ce9b549cac42f6fc1ea356481e939bf63655fd9d757781456c1ef + languageName: node + linkType: hard + "import-fresh@npm:^2.0.0": version: 2.0.0 resolution: "import-fresh@npm:2.0.0" @@ -8933,7 +8949,7 @@ __metadata: languageName: node linkType: hard -"mime-types@npm:^2.1.27, mime-types@npm:~2.1.34": +"mime-types@npm:^2.1.27, mime-types@npm:^2.1.35, mime-types@npm:~2.1.34": version: 2.1.35 resolution: "mime-types@npm:2.1.35" dependencies: @@ -10310,6 +10326,7 @@ __metadata: "@types/hoist-non-react-statics": ^3.3.1 "@types/jest": ^29.2.1 "@types/lodash": ^4.0.0 + "@types/mime-types": ^2.1.4 "@types/prop-types": ^15.5.3 "@types/react": 18.3.7 "@types/react-native": 0.73.0 @@ -10333,11 +10350,13 @@ __metadata: eslint-plugin-react-hooks: ^4.0.4 eslint-plugin-react-native: ^4.0.0 hoist-non-react-statics: ^3.0.0 + image-size: ^2.0.1 jest: ^29.6.3 light-date: ^1.2.0 lodash: ^4.17.21 memoize-one: ^5.0.5 metro-react-native-babel-preset: 0.73.10 + mime-types: ^2.1.35 moment: ^2.24.0 object-hash: ^3.0.0 postcss: ^8.4.21