Autocomplete dropdown for relations editor
This commit is contained in:
parent
d47d93566e
commit
24458e26f0
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@recogito/recogito-client-core",
|
||||
"version": "0.1.5",
|
||||
"version": "0.1.6",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
@ -2315,6 +2315,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"compute-scroll-into-view": {
|
||||
"version": "1.0.14",
|
||||
"resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.14.tgz",
|
||||
"integrity": "sha512-mKDjINe3tc6hGelUMNDzuhorIUZ7kS7BwyY0r2wQd2HOH2tRuJykiC06iSEX8y1TuhNzvz4GcJnK16mM2J1NMQ=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
|
@ -2804,6 +2809,27 @@
|
|||
"integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
|
||||
"dev": true
|
||||
},
|
||||
"downshift": {
|
||||
"version": "5.4.6",
|
||||
"resolved": "https://registry.npmjs.org/downshift/-/downshift-5.4.6.tgz",
|
||||
"integrity": "sha512-GtSCmZUQMulQQ0gX3N3jvUAABJNU8IfAMLzFLu0E2fcOTt98xropy0iriYW2PSClRUqJ4QwKAov7FDy9Gk9aOA==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.10.2",
|
||||
"compute-scroll-into-view": "^1.0.14",
|
||||
"prop-types": "^15.7.2",
|
||||
"react-is": "^16.13.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.10.4",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.4.tgz",
|
||||
"integrity": "sha512-UpTN5yUJr9b4EX2CnGNWIvER7Ab83ibv0pcvvHc4UOdrBI5jb8bj+32cCwPX6xu0mt2daFNjYhoi+X7beH0RSw==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"duplexify": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.0",
|
||||
"downshift": "^5.4.6",
|
||||
"node-polyglot": "^2.4.0",
|
||||
"preact": "^10.4.1",
|
||||
"react-contenteditable": "^3.3.3",
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
import React, { useState, useEffect, useRef } from 'react'
|
||||
import { useCombobox } from 'downshift'
|
||||
|
||||
const RelationAutocomplete = props => {
|
||||
|
||||
const element = useRef();
|
||||
|
||||
const [ inputItems, setInputItems ] = useState(props.vocabulary);
|
||||
|
||||
useEffect(() =>
|
||||
element.current?.querySelector('input').focus(), []);
|
||||
|
||||
const onInputValueChange = ({ inputValue }) => {
|
||||
props.onChange(inputValue);
|
||||
setInputItems(
|
||||
props.vocabulary.filter(item =>
|
||||
item.toLowerCase().startsWith(inputValue.toLowerCase())))
|
||||
}
|
||||
|
||||
const {
|
||||
isOpen,
|
||||
getMenuProps,
|
||||
getInputProps,
|
||||
getComboboxProps,
|
||||
highlightedIndex,
|
||||
getItemProps,
|
||||
} = useCombobox({ items: inputItems, onInputValueChange });
|
||||
|
||||
return (
|
||||
<div ref={element}>
|
||||
<div {...getComboboxProps()}>
|
||||
<input
|
||||
{...getInputProps({ onKeyDown: evt => { if (!isOpen) props.onKeyDown(evt); } })}
|
||||
placeholder={props.placeholder}
|
||||
value={props.content} />
|
||||
</div>
|
||||
<ul {...getMenuProps()}>
|
||||
{isOpen && inputItems.map((item, index) => (
|
||||
<li style={
|
||||
highlightedIndex === index
|
||||
? { backgroundColor: '#bde4ff' }
|
||||
: {}
|
||||
}
|
||||
key={`${item}${index}`}
|
||||
{...getItemProps({ item, index })}>
|
||||
{item}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
export default RelationAutocomplete;
|
|
@ -1,6 +1,6 @@
|
|||
import React, { Component } from 'preact/compat';
|
||||
import ContentEditable from 'react-contenteditable';
|
||||
import { TrashIcon, CheckIcon } from '../../Icons';
|
||||
import RelationAutocomplete from './RelationAutocomplete';
|
||||
|
||||
/**
|
||||
* Shorthand to get the label (= first tag body value) from the
|
||||
|
@ -54,13 +54,11 @@ export default class RelationEditor extends Component {
|
|||
|
||||
el.style.top = `${midY}px`;
|
||||
el.style.left = `${midX}px`;
|
||||
|
||||
setTimeout(() => el.querySelector('.input').focus(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
onChange = evt =>
|
||||
this.setState({ content: evt.target.value });
|
||||
onChange = value =>
|
||||
this.setState({ content: value });
|
||||
|
||||
onKeyDown = evt => {
|
||||
if (evt.which === 13) { // Enter = Submit
|
||||
|
@ -103,13 +101,12 @@ export default class RelationEditor extends Component {
|
|||
return(
|
||||
<div className="r6o-relation-editor" ref={this.element}>
|
||||
<div className="input-wrapper">
|
||||
<ContentEditable
|
||||
className="input"
|
||||
html={this.state.content}
|
||||
data-placeholder="Tag..."
|
||||
<RelationAutocomplete
|
||||
content={this.state.content}
|
||||
placeholder="Tag..."
|
||||
onChange={this.onChange}
|
||||
onKeyDown={this.onKeyDown}
|
||||
/>
|
||||
onKeyDown={this.onKeyDown}
|
||||
vocabulary={this.props.vocabulary || []} />
|
||||
</div>
|
||||
|
||||
<div className="buttons">
|
||||
|
|
|
@ -14,30 +14,56 @@
|
|||
}
|
||||
|
||||
* {
|
||||
line-height:31px;
|
||||
box-sizing:border-box;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
height:34px;
|
||||
padding:10px 6px;
|
||||
padding:0 6px;
|
||||
margin-right:68px;
|
||||
font-size:14px;
|
||||
min-width:80px;
|
||||
background-color:$blueish-white;
|
||||
cursor:text;
|
||||
@include rounded-corners-left(3px);
|
||||
}
|
||||
|
||||
.input {
|
||||
outline:none;
|
||||
line-height:14px;
|
||||
white-space:pre;
|
||||
}
|
||||
div[role=combobox] {
|
||||
height:34px;
|
||||
}
|
||||
|
||||
.input:empty:before {
|
||||
content:attr(data-placeholder);
|
||||
color:darken($lightgrey-border-darker, 15%);
|
||||
input {
|
||||
outline:none;
|
||||
border:none;
|
||||
width:80px;
|
||||
height:100%;
|
||||
line-height:14px;
|
||||
white-space:pre;
|
||||
box-sizing:border-box;
|
||||
background-color:transparent;
|
||||
font-size:14px;
|
||||
color:$standard-type;
|
||||
}
|
||||
|
||||
ul {
|
||||
position:relative;
|
||||
left:-6px;
|
||||
margin:0;
|
||||
padding:0;
|
||||
list-style-type:none;
|
||||
background-color:#fff;
|
||||
min-width:100%;
|
||||
border-radius:3px;
|
||||
border:1px solid #d6d7d9;
|
||||
box-sizing:border-box;
|
||||
box-shadow:0 0 20px rgba(0,0,0,0.25);
|
||||
}
|
||||
|
||||
li {
|
||||
box-sizing:border-box;
|
||||
padding:2px 12px;
|
||||
width:100%;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.buttons {
|
||||
|
|
Loading…
Reference in New Issue