Skip to content

Commit

Permalink
Mobile - Slash inserter (#29772)
Browse files Browse the repository at this point in the history
* Crudely fork web implementation to remove invariant violations

* WIP - Better UI for testing. Reset Autocomplete on delete content

* Use a synthetic event to bind onEnter to the autocompletion

* Mobile - Move onEnter logic to native Rich Text

* Simplify event conformance in native RichText

* Remove native variant of block-editor autocomplete

This adds the block completer

* Use Platform variant to wrap text on native, leaving web unchanged

* Extract autocomplete functions

* Merge autocomplete variants, and create an autocomplete ui variant

* WIP - add Popover library and begin experimenting

* Move optional from this to function

* WIP - Popover changes

* Extract default useItems hook from autocomplete ui

* Rename extracted ui for consistency

* Remove Popover library. Add UI for the slash inserter selector

* Remove lodash usage

* Fix preventDefault on Android

* Simplify customEditableOnKeyDown stubs

* Rename autocompletion slot fill and move to mobile components

* Reset scroll position when autocomplete items change

* Adjust styles for BlurView

(for iOS - needs Android changes)

* Update autocompletion item container view styles for Android

* Update autocomplete styles

* Fix order of imports

* Put feature behind __DEV__ flag

* Remove mobile wrapper since it no longer uses the label attribute to render the block name

* Add missing line break

* Update active item styles

* Revert "Put feature behind __DEV__ flag"

This reverts commit 528a83f.

* Add slash-inserter e2e test

* Update light-mode active background color for slash complete option

* Update placeholder text to hint the slash inserter

* Slash inserter animation and active item color update

* Styles cleanup

* Pass isVisible to useCallback

* Adjust autocompleter animation timing

* Remove redundant text decoration

* Adjust colors

* Use shorter writing prompt

This is taken from #29611

* Make test description more specific

Co-authored-by: Enej Bajgoric <[email protected]>

* Styles update

* Mobile: Update default text placeholder, fix autocompleter position in Android

* Use I with serif for italic format library icon

This is to distinguish the character from the slash character to avoid
confusion with the slash inserter (especially with the new writing
prompt that says to type "/").

* Mobile - Add Slash inserter e2e tests

* Unify e2e files

* Revert placeholders change

* Mark AutocompletionItemsSlot and AutocompletionItemsFill as __unstable

* Update styles to BEM naming

* Revert "Use I with serif for italic format library icon"

This reverts commit c251037.

* Fix slash inserter e2e

* Fix e2e test on Android

* Extract slash inserter assertion into separate function

* Extract platform variants for BackgroundView to separate files

Co-authored-by: Matthew Kevins <[email protected]>
Co-authored-by: Matthew Kevins <[email protected]>
Co-authored-by: Enej Bajgoric <[email protected]>
  • Loading branch information
4 people authored May 21, 2021
1 parent a584b1e commit 2db0d5c
Show file tree
Hide file tree
Showing 17 changed files with 736 additions and 177 deletions.
1 change: 1 addition & 0 deletions packages/base-styles/_colors.native.scss
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ $gray-lighten-10: lighten($gray, 10%); // #a8bece
$gray-lighten-20: lighten($gray, 20%); // #c8d7e1
$gray-lighten-30: lighten($gray, 30%); // #e9eff3
$gray-darken-20: darken($gray, 20%); // #4f748e
$gray-darken-30: darken($gray, 30%); // #3d596d

// Custom
$toolbar-button: #7b9ab1;
Expand Down

This file was deleted.

86 changes: 86 additions & 0 deletions packages/components/src/autocomplete/autocompleter-ui.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* External dependencies
*/
import classnames from 'classnames';
import { map } from 'lodash';

/**
* WordPress dependencies
*/
import { useLayoutEffect } from '@wordpress/element';
import { useAnchorRef } from '@wordpress/rich-text';

/**
* Internal dependencies
*/
import getDefaultUseItems from './get-default-use-items';
import Button from '../button';
import Popover from '../popover';

export function getAutoCompleterUI( autocompleter ) {
const useItems = autocompleter.useItems
? autocompleter.useItems
: getDefaultUseItems( autocompleter );

function AutocompleterUI( {
filterValue,
instanceId,
listBoxId,
className,
selectedIndex,
onChangeOptions,
onSelect,
onReset,
value,
contentRef,
} ) {
const [ items ] = useItems( filterValue );
const anchorRef = useAnchorRef( { ref: contentRef, value } );

useLayoutEffect( () => {
onChangeOptions( items );
}, [ items ] );

if ( ! items.length > 0 ) {
return null;
}

return (
<Popover
focusOnMount={ false }
onClose={ onReset }
position="top right"
className="components-autocomplete__popover"
anchorRef={ anchorRef }
>
<div
id={ listBoxId }
role="listbox"
className="components-autocomplete__results"
>
{ map( items, ( option, index ) => (
<Button
key={ option.key }
id={ `components-autocomplete-item-${ instanceId }-${ option.key }` }
role="option"
aria-selected={ index === selectedIndex }
disabled={ option.isDisabled }
className={ classnames(
'components-autocomplete__result',
className,
{
'is-selected': index === selectedIndex,
}
) }
onClick={ () => onSelect( option ) }
>
{ option.label }
</Button>
) ) }
</div>
</Popover>
);
}

return AutocompleterUI;
}
213 changes: 213 additions & 0 deletions packages/components/src/autocomplete/autocompleter-ui.native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/**
* External dependencies
*/
import {
View,
Animated,
StyleSheet,
Text,
TouchableOpacity,
ScrollView,
} from 'react-native';

/**
* WordPress dependencies
*/
import {
useLayoutEffect,
useEffect,
useRef,
useState,
useCallback,
} from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import {
Icon,
__unstableAutocompletionItemsFill as AutocompletionItemsFill,
} from '@wordpress/components';
import { usePreferredColorSchemeStyle } from '@wordpress/compose';

/**
* Internal dependencies
*/
import BackgroundView from './background-view';
import getDefaultUseItems from './get-default-use-items';
import styles from './style.scss';

const { compose: stylesCompose } = StyleSheet;

export function getAutoCompleterUI( autocompleter ) {
const useItems = autocompleter.useItems
? autocompleter.useItems
: getDefaultUseItems( autocompleter );

function AutocompleterUI( {
filterValue,
selectedIndex,
onChangeOptions,
onSelect,
value,
reset,
} ) {
const [ items ] = useItems( filterValue );
const scrollViewRef = useRef();
const animationValue = useRef( new Animated.Value( 0 ) ).current;
const [ isVisible, setIsVisible ] = useState( false );
const { text } = value;

useEffect( () => {
if ( ! isVisible && text.length > 0 ) {
setIsVisible( true );
}
}, [ isVisible, text ] );

useLayoutEffect( () => {
onChangeOptions( items );
scrollViewRef.current?.scrollTo( { x: 0, animated: false } );

if ( isVisible && text.length > 0 ) {
startAnimation( true );
} else if ( isVisible && text.length === 0 ) {
startAnimation( false );
}
}, [ items, isVisible, text ] );

const activeItemStyles = usePreferredColorSchemeStyle(
styles[ 'components-autocomplete__item-active' ],
styles[ 'components-autocomplete__item-active-dark' ]
);

const iconStyles = usePreferredColorSchemeStyle(
styles[ 'components-autocomplete__icon' ],
styles[ 'components-autocomplete__icon-active-dark' ]
);

const activeIconStyles = usePreferredColorSchemeStyle(
styles[ 'components-autocomplete__icon-active ' ],
styles[ 'components-autocomplete__icon-active-dark' ]
);

const textStyles = usePreferredColorSchemeStyle(
styles[ 'components-autocomplete__text' ],
styles[ 'components-autocomplete__text-dark' ]
);

const activeTextStyles = usePreferredColorSchemeStyle(
styles[ 'components-autocomplete__text-active' ],
styles[ 'components-autocomplete__text-active-dark' ]
);

const startAnimation = useCallback(
( show ) => {
Animated.timing( animationValue, {
toValue: show ? 1 : 0,
duration: show ? 200 : 100,
useNativeDriver: true,
} ).start( ( { finished } ) => {
if ( finished && ! show && isVisible ) {
setIsVisible( false );
reset();
}
} );
},
[ isVisible ]
);

const contentStyles = {
transform: [
{
translateY: animationValue.interpolate( {
inputRange: [ 0, 1 ],
outputRange: [
styles[ 'components-autocomplete' ].height,
0,
],
} ),
},
],
};

if ( ! items.length > 0 || ! isVisible ) {
return null;
}

return (
<AutocompletionItemsFill>
<View style={ styles[ 'components-autocomplete' ] }>
<Animated.View style={ contentStyles }>
<BackgroundView>
<ScrollView
ref={ scrollViewRef }
horizontal
contentContainerStyle={
styles[ 'components-autocomplete__content' ]
}
showsHorizontalScrollIndicator={ false }
keyboardShouldPersistTaps="always"
accessibilityLabel={
// translators: Slash inserter autocomplete results
__( 'Slash inserter results' )
}
>
{ items.map( ( option, index ) => {
const isActive = index === selectedIndex;
const itemStyle = stylesCompose(
styles[
'components-autocomplete__item'
],
isActive && activeItemStyles
);
const textStyle = stylesCompose(
textStyles,
isActive && activeTextStyles
);
const iconStyle = stylesCompose(
iconStyles,
isActive && activeIconStyles
);

return (
<TouchableOpacity
activeOpacity={ 0.5 }
style={ itemStyle }
key={ index }
onPress={ () => onSelect( option ) }
accessibilityLabel={ sprintf(
// translators: %s: Block name e.g. "Image block"
__( '%s block' ),
option?.value?.title
) }
>
<View
style={
styles[
'components-autocomplete__icon'
]
}
>
<Icon
icon={
option?.value?.icon?.src
}
size={ 24 }
style={ iconStyle }
/>
</View>
<Text style={ textStyle }>
{ option?.value?.title }
</Text>
</TouchableOpacity>
);
} ) }
</ScrollView>
</BackgroundView>
</Animated.View>
</View>
</AutocompletionItemsFill>
);
}

return AutocompleterUI;
}

export default getAutoCompleterUI;
25 changes: 25 additions & 0 deletions packages/components/src/autocomplete/background-view.android.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* External dependencies
*/
import { View } from 'react-native';

/**
* WordPress dependencies
*/
import { usePreferredColorSchemeStyle } from '@wordpress/compose';

/**
* Internal dependencies
*/
import styles from './style.scss';

const BackgroundView = ( { children } ) => {
const backgroundStyles = usePreferredColorSchemeStyle(
styles[ 'components-autocomplete__background' ],
styles[ 'components-autocomplete__background-dark' ]
);

return <View style={ backgroundStyles }>{ children }</View>;
};

export default BackgroundView;
23 changes: 23 additions & 0 deletions packages/components/src/autocomplete/background-view.ios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* External dependencies
*/
import { BlurView } from '@react-native-community/blur';

/**
* Internal dependencies
*/
import styles from './style.scss';

const BackgroundView = ( { children } ) => {
return (
<BlurView
style={ styles[ 'components-autocomplete__background-blur' ] }
blurType="prominent"
blurAmount={ 10 }
>
{ children }
</BlurView>
);
};

export default BackgroundView;
Loading

0 comments on commit 2db0d5c

Please sign in to comment.