├── .editorconfig ├── .gitattributes ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── example ├── app.json ├── babel.config.js ├── index.js ├── metro.config.js ├── package.json ├── patches │ ├── @storybook+react-native+5.3.23.patch │ └── core-js+3.8.3.patch ├── src │ └── App.tsx ├── storybook │ ├── addons.js │ ├── index.js │ ├── rn-addons.js │ └── stories │ │ ├── Button │ │ ├── Button.stories.tsx │ │ ├── Button.tsx │ │ ├── ToggleButton.stories.tsx │ │ └── ToggleButton.tsx │ │ ├── Checkbox │ │ ├── Checkbox.stories.tsx │ │ ├── Checkbox.tsx │ │ └── CheckboxGroup.tsx │ │ ├── Combobox │ │ ├── Combobox.stories.tsx │ │ └── index.tsx │ │ ├── Disclosure │ │ ├── Disclosure.stories.tsx │ │ ├── Disclosure.tsx │ │ └── index.tsx │ │ ├── Listbox │ │ ├── Listbox.stories.tsx │ │ └── index.tsx │ │ ├── Menu │ │ ├── Menu.stories.tsx │ │ ├── Menu.tsx │ │ └── index.tsx │ │ ├── Modal │ │ ├── Modal.stories.tsx │ │ └── index.tsx │ │ ├── Overlays │ │ ├── Overlays.stories.tsx │ │ └── index.tsx │ │ ├── Popover │ │ ├── Popover.stories.tsx │ │ └── index.tsx │ │ ├── Radio │ │ ├── Radio.stories.tsx │ │ └── index.tsx │ │ ├── Slider │ │ ├── Slider.stories.tsx │ │ └── index.tsx │ │ ├── Switch │ │ ├── Switch.stories.tsx │ │ └── index.tsx │ │ ├── Tabs │ │ ├── Tabs.stories.tsx │ │ └── index.tsx │ │ ├── Tooltip │ │ ├── Tooltip.stories.tsx │ │ └── index.tsx │ │ ├── Wrapper.tsx │ │ ├── index.js │ │ └── useOverlayPosition │ │ ├── useOverlayPosition.stories.tsx │ │ └── useOverlayPosition.tsx ├── tsconfig.json ├── webpack.config.js └── yarn.lock ├── lerna.json ├── package.json ├── packages ├── accordion │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── types.ts │ │ ├── useAccordion.ts │ │ └── useAccordionItem.ts │ └── tsconfig.build.json ├── button │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useButton.ts │ │ ├── useToggleButton.ts │ │ └── useToggleButton.web.ts │ └── tsconfig.build.json ├── checkbox │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useCheckbox.ts │ │ ├── useCheckboxGroup.ts │ │ ├── useCheckboxGroup.web.ts │ │ ├── useCheckboxGroupItem.ts │ │ └── utils.ts │ └── tsconfig.build.json ├── combobox │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useComboBox.ts │ │ └── useComboBox.web.ts │ └── tsconfig.build.json ├── dialog │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useDialog.ts │ │ └── useDialog.web.ts │ └── tsconfig.build.json ├── disclosure │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useDisclosure.ts │ │ ├── useDisclosure.web.ts │ │ ├── useDisclosureButton.ts │ │ ├── useDisclosureButton.web.ts │ │ └── utils.ts │ └── tsconfig.build.json ├── focus │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── FocusScope.tsx │ │ ├── FocusScope.web.tsx │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useFocus.ts │ │ ├── useFocusRing.ts │ │ └── useFocusRing.web.ts │ └── tsconfig.build.json ├── interactions │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useHover.ts │ │ ├── useHover.web.ts │ │ ├── useKeyboardDismisssable.ts │ │ └── usePress.ts │ └── tsconfig.build.json ├── listbox │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useListBox.ts │ │ ├── useListBox.web.ts │ │ ├── useOption.ts │ │ ├── useOption.web.ts │ │ └── utils.ts │ └── tsconfig.build.json ├── menu │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useMenu.ts │ │ ├── useMenu.web.ts │ │ ├── useMenuItem.ts │ │ ├── useMenuItem.web.ts │ │ ├── useMenuSection.ts │ │ ├── useMenuTrigger.ts │ │ └── useMenuTrigger.web.ts │ └── tsconfig.build.json ├── overlays │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── Portal.tsx │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useOverlay.ts │ │ ├── useOverlay.web.ts │ │ ├── useOverlayPosition.ts │ │ ├── useOverlayPosition.web.ts │ │ ├── useOverlayTrigger.ts │ │ ├── useOverlayTrigger.web.ts │ │ ├── usePreventScroll.ts │ │ ├── usePreventScroll.web.ts │ │ ├── utils.ts │ │ └── web │ │ │ └── overlays │ │ │ ├── index.ts │ │ │ └── src │ │ │ ├── calculatePosition.ts │ │ │ ├── index.ts │ │ │ ├── useCloseOnScroll.ts │ │ │ └── useOverlayPosition.ts │ └── tsconfig.build.json ├── radio │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useRadio.ts │ │ ├── useRadioGroup.ts │ │ └── useRadioGroup.web.ts │ └── tsconfig.build.json ├── react-native-aria │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ └── index.ts │ └── tsconfig.build.json ├── separator │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ └── useSeparator.ts │ └── tsconfig.build.json ├── slider │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── textSelection.ts │ │ ├── useMove.ts │ │ ├── useMove.web.ts │ │ ├── usePanResponder.ts │ │ ├── useSlider.ts │ │ ├── useSlider.web.ts │ │ ├── useSliderThumb.ts │ │ ├── useSliderThumb.web.ts │ │ └── utils.ts │ └── tsconfig.build.json ├── switch │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useSwitch.ts │ │ └── useSwitch.web.ts │ └── tsconfig.build.json ├── tabs │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── TabsKeyboardDelegate.ts │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useTab.ts │ │ ├── useTab.web.ts │ │ ├── useTabs.ts │ │ └── useTabs.web.ts │ └── tsconfig.build.json ├── toggle │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useToggle.ts │ │ └── useToggle.web.ts │ └── tsconfig.build.json ├── tooltip │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ │ └── bootstrap.js │ ├── src │ │ ├── index.ts │ │ ├── index.web.ts │ │ ├── useTooltip.web.ts │ │ └── useTooltipTrigger.web.ts │ └── tsconfig.build.json ├── tsconfig.json └── utils │ ├── README.md │ ├── babel.config.js │ ├── package.json │ ├── scripts │ └── bootstrap.js │ ├── src │ ├── ariaToAccessibilityMap.ts │ └── index.ts │ └── tsconfig.build.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | 9 | indent_style = space 10 | indent_size = 2 11 | 12 | end_of_line = lf 13 | charset = utf-8 14 | trim_trailing_whitespace = true 15 | insert_final_newline = true 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | # specific for windows script files 3 | *.bat text eol=crlf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # XDE 6 | .expo/ 7 | 8 | # VSCode 9 | .vscode/ 10 | jsconfig.json 11 | 12 | # Xcode 13 | # 14 | build/ 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata 24 | *.xccheckout 25 | *.moved-aside 26 | DerivedData 27 | *.hmap 28 | *.ipa 29 | *.xcuserstate 30 | project.xcworkspace 31 | 32 | # Android/IJ 33 | # 34 | .idea 35 | .gradle 36 | local.properties 37 | android.iml 38 | 39 | # Cocoapods 40 | # 41 | example/ios/Pods 42 | 43 | # node.js 44 | # 45 | node_modules/ 46 | npm-debug.log 47 | yarn-debug.log 48 | yarn-error.log 49 | 50 | # BUCK 51 | buck-out/ 52 | \.buckd/ 53 | android/app/libs 54 | android/keystores/debug.keystore 55 | 56 | # Expo 57 | .expo/* 58 | 59 | # generated by bob 60 | lib/ 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 nishan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-bob-mono-example", 3 | "displayName": "BobMono Example", 4 | "expo": { 5 | "name": "react-native-bob-mono-example", 6 | "slug": "react-native-bob-mono-example", 7 | "description": "Example app for react-native-bob-mono", 8 | "privacy": "public", 9 | "version": "1.0.0", 10 | "platforms": [ 11 | "ios", 12 | "android", 13 | "web" 14 | ], 15 | "ios": { 16 | "supportsTablet": true 17 | }, 18 | "assetBundlePatterns": [ 19 | "**/*" 20 | ] 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const pak = require("../package.json"); 3 | 4 | const packagesPath = path.join(__dirname, "..", "packages"); 5 | const rootNodeModules = path.join(__dirname, "..", "node_modules"); 6 | 7 | const alias = { 8 | "^@react-native-aria/(.+)": `${packagesPath}/\\1/src`, 9 | // Major Hack : Fix later, Resolve to root react to prevent invalid hook call error 10 | react: `${rootNodeModules}/react`, 11 | }; 12 | 13 | module.exports = function (api) { 14 | api.cache(true); 15 | 16 | return { 17 | presets: ["babel-preset-expo"], 18 | plugins: [ 19 | [ 20 | "module-resolver", 21 | { 22 | alias: { 23 | // For development, we want to alias the library to the source 24 | ...alias, 25 | }, 26 | }, 27 | ], 28 | ], 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | import { registerRootComponent } from 'expo'; 2 | 3 | import App from './src/App'; 4 | 5 | // registerRootComponent calls AppRegistry.registerComponent('main', () => App); 6 | // It also ensures that whether you load the app in the Expo client or in a native build, 7 | // the environment is set up appropriately 8 | registerRootComponent(App); 9 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const blacklist = require("metro-config/src/defaults/blacklist"); 3 | const escape = require("escape-string-regexp"); 4 | const pak = require("../package.json"); 5 | 6 | const root = path.resolve(__dirname, ".."); 7 | 8 | const modules = Object.keys({ 9 | ...pak.peerDependencies, 10 | }); 11 | 12 | module.exports = { 13 | projectRoot: __dirname, 14 | watchFolders: [root], 15 | 16 | // We need to make sure that only one version is loaded for peerDependencies 17 | // So we blacklist them at the root, and alias them to the versions in example's node_modules 18 | resolver: { 19 | blacklistRE: blacklist( 20 | modules.map( 21 | (m) => 22 | new RegExp(`^${escape(path.join(root, "node_modules", m))}\\/.*$`) 23 | ) 24 | ), 25 | 26 | extraNodeModules: modules.reduce((acc, name) => { 27 | acc[name] = path.join(__dirname, "node_modules", name); 28 | return acc; 29 | }, {}), 30 | }, 31 | 32 | transformer: { 33 | getTransformOptions: async () => ({ 34 | transform: { 35 | experimentalImportSupport: false, 36 | inlineRequires: true, 37 | }, 38 | }), 39 | }, 40 | }; 41 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-bob-mono-example", 3 | "description": "Example app for react-native-bob-mono", 4 | "version": "0.0.1", 5 | "private": true, 6 | "main": "index", 7 | "scripts": { 8 | "android": "expo start --android", 9 | "ios": "expo start --ios", 10 | "web": "expo start --web", 11 | "start": "expo start", 12 | "test": "jest", 13 | "storybook": "start-storybook -p 7007", 14 | "build-storybook": "build-storybook", 15 | "postinstall": "patch-package" 16 | }, 17 | "dependencies": { 18 | "expo": "^40.0.0", 19 | "expo-splash-screen": "~0.8.1", 20 | "patch-package": "^6.2.2", 21 | "react-native": "0.72.5", 22 | "react-native-safe-area-context": "3.1.9", 23 | "react-native-unimodules": "~0.12.0", 24 | "react-native-web": "^0.15.6" 25 | }, 26 | "devDependencies": { 27 | "@babel/core": "~7.12.10", 28 | "@babel/runtime": "^7.9.6", 29 | "@storybook/addon-actions": "^5.3", 30 | "@storybook/addon-knobs": "^5.3", 31 | "@storybook/addon-links": "^5.3", 32 | "@storybook/addon-ondevice-actions": "^5.3.23", 33 | "@storybook/addon-ondevice-knobs": "^5.3.23", 34 | "@storybook/react-native": "^5.3.23", 35 | "@storybook/react-native-server": "^5.3.23", 36 | "babel-loader": "^8.2.2", 37 | "babel-plugin-module-resolver": "^4.0.0", 38 | "babel-preset-expo": "8.3.0", 39 | "expo-cli": "^4.0.13" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/patches/@storybook+react-native+5.3.23.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@storybook/react-native/dist/preview/components/OnDeviceUI/index.js b/node_modules/@storybook/react-native/dist/preview/components/OnDeviceUI/index.js 2 | index bb73f6a..c94d1b2 100644 3 | --- a/node_modules/@storybook/react-native/dist/preview/components/OnDeviceUI/index.js 4 | +++ b/node_modules/@storybook/react-native/dist/preview/components/OnDeviceUI/index.js 5 | @@ -99,8 +99,7 @@ var OnDeviceUI = /** @class */ (function (_super) { 6 | animation_1.getPreviewPosition(this.animatedValue, previewWidth, previewHeight, slideBetweenAnimation), 7 | ]; 8 | var previewStyles = [flex, animation_1.getPreviewScale(this.animatedValue, slideBetweenAnimation)]; 9 | - return (react_1.default.createElement(react_native_1.SafeAreaView, { style: flex }, 10 | - react_1.default.createElement(react_native_1.KeyboardAvoidingView, { enabled: !shouldDisableKeyboardAvoidingView || tabOpen !== constants_1.PREVIEW, behavior: IS_IOS ? 'padding' : null, keyboardVerticalOffset: keyboardAvoidingViewVerticalOffset, style: flex }, 11 | + return (react_1.default.createElement(react_native_1.KeyboardAvoidingView, { enabled: !shouldDisableKeyboardAvoidingView || tabOpen !== constants_1.PREVIEW, behavior: IS_IOS ? 'padding' : null, keyboardVerticalOffset: keyboardAvoidingViewVerticalOffset, style: flex }, 12 | react_1.default.createElement(absolute_positioned_keyboard_aware_view_1.default, { onLayout: this.onLayout, previewHeight: previewHeight, previewWidth: previewWidth }, 13 | react_1.default.createElement(react_native_1.Animated.View, { style: previewWrapperStyles }, 14 | react_1.default.createElement(react_native_1.Animated.View, { style: previewStyles }, 15 | @@ -111,7 +110,7 @@ var OnDeviceUI = /** @class */ (function (_super) { 16 | react_1.default.createElement(StoryListView_1.default, { stories: stories })), 17 | react_1.default.createElement(panel_1.default, { style: animation_1.getAddonPanelPosition(this.animatedValue, previewWidth) }, 18 | react_1.default.createElement(addons_2.default, null))), 19 | - react_1.default.createElement(navigation_1.default, { tabOpen: tabOpen, onChangeTab: this.handleToggleTab, initialUiVisible: !isUIHidden })))); 20 | + react_1.default.createElement(navigation_1.default, { tabOpen: tabOpen, onChangeTab: this.handleToggleTab, initialUiVisible: !isUIHidden }))); 21 | }; 22 | return OnDeviceUI; 23 | }(react_1.PureComponent)); 24 | -------------------------------------------------------------------------------- /example/src/App.tsx: -------------------------------------------------------------------------------- 1 | //@ts-ignore 2 | export { default } from "../storybook"; 3 | -------------------------------------------------------------------------------- /example/storybook/addons.js: -------------------------------------------------------------------------------- 1 | // import '@storybook/addon-actions/register'; 2 | // import '@storybook/addon-links/register'; 3 | // import '@storybook/addon-knobs/register'; 4 | -------------------------------------------------------------------------------- /example/storybook/index.js: -------------------------------------------------------------------------------- 1 | // if you use expo remove this line 2 | import { AppRegistry } from 'react-native'; 3 | 4 | import { getStorybookUI, configure, addDecorator } from '@storybook/react-native'; 5 | import { withKnobs } from '@storybook/addon-knobs'; 6 | 7 | import './rn-addons'; 8 | 9 | // enables knobs for all stories 10 | addDecorator(withKnobs); 11 | 12 | // import stories 13 | configure(() => { 14 | require('./stories'); 15 | }, module); 16 | 17 | // Refer to https://github.com/storybookjs/storybook/tree/master/app/react-native#start-command-parameters 18 | // To find allowed options for getStorybookUI 19 | const StorybookUIRoot = getStorybookUI({}); 20 | 21 | // If you are using React Native vanilla and after installation you don't see your app name here, write it manually. 22 | // If you use Expo you should remove this line. 23 | AppRegistry.registerComponent('%APP_NAME%', () => StorybookUIRoot); 24 | 25 | export default StorybookUIRoot; 26 | -------------------------------------------------------------------------------- /example/storybook/rn-addons.js: -------------------------------------------------------------------------------- 1 | // import '@storybook/addon-ondevice-actions/register'; 2 | // import '@storybook/addon-ondevice-knobs/register'; 3 | -------------------------------------------------------------------------------- /example/storybook/stories/Button/Button.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { Button } from "./Button"; 4 | import { Wrapper } from "../Wrapper"; 5 | 6 | export const Example = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | storiesOf("Button", module).add("Button", Example); 15 | -------------------------------------------------------------------------------- /example/storybook/stories/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useHover } from "@react-native-aria/interactions"; 3 | import { useButton } from "@react-native-aria/button"; 4 | import { useFocusRing } from "@react-native-aria/focus"; 5 | import { OverlayContainer, OverlayProvider } from "@react-native-aria/overlays"; 6 | import { Pressable, Text, View } from "react-native"; 7 | import { useRef } from "react"; 8 | 9 | export function Button(props: any) { 10 | const ref = useRef(null); 11 | let { buttonProps, isPressed } = useButton(props); 12 | 13 | const { isHovered, hoverProps } = useHover({}, ref); 14 | 15 | const { focusProps, isFocusVisible } = useFocusRing(); 16 | 17 | return ( 18 | 19 | 29 | 34 | A simple button 35 | 36 | 37 | 38 | 39 | 40 | {isFocusVisible ? "focus visible" : "not focus visible"} 41 | 42 | 43 | {isHovered ? "Hovering" : "Not hovering"} 44 | 45 | 46 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /example/storybook/stories/Button/ToggleButton.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { ToggleButton } from "./ToggleButton"; 4 | import { Wrapper } from "../Wrapper"; 5 | 6 | export const Example = () => { 7 | return ( 8 | 9 | Toggle button 10 | 11 | ); 12 | }; 13 | 14 | storiesOf("Button", module).add("Toggle Button", Example); 15 | -------------------------------------------------------------------------------- /example/storybook/stories/Button/ToggleButton.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useHover } from "@react-native-aria/interactions"; 3 | import { useToggleButton } from "@react-native-aria/button"; 4 | import { useFocusRing } from "@react-native-aria/focus"; 5 | import { useToggleState } from "@react-stately/toggle"; 6 | import { Pressable, Text, View } from "react-native"; 7 | import { useRef } from "react"; 8 | 9 | export function ToggleButton(props: any) { 10 | const ref = useRef(null); 11 | let state = useToggleState(props); 12 | let { buttonProps, isPressed } = useToggleButton(props, state, ref); 13 | 14 | const { isHovered, hoverProps } = useHover({}, ref); 15 | 16 | const { focusProps, isFocusVisible } = useFocusRing(); 17 | 18 | return ( 19 | 20 | 30 | 35 | A simple toggle button 36 | 37 | 38 | 39 | 40 | 41 | {isFocusVisible ? "focus visible" : "not focus visible"} 42 | 43 | 44 | {isHovered ? "Hovering" : "Not hovering"} 45 | 46 | 47 | {state.isSelected ? "Selected" : "Not Selected"} 48 | 49 | 50 | 51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /example/storybook/stories/Checkbox/Checkbox.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { CheckboxGroup } from "./CheckboxGroup"; 4 | import { Checkbox } from "./Checkbox"; 5 | import { Text } from "react-native"; 6 | import { Wrapper } from "../Wrapper"; 7 | 8 | const CheckboxExample = () => { 9 | const [state, setCheckbox] = React.useState([]); 10 | 11 | return ( 12 | { 16 | setCheckbox(val); 17 | }} 18 | > 19 | 20 | Soccer 21 | 22 | 23 | Baseball 24 | 25 | 26 | Basketball 27 | 28 | 29 | ); 30 | }; 31 | 32 | export const Example = () => { 33 | return ( 34 | 35 | 36 | 37 | ); 38 | }; 39 | 40 | storiesOf("Checkbox", module).add("Checkbox group", Example); 41 | -------------------------------------------------------------------------------- /example/storybook/stories/Checkbox/Checkbox.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext, useRef } from 'react'; 2 | import { Platform, Pressable, View } from 'react-native'; 3 | import { useCheckbox, useCheckboxGroupItem } from '@react-native-aria/checkbox'; 4 | import { useFocusRing } from '@react-native-aria/focus'; 5 | import { VisuallyHidden } from '@react-aria/visually-hidden'; 6 | import { useToggleState } from '@react-stately/toggle'; 7 | import { MaterialCommunityIcons } from '@expo/vector-icons'; 8 | import { CheckboxGroupContext } from './CheckboxGroup'; 9 | 10 | export function Checkbox(props: any) { 11 | let groupState = useContext(CheckboxGroupContext); 12 | let inputRef = useRef(null); 13 | 14 | let { isFocusVisible, focusProps } = useFocusRing(); 15 | 16 | let { inputProps } = groupState 17 | ? // eslint-disable-next-line react-hooks/rules-of-hooks 18 | useCheckboxGroupItem( 19 | { 20 | ...props, 21 | // Only pass isRequired and validationState to react-aria if they came from 22 | // the props for this individual checkbox, and not from the group via context. 23 | isRequired: props.isRequired, 24 | validationState: props.validationState, 25 | }, 26 | groupState, 27 | inputRef 28 | ) 29 | : // eslint-disable-next-line react-hooks/rules-of-hooks 30 | useCheckbox(props, useToggleState(props), inputRef); 31 | 32 | let icon: any = props.isIndeterminate 33 | ? 'checkbox-intermediate' 34 | : inputProps.checked 35 | ? 'checkbox-marked' 36 | : 'checkbox-blank-outline'; 37 | 38 | const iconColor = props.isDisabled ? '#d1d1d1' : '#000'; 39 | 40 | return ( 41 | 42 | {Platform.OS === 'web' ? ( 43 | 53 | ) : ( 54 | 55 | 56 | 57 | {props.children} 58 | 59 | 60 | )} 61 | 62 | ); 63 | } 64 | -------------------------------------------------------------------------------- /example/storybook/stories/Checkbox/CheckboxGroup.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useCheckboxGroupState } from "@react-stately/checkbox"; 3 | import { useCheckboxGroup } from "@react-native-aria/checkbox"; 4 | import { Text, View } from "react-native"; 5 | 6 | export let CheckboxGroupContext = React.createContext(null); 7 | 8 | export function CheckboxGroup(props: any) { 9 | let { children, label } = props; 10 | let state = useCheckboxGroupState(props); 11 | let { groupProps, labelProps } = useCheckboxGroup(props, state); 12 | 13 | return ( 14 | 15 | {label && {label}} 16 | 17 | {children} 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /example/storybook/stories/Combobox/Combobox.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { Wrapper } from "../Wrapper"; 4 | import { ComboBox } from "./index"; 5 | import { Item } from "@react-stately/collections"; 6 | 7 | export const Example = () => { 8 | return ( 9 | 10 | 11 | Red Panda 12 | Cat 13 | Dog 14 | Aardvark 15 | Kangaroo 16 | Snake 17 | 18 | 19 | ); 20 | }; 21 | 22 | storiesOf("Combobox", module).add("Basic", () => ); 23 | -------------------------------------------------------------------------------- /example/storybook/stories/Disclosure/Disclosure.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { View } from "react-native"; 4 | import { Disclosure } from "./index"; 5 | import { Wrapper } from "../Wrapper"; 6 | 7 | const DisclosureExample = () => { 8 | return ; 9 | }; 10 | 11 | const Example = () => { 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | storiesOf("Disclosure", module).add("Disclosure", Example); 22 | -------------------------------------------------------------------------------- /example/storybook/stories/Disclosure/Disclosure.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useToggleState } from "@react-stately/toggle"; 3 | import { 4 | useDisclosureButton, 5 | useDisclosure, 6 | } from "@react-native-aria/disclosure"; 7 | import { Pressable, Text, View } from "react-native"; 8 | 9 | type Props = { 10 | isOpen?: boolean; 11 | onChange?: (val: boolean) => void; 12 | onToggle?: () => void; 13 | defaultOpen?: boolean; 14 | isDisabled?: boolean; 15 | }; 16 | 17 | export const Disclosure = (props: Props) => { 18 | const state = useToggleState({ 19 | isSelected: props.isOpen, 20 | isDisabled: props.isDisabled, 21 | onChange: (val) => { 22 | props.onChange && props.onChange(val); 23 | props.onToggle && props.onToggle(); 24 | }, 25 | }); 26 | 27 | const { buttonProps } = useDisclosureButton({}, state); 28 | const { disclosureProps } = useDisclosure({}, state); 29 | 30 | return ( 31 | 32 | 33 | Press me to toggle below content 34 | 35 | {state.isSelected && ( 36 | 37 | Hello. My name is disclosed 38 | 39 | )} 40 | 41 | ); 42 | }; 43 | -------------------------------------------------------------------------------- /example/storybook/stories/Disclosure/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Disclosure"; 2 | -------------------------------------------------------------------------------- /example/storybook/stories/Listbox/Listbox.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { Wrapper } from "../Wrapper"; 4 | import { ListBox } from "./index"; 5 | import { Item } from "@react-stately/collections"; 6 | 7 | export const Example = () => { 8 | return ( 9 | 10 | 16 | One 17 | Two 18 | Three 19 | 20 | 21 | ); 22 | }; 23 | 24 | storiesOf("Listbox", module).add("Basic", Example); 25 | -------------------------------------------------------------------------------- /example/storybook/stories/Listbox/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useListBox, useOption } from "@react-native-aria/listbox"; 3 | import { useListState } from "@react-stately/list"; 4 | import { SpectrumListBoxProps } from "@react-types/listbox"; 5 | import { useFocusRing } from "@react-native-aria/focus"; 6 | import { mergeProps } from "@react-aria/utils"; 7 | import { Pressable, Text, View } from "react-native"; 8 | 9 | type IListBoxProps = SpectrumListBoxProps & { 10 | children: any; 11 | label?: string; 12 | }; 13 | 14 | export function ListBox(props: IListBoxProps) { 15 | // Create state based on the incoming props 16 | let state = useListState(props); 17 | 18 | // Get props for the listbox element 19 | let ref = React.useRef(); 20 | let { listBoxProps, labelProps } = useListBox(props, state, ref); 21 | 22 | return ( 23 | <> 24 | {props.label} 25 | 35 | {[...state.collection].map((item) => ( 36 | 39 | 40 | ); 41 | } 42 | 43 | function Option({ item, state }) { 44 | // Get props for the option element 45 | let ref = React.useRef(); 46 | let isDisabled = state.disabledKeys.has(item.key); 47 | let isSelected = state.selectionManager.isSelected(item.key); 48 | let { optionProps } = useOption( 49 | { 50 | key: item.key, 51 | isDisabled, 52 | isSelected, 53 | }, 54 | state, 55 | ref 56 | ); 57 | 58 | // Determine whether we should show a keyboard 59 | // focus ring for accessibility 60 | let { isFocusVisible, focusProps } = useFocusRing(); 61 | 62 | return ( 63 | 74 | {item.rendered} 75 | 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /example/storybook/stories/Menu/Menu.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { MenuButton } from "./index"; 4 | import { Item, Section } from "@react-stately/collections"; 5 | import { View } from "react-native"; 6 | import { Wrapper } from "../Wrapper"; 7 | 8 | const MenuExample = () => { 9 | return ( 10 | 11 | 18 |
19 | Copy 20 | Cut 21 | Paste 22 |
23 |
24 | Copy 25 | Cut 26 | Paste 27 |
28 |
29 |
30 | ); 31 | }; 32 | 33 | const Example = () => { 34 | return ( 35 | 36 | 37 | 38 | ); 39 | }; 40 | 41 | storiesOf("Menu", module).add("Menu", Example); 42 | -------------------------------------------------------------------------------- /example/storybook/stories/Menu/index.tsx: -------------------------------------------------------------------------------- 1 | export * from './Menu'; 2 | -------------------------------------------------------------------------------- /example/storybook/stories/Modal/Modal.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { View, Pressable } from "react-native"; 4 | import { Wrapper } from "../Wrapper"; 5 | import type { AriaDialogProps } from "react-aria"; 6 | import { useDialog } from "@react-native-aria/dialog"; 7 | 8 | interface DialogProps extends AriaDialogProps { 9 | title?: React.ReactNode; 10 | children: React.ReactNode; 11 | } 12 | 13 | function Dialog({ title, children, ...props }: DialogProps) { 14 | let ref = React.useRef(null); 15 | let { dialogProps, titleProps } = useDialog(props, ref); 16 | 17 | return ( 18 |
19 | {title && ( 20 |

21 | {title} 22 |

23 | )} 24 | {children} 25 |
26 | ); 27 | } 28 | 29 | const ModalExample = () => { 30 | const [isOpen, setIsOpen] = React.useState(false); 31 | return ( 32 | 33 | setIsOpen(true)}>Open 34 | {isOpen && ( 35 | 36 |
37 | 38 | 39 | 40 | 41 | setIsOpen(false)} 43 | style={{ marginTop: 10 }} 44 | > 45 | Submit 46 | 47 |
48 |
49 | )} 50 |
51 | ); 52 | }; 53 | 54 | const Example = () => { 55 | return ( 56 | 57 | 58 | 59 | ); 60 | }; 61 | 62 | storiesOf("Modal", module).add("Modal", Example); 63 | -------------------------------------------------------------------------------- /example/storybook/stories/Modal/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Modal"; 2 | -------------------------------------------------------------------------------- /example/storybook/stories/Overlays/Overlays.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { OverlayContainerExample } from "./index"; 4 | import { Wrapper } from "../Wrapper"; 5 | 6 | const MenuExample = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | storiesOf("Overlay", module).add("Overlay", MenuExample); 15 | -------------------------------------------------------------------------------- /example/storybook/stories/Overlays/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Pressable, 4 | View, 5 | Text, 6 | StyleSheet, 7 | TouchableWithoutFeedback, 8 | Button, 9 | } from "react-native"; 10 | import { 11 | OverlayContainer, 12 | useOverlayPosition, 13 | } from "@react-native-aria/overlays"; 14 | 15 | function CloseButton(props: any) { 16 | return ( 17 | 22 | 23 | 24 | ); 25 | } 26 | 27 | const OverlayContent = ({ targetRef }) => { 28 | let overlayRef = React.useRef(null); 29 | const { overlayProps } = useOverlayPosition({ 30 | placement: "bottom", 31 | targetRef, 32 | overlayRef, 33 | }); 34 | 35 | return ( 36 | 43 | This content will be mounted in OverlayProvider 44 | 45 | ); 46 | }; 47 | 48 | export function OverlayContainerExample(props: any) { 49 | const [visible, setVisible] = React.useState(false); 50 | 51 | let ref = React.useRef(null); 52 | 53 | return ( 54 | 55 | setVisible(!visible)} 59 | style={{ 60 | backgroundColor: "#F3F4F6", 61 | maxWidth: 100, 62 | padding: 10, 63 | justifyContent: "center", 64 | alignItems: "center", 65 | }} 66 | > 67 | Press me 68 | 69 | {visible && ( 70 | 71 | setVisible(!visible)} /> 72 | 73 | 74 | )} 75 | 76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /example/storybook/stories/Popover/Popover.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { PopoverExample } from "./index"; 4 | import { Wrapper } from "../Wrapper"; 5 | 6 | export const Example = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | storiesOf("Popover", module).add("Popover", Example); 15 | -------------------------------------------------------------------------------- /example/storybook/stories/Popover/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Pressable, 4 | View, 5 | Text, 6 | StyleSheet, 7 | TouchableWithoutFeedback, 8 | Button, 9 | } from "react-native"; 10 | import { 11 | OverlayContainer, 12 | OverlayProvider, 13 | useOverlayPosition, 14 | } from "@react-native-aria/overlays"; 15 | 16 | function CloseButton(props: any) { 17 | return ( 18 | 23 | 24 | 25 | ); 26 | } 27 | 28 | const PopoverContent = ({ targetRef }) => { 29 | let overlayRef = React.useRef(null); 30 | const { overlayProps } = useOverlayPosition({ 31 | placement: "top", 32 | targetRef, 33 | overlayRef, 34 | }); 35 | 36 | return ( 37 | 44 | 52 | 53 | Popover Title 54 | 55 | 56 | 57 | Lorem Ipsum is simply dummy text of the printing and typesetting 58 | industry. Lorem Ipsum has been the industry's standard dummy text 59 | ever since the 1500s, when an unknown printer took a galley of type 60 | and scrambled it to make a type specimen book. 61 | 62 | 63 | 70 | 71 | 72 | 73 | 74 | 75 | ); 76 | }; 77 | 78 | export function PopoverExample(props: any) { 79 | const [visible, setVisible] = React.useState(false); 80 | 81 | let ref = React.useRef(null); 82 | 83 | return ( 84 | 85 | setVisible(!visible)} 89 | style={{ 90 | backgroundColor: "#F3F4F6", 91 | maxWidth: 100, 92 | padding: 10, 93 | justifyContent: "center", 94 | alignItems: "center", 95 | }} 96 | > 97 | Press me 98 | 99 | {visible && ( 100 | 101 | setVisible(false)}> 102 | 103 | 104 | )} 105 | 106 | ); 107 | } 108 | -------------------------------------------------------------------------------- /example/storybook/stories/Radio/Radio.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { RadioGroup, Radio } from "./index"; 4 | import { Wrapper } from "../Wrapper"; 5 | 6 | const RadioExample = () => { 7 | return ( 8 | 9 | Dogs 10 | Cats 11 | 12 | ); 13 | }; 14 | 15 | export const Example = () => { 16 | return ( 17 | 18 | 19 | 20 | ); 21 | }; 22 | 23 | storiesOf("Radio", module).add("Radio group", Example); 24 | -------------------------------------------------------------------------------- /example/storybook/stories/Radio/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useRadioGroupState } from "@react-stately/radio"; 3 | import { useRadio, useRadioGroup } from "@react-native-aria/radio"; 4 | import { Platform, Pressable, Text, View } from "react-native"; 5 | import { VisuallyHidden } from "@react-aria/visually-hidden"; 6 | import { MaterialCommunityIcons } from "@expo/vector-icons"; 7 | import { useFocusRing } from "@react-native-aria/focus"; 8 | 9 | let RadioContext = React.createContext({}); 10 | 11 | export function RadioGroup(props: any) { 12 | let { children, label } = props; 13 | let state = useRadioGroupState(props); 14 | let { radioGroupProps, labelProps } = useRadioGroup(props, state); 15 | 16 | return ( 17 | 18 | {label} 19 | 26 | {children} 27 | 28 | 29 | ); 30 | } 31 | 32 | export function Radio(props: any) { 33 | let { state, isReadOnly, isDisabled } = React.useContext(RadioContext); 34 | const inputRef = React.useRef(null); 35 | let { inputProps } = useRadio( 36 | { isReadOnly, isDisabled, ...props }, 37 | state, 38 | inputRef 39 | ); 40 | let { isFocusVisible, focusProps } = useFocusRing(); 41 | 42 | let isSelected = state.selectedValue === props.value; 43 | const icon = isSelected ? "radiobox-marked" : "radiobox-blank"; 44 | 45 | return ( 46 | <> 47 | {Platform.OS === "web" ? ( 48 | 60 | ) : ( 61 | 62 | 63 | 64 | 65 | 66 | {props.children} 67 | 68 | {isSelected ? "selected" : "not selected"} 69 | 70 | )} 71 | 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /example/storybook/stories/Slider/Slider.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { Slider } from "./index"; 4 | import { Wrapper } from "../Wrapper"; 5 | 6 | const SliderExample = () => { 7 | return ( 8 | 9 | ); 10 | }; 11 | 12 | export const Example = () => { 13 | return ( 14 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | storiesOf("Slider", module).add("basic", Example); 21 | -------------------------------------------------------------------------------- /example/storybook/stories/Slider/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, Text, Pressable, Platform } from "react-native"; 3 | import { useSlider, useSliderThumb } from "@react-native-aria/slider"; 4 | import { useSliderState } from "@react-stately/slider"; 5 | import { useFocusRing } from "@react-native-aria/focus"; 6 | import { VisuallyHidden } from "@react-aria/visually-hidden"; 7 | import { mergeProps } from "@react-aria/utils"; 8 | 9 | const useLayout = () => { 10 | const [layout, setLayout] = React.useState({}); 11 | return { 12 | onLayout: (e) => { 13 | setLayout(e.nativeEvent.layout); 14 | }, 15 | layout, 16 | }; 17 | }; 18 | 19 | export function Slider(props) { 20 | let trackRef = React.useRef(null); 21 | const { onLayout, layout } = useLayout(); 22 | let state = useSliderState({ 23 | ...props, 24 | numberFormatter: { format: (e) => e }, 25 | }); 26 | let { groupProps, trackProps, labelProps, outputProps } = useSlider( 27 | props, 28 | state, 29 | layout 30 | ); 31 | 32 | return ( 33 | 40 | {/* Create a flex container for the label and output element. */} 41 | 42 | {props.label && {props.label}} 43 | {Platform.OS === "web" && ( 44 | 48 | {state.getThumbValueLabel(0)} 49 | 50 | )} 51 | 52 | {/* The track element holds the visible track line and the thumb. */} 53 | 54 | 63 | 72 | 73 | 74 | 75 | 76 | ); 77 | } 78 | 79 | function Thumb(props) { 80 | let { state, trackLayout, index } = props; 81 | let inputRef = React.useRef(null); 82 | const { onLayout, layout } = useLayout(); 83 | let { thumbProps, inputProps } = useSliderThumb( 84 | { 85 | index, 86 | trackLayout, 87 | inputRef, 88 | }, 89 | state 90 | ); 91 | 92 | let { focusProps, isFocusVisible } = useFocusRing(); 93 | 94 | return ( 95 | 104 | 117 | {Platform.OS === "web" && ( 118 | 119 | 120 | 121 | )} 122 | 123 | 124 | ); 125 | } 126 | -------------------------------------------------------------------------------- /example/storybook/stories/Switch/Switch.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { ControlledSwitch } from "./index"; 4 | import { Wrapper } from "../Wrapper"; 5 | 6 | export const Example = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | storiesOf("Switch", module).add("Switch", Example); 15 | -------------------------------------------------------------------------------- /example/storybook/stories/Tabs/Tabs.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { Wrapper } from "../Wrapper"; 4 | import { TabsExample } from "./index"; 5 | import { Item } from "@react-stately/collections"; 6 | import { Text } from "react-native"; 7 | 8 | export const Example = () => { 9 | return ( 10 | 11 | 12 | 13 | Tab 1 Content 14 | 15 | 16 | Tab 2 Content 17 | 18 | 19 | Tab 3 Content 20 | 21 | 22 | 23 | ); 24 | }; 25 | 26 | storiesOf("Tabs", module).add("Basic", Example); 27 | -------------------------------------------------------------------------------- /example/storybook/stories/Tabs/index.tsx: -------------------------------------------------------------------------------- 1 | import React, { MutableRefObject } from "react"; 2 | import { Pressable, StyleSheet, Text } from "react-native"; 3 | import { useTabsState } from "@react-stately/tabs"; 4 | import { useTabs, useTab } from "@react-native-aria/tabs"; 5 | import { SpectrumTabsProps } from "@react-types/tabs"; 6 | import { Orientation, DOMProps, Node } from "@react-types/shared"; 7 | import { SingleSelectListState } from "@react-stately/list"; 8 | import { View } from "react-native"; 9 | 10 | 11 | export function TabsExample(props: SpectrumTabsProps) { 12 | let state = useTabsState(props); 13 | let tablistRef = React.useRef(); 14 | let { tabListProps, tabPanelProps } = useTabs(props, state, tablistRef); 15 | 16 | return ( 17 | <> 18 | 24 | 25 | {state.selectedItem && state.selectedItem.props.children} 26 | 27 | 28 | ); 29 | } 30 | 31 | interface TabListProps { 32 | isQuiet?: boolean; 33 | density?: "compact" | "regular"; 34 | isDisabled?: boolean; 35 | orientation?: Orientation; 36 | state: SingleSelectListState; 37 | selectedTab: HTMLElement; 38 | className?: string; 39 | } 40 | 41 | const TabList = React.forwardRef(function ( 42 | props: TabListProps, 43 | ref: any 44 | ) { 45 | let { 46 | isQuiet, 47 | density, 48 | state, 49 | isDisabled, 50 | orientation, 51 | selectedTab, 52 | className, 53 | ...otherProps 54 | } = props; 55 | 56 | return ( 57 | 58 | {[...state.collection].map((item) => ( 59 | 66 | ))} 67 | 68 | ); 69 | }); 70 | 71 | interface TabProps extends DOMProps { 72 | item: Node; 73 | state: SingleSelectListState; 74 | isDisabled?: boolean; 75 | orientation?: Orientation; 76 | } 77 | 78 | export function Tab(props: TabProps) { 79 | let { item, state, isDisabled: propsDisabled } = props; 80 | let { key, rendered } = item; 81 | let isDisabled = propsDisabled || state.disabledKeys.has(key); 82 | let ref = React.useRef(); 83 | let { tabProps } = useTab({ item, isDisabled }, state, ref); 84 | let isSelected = state.selectedKey === key; 85 | const style = styles({ ...props, isSelected }).tab; 86 | const textStyle = styles({ ...props, isSelected }).tabText; 87 | 88 | 89 | return ( 90 | 91 | {typeof rendered === "string" ? {rendered} : rendered} 92 | 93 | ); 94 | } 95 | 96 | const styles = (props: any) => { 97 | return StyleSheet.create({ 98 | tab: { 99 | marginLeft: 10, 100 | }, 101 | tabText: { 102 | color: props.isSelected ? "#1E40AF" : "#1F2937", 103 | borderBottomColor: "#1E40AF", 104 | borderBottomWidth: props.isSelected ? 2 : 0, 105 | padding: 10, 106 | }, 107 | tabButtons: { 108 | flexDirection: props.orientation === "vertical" ? "column" : "row", 109 | }, 110 | }); 111 | }; 112 | -------------------------------------------------------------------------------- /example/storybook/stories/Tooltip/Tooltip.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Platform } from "react-native"; 3 | import { storiesOf } from "@storybook/react-native"; 4 | import { TooltipExample } from "./index"; 5 | import { Wrapper } from "../Wrapper"; 6 | 7 | export const Example = () => { 8 | if (Platform.OS === "web") { 9 | return ( 10 | 11 | 12 | 13 | ); 14 | } 15 | 16 | return null; 17 | }; 18 | 19 | storiesOf("Tooltip", module).add("Tooltip", Example); 20 | -------------------------------------------------------------------------------- /example/storybook/stories/Tooltip/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { View, Pressable, Text } from "react-native"; 3 | import { useTooltipTriggerState } from "@react-stately/tooltip"; 4 | import { mergeProps } from "@react-aria/utils"; 5 | import { useTooltip, useTooltipTrigger } from "@react-native-aria/tooltip"; 6 | 7 | function Tooltip({ state, ...props }) { 8 | let { tooltipProps } = useTooltip(props, state); 9 | 10 | return ( 11 | 22 | {props.children} 23 | 24 | ); 25 | } 26 | 27 | export function TooltipExample(props) { 28 | let state = useTooltipTriggerState(props); 29 | let ref = React.useRef(); 30 | 31 | // Get props for the trigger and its tooltip 32 | let { triggerProps, tooltipProps } = useTooltipTrigger(props, state, ref); 33 | 34 | return ( 35 | 36 | 41 | I have a tooltip 42 | 43 | {state.isOpen && ( 44 | 45 | And the tooltip tells you more information. 46 | 47 | )} 48 | 49 | ); 50 | } 51 | -------------------------------------------------------------------------------- /example/storybook/stories/Wrapper.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { SafeAreaView } from "react-native"; 3 | import { OverlayProvider } from "@react-native-aria/overlays"; 4 | import { SSRProvider } from "@react-native-aria/utils"; 5 | 6 | export function Wrapper({ children }) { 7 | return ( 8 | 9 | 10 | {children} 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /example/storybook/stories/index.js: -------------------------------------------------------------------------------- 1 | import "./Slider/Slider.stories"; 2 | import "./Combobox/Combobox.stories"; 3 | import "./Listbox/Listbox.stories"; 4 | import "./Tabs/Tabs.stories"; 5 | import "./Menu/Menu.stories"; 6 | import "./Modal/Modal.stories" 7 | // import "./Button/Button.stories"; 8 | // import "./Button/ToggleButton.stories"; 9 | // import "./Checkbox/Checkbox.stories"; 10 | // import "./Radio/Radio.stories"; 11 | // import "./Switch/Switch.stories"; 12 | // import "./useOverlayPosition/useOverlayPosition.stories"; 13 | // import "./Tooltip/Tooltip.stories"; 14 | // import "./Overlays/Overlays.stories"; 15 | // import "./Disclosure/Disclosure.stories"; 16 | -------------------------------------------------------------------------------- /example/storybook/stories/useOverlayPosition/useOverlayPosition.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { storiesOf } from "@storybook/react-native"; 3 | import { TriggerWrapper } from "./useOverlayPosition"; 4 | import { Wrapper } from "../Wrapper"; 5 | 6 | const Example = () => { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | }; 13 | 14 | storiesOf("useOverlayPosition", module).add("useOverlayPosition", Example); 15 | -------------------------------------------------------------------------------- /example/storybook/stories/useOverlayPosition/useOverlayPosition.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | OverlayContainer, 4 | useOverlayPosition, 5 | } from "@react-native-aria/overlays"; 6 | import { useButton } from "@react-native-aria/button"; 7 | import { 8 | View, 9 | Text, 10 | Pressable, 11 | TouchableWithoutFeedback, 12 | StyleSheet, 13 | findNodeHandle, 14 | ScrollView, 15 | Platform, 16 | SafeAreaView, 17 | } from "react-native"; 18 | import { useToggleState } from "@react-stately/toggle"; 19 | 20 | // Button to close overlay on outside click 21 | function CloseButton(props) { 22 | return ( 23 | 28 | 29 | 30 | ); 31 | } 32 | 33 | const positions = [ 34 | "top", 35 | "left", 36 | "right", 37 | "bottom", 38 | "top left", 39 | "top right", 40 | "left top", 41 | "left bottom", 42 | "bottom right, bottom left", 43 | "right top", 44 | "right bottom", 45 | ]; 46 | 47 | export function TriggerWrapper() { 48 | const [placement, setPlacement] = React.useState(-1); 49 | React.useEffect(() => { 50 | const id = setInterval(() => { 51 | setPlacement((prev) => (prev + 1) % positions.length); 52 | }, 2000); 53 | return () => clearInterval(id); 54 | }, []); 55 | 56 | return ; 57 | } 58 | 59 | const OverlayView = ({ targetRef, placement }) => { 60 | let overlayRef = React.useRef(); 61 | 62 | const { overlayProps } = useOverlayPosition({ 63 | placement: "top", 64 | targetRef, 65 | overlayRef, 66 | offset: 10, 67 | }); 68 | 69 | return ( 70 | { 79 | if (Platform.OS === "web") { 80 | overlayRef.current = findNodeHandle(node); 81 | } else { 82 | overlayRef.current = node; 83 | } 84 | }} 85 | > 86 | 92 | Hello world 93 | 94 | 95 | ); 96 | }; 97 | 98 | export default function Trigger({ placement }: any) { 99 | let ref = React.useRef(); 100 | const toggleState = useToggleState(); 101 | 102 | let { buttonProps } = useButton({ onPress: toggleState.toggle }, ref); 103 | 104 | return ( 105 | 112 | 118 | 126 | Trigger 127 | 128 | 129 | {toggleState.isSelected && ( 130 | 131 | 132 | 133 | 134 | )} 135 | 136 | ); 137 | } 138 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react-native", 4 | "target": "esnext", 5 | "lib": [ 6 | "esnext" 7 | ], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "noEmit": true, 11 | "allowSyntheticDefaultImports": true, 12 | "resolveJsonModule": true, 13 | "esModuleInterop": true, 14 | "moduleResolution": "node", 15 | "paths": { 16 | "@react-native-aria/*": ["../packages/*/src/index"] 17 | }, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const createExpoWebpackConfigAsync = require("@expo/webpack-config"); 3 | const { resolver } = require("./metro.config"); 4 | 5 | const root = path.resolve(__dirname, ".."); 6 | const node_modules = path.join(__dirname, "node_modules"); 7 | 8 | module.exports = async function (env, argv) { 9 | const config = await createExpoWebpackConfigAsync(env, argv); 10 | 11 | config.module.rules.push({ 12 | test: /\.(js|jsx|ts|tsx)$/, 13 | include: path.resolve(root, "packages"), 14 | use: "babel-loader", 15 | }); 16 | 17 | // We need to make sure that only one version is loaded for peerDependencies 18 | // So we alias them to the versions in example's node_modules 19 | Object.assign(config.resolve.alias, { 20 | ...resolver.extraNodeModules, 21 | "react-native-web": path.join(node_modules, "react-native-web"), 22 | // Major Hack : Fix later, Resolve to root react to prevent invalid hook call error 23 | react: path.join(root, "node_modules", "react"), 24 | }); 25 | 26 | return config; 27 | }; 28 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "lerna": "3.0.0", 3 | "version": "independent", 4 | "useWorkspaces": true, 5 | "command": { 6 | "publish": { 7 | "allowBranch": [ 8 | "main", 9 | "dev" 10 | ] 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-bob-mono", 3 | "version": "0.1.0", 4 | "description": "mono repo setup with bob", 5 | "private": true, 6 | "workspaces": [ 7 | "packages/*" 8 | ], 9 | "devDependencies": { 10 | "@commitlint/config-conventional": "^11.0.0", 11 | "@react-native-community/eslint-config": "^2.0.0", 12 | "@release-it/conventional-changelog": "^2.0.0", 13 | "@types/jest": "^26.0.0", 14 | "@types/react": "^16.9.19", 15 | "@types/react-native": "0.72.3", 16 | "commitlint": "^11.0.0", 17 | "eslint": "^7.2.0", 18 | "eslint-config-prettier": "^7.0.0", 19 | "eslint-plugin-prettier": "^3.1.3", 20 | "husky": "^4.2.5", 21 | "jest": "^26.0.1", 22 | "pod-install": "^0.1.0", 23 | "prettier": "^2.0.5", 24 | "react": "16.13.1", 25 | "react-dom": "16.13.1", 26 | "react-native": "0.72.5", 27 | "react-native-builder-bob": "^0.17.1", 28 | "typescript": "^4.1.3" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/accordion/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ```js 4 | yarn add @react-native-aria/accordion 5 | ``` 6 | -------------------------------------------------------------------------------- /packages/accordion/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/accordion/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-native-aria/accordion", 3 | "version": "0.0.2", 4 | "description": "mono repo setup with bob", 5 | "main": "lib/commonjs/index", 6 | "module": "lib/module/index", 7 | "typings": "lib/typescript/index.d.ts", 8 | "react-native": "src/index", 9 | "source": "src/index", 10 | "files": [ 11 | "src", 12 | "lib", 13 | "android", 14 | "ios", 15 | "cpp", 16 | "react-native-bob-mono.podspec", 17 | "!lib/typescript/example", 18 | "!android/build", 19 | "!ios/build", 20 | "!**/__tests__", 21 | "!**/__fixtures__", 22 | "!**/__mocks__" 23 | ], 24 | "scripts": { 25 | "test": "jest", 26 | "typescript": "tsc --noEmit", 27 | "lint": "eslint \"**/*.{js,ts,tsx}\"", 28 | "prepare": "bob build", 29 | "release": "release-it", 30 | "example": "yarn --cwd example", 31 | "pods": "cd example && pod-install --quiet", 32 | "bootstrap": "yarn example && yarn && yarn pods" 33 | }, 34 | "keywords": [ 35 | "react-native", 36 | "ios", 37 | "android" 38 | ], 39 | "repository": "https://github.com/intergalacticspacehighway/react-native-bob-mono", 40 | "author": "nishan (https://github.com/intergalacticspacehighway)", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/intergalacticspacehighway/react-native-bob-mono/issues" 44 | }, 45 | "homepage": "https://github.com/intergalacticspacehighway/react-native-bob-mono#readme", 46 | "publishConfig": { 47 | "registry": "https://registry.npmjs.org/" 48 | }, 49 | "dependencies": {}, 50 | "peerDependencies": { 51 | "react": "*", 52 | "react-native": "*" 53 | }, 54 | "jest": { 55 | "preset": "react-native", 56 | "modulePathIgnorePatterns": [ 57 | "/example/node_modules", 58 | "/lib/" 59 | ] 60 | }, 61 | "husky": { 62 | "hooks": { 63 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 64 | "pre-commit": "yarn lint && yarn typescript" 65 | } 66 | }, 67 | "commitlint": { 68 | "extends": [ 69 | "@commitlint/config-conventional" 70 | ] 71 | }, 72 | "release-it": { 73 | "git": { 74 | "commitMessage": "chore: release ${version}", 75 | "tagName": "v${version}" 76 | }, 77 | "npm": { 78 | "publish": true 79 | }, 80 | "github": { 81 | "release": true 82 | }, 83 | "plugins": { 84 | "@release-it/conventional-changelog": { 85 | "preset": "angular" 86 | } 87 | } 88 | }, 89 | "eslintConfig": { 90 | "root": true, 91 | "extends": [ 92 | "@react-native-community", 93 | "prettier" 94 | ], 95 | "rules": { 96 | "prettier/prettier": [ 97 | "error", 98 | { 99 | "quoteProps": "consistent", 100 | "singleQuote": true, 101 | "tabWidth": 2, 102 | "trailingComma": "es5", 103 | "useTabs": false 104 | } 105 | ] 106 | } 107 | }, 108 | "eslintIgnore": [ 109 | "node_modules/", 110 | "lib/" 111 | ], 112 | "prettier": { 113 | "quoteProps": "consistent", 114 | "singleQuote": true, 115 | "tabWidth": 2, 116 | "trailingComma": "es5", 117 | "useTabs": false 118 | }, 119 | "react-native-builder-bob": { 120 | "source": "src", 121 | "output": "lib", 122 | "targets": [ 123 | "commonjs", 124 | "module", 125 | [ 126 | "typescript", 127 | { 128 | "project": "tsconfig.build.json" 129 | } 130 | ] 131 | ] 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /packages/accordion/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const child_process = require('child_process'); 3 | 4 | const root = path.resolve(__dirname, '..'); 5 | const args = process.argv.slice(2); 6 | const options = { 7 | cwd: process.cwd(), 8 | env: process.env, 9 | stdio: 'inherit', 10 | encoding: 'utf-8', 11 | }; 12 | 13 | let result; 14 | 15 | if (process.cwd() !== root || args.length) { 16 | // We're not in the root of the project, or additional arguments were passed 17 | // In this case, forward the command to `yarn` 18 | result = child_process.spawnSync('yarn', args, options); 19 | } else { 20 | // If `yarn` is run without arguments, perform bootstrap 21 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 22 | } 23 | 24 | process.exitCode = result.status; 25 | -------------------------------------------------------------------------------- /packages/accordion/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useAccordion'; 2 | export * from './useAccordionItem'; 3 | -------------------------------------------------------------------------------- /packages/accordion/src/types.ts: -------------------------------------------------------------------------------- 1 | export type State = { 2 | selectedValues: string[]; 3 | toggleItem: (itemValue: string, isDisabled?: boolean) => void; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/accordion/src/useAccordion.ts: -------------------------------------------------------------------------------- 1 | type Props = { 2 | type: 'single' | 'multiple'; 3 | isCollapsible: boolean; 4 | selectedValues: string[]; 5 | setSelectedValues: (values: string[]) => void; 6 | }; 7 | 8 | export const useAccordion = (props: Props) => { 9 | const { type, isCollapsible, selectedValues, setSelectedValues } = props; 10 | 11 | /* 12 | * The toggleItem function is responsible for updating the selected values 13 | * based on the type of accordion (single or multiple) and whether or not 14 | * the accordion is collapsible. 15 | */ 16 | const toggleItem = (itemValue: string, isDisabled = false) => { 17 | if (isDisabled || !itemValue) return; 18 | 19 | if (type === 'single') { 20 | if (isCollapsible) { 21 | if (selectedValues.includes(itemValue)) { 22 | setSelectedValues([]); 23 | } else { 24 | setSelectedValues([itemValue]); 25 | } 26 | } else { 27 | if (selectedValues.includes(itemValue)) return; 28 | setSelectedValues([itemValue]); 29 | } 30 | } else { 31 | if (isCollapsible) { 32 | if (selectedValues.includes(itemValue)) { 33 | setSelectedValues(selectedValues.filter((v) => v !== itemValue)); 34 | } else { 35 | setSelectedValues([...selectedValues, itemValue]); 36 | } 37 | } else { 38 | if (selectedValues.includes(itemValue)) return; 39 | setSelectedValues([...selectedValues, itemValue]); 40 | } 41 | } 42 | }; 43 | 44 | return { 45 | state: { 46 | selectedValues, 47 | toggleItem, 48 | }, 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /packages/accordion/src/useAccordionItem.ts: -------------------------------------------------------------------------------- 1 | import { State } from './types'; 2 | 3 | type Props = { 4 | value: string; 5 | isExpanded: boolean; 6 | isDisabled: boolean; 7 | }; 8 | 9 | export const useAccordionItem = (state: State, props: Props) => { 10 | const { toggleItem } = state; 11 | const { value, isExpanded, isDisabled } = props; 12 | 13 | // Generate unique IDs for each accordion trigger and region 14 | const buttonId = `accordion-button-${value}`; 15 | const regionId = `accordion-region-${value}`; 16 | 17 | const toggle = () => { 18 | toggleItem(value, isDisabled); 19 | }; 20 | 21 | return { 22 | regionProps: { 23 | 'id': regionId, 24 | 'aria-labelledby': buttonId, 25 | 'role': 'region', 26 | }, 27 | buttonProps: { 28 | 'id': buttonId, 29 | 'aria-controls': regionId, 30 | 'aria-disabled': isDisabled, 31 | 'aria-expanded': isExpanded, 32 | 'onPress': toggle, 33 | 'role': 'button', 34 | }, 35 | isExpanded, 36 | }; 37 | }; 38 | -------------------------------------------------------------------------------- /packages/accordion/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "../tsconfig", 4 | } 5 | -------------------------------------------------------------------------------- /packages/button/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ```js 4 | yarn add @react-native-aria/button 5 | ``` 6 | -------------------------------------------------------------------------------- /packages/button/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/button/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const child_process = require('child_process'); 3 | 4 | const root = path.resolve(__dirname, '..'); 5 | const args = process.argv.slice(2); 6 | const options = { 7 | cwd: process.cwd(), 8 | env: process.env, 9 | stdio: 'inherit', 10 | encoding: 'utf-8', 11 | }; 12 | 13 | let result; 14 | 15 | if (process.cwd() !== root || args.length) { 16 | // We're not in the root of the project, or additional arguments were passed 17 | // In this case, forward the command to `yarn` 18 | result = child_process.spawnSync('yarn', args, options); 19 | } else { 20 | // If `yarn` is run without arguments, perform bootstrap 21 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 22 | } 23 | 24 | process.exitCode = result.status; 25 | -------------------------------------------------------------------------------- /packages/button/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useButton'; 2 | export * from './useToggleButton'; 3 | -------------------------------------------------------------------------------- /packages/button/src/index.web.ts: -------------------------------------------------------------------------------- 1 | export * from "./useButton"; 2 | export * from "./useToggleButton.web"; 3 | -------------------------------------------------------------------------------- /packages/button/src/useButton.ts: -------------------------------------------------------------------------------- 1 | import { ReactNode } from 'react'; 2 | import { PressEvents, usePress } from '@react-native-aria/interactions'; 3 | import { AccessibilityProps, PressableProps } from 'react-native'; 4 | import { mergeProps } from '@react-aria/utils'; 5 | 6 | interface ButtonProps extends PressEvents { 7 | /** Whether the button is disabled. */ 8 | isDisabled?: boolean; 9 | /** The content to display in the button. */ 10 | children?: ReactNode; 11 | } 12 | 13 | export interface RNAriaButtonProps extends AccessibilityProps, ButtonProps {} 14 | 15 | export interface ButtonAria { 16 | /** Props for the button element. */ 17 | buttonProps: PressableProps; 18 | /** Whether the button is currently pressed. */ 19 | isPressed: boolean; 20 | } 21 | 22 | export function useButton(props: RNAriaButtonProps): ButtonAria { 23 | let { 24 | isDisabled, 25 | onPress, 26 | onPressStart, 27 | onPressEnd, 28 | onPressChange, 29 | ...rest 30 | } = props; 31 | 32 | let { pressProps, isPressed } = usePress({ 33 | onPressStart, 34 | onPressEnd, 35 | onPressChange, 36 | onPress, 37 | isDisabled, 38 | }); 39 | 40 | const mergedProps = mergeProps(pressProps, rest, { 41 | 'aria-disabled': isDisabled, 42 | 'role': 'button', 43 | 'disabled': isDisabled, 44 | }); 45 | 46 | return { 47 | isPressed, 48 | buttonProps: mergedProps, 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /packages/button/src/useToggleButton.ts: -------------------------------------------------------------------------------- 1 | import { ButtonAria, useButton } from './useButton'; 2 | import type { ToggleState } from '@react-stately/toggle'; 3 | import { chain, mergeProps } from '@react-aria/utils'; 4 | import type { PressEvents } from '@react-native-aria/interactions'; 5 | import type { PressableProps } from 'react-native'; 6 | 7 | export type AriaButtonProps = PressableProps & 8 | PressEvents & { 9 | isDisabled: boolean; 10 | }; 11 | 12 | export interface AriaToggleButtonProps extends AriaButtonProps { 13 | /** Whether the element should be selected (controlled). */ 14 | isSelected?: boolean; 15 | /** Whether the element should be selected (uncontrolled). */ 16 | defaultSelected?: boolean; 17 | /** Handler that is called when the element's selection state changes. */ 18 | onChange?: (isSelected: boolean) => void; 19 | } 20 | 21 | export function useToggleButton( 22 | props: AriaToggleButtonProps, 23 | state: ToggleState 24 | ): ButtonAria { 25 | const { isSelected } = state; 26 | const { isPressed, buttonProps } = useButton({ 27 | ...props, 28 | onPress: chain(state.toggle, props.onPress), 29 | }); 30 | 31 | return { 32 | isPressed, 33 | buttonProps: mergeProps(buttonProps, { 34 | 'aria-selected': isSelected, 35 | }), 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /packages/button/src/useToggleButton.web.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { ElementType } from 'react'; 14 | import { AriaToggleButtonProps } from '@react-types/button'; 15 | import { useButton } from './useButton'; 16 | import { chain } from '@react-aria/utils'; 17 | import { mergeProps } from '@react-aria/utils'; 18 | import { ToggleState } from '@react-stately/toggle'; 19 | 20 | /** 21 | * Provides the behavior and accessibility implementation for a toggle button component. 22 | * ToggleButtons allow users to toggle a selection on or off, for example switching between two states or modes. 23 | */ 24 | export function useToggleButton( 25 | props: AriaToggleButtonProps, 26 | state: ToggleState 27 | ): any { 28 | /* eslint-enable no-redeclare */ 29 | const { isSelected } = state; 30 | const { isPressed, buttonProps } = useButton({ 31 | ...props, 32 | onPress: chain(state.toggle, props.onPress), 33 | }); 34 | 35 | return { 36 | isPressed, 37 | buttonProps: mergeProps(buttonProps, { 38 | 'aria-pressed': isSelected, 39 | }), 40 | }; 41 | } 42 | -------------------------------------------------------------------------------- /packages/button/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "../tsconfig", 4 | } 5 | -------------------------------------------------------------------------------- /packages/checkbox/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ```js 4 | yarn add @react-native-aria/checkbox 5 | ``` 6 | 7 | ## Usage 8 | 9 | ```js 10 | import { 11 | useCheckbox, 12 | useCheckboxGroup, 13 | useCheckboxGroupItem, 14 | } from '@react-native-aria/checkbox'; 15 | ``` 16 | -------------------------------------------------------------------------------- /packages/checkbox/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/checkbox/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const child_process = require('child_process'); 3 | 4 | const root = path.resolve(__dirname, '..'); 5 | const args = process.argv.slice(2); 6 | const options = { 7 | cwd: process.cwd(), 8 | env: process.env, 9 | stdio: 'inherit', 10 | encoding: 'utf-8', 11 | }; 12 | 13 | let result; 14 | 15 | if (process.cwd() !== root || args.length) { 16 | // We're not in the root of the project, or additional arguments were passed 17 | // In this case, forward the command to `yarn` 18 | result = child_process.spawnSync('yarn', args, options); 19 | } else { 20 | // If `yarn` is run without arguments, perform bootstrap 21 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 22 | } 23 | 24 | process.exitCode = result.status; 25 | -------------------------------------------------------------------------------- /packages/checkbox/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useCheckbox'; 2 | export * from './useCheckboxGroup'; 3 | export * from './useCheckboxGroupItem'; 4 | -------------------------------------------------------------------------------- /packages/checkbox/src/index.web.ts: -------------------------------------------------------------------------------- 1 | export { useCheckbox } from "@react-aria/checkbox"; 2 | export { useCheckboxGroup } from "./useCheckboxGroup.web"; 3 | export { useCheckboxGroupItem } from "@react-aria/checkbox"; 4 | -------------------------------------------------------------------------------- /packages/checkbox/src/useCheckbox.ts: -------------------------------------------------------------------------------- 1 | import type { RefObject } from 'react'; 2 | import type { ToggleState } from '@react-stately/toggle'; 3 | import { mergeProps } from '@react-aria/utils'; 4 | import { useToggle } from '@react-native-aria/toggle'; 5 | import type { AccessibilityProps } from 'react-native'; 6 | import { AriaCheckboxProps } from '@react-types/checkbox'; 7 | 8 | export interface CheckboxAria extends AccessibilityProps { 9 | /** Props for the input or Pressable/Touchable element. */ 10 | inputProps: any; 11 | } 12 | 13 | /** 14 | * Provides the behavior and accessibility implementation for a checkbox component. 15 | * Checkboxes allow users to select multiple items from a list of individual items, or 16 | * to mark one individual item as selected. 17 | * @param props - Props for the checkbox. 18 | * @param state - State for the checkbox, as returned by `useToggleState`. 19 | * @param inputRef - A ref for the HTML input element. 20 | */ 21 | export function useCheckbox( 22 | props: AriaCheckboxProps, 23 | state: ToggleState, 24 | inputRef: RefObject 25 | ): CheckboxAria { 26 | let { inputProps } = useToggle(props, state, inputRef); 27 | let { isSelected } = state; 28 | 29 | let { isIndeterminate } = props; 30 | 31 | return { 32 | inputProps: mergeProps(inputProps, { 33 | 'checked': isSelected, 34 | 'role': 'checkbox', 35 | 'aria-checked': isIndeterminate ? 'mixed' : isSelected, 36 | 'aria-disabled': props.isDisabled, 37 | }), 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /packages/checkbox/src/useCheckboxGroup.ts: -------------------------------------------------------------------------------- 1 | import type { CheckboxGroupState } from '@react-stately/checkbox'; 2 | import { mergeProps, filterDOMProps } from '@react-aria/utils'; 3 | import { getLabel } from '@react-native-aria/utils'; 4 | import { AriaCheckboxGroupProps } from '@react-types/checkbox'; 5 | 6 | interface CheckboxGroupAria { 7 | /** Props for the checkbox group wrapper element. */ 8 | groupProps: any; 9 | /** Props for the checkbox group's visible label (if any). */ 10 | labelProps: any; 11 | } 12 | 13 | /** 14 | * Provides the behavior and accessibility implementation for a checkbox group component. 15 | * Checkbox groups allow users to select multiple items from a list of options. 16 | * @param props - Props for the checkbox group. 17 | * @param state - State for the checkbox group, as returned by `useCheckboxGroupState`. 18 | */ 19 | export function useCheckboxGroup( 20 | props: AriaCheckboxGroupProps, 21 | _state: CheckboxGroupState 22 | ): CheckboxGroupAria { 23 | let { isDisabled } = props; 24 | 25 | let domProps = filterDOMProps(props, { labelable: true }); 26 | 27 | return { 28 | groupProps: mergeProps(domProps, { 29 | ' aria-disabled': isDisabled, 30 | 'aria-label': getLabel(props), 31 | }), 32 | labelProps: {}, 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /packages/checkbox/src/useCheckboxGroup.web.ts: -------------------------------------------------------------------------------- 1 | import { useCheckboxGroup as useCheckboxGroupWeb } from '@react-aria/checkbox'; 2 | import { AriaCheckboxGroupProps } from '@react-types/checkbox'; 3 | import { CheckboxGroupState } from '@react-stately/checkbox'; 4 | import { mapDomPropsToRN } from '@react-native-aria/utils'; 5 | 6 | interface CheckboxGroupAria { 7 | /** Props for the checkbox group wrapper element. */ 8 | groupProps: any; 9 | /** Props for the checkbox group's visible label (if any). */ 10 | labelProps: any; 11 | } 12 | 13 | /** 14 | * Provides the behavior and accessibility implementation for a checkbox group component. 15 | * Checkbox groups allow users to select multiple items from a list of options. 16 | * @param props - Props for the checkbox group. 17 | * @param state - State for the checkbox group, as returned by `useCheckboxGroupState`. 18 | */ 19 | export function useCheckboxGroup( 20 | props: AriaCheckboxGroupProps, 21 | state: CheckboxGroupState 22 | ): CheckboxGroupAria { 23 | const params = useCheckboxGroupWeb(props, state); 24 | return { 25 | labelProps: { 26 | ...params.labelProps, 27 | ...mapDomPropsToRN(params.labelProps), 28 | }, 29 | groupProps: { 30 | ...params.groupProps, 31 | ...mapDomPropsToRN(params.groupProps), 32 | }, 33 | }; 34 | } 35 | -------------------------------------------------------------------------------- /packages/checkbox/src/useCheckboxGroupItem.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import type { AriaCheckboxGroupItemProps } from '@react-types/checkbox'; 14 | import { CheckboxAria, useCheckbox } from './useCheckbox'; 15 | import type { CheckboxGroupState } from '@react-stately/checkbox'; 16 | import type { RefObject } from 'react'; 17 | import { useToggleState } from '@react-stately/toggle'; 18 | 19 | /** 20 | * Provides the behavior and accessibility implementation for a checkbox component contained within a checkbox group. 21 | * Checkbox groups allow users to select multiple items from a list of options. 22 | * @param props - Props for the checkbox. 23 | * @param state - State for the checkbox, as returned by `useCheckboxGroupState`. 24 | * @param inputRef - A ref for the HTML input element. 25 | */ 26 | export function useCheckboxGroupItem( 27 | props: AriaCheckboxGroupItemProps, 28 | state: CheckboxGroupState, 29 | inputRef: RefObject 30 | ): CheckboxAria { 31 | const toggleState = useToggleState({ 32 | isReadOnly: props.isReadOnly || state.isReadOnly, 33 | //@ts-ignore 34 | isSelected: state.isSelected(props.value), 35 | onChange(isSelected) { 36 | if (isSelected) { 37 | //@ts-ignore 38 | state.addValue(props.value); 39 | } else { 40 | //@ts-ignore 41 | state.removeValue(props.value); 42 | } 43 | 44 | if (props.onChange) { 45 | props.onChange(isSelected); 46 | } 47 | }, 48 | }); 49 | 50 | let { inputProps } = useCheckbox( 51 | { 52 | ...props, 53 | isReadOnly: props.isReadOnly || state.isReadOnly, 54 | isDisabled: props.isDisabled || state.isDisabled, 55 | }, 56 | toggleState, 57 | inputRef 58 | ); 59 | 60 | return { inputProps }; 61 | } 62 | -------------------------------------------------------------------------------- /packages/checkbox/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { CheckboxGroupState } from "@react-stately/checkbox"; 2 | 3 | export const checkboxGroupNames = new WeakMap(); 4 | -------------------------------------------------------------------------------- /packages/checkbox/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "../tsconfig", 4 | } 5 | -------------------------------------------------------------------------------- /packages/combobox/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ```js 4 | yarn add @react-native-aria/utils 5 | ``` 6 | -------------------------------------------------------------------------------- /packages/combobox/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/combobox/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const child_process = require('child_process'); 3 | 4 | const root = path.resolve(__dirname, '..'); 5 | const args = process.argv.slice(2); 6 | const options = { 7 | cwd: process.cwd(), 8 | env: process.env, 9 | stdio: 'inherit', 10 | encoding: 'utf-8', 11 | }; 12 | 13 | let result; 14 | 15 | if (process.cwd() !== root || args.length) { 16 | // We're not in the root of the project, or additional arguments were passed 17 | // In this case, forward the command to `yarn` 18 | result = child_process.spawnSync('yarn', args, options); 19 | } else { 20 | // If `yarn` is run without arguments, perform bootstrap 21 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 22 | } 23 | 24 | process.exitCode = result.status; 25 | -------------------------------------------------------------------------------- /packages/combobox/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useComboBox'; 2 | -------------------------------------------------------------------------------- /packages/combobox/src/index.web.ts: -------------------------------------------------------------------------------- 1 | export * from './useComboBox.web'; 2 | -------------------------------------------------------------------------------- /packages/combobox/src/useComboBox.ts: -------------------------------------------------------------------------------- 1 | import { ComboBoxProps } from '@react-types/combobox'; 2 | import { ComboBoxState } from '@react-stately/combobox'; 3 | import { RefObject } from 'react'; 4 | // @ts-ignore 5 | import { KeyboardDelegate, PressEvent } from '@react-types/shared'; 6 | import { TextInput, View, Pressable, Touchable } from 'react-native'; 7 | 8 | interface AriaComboBoxProps extends ComboBoxProps { 9 | /** The ref for the input element. */ 10 | inputRef: RefObject; 11 | /** The ref for the list box popover. */ 12 | popoverRef: RefObject; 13 | /** The ref for the list box. */ 14 | listBoxRef: RefObject; 15 | /** The ref for the list box popup trigger button. */ 16 | buttonRef: RefObject; 17 | /** An optional keyboard delegate implementation, to override the default. */ 18 | keyboardDelegate?: KeyboardDelegate; 19 | } 20 | 21 | interface ComboBoxAria { 22 | /** Props for the combo box menu trigger button. */ 23 | buttonProps: any; 24 | /** Props for the combo box input element. */ 25 | inputProps: any; 26 | /** Props for the combo box menu. */ 27 | listBoxProps: any; 28 | /** Props for the combo box label element. */ 29 | labelProps: any; 30 | } 31 | 32 | /** 33 | * Provides the behavior and accessibility implementation for a combo box component. 34 | * A combo box combines a text input with a listbox, allowing users to filter a list of options to items matching a query. 35 | * @param props - Props for the combo box. 36 | * @param state - State for the select, as returned by `useComboBoxState`. 37 | */ 38 | export function useComboBox( 39 | props: AriaComboBoxProps, 40 | state: ComboBoxState 41 | ): ComboBoxAria { 42 | let { inputRef } = props; 43 | 44 | // Press handlers for the ComboBox button 45 | let onPress = () => { 46 | // Focus the input field in case it isn't focused yet 47 | inputRef.current?.focus(); 48 | state.toggle(); 49 | }; 50 | 51 | const onChangeText = state.setInputValue; 52 | 53 | return { 54 | labelProps: {}, 55 | buttonProps: { 56 | onPress, 57 | }, 58 | inputProps: { 59 | onChangeText, 60 | value: state.inputValue, 61 | onFocus: () => { 62 | state.setFocused(true); 63 | }, 64 | onBlur: () => { 65 | state.setFocused(false); 66 | }, 67 | }, 68 | listBoxProps: {}, 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /packages/combobox/src/useComboBox.web.ts: -------------------------------------------------------------------------------- 1 | import { ComboBoxProps } from '@react-types/combobox'; 2 | import { ComboBoxState } from '@react-stately/combobox'; 3 | import { RefObject } from 'react'; 4 | import { KeyboardDelegate } from '@react-types/shared'; 5 | import { TextInput, View, Pressable, Touchable } from 'react-native'; 6 | import { useComboBox as useComboBoxWeb } from '@react-aria/combobox'; 7 | import { mapDomPropsToRN } from '@react-native-aria/utils'; 8 | import { TextInputProps } from 'react-native'; 9 | 10 | interface AriaComboBoxProps extends ComboBoxProps { 11 | /** The ref for the input element. */ 12 | inputRef: RefObject; 13 | /** The ref for the list box popover. */ 14 | popoverRef: RefObject; 15 | /** The ref for the list box. */ 16 | listBoxRef: RefObject; 17 | /** The ref for the list box popup trigger button. */ 18 | buttonRef: RefObject; 19 | /** An optional keyboard delegate implementation, to override the default. */ 20 | keyboardDelegate?: KeyboardDelegate; 21 | } 22 | 23 | interface ComboBoxAria { 24 | /** Props for the combo box menu trigger button. */ 25 | buttonProps: any; 26 | /** Props for the combo box input element. */ 27 | inputProps: TextInputProps; 28 | /** Props for the combo box menu. */ 29 | listBoxProps: any; 30 | /** Props for the combo box label element. */ 31 | labelProps: any; 32 | } 33 | 34 | /** 35 | * Provides the behavior and accessibility implementation for a combo box component. 36 | * A combo box combines a text input with a listbox, allowing users to filter a list of options to items matching a query. 37 | * @param props - Props for the combo box. 38 | * @param state - State for the select, as returned by `useComboBoxState`. 39 | */ 40 | export function useComboBox( 41 | props: AriaComboBoxProps, 42 | state: ComboBoxState 43 | ): ComboBoxAria { 44 | // @ts-ignore 45 | const params = useComboBoxWeb(props, state); 46 | 47 | const onKeyPress = params.inputProps.onKeyDown; 48 | params.inputProps.onKeyDown = undefined; 49 | 50 | // RN Web supports onKeyPress. It's same as onKeyDown 51 | // https://necolas.github.io/react-native-web/docs/text-input/ 52 | params.inputProps.onKeyPress = onKeyPress; 53 | 54 | // @ts-ignore 55 | params.inputProps.blurOnSubmit = false; 56 | params.inputProps.onKeyDown = undefined; 57 | 58 | return { 59 | inputProps: mapDomPropsToRN(params.inputProps), 60 | buttonProps: mapDomPropsToRN(params.buttonProps), 61 | labelProps: mapDomPropsToRN(params.labelProps), 62 | listBoxProps: mapDomPropsToRN(params.listBoxProps), 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /packages/combobox/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "../tsconfig", 4 | } 5 | -------------------------------------------------------------------------------- /packages/dialog/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ```js 4 | yarn add @react-native-aria/dialog 5 | ``` 6 | -------------------------------------------------------------------------------- /packages/dialog/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/dialog/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const child_process = require('child_process'); 3 | 4 | const root = path.resolve(__dirname, '..'); 5 | const args = process.argv.slice(2); 6 | const options = { 7 | cwd: process.cwd(), 8 | env: process.env, 9 | stdio: 'inherit', 10 | encoding: 'utf-8', 11 | }; 12 | 13 | let result; 14 | 15 | if (process.cwd() !== root || args.length) { 16 | // We're not in the root of the project, or additional arguments were passed 17 | // In this case, forward the command to `yarn` 18 | result = child_process.spawnSync('yarn', args, options); 19 | } else { 20 | // If `yarn` is run without arguments, perform bootstrap 21 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 22 | } 23 | 24 | process.exitCode = result.status; 25 | -------------------------------------------------------------------------------- /packages/dialog/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useDialog'; 2 | -------------------------------------------------------------------------------- /packages/dialog/src/index.web.ts: -------------------------------------------------------------------------------- 1 | export * from './useDialog.web'; 2 | -------------------------------------------------------------------------------- /packages/dialog/src/useDialog.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unused-vars */ 2 | /* 3 | * Copyright 2020 Adobe. All rights reserved. 4 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. You may obtain a copy 6 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 7 | * 8 | * Unless required by applicable law or agreed to in writing, software distributed under 9 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 10 | * OF ANY KIND, either express or implied. See the License for the specific language 11 | * governing permissions and limitations under the License. 12 | */ 13 | 14 | import type { AriaDialogProps } from '@react-types/dialog'; 15 | import type { DOMAttributes, FocusableElement } from '@react-types/shared'; 16 | import type { RefObject } from 'react'; 17 | 18 | export interface DialogAria { 19 | /** Props for the dialog container element. */ 20 | dialogProps: DOMAttributes; 21 | 22 | /** Props for the dialog title element. */ 23 | titleProps: DOMAttributes; 24 | } 25 | 26 | export interface DialogProps extends AriaDialogProps { 27 | } 28 | 29 | /** 30 | * Provides the behavior and accessibility implementation for a dialog component. 31 | * A dialog is an overlay shown above other content in an application. 32 | */ 33 | 34 | export function useDialog( 35 | _props: DialogProps, 36 | _ref: RefObject 37 | ): DialogAria { 38 | return { 39 | dialogProps: {}, 40 | titleProps: {}, 41 | }; 42 | } 43 | -------------------------------------------------------------------------------- /packages/dialog/src/useDialog.web.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import type { AriaDialogProps } from '@react-types/dialog'; 14 | import type { DOMAttributes, FocusableElement } from '@react-types/shared'; 15 | import type { RefObject } from 'react'; 16 | 17 | export interface DialogAria { 18 | /** Props for the dialog container element. */ 19 | dialogProps: DOMAttributes; 20 | 21 | /** Props for the dialog title element. */ 22 | titleProps: DOMAttributes; 23 | } 24 | 25 | export interface DialogProps extends AriaDialogProps {} 26 | 27 | /** 28 | * Provides the behavior and accessibility implementation for a dialog component. 29 | * A dialog is an overlay shown above other content in an application. 30 | */ 31 | 32 | import { useDialog as useDialogAria } from '@react-aria/dialog'; 33 | import { mapDomPropsToRN } from '@react-native-aria/utils'; 34 | 35 | export function useDialog( 36 | props: DialogProps, 37 | ref: RefObject 38 | ): DialogAria { 39 | const params = useDialogAria(props, ref); 40 | params.dialogProps = params.dialogProps; 41 | params.dialogProps = mapDomPropsToRN(params.dialogProps); 42 | 43 | return params; 44 | } 45 | -------------------------------------------------------------------------------- /packages/dialog/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "../tsconfig", 4 | } 5 | -------------------------------------------------------------------------------- /packages/disclosure/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ```js 4 | yarn add @react-native-aria/disclosure 5 | ``` 6 | -------------------------------------------------------------------------------- /packages/disclosure/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/disclosure/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const child_process = require('child_process'); 3 | 4 | const root = path.resolve(__dirname, '..'); 5 | const args = process.argv.slice(2); 6 | const options = { 7 | cwd: process.cwd(), 8 | env: process.env, 9 | stdio: 'inherit', 10 | encoding: 'utf-8', 11 | }; 12 | 13 | let result; 14 | 15 | if (process.cwd() !== root || args.length) { 16 | // We're not in the root of the project, or additional arguments were passed 17 | // In this case, forward the command to `yarn` 18 | result = child_process.spawnSync('yarn', args, options); 19 | } else { 20 | // If `yarn` is run without arguments, perform bootstrap 21 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 22 | } 23 | 24 | process.exitCode = result.status; 25 | -------------------------------------------------------------------------------- /packages/disclosure/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useDisclosure'; 2 | export * from './useDisclosureButton'; 3 | -------------------------------------------------------------------------------- /packages/disclosure/src/index.web.ts: -------------------------------------------------------------------------------- 1 | export * from './useDisclosure.web'; 2 | export * from './useDisclosureButton.web'; 3 | -------------------------------------------------------------------------------- /packages/disclosure/src/useDisclosure.ts: -------------------------------------------------------------------------------- 1 | import { ToggleState } from '@react-stately/toggle'; 2 | import { ViewProps } from 'react-native'; 3 | 4 | // Polyfill 5 | export function useDisclosure(_props: ViewProps, _state: ToggleState) { 6 | return { 7 | disclosureProps: _props, 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /packages/disclosure/src/useDisclosure.web.ts: -------------------------------------------------------------------------------- 1 | import { ToggleState } from '@react-stately/toggle'; 2 | import { mergeProps } from '@react-aria/utils'; 3 | import { ViewProps } from 'react-native'; 4 | import { disclosureIds } from './utils'; 5 | 6 | export function useDisclosure(props: ViewProps, state: ToggleState) { 7 | const id = disclosureIds.get(state); 8 | 9 | return { 10 | disclosureProps: mergeProps(props, { 11 | nativeID: id, 12 | }), 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /packages/disclosure/src/useDisclosureButton.ts: -------------------------------------------------------------------------------- 1 | import { ToggleState } from '@react-stately/toggle'; 2 | import { mergeProps } from '@react-aria/utils'; 3 | import { PressableProps } from 'react-native'; 4 | 5 | export function useDisclosureButton( 6 | props: Partial, 7 | state: ToggleState 8 | ) { 9 | const onPress = state.toggle; 10 | 11 | return { 12 | buttonProps: mergeProps(props, { 13 | onPress, 14 | 'aria-expanded': state.isSelected, 15 | 'role': 'button', 16 | }), 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /packages/disclosure/src/useDisclosureButton.web.ts: -------------------------------------------------------------------------------- 1 | import { ToggleState } from '@react-stately/toggle'; 2 | import { useId, mergeProps } from '@react-aria/utils'; 3 | import { mapDomPropsToRN } from '@react-native-aria/utils'; 4 | import { PressableProps } from 'react-native'; 5 | import { disclosureIds } from './utils'; 6 | 7 | export function useDisclosureButton(props: PressableProps, state: ToggleState) { 8 | const id = useId(); 9 | 10 | disclosureIds.set(state, id); 11 | 12 | const onPress = state.toggle; 13 | 14 | const ariaProps = mapDomPropsToRN({ 15 | 'aria-expanded': state.isSelected, 16 | 'aria-controls': state.isSelected ? id : undefined, 17 | }); 18 | 19 | return { 20 | buttonProps: mergeProps(props, { 21 | onPress, 22 | ...ariaProps, 23 | role: 'button', 24 | }), 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /packages/disclosure/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { ToggleState } from '@react-stately/toggle'; 2 | 3 | export const disclosureIds = new WeakMap(); 4 | -------------------------------------------------------------------------------- /packages/disclosure/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "../tsconfig", 4 | } 5 | -------------------------------------------------------------------------------- /packages/focus/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ```js 4 | yarn add @react-native-aria/focus 5 | ``` 6 | -------------------------------------------------------------------------------- /packages/focus/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/focus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@react-native-aria/focus", 3 | "version": "0.2.9", 4 | "description": "mono repo setup with bob", 5 | "main": "lib/commonjs/index", 6 | "module": "lib/module/index", 7 | "typings": "lib/typescript/index.d.ts", 8 | "react-native": "src/index", 9 | "source": "src/index", 10 | "files": [ 11 | "src", 12 | "lib", 13 | "android", 14 | "ios", 15 | "cpp", 16 | "react-native-bob-mono.podspec", 17 | "!lib/typescript/example", 18 | "!android/build", 19 | "!ios/build", 20 | "!**/__tests__", 21 | "!**/__fixtures__", 22 | "!**/__mocks__" 23 | ], 24 | "scripts": { 25 | "test": "jest", 26 | "typescript": "tsc --noEmit", 27 | "lint": "eslint \"**/*.{js,ts,tsx}\"", 28 | "prepare": "bob build", 29 | "release": "release-it", 30 | "example": "yarn --cwd example", 31 | "pods": "cd example && pod-install --quiet", 32 | "bootstrap": "yarn example && yarn && yarn pods" 33 | }, 34 | "dependencies": { 35 | "@react-aria/focus": "^3.2.3" 36 | }, 37 | "keywords": [ 38 | "react-native", 39 | "ios", 40 | "android" 41 | ], 42 | "repository": "https://github.com/intergalacticspacehighway/react-native-bob-mono", 43 | "author": "nishan (https://github.com/intergalacticspacehighway)", 44 | "license": "MIT", 45 | "bugs": { 46 | "url": "https://github.com/intergalacticspacehighway/react-native-bob-mono/issues" 47 | }, 48 | "homepage": "https://github.com/intergalacticspacehighway/react-native-bob-mono#readme", 49 | "publishConfig": { 50 | "registry": "https://registry.npmjs.org/" 51 | }, 52 | "peerDependencies": { 53 | "react": "*", 54 | "react-native": "*" 55 | }, 56 | "jest": { 57 | "preset": "react-native", 58 | "modulePathIgnorePatterns": [ 59 | "/example/node_modules", 60 | "/lib/" 61 | ] 62 | }, 63 | "husky": { 64 | "hooks": { 65 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS", 66 | "pre-commit": "yarn lint && yarn typescript" 67 | } 68 | }, 69 | "commitlint": { 70 | "extends": [ 71 | "@commitlint/config-conventional" 72 | ] 73 | }, 74 | "release-it": { 75 | "git": { 76 | "commitMessage": "chore: release ${version}", 77 | "tagName": "v${version}" 78 | }, 79 | "npm": { 80 | "publish": true 81 | }, 82 | "github": { 83 | "release": true 84 | }, 85 | "plugins": { 86 | "@release-it/conventional-changelog": { 87 | "preset": "angular" 88 | } 89 | } 90 | }, 91 | "eslintConfig": { 92 | "root": true, 93 | "extends": [ 94 | "@react-native-community", 95 | "prettier" 96 | ], 97 | "rules": { 98 | "prettier/prettier": [ 99 | "error", 100 | { 101 | "quoteProps": "consistent", 102 | "singleQuote": true, 103 | "tabWidth": 2, 104 | "trailingComma": "es5", 105 | "useTabs": false 106 | } 107 | ] 108 | } 109 | }, 110 | "eslintIgnore": [ 111 | "node_modules/", 112 | "lib/" 113 | ], 114 | "prettier": { 115 | "quoteProps": "consistent", 116 | "singleQuote": true, 117 | "tabWidth": 2, 118 | "trailingComma": "es5", 119 | "useTabs": false 120 | }, 121 | "react-native-builder-bob": { 122 | "source": "src", 123 | "output": "lib", 124 | "targets": [ 125 | "commonjs", 126 | "module", 127 | [ 128 | "typescript", 129 | { 130 | "project": "tsconfig.build.json" 131 | } 132 | ] 133 | ] 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /packages/focus/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const child_process = require('child_process'); 3 | 4 | const root = path.resolve(__dirname, '..'); 5 | const args = process.argv.slice(2); 6 | const options = { 7 | cwd: process.cwd(), 8 | env: process.env, 9 | stdio: 'inherit', 10 | encoding: 'utf-8', 11 | }; 12 | 13 | let result; 14 | 15 | if (process.cwd() !== root || args.length) { 16 | // We're not in the root of the project, or additional arguments were passed 17 | // In this case, forward the command to `yarn` 18 | result = child_process.spawnSync('yarn', args, options); 19 | } else { 20 | // If `yarn` is run without arguments, perform bootstrap 21 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 22 | } 23 | 24 | process.exitCode = result.status; 25 | -------------------------------------------------------------------------------- /packages/focus/src/FocusScope.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | 3 | export interface FocusScopeProps { 4 | /** The contents of the focus scope. */ 5 | children: ReactNode; 6 | 7 | /** 8 | * Whether to contain focus inside the scope, so users cannot 9 | * move focus outside, for example in a modal dialog. 10 | */ 11 | contain?: boolean; 12 | 13 | /** 14 | * Whether to restore focus back to the element that was focused 15 | * when the focus scope mounted, after the focus scope unmounts. 16 | */ 17 | restoreFocus?: boolean; 18 | 19 | /** Whether to auto focus the first focusable element in the focus scope on mount. */ 20 | autoFocus?: boolean; 21 | } 22 | 23 | /** 24 | * A FocusScope manages focus for its descendants. It supports containing focus inside 25 | * the scope, restoring focus to the previously focused element on unmount, and auto 26 | * focusing children on mount. It also acts as a container for a programmatic focus 27 | * management interface that can be used to move focus forward and back in response 28 | * to user events. 29 | */ 30 | export function FocusScope(props: FocusScopeProps) { 31 | return {props.children}; 32 | } 33 | 34 | // Noop - Implement this for mac and windows 35 | export const useFocusManager = () => {}; 36 | -------------------------------------------------------------------------------- /packages/focus/src/FocusScope.web.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | FocusScope as AriaFocusScope, 4 | useFocusManager, 5 | } from '@react-aria/focus'; 6 | import type { FocusScopeProps } from './FocusScope'; 7 | const FocusScope = ({ children, contain, ...props }: FocusScopeProps) => { 8 | /* Todo: stoping mounted and unMounted everytime contain is change */ 9 | // if (contain === false) return <>; 10 | 11 | return ( 12 | 13 | {children} 14 | 15 | ); 16 | }; 17 | 18 | export { FocusScope, useFocusManager }; 19 | -------------------------------------------------------------------------------- /packages/focus/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './useFocusRing'; 2 | export { FocusScope, useFocusManager } from './FocusScope'; 3 | export { useFocus } from './useFocus'; 4 | -------------------------------------------------------------------------------- /packages/focus/src/index.web.ts: -------------------------------------------------------------------------------- 1 | export { useFocusRing } from './useFocusRing.web'; 2 | export { FocusScope } from './FocusScope.web'; 3 | export { useFocusManager } from './FocusScope.web'; 4 | export { useFocus } from './useFocus'; 5 | -------------------------------------------------------------------------------- /packages/focus/src/useFocus.ts: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | 3 | export const useFocus = () => { 4 | const [isFocused, setFocused] = useState(false); 5 | return { 6 | focusProps: { 7 | onFocus: () => setFocused(true), 8 | onBlur: () => setFocused(false), 9 | }, 10 | isFocused, 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /packages/focus/src/useFocusRing.ts: -------------------------------------------------------------------------------- 1 | export const useFocusRing = () => { 2 | return { 3 | focusProps: {}, 4 | isFocusVisible: false, 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /packages/focus/src/useFocusRing.web.ts: -------------------------------------------------------------------------------- 1 | export { useFocusRing } from '@react-aria/focus'; 2 | -------------------------------------------------------------------------------- /packages/focus/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "../tsconfig", 4 | } 5 | -------------------------------------------------------------------------------- /packages/interactions/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ```js 4 | yarn add @react-native-aria/interactions 5 | ``` 6 | -------------------------------------------------------------------------------- /packages/interactions/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/interactions/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const child_process = require('child_process'); 3 | 4 | const root = path.resolve(__dirname, '..'); 5 | const args = process.argv.slice(2); 6 | const options = { 7 | cwd: process.cwd(), 8 | env: process.env, 9 | stdio: 'inherit', 10 | encoding: 'utf-8', 11 | }; 12 | 13 | let result; 14 | 15 | if (process.cwd() !== root || args.length) { 16 | // We're not in the root of the project, or additional arguments were passed 17 | // In this case, forward the command to `yarn` 18 | result = child_process.spawnSync('yarn', args, options); 19 | } else { 20 | // If `yarn` is run without arguments, perform bootstrap 21 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 22 | } 23 | 24 | process.exitCode = result.status; 25 | -------------------------------------------------------------------------------- /packages/interactions/src/index.ts: -------------------------------------------------------------------------------- 1 | export { useHover } from './useHover'; 2 | export { 3 | usePress, 4 | PressEvents, 5 | PressHookProps, 6 | PressProps, 7 | PressResult, 8 | } from './usePress'; 9 | 10 | export { 11 | keyboardDismissHandlerManager, 12 | useKeyboardDismissable, 13 | useBackHandler, 14 | } from './useKeyboardDismisssable'; 15 | -------------------------------------------------------------------------------- /packages/interactions/src/index.web.ts: -------------------------------------------------------------------------------- 1 | export { useHover } from './useHover.web'; 2 | export { usePress } from './usePress'; 3 | export { 4 | keyboardDismissHandlerManager, 5 | useKeyboardDismissable, 6 | useBackHandler, 7 | } from './useKeyboardDismisssable'; 8 | -------------------------------------------------------------------------------- /packages/interactions/src/useHover.ts: -------------------------------------------------------------------------------- 1 | import { HoverProps } from '@react-aria/interactions'; 2 | import { useState } from 'react'; 3 | 4 | export const useHover = (_props?: HoverProps, _ref?: any) => { 5 | const [isHovered, setHovered] = useState(false); 6 | let params = { 7 | hoverProps: { 8 | onHoverIn: () => setHovered(true), 9 | onHoverOut: () => setHovered(false), 10 | }, 11 | isHovered, 12 | }; 13 | 14 | return params; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/interactions/src/useHover.web.ts: -------------------------------------------------------------------------------- 1 | import { useHover as useHoverWeb, HoverProps } from '@react-aria/interactions'; 2 | import { useEffect } from 'react'; 3 | import { attachEventHandlersOnRef } from '@react-native-aria/utils'; 4 | 5 | export const useHover = (props = {} as HoverProps, ref?: any) => { 6 | let params = useHoverWeb(props); 7 | useEffect(() => { 8 | ref && ref.current && attachEventHandlersOnRef(params.hoverProps, ref); 9 | }, []); 10 | 11 | const finalResult = { 12 | ...params, 13 | hoverProps: { 14 | ...params.hoverProps, 15 | onHoverIn: params.hoverProps.onPointerEnter, 16 | onHoverOut: params.hoverProps.onPointerLeave, 17 | }, 18 | }; 19 | 20 | return finalResult; 21 | }; 22 | -------------------------------------------------------------------------------- /packages/interactions/src/useKeyboardDismisssable.ts: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useEffect } from 'react'; 3 | import { BackHandler } from 'react-native'; 4 | 5 | type IParams = { 6 | enabled?: boolean; 7 | callback: () => any; 8 | }; 9 | 10 | let keyboardDismissHandlers: Array<() => any> = []; 11 | export const keyboardDismissHandlerManager = { 12 | push: (handler: () => any) => { 13 | keyboardDismissHandlers.push(handler); 14 | return () => { 15 | keyboardDismissHandlers = keyboardDismissHandlers.filter( 16 | (h) => h !== handler 17 | ); 18 | }; 19 | }, 20 | length: () => keyboardDismissHandlers.length, 21 | pop: () => { 22 | return keyboardDismissHandlers.pop(); 23 | }, 24 | }; 25 | 26 | /** 27 | * Handles attaching callback for Escape key listener on web and Back button listener on Android 28 | */ 29 | export const useKeyboardDismissable = ({ enabled, callback }: IParams) => { 30 | React.useEffect(() => { 31 | let cleanupFn = () => {}; 32 | if (enabled) { 33 | cleanupFn = keyboardDismissHandlerManager.push(callback); 34 | } else { 35 | cleanupFn(); 36 | } 37 | return () => { 38 | cleanupFn(); 39 | }; 40 | }, [enabled, callback]); 41 | 42 | useBackHandler({ enabled, callback }); 43 | }; 44 | 45 | export function useBackHandler({ enabled, callback }: IParams) { 46 | useEffect(() => { 47 | let backHandler = () => { 48 | callback(); 49 | return true; 50 | }; 51 | if (enabled) { 52 | BackHandler.addEventListener('hardwareBackPress', backHandler); 53 | } else { 54 | BackHandler.removeEventListener('hardwareBackPress', backHandler); 55 | } 56 | return () => 57 | BackHandler.removeEventListener('hardwareBackPress', backHandler); 58 | }, [enabled, callback]); 59 | } 60 | -------------------------------------------------------------------------------- /packages/interactions/src/usePress.ts: -------------------------------------------------------------------------------- 1 | import React, { RefObject } from 'react'; 2 | import { mergeProps } from '@react-aria/utils'; 3 | 4 | export interface PressEvents { 5 | /** Handler that is called when the press is released over the target. */ 6 | onPress?: (e: any) => void; 7 | /** Handler that is called when a press interaction starts. */ 8 | onPressStart?: (e: any) => void; 9 | /** 10 | * Handler that is called when a press interaction ends, either 11 | * over the target or when the pointer leaves the target. 12 | */ 13 | onPressEnd?: (e: any) => void; 14 | /** Handler that is called when the press state changes. */ 15 | onPressChange?: (isPressed: boolean) => void; 16 | /** 17 | * Handler that is called when a press is released over the target, regardless of 18 | * whether it started on the target or not. 19 | */ 20 | onPressUp?: (e: any) => void; 21 | } 22 | 23 | export interface PressProps extends PressEvents { 24 | /** Whether the target is in a controlled press state (e.g. an overlay it triggers is open). */ 25 | isPressed?: boolean; 26 | /** Whether the press events should be disabled. */ 27 | isDisabled?: boolean; 28 | /** Whether the target should not receive focus on press. */ 29 | preventFocusOnPress?: boolean; 30 | } 31 | 32 | export interface PressHookProps extends PressProps { 33 | /** A ref to the target element. */ 34 | ref?: RefObject; 35 | } 36 | 37 | export type PressResult = { 38 | /** Whether the target is currently pressed. */ 39 | isPressed: boolean; 40 | /** Props to spread on the target element. */ 41 | pressProps: any; 42 | }; 43 | 44 | export function usePress({ 45 | isDisabled, 46 | onPress, 47 | onPressStart, 48 | onPressEnd, 49 | onPressUp, // No onPressUp on RN. 50 | onPressChange, 51 | isPressed: isPressedProp, 52 | ...restProps 53 | }: PressHookProps): PressResult { 54 | let [isPressed, setPressed] = React.useState(false); 55 | 56 | let pressProps = { 57 | onPress: (e: any) => { 58 | if (isDisabled) return; 59 | onPress && onPress(e); 60 | }, 61 | onPressIn: (e: any) => { 62 | if (isDisabled) return; 63 | onPressStart && onPressStart(e); 64 | setPressed(true); 65 | onPressChange && onPressChange(true); 66 | }, 67 | onPressOut: (e: any) => { 68 | if (isDisabled) return; 69 | onPressEnd && onPressEnd(e); 70 | setPressed(false); 71 | onPressChange && onPressChange(false); 72 | onPressUp && onPressUp(e); 73 | }, 74 | }; 75 | 76 | pressProps = mergeProps(pressProps, restProps); 77 | 78 | return { 79 | isPressed: isPressedProp || isPressed, 80 | pressProps, 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /packages/interactions/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "extends": "../tsconfig", 4 | } 5 | -------------------------------------------------------------------------------- /packages/listbox/README.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ```js 4 | yarn add @react-native-aria/utils 5 | ``` 6 | -------------------------------------------------------------------------------- /packages/listbox/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/listbox/scripts/bootstrap.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const child_process = require('child_process'); 3 | 4 | const root = path.resolve(__dirname, '..'); 5 | const args = process.argv.slice(2); 6 | const options = { 7 | cwd: process.cwd(), 8 | env: process.env, 9 | stdio: 'inherit', 10 | encoding: 'utf-8', 11 | }; 12 | 13 | let result; 14 | 15 | if (process.cwd() !== root || args.length) { 16 | // We're not in the root of the project, or additional arguments were passed 17 | // In this case, forward the command to `yarn` 18 | result = child_process.spawnSync('yarn', args, options); 19 | } else { 20 | // If `yarn` is run without arguments, perform bootstrap 21 | result = child_process.spawnSync('yarn', ['bootstrap'], options); 22 | } 23 | 24 | process.exitCode = result.status; 25 | -------------------------------------------------------------------------------- /packages/listbox/src/index.ts: -------------------------------------------------------------------------------- 1 | export { useOption } from './useOption'; 2 | export { useListBox } from './useListBox'; 3 | -------------------------------------------------------------------------------- /packages/listbox/src/index.web.ts: -------------------------------------------------------------------------------- 1 | export { useOption } from './useOption.web'; 2 | export { useListBox } from './useListBox.web'; 3 | -------------------------------------------------------------------------------- /packages/listbox/src/useListBox.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { AriaListBoxProps } from '@react-types/listbox'; 14 | import { ReactNode, RefObject } from 'react'; 15 | import { KeyboardDelegate } from '@react-types/shared'; 16 | import { ListState } from '@react-stately/list'; 17 | 18 | interface ListBoxAria { 19 | /** Props for the listbox element. */ 20 | listBoxProps: any; 21 | /** Props for the listbox's visual label element (if any). */ 22 | labelProps: any; 23 | } 24 | 25 | interface AriaListBoxOptions extends Omit, 'children'> { 26 | /** Whether the listbox uses virtual scrolling. */ 27 | isVirtualized?: boolean; 28 | 29 | /** 30 | * An optional keyboard delegate implementation for type to select, 31 | * to override the default. 32 | */ 33 | keyboardDelegate?: KeyboardDelegate; 34 | 35 | /** 36 | * An optional visual label for the listbox. 37 | */ 38 | label?: ReactNode; 39 | } 40 | 41 | /** 42 | * Provides the behavior and accessibility implementation for a listbox component. 43 | * A listbox displays a list of options and allows a user to select one or more of them. 44 | * @param props - Props for the listbox. 45 | * @param state - State for the listbox, as returned by `useListState`. 46 | */ 47 | export function useListBox( 48 | _props: AriaListBoxOptions, 49 | _state: ListState, 50 | _ref: RefObject 51 | ): ListBoxAria { 52 | return { 53 | labelProps: {}, 54 | listBoxProps: {}, 55 | }; 56 | } 57 | -------------------------------------------------------------------------------- /packages/listbox/src/useListBox.web.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Adobe. All rights reserved. 3 | * This file is licensed to you under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0 6 | * 7 | * Unless required by applicable law or agreed to in writing, software distributed under 8 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS 9 | * OF ANY KIND, either express or implied. See the License for the specific language 10 | * governing permissions and limitations under the License. 11 | */ 12 | 13 | import { AriaListBoxProps } from '@react-types/listbox'; 14 | import { filterDOMProps, mergeProps } from '@react-aria/utils'; 15 | import { HTMLAttributes, ReactNode, RefObject } from 'react'; 16 | import { KeyboardDelegate } from '@react-types/shared'; 17 | import { listIds } from './utils'; 18 | import { ListState } from '@react-stately/list'; 19 | import { useId } from '@react-aria/utils'; 20 | import { useLabel } from '@react-aria/label'; 21 | import { useSelectableList } from '@react-aria/selection'; 22 | import { mapDomPropsToRN, useMapDomPropsToRN } from '@react-native-aria/utils'; 23 | 24 | interface ListBoxAria { 25 | /** Props for the listbox element. */ 26 | listBoxProps: HTMLAttributes; 27 | /** Props for the listbox's visual label element (if any). */ 28 | labelProps: HTMLAttributes; 29 | } 30 | 31 | interface AriaListBoxOptions extends Omit, 'children'> { 32 | /** Whether the listbox uses virtual scrolling. */ 33 | isVirtualized?: boolean; 34 | 35 | /** 36 | * An optional keyboard delegate implementation for type to select, 37 | * to override the default. 38 | */ 39 | keyboardDelegate?: KeyboardDelegate; 40 | 41 | /** 42 | * An optional visual label for the listbox. 43 | */ 44 | label?: ReactNode; 45 | } 46 | 47 | /** 48 | * Provides the behavior and accessibility implementation for a listbox component. 49 | * A listbox displays a list of options and allows a user to select one or more of them. 50 | * @param props - Props for the listbox. 51 | * @param state - State for the listbox, as returned by `useListState`. 52 | */ 53 | export function useListBox( 54 | props: AriaListBoxOptions, 55 | state: ListState, 56 | ref: RefObject 57 | ): ListBoxAria { 58 | let domProps = filterDOMProps(props, { labelable: true }); 59 | let { listProps } = useSelectableList({ 60 | ...props, 61 | ref, 62 | selectionManager: state.selectionManager, 63 | collection: state.collection, 64 | disabledKeys: state.disabledKeys, 65 | }); 66 | 67 | let id = useId(props.id); 68 | listIds.set(state, id); 69 | 70 | let { labelProps: _labelProps, fieldProps } = useLabel({ 71 | ...props, 72 | id, 73 | // listbox is not an HTML input element so it 74 | // shouldn't be labeled by a