mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-21 09:38:01 +02:00
This commit is contained in:
parent
4e25377122
commit
a3a7ab2cf0
@ -402,6 +402,7 @@ packages/app-mobile/components/ActionButton.js
|
|||||||
packages/app-mobile/components/BackButtonDialogBox.js
|
packages/app-mobile/components/BackButtonDialogBox.js
|
||||||
packages/app-mobile/components/CameraView.js
|
packages/app-mobile/components/CameraView.js
|
||||||
packages/app-mobile/components/CustomButton.js
|
packages/app-mobile/components/CustomButton.js
|
||||||
|
packages/app-mobile/components/Dropdown.test.js
|
||||||
packages/app-mobile/components/Dropdown.js
|
packages/app-mobile/components/Dropdown.js
|
||||||
packages/app-mobile/components/ExtendedWebView.js
|
packages/app-mobile/components/ExtendedWebView.js
|
||||||
packages/app-mobile/components/FolderPicker.js
|
packages/app-mobile/components/FolderPicker.js
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -388,6 +388,7 @@ packages/app-mobile/components/ActionButton.js
|
|||||||
packages/app-mobile/components/BackButtonDialogBox.js
|
packages/app-mobile/components/BackButtonDialogBox.js
|
||||||
packages/app-mobile/components/CameraView.js
|
packages/app-mobile/components/CameraView.js
|
||||||
packages/app-mobile/components/CustomButton.js
|
packages/app-mobile/components/CustomButton.js
|
||||||
|
packages/app-mobile/components/Dropdown.test.js
|
||||||
packages/app-mobile/components/Dropdown.js
|
packages/app-mobile/components/Dropdown.js
|
||||||
packages/app-mobile/components/ExtendedWebView.js
|
packages/app-mobile/components/ExtendedWebView.js
|
||||||
packages/app-mobile/components/FolderPicker.js
|
packages/app-mobile/components/FolderPicker.js
|
||||||
|
56
packages/app-mobile/components/Dropdown.test.tsx
Normal file
56
packages/app-mobile/components/Dropdown.test.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { describe, it, expect, jest } from '@jest/globals';
|
||||||
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react-native';
|
||||||
|
import '@testing-library/jest-native';
|
||||||
|
|
||||||
|
import Dropdown, { DropdownListItem } from './Dropdown';
|
||||||
|
|
||||||
|
describe('Dropdown', () => {
|
||||||
|
it('should open the dropdown on click', async () => {
|
||||||
|
const items: DropdownListItem[] = [];
|
||||||
|
for (let i = 0; i < 400; i++) {
|
||||||
|
items.push({ label: `Item ${i}`, value: `${i}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const onValueChange = jest.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<Dropdown
|
||||||
|
items={items}
|
||||||
|
selectedValue={'1'}
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Should initially not show any other items
|
||||||
|
expect(screen.queryByText('Item 3')).toBeNull();
|
||||||
|
expect(screen.queryByText('Item 4')).toBeNull();
|
||||||
|
|
||||||
|
const openButton = screen.getByText('Item 1');
|
||||||
|
fireEvent.press(openButton);
|
||||||
|
|
||||||
|
// Other items should now be shown
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getByText('Item 3')).not.toBeNull();
|
||||||
|
expect(screen.getByText('Item 4')).not.toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pressing one of these items should hide the dropdown
|
||||||
|
fireEvent.press(screen.getByText('Item 4'));
|
||||||
|
|
||||||
|
// We haven't changed the selectedValue, so Item 301 should no longer be visible
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Item 4')).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(onValueChange).toHaveBeenLastCalledWith('4');
|
||||||
|
|
||||||
|
// The dropdown should still be clickable
|
||||||
|
fireEvent.press(screen.getByText('Item 1'));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText('Item 2')).not.toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,8 +1,7 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
import { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View, LayoutRectangle, ViewStyle, TextStyle } from 'react-native';
|
import { TouchableOpacity, TouchableWithoutFeedback, Dimensions, Text, Modal, View, LayoutRectangle, ViewStyle, TextStyle, FlatList } from 'react-native';
|
||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import { _ } from '@joplin/lib/locale';
|
import { _ } from '@joplin/lib/locale';
|
||||||
const { ItemList } = require('./ItemList.js');
|
|
||||||
|
|
||||||
type ValueType = string;
|
type ValueType = string;
|
||||||
export interface DropdownListItem {
|
export interface DropdownListItem {
|
||||||
@ -122,7 +121,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
|||||||
this.setState({ listVisible: false });
|
this.setState({ listVisible: false });
|
||||||
};
|
};
|
||||||
|
|
||||||
const itemRenderer = (item: DropdownListItem) => {
|
const itemRenderer = ({ item }: { item: DropdownListItem }) => {
|
||||||
const key = item.value ? item.value.toString() : '__null'; // The top item ("Move item to notebook...") has a null value.
|
const key = item.value ? item.value.toString() : '__null'; // The top item ("Move item to notebook...") has a null value.
|
||||||
return (
|
return (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
@ -194,11 +193,15 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
|
|||||||
<View
|
<View
|
||||||
accessibilityRole='menu'
|
accessibilityRole='menu'
|
||||||
style={wrapperStyle}>
|
style={wrapperStyle}>
|
||||||
<ItemList
|
<FlatList
|
||||||
style={itemListStyle}
|
style={itemListStyle}
|
||||||
items={this.props.items}
|
data={this.props.items}
|
||||||
itemHeight={itemHeight}
|
renderItem={itemRenderer}
|
||||||
itemRenderer={itemRenderer}
|
getItemLayout={(_data, index) => ({
|
||||||
|
length: itemHeight,
|
||||||
|
offset: itemHeight * index,
|
||||||
|
index,
|
||||||
|
})}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
const React = require('react');
|
|
||||||
const { View, ScrollView } = require('react-native');
|
|
||||||
|
|
||||||
class ItemList extends React.Component {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.scrollTop_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
itemCount(props = null) {
|
|
||||||
if (props === null) props = this.props;
|
|
||||||
return this.props.items ? this.props.items.length : this.props.itemComponents.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateStateItemIndexes(props = null, height = null) {
|
|
||||||
if (props === null) props = this.props;
|
|
||||||
|
|
||||||
if (height === null) {
|
|
||||||
if (!this.state) return;
|
|
||||||
height = this.state.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
const topItemIndex = Math.max(0, Math.floor(this.scrollTop_ / props.itemHeight));
|
|
||||||
const visibleItemCount = Math.ceil(height / props.itemHeight);
|
|
||||||
|
|
||||||
let bottomItemIndex = topItemIndex + visibleItemCount - 1;
|
|
||||||
if (bottomItemIndex >= this.itemCount(props)) bottomItemIndex = this.itemCount(props) - 1;
|
|
||||||
|
|
||||||
this.setState({
|
|
||||||
topItemIndex: topItemIndex,
|
|
||||||
bottomItemIndex: bottomItemIndex,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
|
||||||
this.setState({
|
|
||||||
topItemIndex: 0,
|
|
||||||
bottomItemIndex: 0,
|
|
||||||
height: 0,
|
|
||||||
itemHeight: this.props.itemHeight ? this.props.itemHeight : 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.updateStateItemIndexes();
|
|
||||||
}
|
|
||||||
|
|
||||||
UNSAFE_componentWillReceiveProps(newProps) {
|
|
||||||
if (newProps.itemHeight) {
|
|
||||||
this.setState({
|
|
||||||
itemHeight: newProps.itemHeight,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateStateItemIndexes(newProps);
|
|
||||||
}
|
|
||||||
|
|
||||||
onScroll(event) {
|
|
||||||
this.scrollTop_ = Math.floor(event.nativeEvent.contentOffset.y);
|
|
||||||
this.updateStateItemIndexes();
|
|
||||||
}
|
|
||||||
|
|
||||||
onLayout(event) {
|
|
||||||
this.setState({ height: event.nativeEvent.layout.height });
|
|
||||||
this.updateStateItemIndexes(null, event.nativeEvent.layout.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const style = this.props.style ? this.props.style : {};
|
|
||||||
|
|
||||||
// if (!this.props.itemHeight) throw new Error('itemHeight is required');
|
|
||||||
|
|
||||||
let itemComps = [];
|
|
||||||
|
|
||||||
if (this.props.items) {
|
|
||||||
const items = this.props.items;
|
|
||||||
|
|
||||||
const blankItem = function(key, height) {
|
|
||||||
return <View key={key} style={{ height: height }}></View>;
|
|
||||||
};
|
|
||||||
|
|
||||||
itemComps = [blankItem('top', this.state.topItemIndex * this.props.itemHeight)];
|
|
||||||
|
|
||||||
for (let i = this.state.topItemIndex; i <= this.state.bottomItemIndex; i++) {
|
|
||||||
const itemComp = this.props.itemRenderer(items[i]);
|
|
||||||
itemComps.push(itemComp);
|
|
||||||
}
|
|
||||||
|
|
||||||
itemComps.push(blankItem('bottom', (items.length - this.state.bottomItemIndex - 1) * this.props.itemHeight));
|
|
||||||
} else {
|
|
||||||
itemComps = this.props.itemComponents;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ScrollView
|
|
||||||
scrollEventThrottle={500}
|
|
||||||
onLayout={event => {
|
|
||||||
this.onLayout(event);
|
|
||||||
}}
|
|
||||||
style={style}
|
|
||||||
onScroll={event => {
|
|
||||||
this.onScroll(event);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{itemComps}
|
|
||||||
</ScrollView>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = { ItemList };
|
|
Loading…
Reference in New Issue
Block a user