actual/packages/loot-design/src/components/mobile/budget.js
Jed Fox 5217835c55
Implement localization for schedule descriptions (#225)
* monthUtils.{format → nonLocalizedFormat}

* Implement localization for schedule descriptions

* Remove outdated comment

* Add general.ordinal in Spanish

Co-Authored-By: Manuel Eduardo Cánepa Cihuelo <10290593+manuelcanepa@users.noreply.github.com>

* yay time zones?

* fix: re-add missing keys

* fix: fix broken i18n imports/initialisation

* style: linting

* fix: re-add english ordinal keys

* fix: add remaining english ordinal keys

* fix: correct dates in schedules.js

* refactor: store translations keys for loot-core in loot-core

* fix: add ns to i18n.t calls directly so parser can find them

* feat: add spanish translation from manuelcanepa

* fix: add comments to help i18n-parser to find contexts

* fix: add "many" context to spanish translations

Co-authored-by: Manuel Eduardo Cánepa Cihuelo <10290593+manuelcanepa@users.noreply.github.com>
Co-authored-by: Tom French <tom@tomfren.ch>
2022-09-08 14:37:45 +01:00

1157 lines
31 KiB
JavaScript

import React, { useMemo, useEffect, useContext } from 'react';
import {
View,
Text,
ScrollView,
TouchableOpacity,
NativeModules,
findNodeHandle,
Keyboard
} from 'react-native';
import {
RectButton,
PanGestureHandler,
NativeViewGestureHandler
} from 'react-native-gesture-handler';
import Animated, { Easing } from 'react-native-reanimated';
import memoizeOne from 'memoize-one';
import Platform from 'loot-core/src/client/platform';
import { rolloverBudget, reportBudget } from 'loot-core/src/client/queries';
import * as monthUtils from 'loot-core/src/shared/months';
import { amountToInteger, integerToAmount } from 'loot-core/src/shared/util';
import { colors, mobileStyles as styles } from '../../style';
import Add from '../../svg/v1/Add';
import ArrowThinDown from '../../svg/v1/ArrowThinDown';
import ArrowThinLeft from '../../svg/v1/ArrowThinLeft';
import ArrowThinRight from '../../svg/v1/ArrowThinRight';
import ArrowThinUp from '../../svg/v1/ArrowThinUp';
import DotsHorizontalTriple from '../../svg/v1/DotsHorizontalTriple';
import CellValue from '../spreadsheet/CellValue';
import format from '../spreadsheet/format';
import NamespaceContext from '../spreadsheet/NamespaceContext';
import SheetValue from '../spreadsheet/SheetValue';
import useSheetValue from '../spreadsheet/useSheetValue';
import AmountInput, {
MathOperations,
AmountAccessoryContext
} from './AmountInput';
import AndroidKeyboardAvoidingView from './AndroidKeyboardAvoidingView';
import { Button, KeyboardButton, Card, Label } from './common';
import { DragDrop, Draggable, Droppable, DragDropHighlight } from './dragdrop';
import { ListItem, ROW_HEIGHT } from './table';
const ACTScrollViewManager =
NativeModules && NativeModules.ACTScrollViewManager;
export function ToBudget({ toBudget, onPress }) {
return (
<SheetValue binding={toBudget}>
{({ value: amount }) => {
return (
<Button
bare
contentStyle={{ flexDirection: 'column', alignItems: 'flex-start' }}
onPress={onPress}
>
<Label
title={amount < 0 ? 'OVERBUDGETED' : 'TO BUDGET'}
style={{ color: colors.n1 }}
/>
<Text
style={[
styles.smallText,
{
fontWeight: '500',
color: amount < 0 ? colors.r4 : colors.n1
}
]}
>
{format(amount, 'financial')}
</Text>
</Button>
);
}}
</SheetValue>
);
}
function Saved({ projected }) {
let budgetedSaved = useSheetValue(reportBudget.totalBudgetedSaved) || 0;
let totalSaved = useSheetValue(reportBudget.totalSaved) || 0;
let saved = projected ? budgetedSaved : totalSaved;
let isNegative = saved < 0;
return (
<View style={{ flexDirection: 'column', alignItems: 'flex-start' }}>
{projected ? (
<Label title="PROJECTED SAVINGS" style={{ color: colors.n1 }} />
) : (
<Label
title={isNegative ? 'OVERSPENT' : 'SAVED'}
style={{ color: colors.n1 }}
/>
)}
<Text
style={[
styles.smallText,
{
fontWeight: '500',
color: projected ? colors.y3 : isNegative ? colors.r4 : colors.n1
}
]}
>
{format(saved, 'financial')}
</Text>
</View>
);
}
export class BudgetCell extends React.PureComponent {
render() {
const {
name,
binding,
editing,
style,
textStyle,
categoryId,
month,
onBudgetAction
} = this.props;
return (
<SheetValue binding={binding}>
{node => {
return (
<View style={style}>
<AmountInput
value={integerToAmount(node.value || 0)}
inputAccessoryViewID="budget"
style={[
{
height: ROW_HEIGHT - 4,
transform: [{ translateX: 6 }]
},
!editing && { opacity: 0, position: 'absolute', top: 0 }
]}
focused={editing}
scrollIntoView={Platform.OS === 'android'}
textStyle={[styles.smallText, textStyle]}
animationColor="white"
onBlur={value => {
onBudgetAction(month, 'budget-amount', {
category: categoryId,
amount: amountToInteger(value)
});
}}
/>
<View
style={[
{
justifyContent: 'center',
height: ROW_HEIGHT - 4
},
editing && { display: 'none' }
]}
>
<Text style={[styles.smallText, textStyle]} data-testid={name}>
{format(node.value || 0, 'financial')}
</Text>
</View>
</View>
);
}}
</SheetValue>
);
}
}
function BudgetGroupPreview({ group, pending, style }) {
let opacity = useMemo(() => new Animated.Value(0), []);
useEffect(() => {
Animated.timing(opacity, {
toValue: 1,
duration: 100,
easing: Easing.inOut(Easing.ease)
}).start();
}, []);
return (
<Animated.View
style={[
style,
{ opacity },
pending && {
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 3
},
shadowOpacity: 0.45,
shadowRadius: 20,
elevation: 5
}
]}
>
<Card
style={{
marginTop: 7,
marginBottom: 7,
opacity: pending ? 1 : 0.4
}}
>
<TotalsRow group={group} blank={true} />
{group.categories.map((cat, index) => (
<BudgetCategory category={cat} blank={true} index={index} />
))}
</Card>
</Animated.View>
);
}
function BudgetCategoryPreview({ name, pending, style }) {
return (
<Animated.View
style={[
style,
{ opacity: pending ? 1 : 0.4 },
{
backgroundColor: 'white',
shadowColor: '#000',
shadowOffset: {
width: 0,
height: 2
},
shadowOpacity: 0.25,
shadowRadius: 10,
elevation: 5
}
]}
>
<ListItem
style={{
flex: 1,
borderColor: 'transparent',
borderRadius: 4
}}
>
<Text style={styles.smallText}>{name}</Text>
</ListItem>
</Animated.View>
);
}
export class BudgetCategory extends React.PureComponent {
constructor(props) {
super(props);
let { editMode, blank } = props;
this.opacity = new Animated.Value(editMode || blank ? 0 : 1);
}
componentDidUpdate(prevProps) {
if (prevProps.editing !== this.props.editing) {
if (this.props.editing && ACTScrollViewManager) {
ACTScrollViewManager.setFocused(findNodeHandle(this.container));
}
}
if (prevProps.editMode !== this.props.editMode) {
Animated.timing(this.opacity, {
toValue: this.props.editMode ? 0 : 1,
duration: 200,
easing: Easing.inOut(Easing.ease)
}).start();
}
}
render() {
let {
category,
editing,
index,
gestures,
editMode,
style,
month,
onEdit,
onBudgetAction
} = this.props;
let budgeted = rolloverBudget.catBudgeted(category.id);
let balance = rolloverBudget.catBalance(category.id);
let content = (
<ListItem
ref={el => (this.container = el)}
style={[
{
backgroundColor: editing ? colors.p11 : 'transparent',
borderBottomWidth: 0,
borderTopWidth: index > 0 ? 1 : 0
},
style
]}
data-testid="row"
>
<View style={{ flex: 1 }}>
<Text style={styles.smallText}>{category.name}</Text>
</View>
<Animated.View
style={{
flexDirection: 'row',
alignItems: 'center',
opacity: this.opacity
}}
>
<BudgetCell
name="budgeted"
binding={budgeted}
editing={editing}
style={{ width: 90 }}
textStyle={[styles.smallText, { textAlign: 'right' }]}
categoryId={category.id}
month={month}
onBudgetAction={onBudgetAction}
/>
<CellValue
name="balance"
binding={balance}
style={[styles.smallText, { width: 90, textAlign: 'right' }]}
getStyle={value => value < 0 && { color: colors.r4 }}
type="financial"
/>
</Animated.View>
</ListItem>
);
if (!editMode) {
return (
<TouchableOpacity
onPress={() => onEdit(category.id)}
activeOpacity={0.7}
>
{content}
</TouchableOpacity>
);
}
return (
<Draggable
id={category.id}
type="category"
preview={({ pending, style }) => (
<BudgetCategoryPreview
name={category.name}
pending={pending}
style={style}
/>
)}
gestures={gestures}
>
<Droppable
type="category"
getActiveStatus={(x, y, { layout }, { id }) => {
let pos = (y - layout.y) / layout.height;
return pos < 0.5 ? 'top' : 'bottom';
}}
onDrop={(id, type, droppable, status) =>
this.props.onReorder(id.replace('category:', ''), {
aroundCategory: {
id: category.id,
position: status
}
})
}
>
{() => content}
</Droppable>
</Draggable>
);
}
}
export class TotalsRow extends React.PureComponent {
constructor(props) {
super(props);
let { editMode, blank } = props;
this.animation = new Animated.Value(editMode || blank ? 0 : 1);
}
componentDidUpdate(prevProps) {
if (prevProps.editMode !== this.props.editMode) {
Animated.timing(this.animation, {
toValue: this.props.editMode ? 0 : 1,
duration: 200,
easing: Easing.inOut(Easing.ease)
}).start();
}
}
render() {
let { group, editMode, onAddCategory } = this.props;
let content = (
<ListItem
style={{
flexDirection: 'row',
alignItems: 'center',
backgroundColor: colors.n11
}}
data-testid="totals"
>
<View style={{ flex: 1 }}>
<Text
style={[styles.smallText, { fontWeight: '500' }]}
data-testid="name"
>
{group.name}
</Text>
</View>
<Animated.View
style={{
flexDirection: 'row',
alignItems: 'center',
opacity: this.animation
}}
>
<CellValue
binding={rolloverBudget.groupBudgeted(group.id)}
style={[
styles.smallText,
{ width: 90, fontWeight: '500', textAlign: 'right' }
]}
type="financial"
/>
<CellValue
binding={rolloverBudget.groupBalance(group.id)}
style={[
styles.smallText,
{ width: 90, fontWeight: '500', textAlign: 'right' }
]}
type="financial"
/>
</Animated.View>
{editMode && (
<Animated.View
style={{
flexDirection: 'row',
alignItems: 'center',
opacity: this.opacity,
position: 'absolute',
top: 0,
bottom: 0,
right: this.animation.interpolate({
inputRange: [0, 1],
outputRange: [5, -30]
})
}}
>
<RectButton
onPress={() => onAddCategory(group.id)}
style={{ padding: 10 }}
>
<Add width={15} height={15} color={colors.n1} />
</RectButton>
</Animated.View>
)}
</ListItem>
);
if (!editMode) {
return content;
}
return (
<Droppable
type="category"
getActiveStatus={(x, y, { layout }, { id }) => {
return 'bottom';
}}
onDrop={(id, type, droppable, status) =>
this.props.onReorderCategory(id, { inGroup: group.id })
}
>
{() => content}
</Droppable>
);
}
}
export class IncomeCategory extends React.PureComponent {
render() {
const {
name,
budget,
balance,
style,
nameTextStyle,
amountTextStyle
} = this.props;
return (
<ListItem
style={[
{
flexDirection: 'row',
alignItems: 'center',
padding: 10,
backgroundColor: 'transparent'
},
style
]}
>
<View style={{ flex: 1 }}>
<Text style={[styles.smallText, nameTextStyle]} data-testid="name">
{name}
</Text>
</View>
{budget && (
<CellValue
binding={budget}
style={[
styles.smallText,
{ width: 90, textAlign: 'right' },
amountTextStyle
]}
type="financial"
/>
)}
<CellValue
binding={balance}
style={[
styles.smallText,
{ width: 90, textAlign: 'right' },
amountTextStyle
]}
type="financial"
/>
</ListItem>
);
}
}
export function BudgetAccessoryView() {
let emitter = useContext(AmountAccessoryContext);
return (
<View>
<View
style={{
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'stretch',
backgroundColor: colors.n10,
padding: 5,
height: 45
}}
>
<MathOperations emitter={emitter} />
<View style={{ flex: 1 }} />
<KeyboardButton
onPress={() => emitter.emit('moveUp')}
style={{ marginRight: 5 }}
data-testid="up"
>
<ArrowThinUp width={13} height={13} />
</KeyboardButton>
<KeyboardButton
onPress={() => emitter.emit('moveDown')}
style={{ marginRight: 5 }}
data-testid="down"
>
<ArrowThinDown width={13} height={13} />
</KeyboardButton>
<KeyboardButton onPress={() => emitter.emit('done')} data-testid="done">
Done
</KeyboardButton>
</View>
</View>
);
}
export class BudgetGroup extends React.PureComponent {
render() {
const {
group,
editingId,
editMode,
gestures,
month,
onEditCategory,
onReorderCategory,
onReorderGroup,
onAddCategory,
onBudgetAction
} = this.props;
function editable(content) {
if (!editMode) {
return content;
}
return (
<Draggable
id={group.id}
type="group"
preview={({ pending, style }) => (
<BudgetGroupPreview group={group} pending={pending} style={style} />
)}
gestures={gestures}
>
<Droppable
type="group"
getActiveStatus={(x, y, { layout }, { id }) => {
let pos = (y - layout.y) / layout.height;
return pos < 0.5 ? 'top' : 'bottom';
}}
onDrop={(id, type, droppable, status) => {
onReorderGroup(id, group.id, status);
}}
>
{() => content}
</Droppable>
</Draggable>
);
}
return editable(
<Card
style={{
marginTop: 7,
marginBottom: 7
}}
>
<TotalsRow
group={group}
budgeted={rolloverBudget.groupBudgeted(group.id)}
balance={rolloverBudget.groupBalance(group.id)}
editMode={editMode}
onAddCategory={onAddCategory}
onReorderCategory={onReorderCategory}
/>
{group.categories.map((category, index) => {
const editing = editingId === category.id;
return (
<BudgetCategory
key={category.id}
index={index}
category={category}
editing={editing}
editMode={editMode}
gestures={gestures}
month={month}
onEdit={onEditCategory}
onReorder={onReorderCategory}
onBudgetAction={onBudgetAction}
/>
);
})}
</Card>
);
}
}
export class IncomeBudgetGroup extends React.Component {
render() {
const { type, group } = this.props;
return (
<View>
<View
style={{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end',
marginTop: 50,
marginBottom: 5,
marginRight: 14
}}
>
{type === 'report' && (
<Label title="BUDGETED" style={{ width: 90 }} />
)}
<Label title="RECEIVED" style={{ width: 90 }} />
</View>
<Card style={{ marginTop: 0 }}>
<IncomeCategory
name="Income"
budget={
type === 'report' ? reportBudget.groupBudgeted(group.id) : null
}
balance={
type === 'report'
? reportBudget.groupSumAmount(group.id)
: rolloverBudget.groupSumAmount(group.id)
}
nameTextStyle={{ fontWeight: '500' }}
amountTextStyle={{ fontWeight: '500' }}
style={{ backgroundColor: colors.n11 }}
/>
{group.categories.map((category, index) => {
return (
<IncomeCategory
key={category.id}
type={type}
name={category.name}
budget={
type === 'report'
? reportBudget.catBudgeted(category.id)
: null
}
balance={
type === 'report'
? reportBudget.catSumAmount(category.id)
: rolloverBudget.catSumAmount(category.id)
}
index={index}
/>
);
})}
</Card>
</View>
);
}
}
export class BudgetGroups extends React.Component {
getGroups = memoizeOne(groups => {
return {
incomeGroup: groups.find(group => group.is_income),
expenseGroups: groups.filter(group => !group.is_income)
};
});
render() {
const {
type,
categoryGroups,
editingId,
editMode,
gestures,
month,
onEditCategory,
onAddCategory,
onReorderCategory,
onReorderGroup,
onBudgetAction
} = this.props;
const { incomeGroup, expenseGroups } = this.getGroups(categoryGroups);
return (
<View style={{ marginBottom: 15 }}>
{expenseGroups.map(group => {
return (
<BudgetGroup
key={group.id}
group={group}
editingId={editingId}
editMode={editMode}
gestures={gestures}
month={month}
onEditCategory={onEditCategory}
onAddCategory={onAddCategory}
onReorderCategory={onReorderCategory}
onReorderGroup={onReorderGroup}
onBudgetAction={onBudgetAction}
/>
);
})}
{incomeGroup && <IncomeBudgetGroup type={type} group={incomeGroup} />}
</View>
);
}
}
export class BudgetTable extends React.Component {
static contextType = AmountAccessoryContext;
state = { editingCategory: null };
constructor(props) {
super(props);
this.gestures = {
scroll: React.createRef(null),
pan: React.createRef(null),
rows: []
};
}
componentDidMount() {
if (ACTScrollViewManager) {
ACTScrollViewManager.activate(
(this.list.getNode
? this.list.getNode()
: this.list
).getScrollableNode()
);
}
const removeFocus = this.props.navigation.addListener('focus', () => {
if (ACTScrollViewManager) {
ACTScrollViewManager.activate(
(this.list.getNode
? this.list.getNode()
: this.list
).getScrollableNode()
);
}
});
const keyboardWillHide = e => {
if (ACTScrollViewManager) {
ACTScrollViewManager.setFocused(-1);
}
this.onEditCategory(null);
};
let keyListener = Keyboard.addListener(
Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide',
keyboardWillHide
);
let emitter = this.context;
emitter.on('done', this.onKeyboardDone);
emitter.on('moveUp', this.onMoveUp);
emitter.on('moveDown', this.onMoveDown);
this.cleanup = () => {
removeFocus();
keyListener.remove();
emitter.off('done', this.onKeyboardDone);
emitter.off('moveUp', this.onMoveUp);
emitter.off('moveDown', this.onMoveDown);
};
}
componentWillUnmount() {
this.cleanup();
}
onEditCategory = id => {
this.setState({ editingCategory: id });
};
onKeyboardDone = () => {
Keyboard.dismiss();
if (Platform.isReactNativeWeb) {
// TODO: If we are running tests, they can't rely on the
// keyboard events, so manually reset the state here. Hopefully
// we can find a better solution for this in the future.
this.onEditCategory(null);
}
};
onMoveUp = () => {
const { categories } = this.props;
const { editingCategory } = this.state;
const expenseCategories = categories.filter(cat => !cat.is_income);
const idx = expenseCategories.findIndex(cat => editingCategory === cat.id);
if (idx - 1 >= 0) {
this.onEditCategory(expenseCategories[idx - 1].id);
}
};
onMoveDown = () => {
const { categories } = this.props;
const { editingCategory } = this.state;
const expenseCategories = categories.filter(cat => !cat.is_income);
const idx = expenseCategories.findIndex(cat => editingCategory === cat.id);
if (idx + 1 < expenseCategories.length) {
this.onEditCategory(expenseCategories[idx + 1].id);
}
};
render() {
const {
type,
categoryGroups,
month,
monthBounds,
editMode,
refreshControl,
onPrevMonth,
onNextMonth,
onAddCategory,
onReorderCategory,
onReorderGroup,
onShowBudgetDetails,
onOpenActionSheet,
onBudgetAction
} = this.props;
let { editingCategory } = this.state;
let currentMonth = monthUtils.currentMonth();
return (
<NamespaceContext.Provider value={monthUtils.sheetForMonth(month, type)}>
<View
style={{ flex: 1, backgroundColor: 'white' }}
data-testid="budget-table"
>
<BudgetHeader
currentMonth={month}
monthBounds={monthBounds}
editMode={editMode}
onDone={() => this.props.onEditMode(false)}
onOpenActionSheet={onOpenActionSheet}
onPrevMonth={onPrevMonth}
onNextMonth={onNextMonth}
/>
<View
style={{
flexDirection: 'row',
paddingHorizontal: 10,
paddingVertical: 10,
paddingRight: 14,
backgroundColor: 'white',
borderBottomWidth: 1,
borderColor: colors.n9
}}
>
{type === 'report' ? (
<Saved projected={month >= currentMonth} />
) : (
<ToBudget
toBudget={rolloverBudget.toBudget}
onPress={onShowBudgetDetails}
/>
)}
<View style={{ flex: 1, zIndex: -1 }} />
<View style={{ width: 90 }}>
<Label title="BUDGETED" style={{ color: colors.n1 }} />
<CellValue
binding={reportBudget.totalBudgetedExpense}
type="financial"
style={[
styles.smallText,
{ color: colors.n1, textAlign: 'right', fontWeight: '500' }
]}
formatter={value => {
return format(-parseFloat(value || '0'), 'financial');
}}
/>
</View>
<View style={{ width: 90 }}>
<Label title="BALANCE" style={{ color: colors.n1 }} />
<CellValue
binding={rolloverBudget.totalBalance}
type="financial"
style={[
styles.smallText,
{ color: colors.n1, textAlign: 'right', fontWeight: '500' }
]}
/>
</View>
</View>
<AndroidKeyboardAvoidingView includeStatusBar={true}>
{!editMode ? (
<ScrollView
ref={el => (this.list = el)}
keyboardShouldPersistTaps="always"
refreshControl={refreshControl}
style={{ backgroundColor: colors.n10 }}
automaticallyAdjustContentInsets={false}
>
<BudgetGroups
type={type}
categoryGroups={categoryGroups}
editingId={editingCategory}
editMode={editMode}
gestures={this.gestures}
month={month}
onEditCategory={this.onEditCategory}
onAddCategory={onAddCategory}
onReorderCategory={onReorderCategory}
onReorderGroup={onReorderGroup}
onBudgetAction={onBudgetAction}
/>
</ScrollView>
) : (
<DragDrop>
{({
dragging,
onGestureEvent,
onHandlerStateChange,
scrollRef,
onScroll
}) => (
<NativeViewGestureHandler
enabled={!dragging}
waitFor={this.gestures.pan}
ref={this.gestures.scroll}
>
<Animated.ScrollView
ref={el => {
scrollRef.current = el;
this.list = el;
}}
onScroll={onScroll}
keyboardShouldPersistTaps="always"
scrollEventThrottle={16}
scrollEnabled={!dragging}
style={{ backgroundColor: colors.n10 }}
>
<PanGestureHandler
avgTouches
minDeltaX={2}
minDeltaY={2}
maxPointers={1}
onGestureEvent={onGestureEvent}
onHandlerStateChange={onHandlerStateChange}
ref={this.gestures.pan}
waitFor={this.gestures.scroll}
>
<Animated.View>
<BudgetGroups
categoryGroups={categoryGroups}
editingId={editingCategory}
editMode={editMode}
gestures={this.gestures}
onEditCategory={this.onEditCategory}
onAddCategory={onAddCategory}
onReorderCategory={onReorderCategory}
onReorderGroup={onReorderGroup}
/>
</Animated.View>
</PanGestureHandler>
<DragDropHighlight />
</Animated.ScrollView>
</NativeViewGestureHandler>
)}
</DragDrop>
)}
</AndroidKeyboardAvoidingView>
</View>
</NamespaceContext.Provider>
);
}
}
export function BudgetHeader({
currentMonth,
monthBounds,
editMode,
onDone,
onOpenActionSheet,
onPrevMonth,
onNextMonth
}) {
let prevEnabled = currentMonth > monthBounds.start;
let nextEnabled = currentMonth < monthUtils.subMonths(monthBounds.end, 1);
let buttonStyle = {
paddingHorizontal: 15,
backgroundColor: 'transparent'
};
return (
<View
style={{
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'stretch',
backgroundColor: colors.p5
}}
>
{!editMode && (
<Button
bare
hitSlop={{ top: 5, bottom: 5, left: 0, right: 30 }}
onPress={prevEnabled && onPrevMonth}
style={[buttonStyle, { left: 0, opacity: prevEnabled ? 1 : 0.6 }]}
>
<ArrowThinLeft style={{ color: colors.n11 }} width="15" height="15" />
</Button>
)}
<Text
style={[
styles.smallText,
{
marginVertical: 12,
color: colors.n11,
textAlign: 'center',
fontWeight: '600',
zIndex: -1
}
]}
>
{monthUtils.nonLocalizedFormat(currentMonth, "MMMM ''yy")}
</Text>
{editMode ? (
<Button
bare
onPress={onDone}
style={[
buttonStyle,
{ position: 'absolute', top: 0, bottom: 0, right: 0 }
]}
textStyle={{
color: colors.n11,
fontSize: 15,
fontWeight: '500'
}}
>
Done
</Button>
) : (
<>
<Button
bare
onPress={nextEnabled && onNextMonth}
hitSlop={{ top: 5, bottom: 5, left: 30, right: 5 }}
style={[buttonStyle, { opacity: nextEnabled ? 1 : 0.6 }]}
>
<ArrowThinRight
style={{ color: colors.n11 }}
width="15"
height="15"
/>
</Button>
<Button
bare
onPress={onOpenActionSheet}
style={{
position: 'absolute',
top: 0,
bottom: 0,
right: 0,
backgroundColor: 'transparent',
paddingHorizontal: 12
}}
hitSlop={{
top: 5,
bottom: 5,
left: 20,
right: 20
}}
>
<DotsHorizontalTriple
width="20"
height="20"
style={{ color: 'white' }}
/>
</Button>
</>
)}
</View>
);
}