& {
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 |
37 | ))}
38 |
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 |
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 |
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