Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
juancstlm committed Jul 31, 2024
0 parents commit 9d7be21
Show file tree
Hide file tree
Showing 24 changed files with 1,105 additions and 0 deletions.
82 changes: 82 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
# OSX
#
.DS_Store

# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
ios/.xcode.env.local

# Android/IntelliJ
#
build/
.idea
.gradle
local.properties
*.iml
*.hprof
.cxx/
*.keystore
!debug.keystore
.history
.vscode
.git
.run


# node.js
#
node_modules/
npm-debug.log
yarn-error.log

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/

**/fastlane/report.xml
**/fastlane/Preview.html
**/fastlane/screenshots
**/fastlane/test_output

# Bundle artifact
*.jsbundle

# Ruby / CocoaPods
/ios/Pods/
/vendor/bundle/
ios-dev.bundle.map
ios-dev.bundle
ios-release.bundle
ios-release.bundle.map
ios-release.bundle
ios-release.bundle.map

# Coverage Directory
_coverage/*
!_coverage/lcov.info


# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*

# testing
/coverage
44 changes: 44 additions & 0 deletions components/Button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ActivityIndicator, StyleProp, TouchableOpacity, ViewStyle } from 'react-native';
import React from 'react';

import { useButtonStyles } from './styles/ButtonStyle';
import Text from '../Text';

export enum ButtonType {
Primary,
Destructive,
}

export interface ButtonProps {
disabled?: boolean;
type?: ButtonType;
onPress?: () => void;
text: string;
loading?: boolean;
testID?: string;
containerStyle?: StyleProp<ViewStyle>;
}

const Button = ({ testID, disabled = false, onPress, text, loading, type = ButtonType.Primary, containerStyle = {} }: ButtonProps) => {
const { styles, theme } = useButtonStyles();
const buttonStyle = type === ButtonType.Primary ? styles.default : styles.destructiveContainer;

return (
<TouchableOpacity
testID={testID}
style={[buttonStyle, disabled && styles.disabled, containerStyle]}
onPress={onPress}
disabled={disabled}
>
{loading ? (
<ActivityIndicator size="small" color={theme.colors.foregroundOnPrimary} />
) : (
<Text category="h3" style={[styles.defaultText, disabled && styles.disabledText]}>
{text}
</Text>
)}
</TouchableOpacity>
);
};

export default Button;
2 changes: 2 additions & 0 deletions components/Button/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from './Button';
export * from './Button';
44 changes: 44 additions & 0 deletions components/Button/styles/ButtonStyle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { StyleSheet } from 'react-native';
import { useCallback } from 'react';

import { useThemedStyle } from '../../../hooks';

export const useButtonStyles = () =>
useThemedStyle(
useCallback(
theme =>
StyleSheet.create({
default: {
borderRadius: theme.borderRadius,
justifyContent: 'center',
backgroundColor: theme.colors.primary,
paddingHorizontal: theme.size.baseSize * 4,
paddingVertical: theme.size.baseSize * 2,
textAlign: 'center',
},
destructiveContainer: {
borderRadius: theme.borderRadius,
justifyContent: 'center',
paddingHorizontal: theme.size.baseSize * 4,
paddingVertical: theme.size.baseSize * 2,
textAlign: 'center',
backgroundColor: theme.colors.secondaryNegative,
},
destructiveText: {
textAlign: 'center',
color: theme.colors.backgroundPrimary,
},
defaultText: {
textAlign: 'center',
color: theme.colors.foregroundOnPrimary,
},
disabled: {
backgroundColor: theme.colors.primaryLowContrast,
},
disabledText: {
color: theme.colors.foregroundOnPrimary,
},
}),
[]
)
);
172 changes: 172 additions & 0 deletions components/DateTimeInput/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import React, { useCallback, useEffect, useState } from 'react';
import DateTimePicker, { IOSNativeProps, AndroidNativeProps } from '@react-native-community/datetimepicker';
import { View, TouchableOpacity, StyleSheet, StyleProp, ViewStyle } from 'react-native';
import Animated, { interpolate, interpolateColor, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
import IonIcons from 'react-native-vector-icons/Ionicons';
import moment from 'moment';

import Sheet from '../Sheet';
import Text from '../Text';
import MenuItemDescription from '../MenuItemDescription';
import { useThemedStyle } from '../../hooks';
import Button from '../Button';

type Props = {
label: string;
caption?: string;
value: Date;
onChange?: (date: Date) => void;
onSave?: (date: Date) => void;
disabled?: boolean;
testID?: string;
containerStyle?: StyleProp<ViewStyle>;
mode?: IOSNativeProps['mode'] | AndroidNativeProps['mode'];
};

const getFormat = (mode?: Props['mode']) => {
switch (mode) {
case 'date': {
return 'MMM D, YYYY';
}
case 'time': {
return 'h:mm A';
}
case 'datetime':
default: {
return 'MMM D, YYYY h:mm A';
}
}
};

const DateTimeInput = ({
value,
onChange,
testID,
containerStyle,
label,
caption,
mode = 'date',
disabled = false,
onSave,
}: Props) => {
const { styles, theme } = useStyles(disabled);
const [dateSheetOpen, setDateSheetOpen] = useState(false);
const [date, setDate] = useState(value);

useEffect(() => {
if (dateSheetOpen) {
return;
}

setDate(value);
}, [dateSheetOpen, value]);

const animatedValue = useSharedValue(value ? 1 : 0);

const animatedLabelStyles = useAnimatedStyle(() => {
return {
transform: [{ translateY: interpolate(animatedValue.value, [0, 1], [1, -8]) }],
fontSize: interpolate(animatedValue.value, [0, 1], [14, 10]),
};
}, []);

const animatedContainerStyles = useAnimatedStyle(() => {
return {
borderColor: interpolateColor(animatedValue.value, [0, 1], ['#ffffff00', theme.colors.border]),
};
});

const animatedValueStyle = useAnimatedStyle(() => {
return {
flex: 1,
transform: [{ translateY: animatedValue.value * 6 }],
};
});

return (
<>
<View testID={testID} style={[styles.container, containerStyle]}>
<TouchableOpacity onPress={() => setDateSheetOpen(true)} disabled={disabled}>
<Animated.View style={[styles.itemContainer, animatedContainerStyles]}>
<View pointerEvents="none" style={styles.labelContainer}>
<Animated.Text style={[styles.label, animatedLabelStyles]}>{label}</Animated.Text>
</View>
<Animated.View style={animatedValueStyle}>
<Text contrast="low" style={styles.value}>
{moment(value).format(getFormat(mode)).toString()}
</Text>
</Animated.View>
<IonIcons
style={styles.iconRight}
color={!disabled ? theme.colors.foreground : theme.colors.foregroundLowContrast}
name={mode === 'time' ? 'time-outline' : 'calendar-outline'}
size={20}
/>
</Animated.View>
</TouchableOpacity>
{!!caption && <MenuItemDescription description={caption} />}
</View>
<Sheet header={label} open={dateSheetOpen} setOpen={setDateSheetOpen}>
<DateTimePicker
value={date}
mode={mode}
themeVariant={theme.isDarkTheme ? 'dark' : 'light'}
display="spinner"
onChange={(_, newDate) => {
if (!newDate) {
return;
}
setDate(newDate);
onChange?.(newDate);
}}
/>
<Button
disabled={value.getTime() === date.getTime()}
text="Save"
onPress={() => {
onSave?.(date);
}}
/>
</Sheet>
</>
);
};

export default DateTimeInput;

const useStyles = (disabled: boolean) =>
useThemedStyle(
useCallback(
t =>
StyleSheet.create({
container: {
marginBottom: t.size.baseSize * 3,
},
itemContainer: {
backgroundColor: t.colors.backgroundOnPrimary,
borderRadius: t.borderRadius,
paddingHorizontal: t.size.baseSize * 2,
minHeight: t.size.baseSize * 8,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
itemLabel: {
color: t.textPrimary,
},
labelContainer: {
left: t.size.baseSize * 2,
position: 'absolute',
},
label: {
color: !disabled ? t.colors.foreground : t.colors.foregroundLowContrast,
...t.typography.p1,
},
iconRight: {},
value: {
paddingHorizontal: t.size.baseSize * 0,
},
}),
[disabled]
)
);
36 changes: 36 additions & 0 deletions components/MenuItemDescription/MenuItemDescription.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React, { useCallback } from 'react';
import { StyleSheet, View } from 'react-native';

import Text from '../Text';
import { useThemedStyle } from '../../hooks';

type MenuItemDescriptionProps = {
description: string;
};

const MenuItemDescription: React.FC<MenuItemDescriptionProps> = ({ description }) => {
const { styles } = useStyles();
return (
<View style={styles.descriptionContainer}>
<Text category="p2" contrast="low">
{description}
</Text>
</View>
);
};

export default React.memo(MenuItemDescription);

const useStyles = () =>
useThemedStyle(
useCallback(
theme =>
StyleSheet.create({
descriptionContainer: {
marginHorizontal: theme.size.baseSize * 2,
marginTop: theme.size.baseSize,
},
}),
[]
)
);
2 changes: 2 additions & 0 deletions components/MenuItemDescription/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { default } from './MenuItemDescription';
export * from './MenuItemDescription';
Loading

0 comments on commit 9d7be21

Please sign in to comment.