Skip to content

Commit

Permalink
A bit more appealing drum machine example (#185)
Browse files Browse the repository at this point in the history
* feat: a bit more appealing drum machine example

* chore: cleanup

* chore: some cleanup

* feat: ui cleanup
  • Loading branch information
michalsek authored Oct 29, 2024
1 parent c916700 commit 39dcf3d
Show file tree
Hide file tree
Showing 43 changed files with 2,042 additions and 284 deletions.
2 changes: 2 additions & 0 deletions apps/common-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
"@react-navigation/native": "*",
"@react-navigation/native-stack": "*",
"@react-navigation/stack": "*",
"@shopify/react-native-skia": "*",
"@swmansion/icons": "*",
"react": "*",
"react-dom": "*",
"react-native": "*",
Expand Down
62 changes: 41 additions & 21 deletions apps/common-app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,38 +1,55 @@
import React from 'react';
import type { FC } from 'react';
import Animated from 'react-native-reanimated';
import { createStackNavigator } from '@react-navigation/stack';
import { FlatList, StyleSheet, Text, Pressable } from 'react-native';
import {
FlatList,
StyleSheet,
Text,
Pressable,
ListRenderItem,
} from 'react-native';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { NavigationContainer, useNavigation } from '@react-navigation/native';

import Container from './components/Container';
import { Examples, MainStackProps } from './examples';
import { Example, Examples, MainStackProps } from './examples';
import { layout, colors } from './styles';
import { Spacer } from './components';

const Stack = createStackNavigator();

// Our slider component uses the text prop to display shared value
// We need it whitelisted in order to have it "animated".
Animated.addWhitelistedNativeProps({ text: true });

const ItemSeparatorComponent = () => <Spacer.Vertical size={16} />;

const HomeScreen: FC = () => {
const navigation = useNavigation<MainStackProps>();

const renderItem: ListRenderItem<Example> = ({ item }) => (
<Pressable
onPress={() => navigation.navigate(item.key)}
key={item.key}
style={({ pressed }) => [
styles.button,
{ borderStyle: pressed ? 'solid' : 'dashed' },
]}
>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subtitle}>{item.subtitle}</Text>
</Pressable>
);

return (
<Container centered={false}>
<Container>
<FlatList
contentContainerStyle={styles.scrollView}
data={Examples}
renderItem={({ item }) => (
<Pressable
onPress={() => navigation.navigate(item.key)}
key={item.key}
style={({ pressed }) => [
styles.button,
{ borderStyle: pressed ? 'solid' : 'dashed' },
]}
>
<Text style={styles.title}>{item.title}</Text>
<Text style={styles.subtitle}>{item.subtitle}</Text>
</Pressable>
)}
renderItem={renderItem}
keyExtractor={(item) => item.key}
contentContainerStyle={styles.scrollView}
ItemSeparatorComponent={ItemSeparatorComponent}
/>
</Container>
);
Expand All @@ -44,8 +61,10 @@ const App: FC = () => {
<NavigationContainer>
<Stack.Navigator
screenOptions={{
headerShown: true,
headerTransparent: true,
headerStyle: {
backgroundColor: colors.main,
backgroundColor: 'transparent',
},
headerTintColor: colors.white,
}}
Expand Down Expand Up @@ -76,17 +95,18 @@ const styles = StyleSheet.create({
title: {
fontSize: 24,
fontWeight: '700',
color: colors.white,
},
subtitle: {
opacity: 0.6,
color: colors.white,
},
button: {
paddingVertical: layout.spacing * 2,
paddingHorizontal: layout.spacing * 2,
marginBottom: layout.spacing,
borderWidth: 2,
borderColor: colors.border,
borderRadius: layout.radius,
paddingVertical: layout.spacing * 2,
paddingHorizontal: layout.spacing * 2,
},
scrollView: {
padding: layout.spacing * 2,
Expand Down
46 changes: 46 additions & 0 deletions apps/common-app/src/components/BGGradient.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useCallback, useState } from 'react';
import { LayoutChangeEvent, StyleSheet, View } from 'react-native';
import { vec, Rect, Canvas, RadialGradient } from '@shopify/react-native-skia';

import { colors } from '../styles';

const BGGradient = () => {
const [size, setSize] = useState({ width: 0, height: 0 });

const onWrapperLayout = useCallback((event: LayoutChangeEvent) => {
setSize({
width: event.nativeEvent.layout.width,
height: event.nativeEvent.layout.height,
});
}, []);

return (
<View style={styles.wrapper} onLayout={onWrapperLayout}>
<Canvas style={styles.canvas}>
<Rect x={0} y={0} width={size.width} height={size.height}>
<RadialGradient
r={size.width}
c={vec(size.width / 2, 0)}
colors={[colors.backgroundLight, colors.background]}
/>
</Rect>
</Canvas>
</View>
);
};

export default BGGradient;

const styles = StyleSheet.create({
wrapper: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
canvas: {
width: '100%',
height: '100%',
},
});
37 changes: 25 additions & 12 deletions apps/common-app/src/components/Container.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
/* eslint-disable react-native/no-inline-styles */
import type { PropsWithChildren, FC } from 'react';
import { StyleProp, ViewStyle } from 'react-native';
import React, { PropsWithChildren } from 'react';
import { SafeAreaView } from 'react-native-safe-area-context';
import { StyleProp, StyleSheet, ViewStyle } from 'react-native';

import BGGradient from './BGGradient';
import { colors } from '../styles';

type ContainerProps = PropsWithChildren<{
style?: StyleProp<ViewStyle>;
centered?: boolean;
}>;

const Container: FC<ContainerProps> = (props) => {
const headerPadding = 120; // eyeballed

const Container: React.FC<ContainerProps> = (props) => {
const { children, style, centered } = props;

return (
<SafeAreaView
style={[
{
flex: 1,
padding: 8,
},
centered && { justifyContent: 'center', alignItems: 'center' },
style,
]}
edges={['bottom', 'left', 'right']}
style={[styles.basic, centered && styles.centered, style]}
>
<BGGradient />
{children}
</SafeAreaView>
);
};

export default Container;

const styles = StyleSheet.create({
basic: {
flex: 1,
padding: 24,
paddingTop: headerPadding,
backgroundColor: colors.background,
},
centered: {
alignItems: 'center',
justifyContent: 'center',
},
});
121 changes: 121 additions & 0 deletions apps/common-app/src/components/Select.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import { useState } from 'react';
// @ts-expect-error
import { Icon } from '@swmansion/icons';
import { ScrollView } from 'react-native-gesture-handler';
import { Modal, View, Text, Pressable, StyleSheet } from 'react-native';

import withSeparators from '../utils/withSeparators';
import { colors } from '../styles';
import Spacer from './Spacer';

interface SelectProps<T extends string> {
value: T;
options: T[];
onChange: (value: T) => void;
}

function Select<T extends string>(props: SelectProps<T>) {
const { options, value, onChange } = props;
const [isModalOpen, setModalOpen] = useState(false);

const renderSeparator = (index: number) => (
<View key={index} style={styles.separator} />
);

const renderOption = (option: T) => (
<Pressable
key={option}
onPress={() => {
onChange(option);
setModalOpen(false);
}}
>
<View style={styles.optionRow}>
<Icon
size={24}
color={colors.white}
name={option === value ? 'check-circle' : 'circle'}
/>
<Spacer.Horizontal size={12} />
<Text style={styles.selectText}>{option}</Text>
</View>
</Pressable>
);

return (
<>
<Pressable onPress={() => setModalOpen(true)}>
<View style={styles.selectBox}>
<Text style={styles.selectText}>{value}</Text>
<Icon
size={34}
type="broken"
name="list-pointers"
color={colors.white}
/>
</View>
</Pressable>
<Modal visible={isModalOpen} animationType="fade" transparent>
<View style={styles.modalBg} />
<Pressable
style={styles.modalSpacer}
onPress={() => {
setModalOpen(false);
}}
/>
<View style={styles.modalContainer}>
<ScrollView>
{withSeparators(options, renderSeparator, renderOption)}
</ScrollView>
</View>
</Modal>
</>
);
}

export default Select;

const styles = StyleSheet.create({
selectBox: {
borderWidth: 1,
borderColor: colors.border,
borderRadius: 8,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 12,
},
selectText: {
flex: 1,
color: colors.white,
fontSize: 16,
paddingVertical: 12,
},
modalBg: {
backgroundColor: colors.modalBackdrop,
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
modalSpacer: {
flex: 3,
},
modalContainer: {
flex: 2,
backgroundColor: colors.background,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
padding: 24,
},
optionRow: {
flexDirection: 'row',
alignItems: 'center',
},
separator: {
height: 1,
backgroundColor: colors.separator,
marginHorizontal: 12,
marginVertical: 6,
},
});
Loading

0 comments on commit 39dcf3d

Please sign in to comment.