Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions example/.rnstorybook/stories/Organisms/Toaster/Toaster.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,35 @@ const InsideModalDemo = () => (
</View>
);

const LoadingDemo = () => (
<View style={styles.container}>
<Trigger
label="Show Loading"
onPress={() =>
ToasterApi.show({
type: 'info',
title: 'Loading...',
loading: true,
})
}
/>
<Trigger
label="Resolve Loading"
onPress={() =>
ToasterApi.show({
type: 'success',
title: 'Done!',
description: 'Action completed.',
})
}
/>
<Trigger
label="Hide"
onPress={() => ToasterApi.hide()}
/>
</View>
);

const meta = {
title: 'Organisms/Toaster',
component: View,
Expand All @@ -136,3 +165,7 @@ export const Interactive: Story = {
export const InsideModal: Story = {
render: () => <InsideModalDemo />,
};

export const Loading: Story = {
render: () => <LoadingDemo />,
};
12 changes: 10 additions & 2 deletions src/components/Atoms/Icon/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ import { useRubberDuckStore } from '../../../store';
import { buildSize } from './library';

export const Icon: React.FC<IIconProps> = (props) => {
const { icon, size, color = "textPrimary" } = props;
const {
icon,
size,
color = "textPrimary",
hexColor,
} = props;

const colors = useRubberDuckStore((s) => s.colors);
const iconSize = buildSize(size);
const IconComponent = Icons[icon] as LucideIcon;
const resolvedColor = hexColor ?? colors[color];

return <IconComponent size={iconSize} color={colors[color]} />;
return <IconComponent size={iconSize} color={resolvedColor} />;
};

export type { IconName } from './types';
1 change: 1 addition & 0 deletions src/components/Atoms/Icon/types/IIconProps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ export type IIconProps = {
icon: IconName;
size?: IconSize;
color?: keyof IColors;
hexColor?: string;
};
2 changes: 1 addition & 1 deletion src/components/Atoms/Switch/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const Switch: React.FC<ISwitchProps> = (props) => {
const { isOn, disabled } = props;

const colors = useRubberDuckStore((s) => s.colors);
const styles = useStyles();
const styles = useStyles(!!disabled);
const reanimatedStyles = useReanimatedStyles({
isOn: !!isOn,
accentColor: colors.accent,
Expand Down
3 changes: 2 additions & 1 deletion src/components/Atoms/Switch/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useRubberDuckStore } from '../../../store';
import { Tokens } from '../../../tokens/Tokens.class';
import { SWITCH_TRACK_WIDTH, SWITCH_TRACK_HEIGHT, SWITCH_THUMB_SIZE, SWITCH_PADDING } from './constants';

export const useStyles = () => {
export const useStyles = (disabled: boolean) => {
const colors = useRubberDuckStore((s) => s.colors);

return StyleSheet.create({
Expand All @@ -13,6 +13,7 @@ export const useStyles = () => {
borderRadius: Tokens.radii({ key: 'full' }),
padding: SWITCH_PADDING,
justifyContent: 'center',
opacity: disabled ? 0.8 : 1,
},

thumb: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Molecules/Inputs/MoneyField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const MoneyField: React.FC<IMoneyFieldProps> = (props) => {
const reanimatedStyles = useReanimatedStyles({
isFocused,
accentColor: colors.accent,
borderDefaultColor: colors.borderDefault,
borderDefaultColor: colors.borderSubtle,
});

return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Molecules/Inputs/MoneyField/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const useStyles = (props: IMoneyFieldProps) => {

inputWrapper: {
borderWidth: 2,
borderColor: colors.borderDefault,
borderColor: colors.borderSubtle,
borderRadius: Tokens.radii({ key: 'lg' }),
backgroundColor: colors.background,
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/Molecules/Inputs/MultilineText/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const MultilineText: React.FC<IMultilineTextProps> = (props) => {
const reanimatedStyles = useReanimatedStyles({
isFocused,
accentColor: colors.accent,
borderDefaultColor: colors.borderDefault,
borderDefaultColor: colors.borderSubtle,
});

return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Molecules/Inputs/MultilineText/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export const useStyles = (props: IMultilineTextProps) => {

inputWrapper: {
borderWidth: 2,
borderColor: colors.borderDefault,
borderColor: colors.borderSubtle,
borderRadius: Tokens.radii({ key: 'lg' }),
backgroundColor: colors.background,
minHeight: 24 * numberOfLines,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Molecules/Inputs/NumberField/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const NumberField: React.FC<INumberFieldProps> = (props) => {
const reanimatedStyles = useReanimatedStyles({
isFocused,
accentColor: colors.accent,
borderDefaultColor: colors.borderDefault,
borderDefaultColor: colors.borderSubtle,
});

return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Molecules/Inputs/NumberField/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const useStyles = (props: INumberFieldProps) => {
flexDirection: 'row',
alignItems: 'center',
borderWidth: 2,
borderColor: colors.borderDefault,
borderColor: colors.borderSubtle,
borderRadius: Tokens.radii({ key: 'lg' }),
backgroundColor: colors.background,
},
Expand Down
6 changes: 3 additions & 3 deletions src/components/Molecules/Inputs/SimpleText/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ export const SimpleText: React.FC<ISimpleTextProps> = (props) => {

const colors = useRubberDuckStore((s) => s.colors);
const styles = useStyles(props);

const {
isFocused,
onFocus,
onBlur,
onChangeText,
} = useSimpleTextViewModel(props);

const reanimatedStyles = useReanimatedStyles({
isFocused,
accentColor: colors.accent,
borderDefaultColor: colors.borderDefault,
borderDefaultColor: colors.borderSubtle,
});

return (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Molecules/Inputs/SimpleText/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const useStyles = (props: ISimpleTextProps) => {
flexDirection: 'row',
alignItems: 'center',
borderWidth: 2,
borderColor: colors.borderDefault,
borderColor: colors.borderSubtle,
borderRadius: Tokens.radii({ key: 'lg' }),
backgroundColor: colors.background,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,26 +31,32 @@ export const mountRenderItem = (params: IMountRenderItemParams): ListRenderItem<
switch (item.type) {
case 'checkbox':
return (
<View style={styles.selectableItemWrapper}>
<TouchableOpacity
style={styles.selectableItemWrapper}
activeOpacity={0.8}
onPress={onPressItem}>
<CheckBox
title={item.title}
subTitle={item.subTitle}
isChecked={item.isChecked}
onPress={onPressItem}
/>
</View>
</TouchableOpacity>
);

case 'radio':
return (
<View style={styles.selectableItemWrapper}>
<TouchableOpacity
style={styles.selectableItemWrapper}
activeOpacity={0.8}
onPress={onPressItem}>
<RadioButton
title={item.title}
subTitle={item.subTitle}
isChecked={item.isChecked}
onPress={onPressItem}
/>
</View>
</TouchableOpacity>
);

case 'avatar':
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { BottomSheetBackgroundProps } from '@gorhom/bottom-sheet';
import React from 'react';
import Animated from 'react-native-reanimated';
import { useStyles } from './styles';

export const ModalBackground: React.FC<BottomSheetBackgroundProps> = (props) => {
const { style } = props;
const styles = useStyles();

return (
<Animated.View style={[style, styles.background]} />
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { StyleSheet } from 'react-native';
import { useRubberDuckStore } from '../../../../../store';
import { Tokens } from '../../../../../tokens/Tokens.class';

export const useStyles = () => {
const colors = useRubberDuckStore((s) => s.colors);

return StyleSheet.create({
background: {
borderTopRightRadius: Tokens.radii({ key: 'lg' }),
borderTopLeftRadius: Tokens.radii({ key: 'lg' }),
backgroundColor: colors.surface,
},
});
};
1 change: 1 addition & 0 deletions src/components/Organisms/BottomModal/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './ModalBackground';
3 changes: 2 additions & 1 deletion src/components/Organisms/BottomModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
} from '@gorhom/bottom-sheet';
import { useStyles } from './styles';
import { useBottomModalViewModel } from './hooks';
import { ModalBackground } from './components';

export const BottomModal: React.FC<IBottomModalProps> = () => {
const { height: screenHeight } = useWindowDimensions();
Expand All @@ -33,8 +34,8 @@ export const BottomModal: React.FC<IBottomModalProps> = () => {
enableDynamicSizing
maxDynamicContentSize={screenHeight * 0.85}
backdropComponent={renderBackdrop}
backgroundComponent={ModalBackground}
handleIndicatorStyle={styles.handleIndicator}
backgroundStyle={styles.background}
enablePanDownToClose>
{bottomModalProps?.content ?? null}
</BottomSheetModal>
Expand Down
4 changes: 0 additions & 4 deletions src/components/Organisms/BottomModal/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,5 @@ export const useStyles = () => {
height: 4,
borderRadius: Tokens.radii({ key: 'full' }),
},

background: {
backgroundColor: colors.surface,
},
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export const useToasterViewModel = (ref: Ref<IToasterApi> | null) => {
clearTimer();
setToasterPropsState(props);
setIsVisible(true);
timeoutRef.current = setTimeout(() => setIsVisible(false), props.duration ?? DEFAULT_TOAST_DURATION_MS);
if (!props.loading) {
timeoutRef.current = setTimeout(() => setIsVisible(false), props.duration ?? DEFAULT_TOAST_DURATION_MS);
}
};

const hide = () => {
Expand Down
65 changes: 41 additions & 24 deletions src/components/Organisms/Toaster/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,45 +8,62 @@ import { useToasterViewModel, useReanimatedStyles } from './hooks';
import { TYPE_ICON_MAP } from './constants';
import { Text } from '../../Atoms/Text';
import { Icon } from '../../Atoms/Icon';
import { Loading } from '../../Atoms/Loading';

export const Toaster: React.FC<IToasterProps> = ({ ref }) => {
const { toasterProps, isVisible, hide } = useToasterViewModel(ref ?? null);
const styles = useStyles(toasterProps?.type);
const reanimatedStyles = useReanimatedStyles(isVisible, hide);

const isLoading = toasterProps?.loading ?? false;

return (
<GestureDetector gesture={reanimatedStyles.gesture}>
<Animated.View
pointerEvents={isVisible ? 'box-none' : 'none'}
style={[styles.wrapper, reanimatedStyles.wrapper]}>
style={[isLoading ? styles.wrapperLoading : styles.wrapper, reanimatedStyles.wrapper]}>
{toasterProps
?
<>
<Icon
icon={TYPE_ICON_MAP[toasterProps.type]}
size="small_16"
color="white"
/>
<View style={styles.textWrapper}>
<Text size="sm" weight="semibold" color="textInverse">
{toasterProps.title}
</Text>
{toasterProps.description
?
<Text size="sm" color="textInverse">
{toasterProps.description}
{isLoading
?
<>
<Loading size="tiny_48" color="white" />

<Text size="sm" weight="semibold" color="textInverse">
{toasterProps.title}
</Text>
: null
}
</View>
</>
:
<>
<Icon
icon={TYPE_ICON_MAP[toasterProps.type]}
size="small_16"
color="white"
/>
<View style={styles.textWrapper}>
<Text size="sm" weight="semibold" color="textInverse">
{toasterProps.title}
</Text>

{toasterProps.description
?
<Text size="sm" color="textInverse">
{toasterProps.description}
</Text>
: null
}
</View>

<TouchableOpacity
activeOpacity={0.8}
onPress={hide}
style={styles.closeButton}
hitSlop={8}>
<Icon icon="X" size="small_16" color="white" />
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.8}
onPress={hide}
style={styles.closeButton}
hitSlop={8}>
<Icon icon="X" size="small_16" color="white" />
</TouchableOpacity>
</>
}
</>
: null
}
Expand Down
14 changes: 14 additions & 0 deletions src/components/Organisms/Toaster/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,20 @@ export const useStyles = (type: ToasterType | undefined) => {
zIndex: 999,
},

wrapperLoading: {
position: 'absolute',
bottom: Tokens.spacer({ key: 'lg' }),
alignSelf: 'center',
paddingVertical: Tokens.spacer({ key: 'sm' }),
paddingHorizontal: Tokens.spacer({ key: 'md' }),
borderRadius: Tokens.radii({ key: 'full' }),
flexDirection: 'row',
alignItems: 'center',
gap: Tokens.spacer({ key: 'xs' }),
backgroundColor,
zIndex: 999,
},

textWrapper: {
flex: 1,
},
Expand Down
Loading
Loading