Autocomplete dropdown for relations editor
This commit is contained in:
parent
d47d93566e
commit
24458e26f0
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@recogito/recogito-client-core",
|
"name": "@recogito/recogito-client-core",
|
||||||
"version": "0.1.5",
|
"version": "0.1.6",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"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": {
|
"concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
|
@ -2804,6 +2809,27 @@
|
||||||
"integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
|
"integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==",
|
||||||
"dev": true
|
"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": {
|
"duplexify": {
|
||||||
"version": "3.7.1",
|
"version": "3.7.1",
|
||||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
|
||||||
|
|
|
@ -42,6 +42,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.19.0",
|
"axios": "^0.19.0",
|
||||||
|
"downshift": "^5.4.6",
|
||||||
"node-polyglot": "^2.4.0",
|
"node-polyglot": "^2.4.0",
|
||||||
"preact": "^10.4.1",
|
"preact": "^10.4.1",
|
||||||
"react-contenteditable": "^3.3.3",
|
"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 React, { Component } from 'preact/compat';
|
||||||
import ContentEditable from 'react-contenteditable';
|
|
||||||
import { TrashIcon, CheckIcon } from '../../Icons';
|
import { TrashIcon, CheckIcon } from '../../Icons';
|
||||||
|
import RelationAutocomplete from './RelationAutocomplete';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shorthand to get the label (= first tag body value) from the
|
* 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.top = `${midY}px`;
|
||||||
el.style.left = `${midX}px`;
|
el.style.left = `${midX}px`;
|
||||||
|
|
||||||
setTimeout(() => el.querySelector('.input').focus(), 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = evt =>
|
onChange = value =>
|
||||||
this.setState({ content: evt.target.value });
|
this.setState({ content: value });
|
||||||
|
|
||||||
onKeyDown = evt => {
|
onKeyDown = evt => {
|
||||||
if (evt.which === 13) { // Enter = Submit
|
if (evt.which === 13) { // Enter = Submit
|
||||||
|
@ -103,13 +101,12 @@ export default class RelationEditor extends Component {
|
||||||
return(
|
return(
|
||||||
<div className="r6o-relation-editor" ref={this.element}>
|
<div className="r6o-relation-editor" ref={this.element}>
|
||||||
<div className="input-wrapper">
|
<div className="input-wrapper">
|
||||||
<ContentEditable
|
<RelationAutocomplete
|
||||||
className="input"
|
content={this.state.content}
|
||||||
html={this.state.content}
|
placeholder="Tag..."
|
||||||
data-placeholder="Tag..."
|
|
||||||
onChange={this.onChange}
|
onChange={this.onChange}
|
||||||
onKeyDown={this.onKeyDown}
|
onKeyDown={this.onKeyDown}
|
||||||
/>
|
vocabulary={this.props.vocabulary || []} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="buttons">
|
<div className="buttons">
|
||||||
|
|
|
@ -14,30 +14,56 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
line-height:31px;
|
|
||||||
box-sizing:border-box;
|
box-sizing:border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-wrapper {
|
.input-wrapper {
|
||||||
height:34px;
|
height:34px;
|
||||||
padding:10px 6px;
|
padding:0 6px;
|
||||||
margin-right:68px;
|
margin-right:68px;
|
||||||
font-size:14px;
|
font-size:14px;
|
||||||
min-width:80px;
|
|
||||||
background-color:$blueish-white;
|
background-color:$blueish-white;
|
||||||
cursor:text;
|
cursor:text;
|
||||||
@include rounded-corners-left(3px);
|
@include rounded-corners-left(3px);
|
||||||
}
|
|
||||||
|
|
||||||
.input {
|
div[role=combobox] {
|
||||||
outline:none;
|
height:34px;
|
||||||
line-height:14px;
|
}
|
||||||
white-space:pre;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input:empty:before {
|
input {
|
||||||
content:attr(data-placeholder);
|
outline:none;
|
||||||
color:darken($lightgrey-border-darker, 15%);
|
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 {
|
.buttons {
|
||||||
|
|
Loading…
Reference in New Issue