Skip to content

Commit

Permalink
feat(toolbar): add toolbar component
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur Bien authored and arturbien committed Jan 18, 2021
1 parent cbd93dd commit 7bdc5ff
Show file tree
Hide file tree
Showing 6 changed files with 251 additions and 1 deletion.
129 changes: 129 additions & 0 deletions example/src/examples/ToolbarExample.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React from 'react';
import { View, StyleSheet, Image } from 'react-native';
import { Divider, Toolbar, AppBar } from 'react95-native';

import { TouchableOpacity } from 'react-native-gesture-handler';
import { notificationService } from '../util/notifications';

const icons = [
{
label: 'Notepad',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAAFVBMVEUAAAAAAAD///8A///AwMAAgICAgICShRTRAAAAAXRSTlMAQObYZgAAAKxJREFUKM+FksEJwzAMReMN7FTNvaYLBNQFUrV3HzqAIXj/ERpLcuSGQB86Pb4+MskwnOF8kGliDDcZNW5GkElexFN5QWYBqDxUXFoiEQuHGCqITViFCMC5BmCrEGEVZc1SoSS650MFC7tikpXuiqUm+goRe8VUeKV7CMWa6CpEXD9KKrLiggK0cIJfQ4yJrV9YfxPxuBL/rURLjFEhFY6Utwor8e1rt3NP/oQvTyZR+XxB8B0AAAAASUVORK5CYII=',
},
{
label: 'PowerOff',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAGFBMVEUAAACAgID////AwMAAAAD/AAAAAIAAAP/DlrJZAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAHdElNRQflAQwXHQ1lXxPNAAAAxUlEQVQoz32RTQ6CMBBGawJ7B/AAwgmaoXsNDWsTHA9gQC5gPL8z0D8x4Qts3rxOp61SawCOKs2hbn4AQIOVjWWozw1UYC++XiBaewWoHCh0iRIdAGqQlBG47IK2iyCnYQWGbishwtbaDonckhMNAgzdfQ9RRAhNDSssDHEXVuSPgKv8pXMQiZQAQzLMHtgu+Wu63VaqmAz2FEGU0YFJBFH84Viw6fFzeiy3rid/Qdns4h9K9Z8l7/CWWf/ijEFgwk3dy34B82lC6z0gIAEAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDEtMTJUMjM6Mjk6MTMrMDA6MDDJz0whAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTAxLTEyVDIzOjI5OjEzKzAwOjAwuJL0nQAAAABJRU5ErkJggg==',
},
{
label: 'Tree',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAGFBMVEUAAAAAgAD///8A/wD//wAAAACAgACAAADkuDBqAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAHdElNRQflAQwXHQ1lXxPNAAAA20lEQVQoz62QUY7DIAxEnY9mf+u6HAD1BGibe4AqJyegnIA9/47xtkr2uwMSmvGzISEy8RC9NHGMKSJJ7vlylSuL6TzqN4G3evKuC3OEEfOWTPHOwmML8jONA+Jv8av4Pk4ZQxIvNiLZEm9S4puXHQgBr7IJ4qEUpSkZYZdilZDxUB+BASGEguBkxbGXgg5I62NMgLUOaFOFX0JQ9zR30rWovjxRy19dqen7D819bnnubwDIWvMOAPLT1j1A9KzbHgDSNB+DegRA5H9BzR8g+jF4btsBOeHb/x7yCxoPMLnchctIAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTAxLTEyVDIzOjI5OjEzKzAwOjAwyc9MIQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wMS0xMlQyMzoyOToxMyswMDowMLiS9J0AAAAASUVORK5CYII=',
},
{
label: 'Unmute',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAFVBMVEUAAACAgAAAAACAgIDAwMD//////wAkoAZpAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAHdElNRQflAQwXHQ1lXxPNAAAAy0lEQVQoz42RSxKDIAxAg54AK64r1j1T2gNUKweoDRco9P5HKKLycdUsmMnjTSAJwL9RnZezDDkZxiUXAdSXt0hz8uxRJDnUnzvOSUWiXlMGajvsQKyC3kApN8F2E6IA6e8p/Wr0oNxefFhcwVqQaYaIXEaAC+jmPhqN1joFlln3yrj8XboAphqllAebYWpjTCcj8Abn0w4Ipd4IADh3SncLP3XROiPpBaCg6pp1Cy3N5+GUKp8YtKd8pk45TB3aw16gqA6bA37YbYwflrYzo8mrW7wAAAAldEVYdGRhdGU6Y3JlYXRlADIwMjEtMDEtMTJUMjM6Mjk6MTMrMDA6MDDJz0whAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDIxLTAxLTEyVDIzOjI5OjEzKzAwOjAwuJL0nQAAAABJRU5ErkJggg==',
},
{
label: 'Keys',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAAFVBMVEUAAAAAAACAgID////AwMCAgAD//wA+bLkQAAAAAXRSTlMAQObYZgAAAPFJREFUKM9VkEFywyAMRcGTAwDjsg4SYe+EC1Cr3nvR7skk3P8Ixdiy47+S3jz9YRCiRmslPiOvwn3u4OAKcDjdY/COaN4Jku5B2/fMQkxgspHlR7Ew+JBVKq+ZQcKQLykfIOpQwFq73SCN36GUnF9vBhErsHbajC6OaBeBjapgKmWa2KgKpnpTdkMgklmI2996j8kAhOfXcUOD1nZXkCCO5GRhBckj3ZUIrEjvTYQFsNJX4JXofjeli/WkAuHsqrSOBXBL7dC+gdvfU68dqm+g08WtHa10GdtX76U1rometg5On0x/AlLJeAJNOu9V4ukf9rY2A1/ZhUcAAAAASUVORK5CYII=',
},
{
label: 'Defrag',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAM1BMVEUAAACAAICAgID//////wAAAAD/AP+AAACAgADAwMD/AAAAgAAA/wAAAIAAAP8AgIAA//8CrCWZAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAHdElNRQflAQ0AAgEAZoPoAAAA8ElEQVQ4y33SWwKEIAgFUDXRisr2v9oBfOI4Q3/eE2JpzLqsnRacczrftHCb9yOB0EWMkcC+K3J0EcN5xui816QLaiDEZ7LDLCKnnTQg4rACMokgogES9BTA5AsYySs4B4B4FGE6QMQKELcqOsDtuhAzgEuJWBsIkS20KIBTIiBDKtEAE4RdfiOJo4oBkIAQApP8CVbA2kxgmuHuHYTAfMz7edoWRCyDJNUBEQIpC6i5G8Fzp/dNqYKXCxSgN3itAOAyGnCDVMA05F3A+wUAzkaWYCQ/QCcp/QAjoZu1Ao3w3bSwyCthsI4L+RdnMq98AAK4FL2Wak4JAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTAxLTEzVDAwOjAyOjAxKzAwOjAwwLomUgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wMS0xM1QwMDowMjowMSswMDowMLHnnu4AAAAASUVORK5CYII=',
},
{
label: 'MediaCD',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAGFBMVEUAAAAAAACAgID//wDAwMD///8A//8A/wDOM1s/AAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAHdElNRQflAQ0AAgEAZoPoAAAA7UlEQVQoz2XRzW7DIAwHcJcngKjteVhe+wBMyjmJyc6bsgdoG3HfFK2vPxOgHyyn8MvfDrIBAAxUj9E11GJq0Zsa4B+ARsRyjHnVuDfGlwQ7oxXuBbhLhVujj78rcJd/2nyb/SWC16kH7TJMH6nLYZvAe7ueVTAJps9+rVF8MlcB74lWIOb5/QdJAn2XYWRrmi8ishn4rMFif0+wdLNUrha/PIGUEtaJSZryQ+LV93i4gZLAxMM4C6SbSoDZLaEAoAR4WMKcBwIxwO46hgJKAjwIzGX48QauXcKpDF6JDO1i79sRce3Ttjay3/T2B3yQPfBCfRXXAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTAxLTEzVDAwOjAyOjAxKzAwOjAwwLomUgAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wMS0xM1QwMDowMjowMSswMDowMLHnnu4AAAAASUVORK5CYII=',
},
{
label: 'Bookmark',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAJ1BMVEUAAACAgID///8AAADAwMAAgIAAgAAA/wCAAID/AP+AgAD//wAA///5GE4vAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAHdElNRQflAQwXHQ1lXxPNAAAAq0lEQVQoz2NgIAcIggADI4gUAPOFlIAAShqA+GCmMIhQEjE0YBAVEnIBAmEVIOEkkpYMEgCrcAKrKC9GUwERQFJhbIymwtQATUVHM5qKmZPRVCAJQFQgGQpRgWQoRMWqxWgqdm+Gq1BCEkCyFmIokrUYhoYGo6k4cxjNUAwBsKHgUBdWBJFgQ41BwERYBEyDBEJBINjYGEyHGjAYQ2RCQyEMA2jsMQNNIxYAAJmCSHaZSKbTAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTAxLTEyVDIzOjI5OjEzKzAwOjAwyc9MIQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wMS0xMlQyMzoyOToxMyswMDowMLiS9J0AAAAASUVORK5CYII=',
},
{
label: 'Brush',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAALVBMVEUAAAAAAADAwMD//////wCAgAD/AP+AAIAA/wAAgAAAAP+AgID/AACAAAAAAIDFDtfpAAAAAXRSTlMAQObYZgAAATRJREFUKM9tkj1OxDAQhdc3yMTQbKqFiQQICggnsAQICqQUuYLTI6ScIe2KIinTukDiCtROg7aEBlFR5AzMjJ3dLZjCkl8+v/nLYvFPKABI9u9pURSrPUUXiHhU7oArw3ETEDo1orUWMSBAgjG31tZmFtSxEDXm/EZdisUdKU9ioom4phyNrZ/FBCAmaRrDJooK1HjSoQSZaM5q2raVSkxJllxG13UvAdHie0/Emr7n1WoQ4bTremkGltkbC6bv19KLGrI8tBJzLJOUe6ZWjH8nAoZFyrUQce5HQspESS2KAD+yCaQyFZK995utExOfAZnndoAXftzgmYsEp/n6MA/OvUZCBoKO4gexkuL5MQvfJgxWkF/nJrIIk86IeJwmOquwHHUYi6hgu+yKo4x33hbA/Af8AeB3ZmU7WGsvAAAAAElFTkSuQmCC',
},
{
label: 'Earth',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgAgMAAAAOFJJnAAAADFBMVEUAAAAAAAAAAP8A/wB4yMwVAAAAAXRSTlMAQObYZgAAAKtJREFUGNNN0LERgzAMBVDRJBOkockqDBGW8BKswBBp+FxwkzZ4BdYIRSoq50D5skkuavzO9+WzJCJS15LqNOBiZ9HMvquIo+KNiTgbFqLRl2JlRFU3hEoOIWHaMbRSEhG4f7EYQkS/iPM6Equ4PmLMYA/4osMG1oPwhhuRrn7oDD4YSkIV1x3zHw4ZrQH8UCtFBuex/thxCkszKynNLOdx6J9VWgLyEva1fACDe5MuQgDwPAAAAABJRU5ErkJggg==',
},
{
label: 'Dial',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAFVBMVEUAAACAgID///8AAADAwMCAAAD/AADqeraFAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAHdElNRQflAQwXHQ1lXxPNAAAAqElEQVQoz5WOMQ6DMAxFLUXda3yCGLrTwAmC2CtV5QJVuf8RmmCCTaQOtX6Wp+cfA5aBfRpuJQdAkhyASPLToGGQ/FuKVWnTVaWB/R3xakoDUxcmU0rsw9T15lJV9m9VKZc2zLyBXNr6wAqyHwkV5NLRp1OMgRSZC5DSUYGMixUAUeaHKhuZ36q4IW0tH7OUtm7r+jTAxWU9GfA6CwC1AJdKSDt9BVx6XzBwJ8Kxeb3/AAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDIxLTAxLTEyVDIzOjI5OjEzKzAwOjAwyc9MIQAAACV0RVh0ZGF0ZTptb2RpZnkAMjAyMS0wMS0xMlQyMzoyOToxMyswMDowMLiS9J0AAAAASUVORK5CYII=',
},
{
label: 'TimeDate',
uri:
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgBAMAAACBVGfHAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAAHlBMVEUAAACAgIAAAAD////AwMAAAP+AAAD/AAAAAICAgABbUNNuAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAHdElNRQflAQwXHQ1lXxPNAAAA7klEQVQoz22RQY7CMAxFzSy6JkzFunVGPQCmVdlSNBeocoKRkn0W+Aqz58LYUZtGgBdx8vLt7yiwMxr7tJoGAHakcf1O6SCgLUGNCbgEekn1oRGgO3sl5+YCIJLrV7CU0BtwL2BrugAR5Kbzq8ucXLYexaSfASIO3v9JyoohMN9toQjceY6bYmAk8mxz0xDpNOmagZbTsAJzFPGoha1RgPWvXI0XEf7cbgrMMZ5pfJANrTEKsIsnGv+nKcho2MCXAKKHdE1A/gEqthd1Yf2mFKstZBDpbGWwDCrudPRcIRL28rjtDJU8PxYCEG9cz09/GlpfiqrQXAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMS0wMS0xMlQyMzoyOToxMyswMDowMMnPTCEAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjEtMDEtMTJUMjM6Mjk6MTMrMDA6MDC4kvSdAAAAAElFTkSuQmCC',
},
];

const ToolbarExample = () => {
return (
<View style={{ backgroundColor: 'teal', flex: 1 }}>
<AppBar style={{ padding: 2 }}>
<AppBar.BackAction onPress={() => {}} />
<AppBar.Content title='Office' subtitle='tools' />
<View style={styles.wrapper}>
<Divider orientation='vertical' style={{ marginRight: 4 }} />
<Divider
variant='raised'
orientation='vertical'
style={{ height: '80%' }}
/>
<View style={styles.toolbarWrapper}>
<Toolbar>
{icons.map(icon => (
<TouchableOpacity
key={icon.label}
onPress={() =>
notificationService.send({
message: `You just pressed ${icon.label} icon!`,
closeButtonLabel: 'OK!',
})
}
>
<Image
source={{ uri: icon.uri }}
style={styles.item}
key={icon.label}
/>
</TouchableOpacity>
))}
</Toolbar>
</View>
</View>
</AppBar>
</View>
);
};

const styles = StyleSheet.create({
wrapper: {
flexDirection: 'row',
alignItems: 'center',
height: 40,
width: 240,
},
toolbarWrapper: {
flex: 1,
paddingHorizontal: 4,
},
item: {
marginHorizontal: 8,
width: 32,
height: 32,
},
});

export default ToolbarExample;
6 changes: 6 additions & 0 deletions example/src/examples/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import SliderExample from './SliderExample';
import SnackbarExample from './SnackbarExample';
import TabsExample from './TabsExample';
import TextInputExample from './TextInputExample';
import ToolbarExample from './ToolbarExample';
import TypographyExample from './TypographyExample';
import WindowExample from './WindowExample';

Expand Down Expand Up @@ -97,6 +98,11 @@ export default [
component: SnackbarExample,
title: 'Snackbar',
},
{
name: 'ToolbarExample',
component: ToolbarExample,
title: 'Toolbar',
},
{ name: 'WindowExample', component: WindowExample, title: 'Window' },
],
},
Expand Down
2 changes: 1 addition & 1 deletion example/src/util/notifications.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export const NotificationProvider = () => {
label: notification.closeButtonLabel || 'OK',
onPress: () => notificationService.remove(notification),
}}
duration={Snackbar.DURATION_SHORT}
duration={1000}
>
{notification.message}
</Snackbar>
Expand Down
113 changes: 113 additions & 0 deletions src/components/Toolbar/Toolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';
import {
StyleSheet,
View,
ScrollView as RNScrollView,
ViewStyle,
StyleProp,
} from 'react-native';
import type {
LayoutChangeEvent,
NativeScrollEvent,
NativeSyntheticEvent,
} from 'react-native';

import { ChevronIcon } from '../..';

const chevronIconSize = 20;

type ScrollViewProps = React.ComponentProps<typeof View> & {
children: React.ReactNode;
scrollViewProps?: React.ComponentProps<typeof RNScrollView>;
style?: StyleProp<ViewStyle>;
};

// TODO: performance improvements (callbacks, refs ...etc)
const ScrollView = ({
children,
scrollViewProps = {},
style,
...rest
}: ScrollViewProps) => {
const [contentOffset, setContentOffset] = React.useState(0);
const [contentSize, setContentSize] = React.useState(0);
const [scrollViewSize, setScrollViewSize] = React.useState(0);

// TODO: that's a naive approach. if it causes problems
// trigger a callback when last child becomes fully visible
const lastElementVisible =
contentOffset + scrollViewSize < contentSize - chevronIconSize;

const handleScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
scrollViewProps.onScroll?.(e);
setContentOffset(e.nativeEvent.contentOffset.x);
};

const handleContentSizeChange = (width: number, height: number) => {
scrollViewProps.onContentSizeChange?.(width, height);
setContentSize(width);
};

const handleLayout = (e: LayoutChangeEvent) => {
scrollViewProps.onLayout?.(e);
setScrollViewSize(e.nativeEvent.layout.width);
};

return (
<View
style={[
styles.wrapper,
{
flexDirection: 'row',
},
style,
]}
{...rest}
>
<View style={[styles.content]}>
<RNScrollView
{...scrollViewProps}
showsHorizontalScrollIndicator={false}
scrollEventThrottle={100}
onScroll={handleScroll}
onContentSizeChange={handleContentSizeChange}
onLayout={handleLayout}
horizontal
>
{children}
</RNScrollView>
</View>
<View style={styles.chevronIcon}>
{lastElementVisible && (
<>
<ChevronIcon
segments={3}
direction='right'
style={{ marginRight: 1 }}
/>
<ChevronIcon segments={3} direction='right' />
</>
)}
</View>
</View>
);
};

const styles = StyleSheet.create({
wrapper: {
display: 'flex',
position: 'relative',
},
content: {
flexGrow: 1,
flexShrink: 1,
},
chevronIcon: {
justifyContent: 'center',
flexDirection: 'row',
alignSelf: 'flex-start',
width: chevronIconSize,
},
});

export default ScrollView;
1 change: 1 addition & 0 deletions src/components/Toolbar/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Toolbar';
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export { default as Slider } from './components/Slider';
export { default as Snackbar } from './components/Snackbar';
export { default as Tabs } from './components/Tabs';
export { default as TextInput } from './components/TextInput';
export { default as Toolbar } from './components/Toolbar';
export { default as Window } from './components/Window';
export { Select, SelectBox } from './components/Select';
export { Text, Title, Anchor } from './components/Typography';
Expand Down

0 comments on commit 7bdc5ff

Please sign in to comment.