Visual improvements to web autocomplete (#591)
* Visual improvements to web autocomplete * Remove stray stylingzio/stable
parent
84046f42d5
commit
cdfb1c7abf
|
@ -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 */
|
/* Remove default link styling */
|
||||||
a {
|
a {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
|
@ -106,28 +98,16 @@
|
||||||
color: #0085ff;
|
color: #0085ff;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
/* OLLIE: TODO -- this is not accessible */
|
||||||
|
/* Remove focus state on inputs */
|
||||||
|
.ProseMirror-focused {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
|
input:focus {
|
||||||
|
outline: 0;
|
||||||
|
}
|
||||||
.tippy-content .items {
|
.tippy-content .items {
|
||||||
border-radius: 6px;
|
width: fit-content;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% include "scripts.html" %}
|
{% include "scripts.html" %}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import React, {
|
import React, {
|
||||||
forwardRef,
|
forwardRef,
|
||||||
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
|
useMemo,
|
||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
|
import {StyleSheet, View} from 'react-native'
|
||||||
import {ReactRenderer} from '@tiptap/react'
|
import {ReactRenderer} from '@tiptap/react'
|
||||||
import tippy, {Instance as TippyInstance} from 'tippy.js'
|
import tippy, {Instance as TippyInstance} from 'tippy.js'
|
||||||
import {
|
import {
|
||||||
|
@ -12,6 +15,10 @@ import {
|
||||||
SuggestionKeyDownProps,
|
SuggestionKeyDownProps,
|
||||||
} from '@tiptap/suggestion'
|
} from '@tiptap/suggestion'
|
||||||
import {UserAutocompleteModel} from 'state/models/discovery/user-autocomplete'
|
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 {
|
interface MentionListRef {
|
||||||
onKeyDown: (props: SuggestionKeyDownProps) => boolean
|
onKeyDown: (props: SuggestionKeyDownProps) => boolean
|
||||||
|
@ -26,7 +33,7 @@ export function createSuggestion({
|
||||||
async items({query}) {
|
async items({query}) {
|
||||||
autocompleteView.setActive(true)
|
autocompleteView.setActive(true)
|
||||||
await autocompleteView.setPrefix(query)
|
await autocompleteView.setPrefix(query)
|
||||||
return autocompleteView.suggestions.slice(0, 8).map(s => s.handle)
|
return autocompleteView.suggestions.slice(0, 8)
|
||||||
},
|
},
|
||||||
|
|
||||||
render: () => {
|
render: () => {
|
||||||
|
@ -91,12 +98,14 @@ export function createSuggestion({
|
||||||
const MentionList = forwardRef<MentionListRef, SuggestionProps>(
|
const MentionList = forwardRef<MentionListRef, SuggestionProps>(
|
||||||
(props: SuggestionProps, ref) => {
|
(props: SuggestionProps, ref) => {
|
||||||
const [selectedIndex, setSelectedIndex] = useState(0)
|
const [selectedIndex, setSelectedIndex] = useState(0)
|
||||||
|
const pal = usePalette('default')
|
||||||
|
const splitter = useMemo(() => new Graphemer(), [])
|
||||||
|
|
||||||
const selectItem = (index: number) => {
|
const selectItem = (index: number) => {
|
||||||
const item = props.items[index]
|
const item = props.items[index]
|
||||||
|
|
||||||
if (item) {
|
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 (
|
return (
|
||||||
<div className="items">
|
<div className="items">
|
||||||
{props.items.length ? (
|
<View style={[pal.borderDark, pal.view, styles.container]}>
|
||||||
props.items.map((item, index) => (
|
{items.length > 0 ? (
|
||||||
<button
|
items.map((item, index) => {
|
||||||
className={`item ${index === selectedIndex ? 'is-selected' : ''}`}
|
const displayName = getDisplayedName(
|
||||||
key={index}
|
item.displayName ?? item.handle,
|
||||||
onClick={() => selectItem(index)}>
|
)
|
||||||
{item}
|
const isSelected = selectedIndex === index
|
||||||
</button>
|
|
||||||
))
|
return (
|
||||||
) : (
|
<View
|
||||||
<div className="item">No result</div>
|
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>
|
</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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
|
@ -110,27 +110,7 @@
|
||||||
outline: 0;
|
outline: 0;
|
||||||
}
|
}
|
||||||
.tippy-content .items {
|
.tippy-content .items {
|
||||||
border-radius: 6px;
|
width: fit-content;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
Loading…
Reference in New Issue