diff options
-rw-r--r-- | bskyweb/templates/base.html | 38 | ||||
-rw-r--r-- | src/view/com/composer/text-input/web/Autocomplete.tsx | 122 | ||||
-rw-r--r-- | web/index.html | 22 |
3 files changed, 118 insertions, 64 deletions
diff --git a/bskyweb/templates/base.html b/bskyweb/templates/base.html index 3bc8098ae..1d51b4f20 100644 --- a/bskyweb/templates/base.html +++ b/bskyweb/templates/base.html @@ -57,14 +57,6 @@ } }*/ - /* OLLIE: TODO -- this is not accessible */ - /* Remove focus state on inputs */ - .ProseMirror-focused { - outline: 0; - } - input:focus { - outline: 0; - } /* Remove default link styling */ a { color: inherit; @@ -106,28 +98,16 @@ color: #0085ff; cursor: pointer; } + /* OLLIE: TODO -- this is not accessible */ + /* Remove focus state on inputs */ + .ProseMirror-focused { + outline: 0; + } + input:focus { + outline: 0; + } .tippy-content .items { - border-radius: 6px; - background: #F3F3F8; - border: 1px solid #e0d9d9; - padding: 3px 3px; - } - .tippy-content .items .item { - display: block; - background: transparent; - color: #8a8c9a; - border: 0; - font: 17px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - padding: 7px 10px 8px; - width: 100%; - text-align: left; - box-sizing: border-box; - letter-spacing: 0.2px; - } - .tippy-content .items .item.is-selected { - background: #fff; - border-radius: 4px; - color: #333; + width: fit-content; } </style> {% include "scripts.html" %} diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx index 7c6f8770b..20dbbbbe8 100644 --- a/src/view/com/composer/text-input/web/Autocomplete.tsx +++ b/src/view/com/composer/text-input/web/Autocomplete.tsx @@ -1,9 +1,12 @@ import React, { forwardRef, + useCallback, useEffect, useImperativeHandle, + useMemo, useState, } from 'react' +import {StyleSheet, View} from 'react-native' import {ReactRenderer} from '@tiptap/react' import tippy, {Instance as TippyInstance} from 'tippy.js' import { @@ -12,6 +15,10 @@ import { SuggestionKeyDownProps, } from '@tiptap/suggestion' import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete' +import {usePalette} from 'lib/hooks/usePalette' +import Graphemer from 'graphemer' +import {Text} from 'view/com/util/text/Text' +import {UserAvatar} from 'view/com/util/UserAvatar' interface MentionListRef { onKeyDown: (props: SuggestionKeyDownProps) => boolean @@ -26,7 +33,7 @@ export function createSuggestion({ async items({query}) { autocompleteView.setActive(true) await autocompleteView.setPrefix(query) - return autocompleteView.suggestions.slice(0, 8).map(s => s.handle) + return autocompleteView.suggestions.slice(0, 8) }, render: () => { @@ -91,12 +98,14 @@ export function createSuggestion({ const MentionList = forwardRef<MentionListRef, SuggestionProps>( (props: SuggestionProps, ref) => { const [selectedIndex, setSelectedIndex] = useState(0) + const pal = usePalette('default') + const splitter = useMemo(() => new Graphemer(), []) const selectItem = (index: number) => { const item = props.items[index] if (item) { - props.command({id: item}) + props.command({id: item.handle}) } } @@ -137,21 +146,106 @@ const MentionList = forwardRef<MentionListRef, SuggestionProps>( }, })) + const {items} = props + + const getDisplayedName = useCallback( + (name: string) => { + // Heuristic value based on max display name and handle lengths + const DISPLAY_LIMIT = 30 + if (name.length > DISPLAY_LIMIT) { + const graphemes = splitter.splitGraphemes(name) + + if (graphemes.length > DISPLAY_LIMIT) { + return graphemes.length > DISPLAY_LIMIT + ? `${graphemes.slice(0, DISPLAY_LIMIT).join('')}...` + : name.substring(0, DISPLAY_LIMIT) + } + } + + return name + }, + [splitter], + ) + return ( <div className="items"> - {props.items.length ? ( - props.items.map((item, index) => ( - <button - className={`item ${index === selectedIndex ? 'is-selected' : ''}`} - key={index} - onClick={() => selectItem(index)}> - {item} - </button> - )) - ) : ( - <div className="item">No result</div> - )} + <View style={[pal.borderDark, pal.view, styles.container]}> + {items.length > 0 ? ( + items.map((item, index) => { + const displayName = getDisplayedName( + item.displayName ?? item.handle, + ) + const isSelected = selectedIndex === index + + return ( + <View + key={item.handle} + style={[ + isSelected ? pal.viewLight : undefined, + pal.borderDark, + styles.mentionContainer, + index === 0 + ? styles.firstMention + : index === items.length - 1 + ? styles.lastMention + : undefined, + ]}> + <View style={styles.avatarAndDisplayName}> + <UserAvatar avatar={item.avatar ?? null} size={26} /> + <Text style={pal.text} numberOfLines={1}> + {displayName} + </Text> + </View> + <Text type="xs" style={pal.textLight} numberOfLines={1}> + {item.handle} + </Text> + </View> + ) + }) + ) : ( + <Text type="sm" style={[pal.text, styles.noResult]}> + No result + </Text> + )} + </View> </div> ) }, ) + +const styles = StyleSheet.create({ + container: { + width: 500, + borderRadius: 6, + borderWidth: 1, + borderStyle: 'solid', + padding: 4, + }, + mentionContainer: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + flexDirection: 'row', + paddingHorizontal: 12, + paddingVertical: 8, + gap: 4, + }, + firstMention: { + borderTopLeftRadius: 2, + borderTopRightRadius: 2, + }, + lastMention: { + borderBottomLeftRadius: 2, + borderBottomRightRadius: 2, + }, + avatarAndDisplayName: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + gap: 6, + }, + noResult: { + paddingHorizontal: 12, + paddingVertical: 8, + }, +}) diff --git a/web/index.html b/web/index.html index f88fd727b..f518665ca 100644 --- a/web/index.html +++ b/web/index.html @@ -110,27 +110,7 @@ outline: 0; } .tippy-content .items { - border-radius: 6px; - background: #F3F3F8; - border: 1px solid #e0d9d9; - padding: 3px 3px; - } - .tippy-content .items .item { - display: block; - background: transparent; - color: #8a8c9a; - border: 0; - font: 17px -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; - padding: 7px 10px 8px; - width: 100%; - text-align: left; - box-sizing: border-box; - letter-spacing: 0.2px; - } - .tippy-content .items .item.is-selected { - background: #fff; - border-radius: 4px; - color: #333; + width: fit-content; } </style> </head> |