Skip to content

Commit

Permalink
feat(foundation): added Placeholder component
Browse files Browse the repository at this point in the history
  • Loading branch information
bang9 committed Mar 7, 2022
1 parent b25b4b7 commit e68d9a6
Show file tree
Hide file tree
Showing 16 changed files with 262 additions and 17 deletions.
25 changes: 25 additions & 0 deletions packages/uikit-react-native-core/src/localization/label.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ export interface LabelSet {
/** @domain InviteMembers > Header > Right */
HEADER_RIGHT: <T>(params: { selectedUsers: Array<T> }) => string;
};
PLACEHOLDER: {
NO_BANNED_MEMBERS: string;
NO_CHANNELS: string;
NO_MESSAGES: string;
NO_MUTED_MEMBERS: string;
NO_RESULTS_FOUND: string;
ERROR_SOMETHING_IS_WRONG: {
MESSAGE: string;
RETRY_LABEL: string;
};
};
}

type LabelCreateOptions = {
Expand Down Expand Up @@ -77,6 +88,7 @@ export const createBaseLabel = ({ dateLocale, overrides }: LabelCreateOptions):
return 'Turn off notifications';
},
MENU_LEAVE_CHANNEL: 'Leave channel',
...overrides?.GROUP_CHANNEL_LIST?.CHANNEL_MENU,
},
},
INVITE_MEMBERS: {
Expand All @@ -88,4 +100,17 @@ export const createBaseLabel = ({ dateLocale, overrides }: LabelCreateOptions):
},
...overrides?.INVITE_MEMBERS,
},
PLACEHOLDER: {
NO_BANNED_MEMBERS: 'No banned members',
NO_CHANNELS: 'There are no channels',
NO_MESSAGES: 'There are no messages',
NO_MUTED_MEMBERS: 'No muted members',
NO_RESULTS_FOUND: 'No results found',
...overrides?.PLACEHOLDER,
ERROR_SOMETHING_IS_WRONG: {
MESSAGE: 'Something is wrong',
RETRY_LABEL: 'Retry',
...overrides?.PLACEHOLDER?.ERROR_SOMETHING_IS_WRONG,
},
},
});
2 changes: 2 additions & 0 deletions packages/uikit-react-native-foundation/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ export { DialogProvider, useActionMenu, useAlert, usePrompt } from './ui/Dialog'
export { default as Divider } from './ui/Divider';
export { default as Header } from './ui/Header';
export { default as Icon } from './ui/Icon';
export { default as LoadingSpinner } from './ui/LoadingSpinner';
export { default as Modal } from './ui/Modal';
export { default as Placeholder } from './ui/Placeholder';
export { default as Prompt } from './ui/Prompt';
export { default as Switch } from './ui/Switch';
export { default as Text } from './ui/Text';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,14 @@ const DarkUIKitTheme: UIKitTheme = {
},
},
},
placeholder: {
default: {
none: {
content: Palette.onBackgroundDark03,
highlight: Palette.primary200,
},
},
},
},
},
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import createAppearanceHelper from '../styles/appearanceHelper';
import type { UIKitTheme } from '../types';
import Palette from './Palette';
import { defaultTypography } from './Typography';
import createAppearanceHelper from "../styles/appearanceHelper";
import type { UIKitTheme } from "../types";
import Palette from "./Palette";
import { defaultTypography } from "./Typography";

const appearance = 'light';
const LightUIKitTheme: UIKitTheme = {
Expand Down Expand Up @@ -112,6 +112,14 @@ const LightUIKitTheme: UIKitTheme = {
},
},
},
placeholder: {
default: {
none: {
content: Palette.onBackgroundLight03,
highlight: Palette.primary300,
},
},
},
},
},
};
Expand Down
6 changes: 5 additions & 1 deletion packages/uikit-react-native-foundation/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface UIKitTheme extends AppearanceHelper {
typography: Typography;
}

type Component = 'Header' | 'Button' | 'Dialog' | 'Input' | 'Badge';
type Component = 'Header' | 'Button' | 'Dialog' | 'Input' | 'Badge' | 'Placeholder';
type GetColorTree<
Tree extends {
Variant: {
Expand All @@ -50,20 +50,23 @@ export type ComponentColorTree = GetColorTree<{
Dialog: 'default';
Input: 'default' | 'underline';
Badge: 'default';
Placeholder: 'default';
};
State: {
Header: 'none';
Button: 'enabled' | 'pressed' | 'disabled';
Dialog: 'none';
Input: 'active' | 'disabled';
Badge: 'none';
Placeholder: 'none';
};
ColorPart: {
Header: 'background' | 'borderBottom';
Button: 'background' | 'content';
Dialog: 'background' | 'text' | 'message' | 'highlight' | 'destructive';
Input: 'text' | 'placeholder' | 'background' | 'highlight';
Badge: 'text' | 'background';
Placeholder: 'content' | 'highlight';
};
}>;
type ComponentColors<T extends Component> = {
Expand Down Expand Up @@ -104,6 +107,7 @@ export type UIKitColors = {
dialog: ComponentColors<'Dialog'>;
input: ComponentColors<'Input'>;
badge: ComponentColors<'Badge'>;
placeholder: ComponentColors<'Placeholder'>;
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React, { useState } from 'react';
import { ActivityIndicator, TouchableOpacity, View } from 'react-native';
import { TouchableOpacity, View } from 'react-native';

import { Logger } from '@sendbird/uikit-utils';

import createStyleSheet from '../../styles/createStyleSheet';
import useHeaderStyle from '../../styles/useHeaderStyle';
import useUIKitTheme from '../../theme/useUIKitTheme';
import DialogBox from '../Dialog/DialogBox';
import LoadingSpinner from '../LoadingSpinner';
import Modal from '../Modal';
import Text from '../Text';

Expand Down Expand Up @@ -49,8 +50,8 @@ const ActionMenu: React.FC<Props> = ({ visible, onHide, onError, onDismiss, titl
{title}
</Text>
{pending && (
<ActivityIndicator
size={'small'}
<LoadingSpinner
size={20}
color={colors.ui.dialog.default.none.highlight}
style={{ width: '10%', marginLeft: '4%' }}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ const Button: React.FC<Props> = ({

return (
<>
{icon && <Icon size={24} icon={icon} color={stateColor.content} containerStyle={styles.icon} />}
{icon && (
<Icon size={24} icon={icon} color={contentColor ?? stateColor.content} containerStyle={styles.icon} />
)}
<Text button color={contentColor ?? stateColor.content} style={styles.text}>
{children}
</Text>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useEffect, useRef } from 'react';
import { Animated, Easing, StyleProp, ViewStyle } from 'react-native';

import useUIKitTheme from '../../theme/useUIKitTheme';
import Icon from '../Icon';

type Props = {
size?: number;
color?: string;
style?: StyleProp<ViewStyle>;
};

const LoadingSpinner: React.FC<Props> = ({ size = 24, color, style }) => {
const { colors } = useUIKitTheme();
return (
<Rotate style={style}>
<Icon icon={'spinner'} size={size} color={color ?? colors.primary} />
</Rotate>
);
};

const useLoopAnimated = (duration: number, useNativeDriver = true) => {
const animated = useRef(new Animated.Value(0)).current;

useEffect(() => {
Animated.loop(
Animated.timing(animated, { toValue: 1, duration, useNativeDriver, easing: Easing.inOut(Easing.linear) }),
{ resetBeforeIteration: true },
).start();

return () => {
animated.stopAnimation();
animated.setValue(0);
};
}, []);

return animated;
};

const Rotate: React.FC<{ style: StyleProp<ViewStyle> }> = ({ children, style }) => {
const loop = useLoopAnimated(1000);
const rotate = loop.interpolate({ inputRange: [0, 1], outputRange: ['0deg', '360deg'] });
return <Animated.View style={[style, { transform: [{ rotate }] }]}>{children}</Animated.View>;
};

export default LoadingSpinner;
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import { View } from 'react-native';

import createStyleSheet from '../../styles/createStyleSheet';
import useUIKitTheme from '../../theme/useUIKitTheme';
import Button from '../Button';
import Icon from '../Icon';
import LoadingSpinner from '../LoadingSpinner';
import Text from '../Text';

type Props = {
loading?: boolean;

icon: keyof typeof Icon.Assets;
message?: string;
errorRetryLabel?: string;
onPressRetry?: () => void;
};

const Placeholder: React.FC<Props> = ({ icon, loading = false, message = '', errorRetryLabel, onPressRetry }) => {
const { colors } = useUIKitTheme();

return (
<View style={loading ? styles.containerLoading : errorRetryLabel ? styles.containerError : styles.container}>
{loading ? (
<LoadingSpinner size={64} color={colors.ui.placeholder.default.none.highlight} />
) : (
<Icon icon={icon} size={64} color={colors.ui.placeholder.default.none.content} />
)}
{Boolean(message) && !loading && (
<Text body3 color={colors.ui.placeholder.default.none.content}>
{message}
</Text>
)}
{Boolean(errorRetryLabel) && !loading && (
<Button
variant={'text'}
onPress={onPressRetry}
contentColor={colors.ui.placeholder.default.none.highlight}
icon={'refresh'}
>
{errorRetryLabel}
</Button>
)}
</View>
);
};

const styles = createStyleSheet({
container: {
width: 200,
height: 100,
alignItems: 'center',
justifyContent: 'space-between',
},
containerError: {
width: 200,
height: 148,
alignItems: 'center',
justifyContent: 'space-between',
},
containerLoading: {
width: 200,
height: 100,
alignItems: 'center',
justifyContent: 'center',
},
});

export default Placeholder;
1 change: 1 addition & 0 deletions packages/uikit-react-native/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** UI **/
export { default as GroupChannelPreview } from './ui/GroupChannelPreview';
export { default as TypedPlaceholder } from './ui/TypedPlaceholder';

/** Fragments **/
export { default as createGroupChannelListFragment } from './fragments/createGroupChannelListFragment';
Expand Down
42 changes: 42 additions & 0 deletions packages/uikit-react-native/src/ui/TypedPlaceholder/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';

import { useLocalization } from '@sendbird/uikit-react-native-core';
import Placeholder from '@sendbird/uikit-react-native-foundation/src/ui/Placeholder';

type Props = {
type:
| 'no-banned-members'
| 'no-channels'
| 'no-messages'
| 'no-muted-members'
| 'no-results-found'
| 'error-wrong'
| 'loading';
};
const TypedPlaceholder: React.FC<Props> = ({ type }) => {
const { LABEL } = useLocalization();
switch (type) {
case 'no-banned-members':
return <Placeholder icon={'ban'} message={LABEL.PLACEHOLDER.NO_BANNED_MEMBERS} />;
case 'no-channels':
return <Placeholder icon={'chat'} message={LABEL.PLACEHOLDER.NO_BANNED_MEMBERS} />;
case 'no-messages':
return <Placeholder icon={'message'} message={LABEL.PLACEHOLDER.NO_BANNED_MEMBERS} />;
case 'no-muted-members':
return <Placeholder icon={'mute'} message={LABEL.PLACEHOLDER.NO_BANNED_MEMBERS} />;
case 'no-results-found':
return <Placeholder icon={'search'} message={LABEL.PLACEHOLDER.NO_BANNED_MEMBERS} />;
case 'error-wrong':
return (
<Placeholder
icon={'error'}
message={LABEL.PLACEHOLDER.ERROR_SOMETHING_IS_WRONG.MESSAGE}
errorRetryLabel={LABEL.PLACEHOLDER.ERROR_SOMETHING_IS_WRONG.RETRY_LABEL}
/>
);
case 'loading':
return <Placeholder loading icon={'spinner'} />;
}
};

export default TypedPlaceholder;
6 changes: 3 additions & 3 deletions sample/.storybook/preview.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds';
import React from 'react';
import { View } from 'react-native';
import { Appearance, View } from 'react-native';

import { Palette } from '@sendbird/uikit-react-native-foundation';

Expand All @@ -12,8 +12,8 @@ export const decorators = [
];
export const parameters = {
backgrounds: [
{ name: 'light', value: Palette.background50, default: true },
{ name: 'dark', value: Palette.background600 },
{ name: 'light', value: Palette.background50, default: Appearance.getColorScheme() === 'light' },
{ name: 'dark', value: Palette.background600, default: Appearance.getColorScheme() === 'dark' },
],
layout: 'fullscreen',
options: {
Expand Down
1 change: 1 addition & 0 deletions sample/.storybook/storybook.requires.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const getStories = () => {
require("../stories/Dialog.stories.tsx"),
require("../stories/GroupChannelPreview.stories.tsx"),
require("../stories/Icon.stories.tsx"),
require("../stories/Placeholder.stories.tsx"),
require("../stories/Text.stories.tsx"),
];
};
Expand Down
4 changes: 2 additions & 2 deletions sample/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { Routes } from './hooks/useAppNavigation';
import useAppearance from './hooks/useAppearance';
import { GroupChannelTabs, HomeScreen, InviteMembersScreen, PaletteScreen, ThemeColorsScreen } from './screens';

Platform.OS === 'android' && StatusBar.setTranslucent(false);
Platform.OS === 'android' && StatusBar.setTranslucent(true);
const sdkInstance = new SendBird({ appId: APP_ID }) as SendbirdChatSDK;
const filePicker = createFilePickerServiceNative(ImagePicker, Permissions);
const RootStack = createNativeStackNavigator();
Expand All @@ -31,7 +31,7 @@ const App = () => {
services={{ filePicker, notification: {} as any }}
styles={{
theme: isLightTheme ? LightUIKitTheme : DarkUIKitTheme,
statusBarTranslucent: Platform.select({ ios: true, android: false }),
statusBarTranslucent: Platform.select({ ios: true, android: true }),
}}
>
<NavigationContainer theme={isLightTheme ? DefaultTheme : DarkTheme}>
Expand Down
Loading

0 comments on commit e68d9a6

Please sign in to comment.