Skip to content

Commit d273ed3

Browse files
author
amnesia
committed
feat: initial implementation of Alphabet component
1 parent 0816e70 commit d273ed3

File tree

9 files changed

+13929
-21
lines changed

9 files changed

+13929
-21
lines changed

example/babel.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module.exports = function (api) {
1010
return getConfig(
1111
{
1212
presets: ['babel-preset-expo'],
13+
plugins: ['react-native-reanimated/plugin'],
1314
},
1415
{ root, pkg }
1516
);

example/metro.config.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
const path = require('path');
22
const { getDefaultConfig } = require('@expo/metro-config');
3-
const { withMetroConfig } = require('react-native-monorepo-config');
4-
5-
const root = path.resolve(__dirname, '..');
63

74
/**
85
* Metro configuration
96
* https://facebook.github.io/metro/docs/configuration
107
*
118
* @type {import('metro-config').MetroConfig}
129
*/
13-
const config = withMetroConfig(getDefaultConfig(__dirname), {
14-
root,
15-
dirname: __dirname,
16-
});
10+
const config = getDefaultConfig(__dirname);
11+
12+
// Allow resolving the local library from one directory up
13+
config.resolver.unstable_enableSymlinks = true;
14+
config.watchFolders = [path.resolve(__dirname, '..')];
15+
16+
// Map the package name to the local library path
17+
config.resolver.extraNodeModules = {
18+
// Point directly to the source folder so you can run without building lib/
19+
'react-native-alphabet': path.resolve(__dirname, '..', 'src'),
20+
};
1721

1822
config.resolver.unstable_enablePackageExports = true;
1923

example/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
"expo-status-bar": "~2.2.3",
1515
"react": "19.0.0",
1616
"react-dom": "19.0.0",
17-
"react-native": "0.79.6",
17+
"react-native": "0.79.5",
18+
"react-native-gesture-handler": "~2.24.0",
19+
"react-native-reanimated": "~3.17.4",
1820
"react-native-web": "~0.20.0"
1921
},
2022
"devDependencies": {

example/src/App.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
1-
import { Text, View, StyleSheet } from 'react-native';
2-
import { multiply } from 'react-native-alphabet';
3-
4-
const result = multiply(3, 7);
1+
import { View, StyleSheet } from 'react-native';
2+
import Alphabet from 'react-native-alphabet';
53

64
export default function App() {
75
return (
86
<View style={styles.container}>
9-
<Text>Result: {result}</Text>
7+
<Alphabet data={['A', 'B', 'C']} />
108
</View>
119
);
1210
}
1311

1412
const styles = StyleSheet.create({
1513
container: {
1614
flex: 1,
17-
alignItems: 'center',
18-
justifyContent: 'center',
1915
},
2016
});

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "react-native-alphabet",
3-
"version": "0.1.0",
3+
"version": "1.0.0",
44
"description": "Description",
55
"main": "./lib/module/index.js",
66
"types": "./lib/typescript/src/index.d.ts",
@@ -49,7 +49,7 @@
4949
"type": "git",
5050
"url": "git+https://github.com/kokurin-michael/react-native-alphabet.git"
5151
},
52-
"author": "amnesia <amnesia@amnesia> (https://github.com/kokurin-michael)",
52+
"author": "Kokurin Michael <[email protected]> (https://github.com/kokurin-michael)",
5353
"license": "MIT",
5454
"bugs": {
5555
"url": "https://github.com/kokurin-michael/react-native-alphabet/issues"
@@ -79,12 +79,16 @@
7979
"react": "19.0.0",
8080
"react-native": "0.79.6",
8181
"react-native-builder-bob": "^0.40.13",
82+
"react-native-gesture-handler": "^2.16.0",
83+
"react-native-reanimated": "^3.18.0",
8284
"release-it": "^17.10.0",
8385
"typescript": "^5.8.3"
8486
},
8587
"peerDependencies": {
8688
"react": "*",
87-
"react-native": "*"
89+
"react-native": "*",
90+
"react-native-gesture-handler": ">=2.0.0",
91+
"react-native-reanimated": ">=3.0.0"
8892
},
8993
"workspaces": [
9094
"example"

src/Alphabet.tsx

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { useCallback, useMemo, useRef } from 'react';
2+
import { Text, View } from 'react-native';
3+
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
4+
import { runOnJS } from 'react-native-reanimated';
5+
import type { AlphabetProps } from './types';
6+
7+
const Alphabet = (props: AlphabetProps) => {
8+
const ref = useRef<View | null>(null);
9+
const height = useRef(1);
10+
const lastIndexRef = useRef(-1);
11+
12+
const handle = useCallback(
13+
(localY: number) => {
14+
const data = props.data;
15+
const length = data?.length ?? 0;
16+
if (!data || length === 0) return;
17+
18+
const yRel = Math.max(0, Math.min(height.current, localY));
19+
const itemH = height.current / length;
20+
const idx = Math.max(0, Math.min(length - 1, Math.floor(yRel / itemH)));
21+
22+
if (idx !== lastIndexRef.current) {
23+
lastIndexRef.current = idx;
24+
props.onCharSelect?.(data[idx]!);
25+
}
26+
},
27+
[props]
28+
);
29+
30+
const tap = useMemo(
31+
() =>
32+
Gesture.Tap()
33+
.hitSlop(props.hitSlop)
34+
.onEnd((e) => {
35+
runOnJS(handle)(e.y);
36+
}),
37+
[handle, props.hitSlop]
38+
);
39+
40+
const pan = useMemo(
41+
() =>
42+
Gesture.Pan()
43+
.minDistance(4)
44+
.onChange((e) => {
45+
runOnJS(handle)(e.y);
46+
})
47+
.onFinalize(() => {
48+
lastIndexRef.current = -1;
49+
}),
50+
[handle]
51+
);
52+
53+
const gesture = useMemo(() => Gesture.Race(tap, pan), [tap, pan]);
54+
55+
return (
56+
<GestureDetector gesture={gesture}>
57+
<View
58+
ref={ref}
59+
style={props.containerStyle}
60+
onLayout={(e) => {
61+
height.current = e.nativeEvent.layout.height;
62+
}}
63+
>
64+
{props.data?.map((letter) => (
65+
<View key={letter} style={props.charContainerStyle}>
66+
<Text style={props.charStyle}>{letter}</Text>
67+
</View>
68+
))}
69+
</View>
70+
</GestureDetector>
71+
);
72+
};
73+
74+
export default Alphabet;

src/index.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
1-
export function multiply(a: number, b: number): number {
2-
return a * b;
3-
}
1+
export * from './types';
2+
export { default } from './Alphabet';

src/types.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { StyleProp, TextStyle, ViewStyle } from 'react-native';
2+
import type { HitSlop } from 'react-native-gesture-handler/lib/typescript/handlers/gestureHandlerCommon';
3+
4+
interface AlphabetProps {
5+
data?: string[];
6+
containerStyle?: StyleProp<ViewStyle>;
7+
charContainerStyle?: StyleProp<ViewStyle>;
8+
charStyle?: StyleProp<TextStyle>;
9+
onCharSelect?: (char: string) => void;
10+
hitSlop?: HitSlop;
11+
}
12+
13+
export type { AlphabetProps };

0 commit comments

Comments
 (0)