From 23d9e912c8bcc6f024f7ae9c4da1f84669ab90d2 Mon Sep 17 00:00:00 2001 From: chirag04 Date: Mon, 18 May 2015 23:07:42 -0400 Subject: [PATCH] inital commit --- .flowconfig | 32 ++ .gitignore | 28 ++ README.md | 88 ++++++ ToolTipMenu.xcodeproj/project.pbxproj | 403 ++++++++++++++++++++++++++ ToolTipMenu/RCTToolTipText.h | 14 + ToolTipMenu/RCTToolTipText.m | 54 ++++ ToolTipMenu/RCTToolTipTextManager.h | 5 + ToolTipMenu/RCTToolTipTextManager.m | 14 + ToolTipMenu/ToolTipMenu.h | 6 + ToolTipMenu/ToolTipMenu.m | 41 +++ ToolTipMenuTests/Info.plist | 24 ++ ToolTipText.js | 220 ++++++++++++++ package.json | 25 ++ screenshot.png | Bin 0 -> 24018 bytes 14 files changed, 954 insertions(+) create mode 100644 .flowconfig create mode 100644 .gitignore create mode 100644 README.md create mode 100644 ToolTipMenu.xcodeproj/project.pbxproj create mode 100644 ToolTipMenu/RCTToolTipText.h create mode 100644 ToolTipMenu/RCTToolTipText.m create mode 100644 ToolTipMenu/RCTToolTipTextManager.h create mode 100644 ToolTipMenu/RCTToolTipTextManager.m create mode 100644 ToolTipMenu/ToolTipMenu.h create mode 100644 ToolTipMenu/ToolTipMenu.m create mode 100644 ToolTipMenuTests/Info.plist create mode 100644 ToolTipText.js create mode 100644 package.json create mode 100644 screenshot.png diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..3cf7ab7 --- /dev/null +++ b/.flowconfig @@ -0,0 +1,32 @@ +[ignore] + +# We fork some components by platform. +.*/*.web.js +.*/*.android.js + +# Some modules have their own node_modules with overlap +.*/node_modules/node-haste/.* + +# Ignore react-tools where there are overlaps, but don't ignore anything that +# react-native relies on +.*/node_modules/react-tools/src/vendor/core/ExecutionEnvironment.js +.*/node_modules/react-tools/src/browser/eventPlugins/ResponderEventPlugin.js +.*/node_modules/react-tools/src/browser/ui/React.js +.*/node_modules/react-tools/src/core/ReactInstanceHandles.js +.*/node_modules/react-tools/src/event/EventPropagators.js +.*/node_modules/flux/lib/invariant.js + +# Ignore jest +.*/node_modules/jest-cli/.* + +# Ignore examples +.*/Examples/.* + +[include] + +[libs] +node_modules/react-native/Libraries/react-native/react-native-interface.js +interfaces.js + +[options] +module.system=haste diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b927355 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# OSX +# +.DS_Store + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +project.xcworkspace + +# node.js +# +node_modules/ +npm-debug.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..7850508 --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# react-native-tooltip + +A react-native component from displaying tooltip. Uses UIMenuController. + +### Add it to your project + +1. Run `npm install react-native-tooltip --save` +2. Open your project in XCode, right click on `Libraries` and click `Add + Files to "Your Project Name"` [(Screenshot)](http://url.brentvatne.ca/jQp8) then [(Screenshot)](http://url.brentvatne.ca/1gqUD). +3. Add `libRNToolTip.a` to `Build Phases -> Link Binary With Libraries` + [(Screenshot)](http://url.brentvatne.ca/17Xfe). +4. Whenever you want to use it within React code now you can: `var ToolTipText = require('react-native-tooltip');` + + +## Example +```javascript +var React = require('react-native'); +var { + AppRegistry, + StyleSheet, + Text, + TouchableHighlight, + PixelRatio, + View, +} = React; + +var ToolTipMenu = require('NativeModules').ToolTipMenu; +var ToolTipText = require('react-native-tooltip'); + +var tooltip = React.createClass({ + getInitialState: function() { + return { + input: 'chirag', + } + }, + handleChange: function(event) { + this.setState({input: event.nativeEvent.text}); + }, + handleFocus: function(change) { + ToolTipMenu.show(this.refs.input.getNodeHandle(), ['x', 'z']); + }, + render: function() { + return ( + + + {this.state.input} + + + ); + } +}); + +var styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: '#F5FCFF', + }, + textinputContainer: { + marginTop: 20, + justifyContent: 'center', + alignItems: 'center', + }, + textinput: { + width: 60, + marginVertical: 2, + marginHorizontal: 2, + borderWidth: 1 / PixelRatio.get(), + borderRadius: 5, + borderColor: '#c7c7cc', + padding: 2, + fontSize: 14, + backgroundColor: 'white', + }, +}); + +AppRegistry.registerComponent('tooltip', () => tooltip); +``` + +## Here is how it looks: +![Demo gif](https://github.com/chirag04/react-native-tooltip/blob/master/screenshot.png) diff --git a/ToolTipMenu.xcodeproj/project.pbxproj b/ToolTipMenu.xcodeproj/project.pbxproj new file mode 100644 index 0000000..77f1d24 --- /dev/null +++ b/ToolTipMenu.xcodeproj/project.pbxproj @@ -0,0 +1,403 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 4681C0251B05271A004D67D4 /* ToolTipMenu.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4681C0241B05271A004D67D4 /* ToolTipMenu.h */; }; + 4681C0271B05271A004D67D4 /* ToolTipMenu.m in Sources */ = {isa = PBXBuildFile; fileRef = 4681C0261B05271A004D67D4 /* ToolTipMenu.m */; }; + 4681C02D1B05271A004D67D4 /* libToolTipMenu.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4681C0211B05271A004D67D4 /* libToolTipMenu.a */; }; + 469F58B71B069FF300C571E8 /* RCTToolTipText.m in Sources */ = {isa = PBXBuildFile; fileRef = 469F58B61B069FF300C571E8 /* RCTToolTipText.m */; }; + 469F58B91B06A00C00C571E8 /* RCTToolTipTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 469F58B81B06A00C00C571E8 /* RCTToolTipTextManager.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 4681C02E1B05271A004D67D4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4681C0191B05271A004D67D4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4681C0201B05271A004D67D4; + remoteInfo = ToolTipMenu; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 4681C01F1B05271A004D67D4 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + 4681C0251B05271A004D67D4 /* ToolTipMenu.h in CopyFiles */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 4681C0211B05271A004D67D4 /* libToolTipMenu.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libToolTipMenu.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 4681C0241B05271A004D67D4 /* ToolTipMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ToolTipMenu.h; sourceTree = ""; }; + 4681C0261B05271A004D67D4 /* ToolTipMenu.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ToolTipMenu.m; sourceTree = ""; }; + 4681C02C1B05271A004D67D4 /* ToolTipMenuTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ToolTipMenuTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 4681C0321B05271A004D67D4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 469F58B31B069F9800C571E8 /* RCTToolTipText.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTToolTipText.h; sourceTree = ""; }; + 469F58B61B069FF300C571E8 /* RCTToolTipText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTToolTipText.m; sourceTree = ""; }; + 469F58B81B06A00C00C571E8 /* RCTToolTipTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTToolTipTextManager.m; sourceTree = ""; }; + 469F58BA1B06A01D00C571E8 /* RCTToolTipTextManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RCTToolTipTextManager.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 4681C01E1B05271A004D67D4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4681C0291B05271A004D67D4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 4681C02D1B05271A004D67D4 /* libToolTipMenu.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 4681C0181B05271A004D67D4 = { + isa = PBXGroup; + children = ( + 4681C0231B05271A004D67D4 /* ToolTipMenu */, + 4681C0301B05271A004D67D4 /* ToolTipMenuTests */, + 4681C0221B05271A004D67D4 /* Products */, + ); + sourceTree = ""; + }; + 4681C0221B05271A004D67D4 /* Products */ = { + isa = PBXGroup; + children = ( + 4681C0211B05271A004D67D4 /* libToolTipMenu.a */, + 4681C02C1B05271A004D67D4 /* ToolTipMenuTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 4681C0231B05271A004D67D4 /* ToolTipMenu */ = { + isa = PBXGroup; + children = ( + 4681C0241B05271A004D67D4 /* ToolTipMenu.h */, + 4681C0261B05271A004D67D4 /* ToolTipMenu.m */, + 469F58B31B069F9800C571E8 /* RCTToolTipText.h */, + 469F58B61B069FF300C571E8 /* RCTToolTipText.m */, + 469F58BA1B06A01D00C571E8 /* RCTToolTipTextManager.h */, + 469F58B81B06A00C00C571E8 /* RCTToolTipTextManager.m */, + ); + path = ToolTipMenu; + sourceTree = ""; + }; + 4681C0301B05271A004D67D4 /* ToolTipMenuTests */ = { + isa = PBXGroup; + children = ( + 4681C0311B05271A004D67D4 /* Supporting Files */, + ); + path = ToolTipMenuTests; + sourceTree = ""; + }; + 4681C0311B05271A004D67D4 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + 4681C0321B05271A004D67D4 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 4681C0201B05271A004D67D4 /* ToolTipMenu */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4681C0351B05271A004D67D4 /* Build configuration list for PBXNativeTarget "ToolTipMenu" */; + buildPhases = ( + 4681C01D1B05271A004D67D4 /* Sources */, + 4681C01E1B05271A004D67D4 /* Frameworks */, + 4681C01F1B05271A004D67D4 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ToolTipMenu; + productName = ToolTipMenu; + productReference = 4681C0211B05271A004D67D4 /* libToolTipMenu.a */; + productType = "com.apple.product-type.library.static"; + }; + 4681C02B1B05271A004D67D4 /* ToolTipMenuTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4681C0381B05271A004D67D4 /* Build configuration list for PBXNativeTarget "ToolTipMenuTests" */; + buildPhases = ( + 4681C0281B05271A004D67D4 /* Sources */, + 4681C0291B05271A004D67D4 /* Frameworks */, + 4681C02A1B05271A004D67D4 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4681C02F1B05271A004D67D4 /* PBXTargetDependency */, + ); + name = ToolTipMenuTests; + productName = ToolTipMenuTests; + productReference = 4681C02C1B05271A004D67D4 /* ToolTipMenuTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 4681C0191B05271A004D67D4 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0630; + ORGANIZATIONNAME = "Chirag Jain"; + TargetAttributes = { + 4681C0201B05271A004D67D4 = { + CreatedOnToolsVersion = 6.3.1; + }; + 4681C02B1B05271A004D67D4 = { + CreatedOnToolsVersion = 6.3.1; + }; + }; + }; + buildConfigurationList = 4681C01C1B05271A004D67D4 /* Build configuration list for PBXProject "ToolTipMenu" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 4681C0181B05271A004D67D4; + productRefGroup = 4681C0221B05271A004D67D4 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 4681C0201B05271A004D67D4 /* ToolTipMenu */, + 4681C02B1B05271A004D67D4 /* ToolTipMenuTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 4681C02A1B05271A004D67D4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4681C01D1B05271A004D67D4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4681C0271B05271A004D67D4 /* ToolTipMenu.m in Sources */, + 469F58B91B06A00C00C571E8 /* RCTToolTipTextManager.m in Sources */, + 469F58B71B069FF300C571E8 /* RCTToolTipText.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 4681C0281B05271A004D67D4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 4681C02F1B05271A004D67D4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4681C0201B05271A004D67D4 /* ToolTipMenu */; + targetProxy = 4681C02E1B05271A004D67D4 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 4681C0331B05271A004D67D4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 4681C0341B05271A004D67D4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 8.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 4681C0361B05271A004D67D4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)/../node_modules/react-native/**", + ); + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 4681C0371B05271A004D67D4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../node_modules/react-native/React/**", + "$(SRCROOT)/../node_modules/react-native/**", + ); + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + 4681C0391B05271A004D67D4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + INFOPLIST_FILE = ToolTipMenuTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 4681C03A1B05271A004D67D4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + FRAMEWORK_SEARCH_PATHS = ( + "$(SDKROOT)/Developer/Library/Frameworks", + "$(inherited)", + ); + INFOPLIST_FILE = ToolTipMenuTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4681C01C1B05271A004D67D4 /* Build configuration list for PBXProject "ToolTipMenu" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4681C0331B05271A004D67D4 /* Debug */, + 4681C0341B05271A004D67D4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4681C0351B05271A004D67D4 /* Build configuration list for PBXNativeTarget "ToolTipMenu" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4681C0361B05271A004D67D4 /* Debug */, + 4681C0371B05271A004D67D4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 4681C0381B05271A004D67D4 /* Build configuration list for PBXNativeTarget "ToolTipMenuTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4681C0391B05271A004D67D4 /* Debug */, + 4681C03A1B05271A004D67D4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 4681C0191B05271A004D67D4 /* Project object */; +} diff --git a/ToolTipMenu/RCTToolTipText.h b/ToolTipMenu/RCTToolTipText.h new file mode 100644 index 0000000..e6d8d4c --- /dev/null +++ b/ToolTipMenu/RCTToolTipText.h @@ -0,0 +1,14 @@ +#import +#import "RCTText.h" + +@class RCTEventDispatcher; + +@interface RCTToolTipText : RCTText + +@property(nonatomic, strong) RCTEventDispatcher *_eventDispatcher; + +- (void)tappedMenuItem:(NSString *)text; + +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; + +@end diff --git a/ToolTipMenu/RCTToolTipText.m b/ToolTipMenu/RCTToolTipText.m new file mode 100644 index 0000000..f9796f4 --- /dev/null +++ b/ToolTipMenu/RCTToolTipText.m @@ -0,0 +1,54 @@ +#import "RCTToolTipText.h" +#import "RCTEventDispatcher.h" +#import "UIView+React.h" + +@implementation RCTToolTipText + +- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher +{ + if ((self = [super initWithFrame:CGRectZero])) { + self._eventDispatcher = eventDispatcher; + } + + return self; +} + +- (BOOL) canBecomeFirstResponder +{ + return YES; +} + +- (void)tappedMenuItem:(NSString *)text { + [self._eventDispatcher sendTextEventWithType:RCTTextEventTypeChange + reactTag:self.reactTag + text:text]; +} + +- (BOOL)canPerformAction:(SEL)action withSender:(id)sender { + NSString *sel = NSStringFromSelector(action); + NSRange match = [sel rangeOfString:@"magic_"]; + if (match.location == 0) { + return YES; + } + return NO; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel { + if ([super methodSignatureForSelector:sel]) { + return [super methodSignatureForSelector:sel]; + } + return [super methodSignatureForSelector:@selector(tappedMenuItem:)]; +} + +- (void)forwardInvocation:(NSInvocation *)invocation { + NSString *sel = NSStringFromSelector([invocation selector]); + NSRange match = [sel rangeOfString:@"magic_"]; + if (match.location == 0) { + [self tappedMenuItem:[sel substringFromIndex:6]]; + } else { + [super forwardInvocation:invocation]; + } +} + + +@end diff --git a/ToolTipMenu/RCTToolTipTextManager.h b/ToolTipMenu/RCTToolTipTextManager.h new file mode 100644 index 0000000..904fd92 --- /dev/null +++ b/ToolTipMenu/RCTToolTipTextManager.h @@ -0,0 +1,5 @@ +#import "RCTTextManager.h" + +@interface RCTToolTipTextManager : RCTTextManager + +@end diff --git a/ToolTipMenu/RCTToolTipTextManager.m b/ToolTipMenu/RCTToolTipTextManager.m new file mode 100644 index 0000000..eb7326d --- /dev/null +++ b/ToolTipMenu/RCTToolTipTextManager.m @@ -0,0 +1,14 @@ +#import "RCTToolTipTextManager.h" +#import "RCTToolTipText.h" +#import "RCTBridge.h" + +@implementation RCTToolTipTextManager + +RCT_EXPORT_MODULE() + +- (UIView *)view +{ + return [[RCTToolTipText alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; +} + +@end diff --git a/ToolTipMenu/ToolTipMenu.h b/ToolTipMenu/ToolTipMenu.h new file mode 100644 index 0000000..6985356 --- /dev/null +++ b/ToolTipMenu/ToolTipMenu.h @@ -0,0 +1,6 @@ +#import +#import "RCTBridgeModule.h" + +@interface ToolTipMenu : NSObject + +@end \ No newline at end of file diff --git a/ToolTipMenu/ToolTipMenu.m b/ToolTipMenu/ToolTipMenu.m new file mode 100644 index 0000000..abb7ea1 --- /dev/null +++ b/ToolTipMenu/ToolTipMenu.m @@ -0,0 +1,41 @@ +#import "ToolTipMenu.h" + +#import "RCTToolTipText.h" +#import "RCTSparseArray.h" +#import "RCTUIManager.h" + +@implementation ToolTipMenu + +@synthesize bridge = _bridge; + +RCT_EXPORT_MODULE() + +RCT_EXPORT_METHOD(show:(NSNumber *)reactTag + items: (NSArray *)items) +{ + [self.bridge.uiManager addUIBlock:^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) { + + RCTToolTipText *view = viewRegistry[reactTag]; + if (!view) { + RCTLogError(@"Cannot find view with tag #%@", reactTag); + return; + } + + NSArray *buttons = items; + NSMutableArray *menuItems = [NSMutableArray array]; + for (NSString *buttonText in buttons) { + NSString *sel = [NSString stringWithFormat:@"magic_%@", buttonText]; + [menuItems addObject:[[UIMenuItem alloc] + initWithTitle:buttonText + action:NSSelectorFromString(sel)]]; + } + [view becomeFirstResponder]; + UIMenuController *menuCont = [UIMenuController sharedMenuController]; + [menuCont setTargetRect:view.frame inView:view.superview]; + menuCont.arrowDirection = UIMenuControllerArrowDown; + menuCont.menuItems = menuItems; + [menuCont setMenuVisible:YES animated:YES]; + }]; +} + +@end diff --git a/ToolTipMenuTests/Info.plist b/ToolTipMenuTests/Info.plist new file mode 100644 index 0000000..19d31a6 --- /dev/null +++ b/ToolTipMenuTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + cj.$(PRODUCT_NAME:rfc1034identifier) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/ToolTipText.js b/ToolTipText.js new file mode 100644 index 0000000..0fa616f --- /dev/null +++ b/ToolTipText.js @@ -0,0 +1,220 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * @providesModule ToolTipText + * @flow + */ +'use strict'; + +var NativeMethodsMixin = require('NativeMethodsMixin'); +var React = require('React'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +var StyleSheetPropType = require('StyleSheetPropType'); +var TextStylePropTypes = require('TextStylePropTypes'); +var Touchable = require('Touchable'); + +var createReactNativeComponentClass = + require('createReactNativeComponentClass'); +var merge = require('merge'); + +var stylePropType = StyleSheetPropType(TextStylePropTypes); + +var viewConfig = { + validAttributes: merge(ReactNativeViewAttributes.UIView, { + isHighlighted: true, + numberOfLines: true, + }), + uiViewClassName: 'RCTToolTipText', +}; + +/** + * A React component for displaying text which supports nesting, + * styling, and touch handling. In the following example, the nested title and + * body text will inherit the `fontFamily` from `styles.baseText`, but the title + * provides its own additional styles. The title and body will stack on top of + * each other on account of the literal newlines: + * + * ``` + * renderText: function() { + * return ( + * + * + * {this.state.titleText + '\n\n'} + * + * + * {this.state.bodyText} + * + * + * ); + * }, + * ... + * var styles = StyleSheet.create({ + * baseText: { + * fontFamily: 'Cochin', + * }, + * titleText: { + * fontSize: 20, + * fontWeight: 'bold', + * }, + * }; + * ``` + */ + +var Text = React.createClass({ + + mixins: [Touchable.Mixin, NativeMethodsMixin], + + propTypes: { + /** + * Used to truncate the text with an elipsis after computing the text + * layout, including line wrapping, such that the total number of lines does + * not exceed this number. + */ + numberOfLines: React.PropTypes.number, + /** + * This function is called on press. Text intrinsically supports press + * handling with a default highlight state (which can be disabled with + * `suppressHighlighting`). + */ + onPress: React.PropTypes.func, + /** + * Callback that is called when the text input's text changes. + */ + onChange: React.PropTypes.func, + /** + * When true, no visual change is made when text is pressed down. By + * default, a gray oval highlights the text on press down. + */ + suppressHighlighting: React.PropTypes.bool, + style: stylePropType, + /** + * Used to locate this view in end-to-end tests. + */ + testID: React.PropTypes.string, + /** + * Invoked on mount and layout changes with + * + * {nativeEvent: { layout: {x, y, width, height}}}. + */ + onLayout: React.PropTypes.func, + }, + + viewConfig: viewConfig, + + getInitialState: function() { + return merge(this.touchableGetInitialState(), { + isHighlighted: false, + }); + }, + + onStartShouldSetResponder: function(): bool { + var shouldSetFromProps = this.props.onStartShouldSetResponder && + this.props.onStartShouldSetResponder(); + return shouldSetFromProps || !!this.props.onPress; + }, + + /* + * Returns true to allow responder termination + */ + handleResponderTerminationRequest: function(): bool { + // Allow touchable or props.onResponderTerminationRequest to deny + // the request + var allowTermination = this.touchableHandleResponderTerminationRequest(); + if (allowTermination && this.props.onResponderTerminationRequest) { + allowTermination = this.props.onResponderTerminationRequest(); + } + return allowTermination; + }, + + handleResponderGrant: function(e: SyntheticEvent, dispatchID: string) { + this.touchableHandleResponderGrant(e, dispatchID); + this.props.onResponderGrant && + this.props.onResponderGrant.apply(this, arguments); + }, + + handleResponderMove: function(e: SyntheticEvent) { + this.touchableHandleResponderMove(e); + this.props.onResponderMove && + this.props.onResponderMove.apply(this, arguments); + }, + + handleResponderRelease: function(e: SyntheticEvent) { + this.touchableHandleResponderRelease(e); + this.props.onResponderRelease && + this.props.onResponderRelease.apply(this, arguments); + }, + + handleResponderTerminate: function(e: SyntheticEvent) { + this.touchableHandleResponderTerminate(e); + this.props.onResponderTerminate && + this.props.onResponderTerminate.apply(this, arguments); + }, + + touchableHandleActivePressIn: function() { + if (this.props.suppressHighlighting || !this.props.onPress) { + return; + } + this.setState({ + isHighlighted: true, + }); + }, + + touchableHandleActivePressOut: function() { + if (this.props.suppressHighlighting || !this.props.onPress) { + return; + } + this.setState({ + isHighlighted: false, + }); + }, + + touchableHandlePress: function() { + this.props.onPress && this.props.onPress(); + }, + + touchableGetPressRectOffset: function(): RectOffset { + return PRESS_RECT_OFFSET; + }, + + _onChange: function(event: Event) { + this.props.onChange && this.props.onChange(event); + }, + + render: function() { + var props = {}; + for (var key in this.props) { + props[key] = this.props[key]; + } + // Text is accessible by default + if (props.accessible !== false) { + props.accessible = true; + } + props.isHighlighted = this.state.isHighlighted; + props.onStartShouldSetResponder = this.onStartShouldSetResponder; + props.onResponderTerminationRequest = + this.handleResponderTerminationRequest; + props.onResponderGrant = this.handleResponderGrant; + props.onResponderMove = this.handleResponderMove; + props.onResponderRelease = this.handleResponderRelease; + props.onResponderTerminate = this.handleResponderTerminate; + return ; + }, +}); + +type RectOffset = { + top: number; + left: number; + right: number; + bottom: number; +} + +var PRESS_RECT_OFFSET = {top: 20, left: 20, right: 20, bottom: 30}; + +var RCTText = createReactNativeComponentClass(viewConfig); + +module.exports = Text; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6e71f26 --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "react-native-tooltip", + "version": "0.1.0", + "description": "A react-native wrapper for showing tooltips", + "main": "ToolTipText.ios.js", + "author": { + "name": "Chirag Jain", + "email": "jain_chirag04@yahoo.com", + "url": "http://chiragjain.tumblr.com" + }, + "repository": { + "type": "git", + "url": "git@github.com:chirag04/react-native-tooltip.git" + }, + "keywords": [ + "react", + "react-native", + "react-component", + "ios", + "tooltip", + ], + "dependencies": { + "react-native": "^0.4.4" + } +} diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..9a9f6a7eb4affdfbccd77a7c3242a47769c31a9d GIT binary patch literal 24018 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 literal 0 HcmV?d00001