Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TagInput - Functional component for tag-like inputs #3735

Merged
merged 21 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e8e16f4
Initial commit: Rename component, set basic structure
Gazook89 Sep 17, 2024
ea7f18e
Merge branch 'master' into Functional-Tag-Editor
Gazook89 Sep 17, 2024
d505e4e
Render element for each value from props
Gazook89 Sep 18, 2024
d5c5b43
Render tags as "write" or "read"
Gazook89 Sep 18, 2024
36aa4ea
Add click handler for readTags to open text input
Gazook89 Sep 18, 2024
c5033db
add editing of input functionality
Gazook89 Sep 19, 2024
d1686c4
Add in handlers for TagInput value changes
Gazook89 Sep 19, 2024
70a3cb9
Add method for adding new tags
Gazook89 Sep 19, 2024
c65210b
Add 'remove' button and method
Gazook89 Sep 19, 2024
c1288ce
Use index to find and remove tags
Gazook89 Sep 19, 2024
a54adc1
Set new tag input to clear itself after submission
Gazook89 Sep 19, 2024
5b4a7c1
Add comma to "submit" buttons
Gazook89 Sep 19, 2024
7ea1696
Adjust html structure to handle tags as list
Gazook89 Sep 19, 2024
8a67e1e
Merge branch 'Functional-Tag-Editor' into Func-Tag-Editor-Features
Gazook89 Sep 19, 2024
b585e85
Fix multiple duplicate tags updating at once
Gazook89 Sep 19, 2024
10a9bc2
Merge branch 'master' into Functional-Tag-Editor
Gazook89 Sep 19, 2024
433f016
rename tag container class to unify with fields
Gazook89 Sep 19, 2024
a9b6d5f
Merge branch 'master' into Functional-Tag-Editor
Gazook89 Sep 23, 2024
5af45f1
remove tagInput-class
Gazook89 Sep 23, 2024
a96ff6e
Variable name changes for clarity
Gazook89 Sep 24, 2024
f4d4334
Merge branch 'master' into Functional-Tag-Editor
calculuschild Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 7 additions & 5 deletions client/homebrew/editor/metadataEditor/metadataEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const _ = require('lodash');
const request = require('../../utils/request-middleware.js');
const Nav = require('naturalcrit/nav/nav.jsx');
const Combobox = require('client/components/combobox.jsx');
const StringArrayEditor = require('../stringArrayEditor/stringArrayEditor.jsx');
const TagInput = require('../tagInput/tagInput.jsx');


const Themes = require('themes/themes.json');
Expand Down Expand Up @@ -341,10 +341,11 @@ const MetadataEditor = createClass({
{this.renderThumbnail()}
</div>

<StringArrayEditor label='tags' valuePatterns={[/^(?:(?:group|meta|system|type):)?[A-Za-z0-9][A-Za-z0-9 \/.\-]{0,40}$/]}
<TagInput label='tags' valuePatterns={[/^(?:(?:group|meta|system|type):)?[A-Za-z0-9][A-Za-z0-9 \/.\-]{0,40}$/]}
placeholder='add tag' unique={true}
values={this.props.metadata.tags}
onChange={(e)=>this.handleFieldChange('tags', e)}/>
onChange={(e)=>this.handleFieldChange('tags', e)}
/>

<div className='field systems'>
<label>systems</label>
Expand All @@ -363,12 +364,13 @@ const MetadataEditor = createClass({

{this.renderAuthors()}

<StringArrayEditor label='invited authors' valuePatterns={[/.+/]}
<TagInput label='invited authors' valuePatterns={[/.+/]}
validators={[(v)=>!this.props.metadata.authors?.includes(v)]}
placeholder='invite author' unique={true}
values={this.props.metadata.invitedAuthors}
notes={['Invited author usernames are case sensitive.', 'After adding an invited author, send them the edit link. There, they can choose to accept or decline the invitation.']}
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}/>
onChange={(e)=>this.handleFieldChange('invitedAuthors', e)}
/>
Gazook89 marked this conversation as resolved.
Show resolved Hide resolved

<h2>Privacy</h2>

Expand Down
2 changes: 1 addition & 1 deletion client/homebrew/editor/metadataEditor/metadataEditor.less
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@
&:last-child { border-radius : 0 0.5em 0.5em 0; }
}

.badge {
.tag {
padding : 0.3em;
margin : 2px;
font-size : 0.9em;
Expand Down
149 changes: 0 additions & 149 deletions client/homebrew/editor/stringArrayEditor/stringArrayEditor.jsx

This file was deleted.

105 changes: 105 additions & 0 deletions client/homebrew/editor/tagInput/tagInput.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
require('./tagInput.less');
const React = require('react');
const { useState, useEffect } = React;
const _ = require('lodash');

const TagInput = ({ unique = true, values = [], ...props }) => {
const [tempInputText, setTempInputText] = useState('');
const [tagList, setTagList] = useState(values.map((value) => ({ value, editing: false })));

useEffect(()=>{
handleChange(tagList.map((context)=>context.value))
}, [tagList])

const handleChange = (value)=>{
props.onChange({
target : { value }
})
};

const handleInputKeyDown = ({ evt, value, index, options = {} }) => {
Gazook89 marked this conversation as resolved.
Show resolved Hide resolved
if (_.includes(['Enter', ','], evt.key)) {
evt.preventDefault();
submitTag(evt.target.value, value, index);
if (options.clear) {
setTempInputText('');
}
}
};

const submitTag = (newValue, originalValue, index) => {
setTagList((prevContext) => {
// remove existing tag
if(newValue === null){
return [...prevContext].filter((context, i)=>i !== index);
}
// add new tag
if(originalValue === null){
return [...prevContext, { value: newValue, editing: false }]
}
// update existing tag
return prevContext.map((context, i) => {
if (i === index) {
return { ...context, value: newValue, editing: false };
}
return context;
});
});
};

const editTag = (index) => {
setTagList((prevContext) => {
return prevContext.map((context, i) => {
if (i === index) {
return { ...context, editing: true };
}
return { ...context, editing: false };
});
});
};

const renderReadTag = (context, index) => {
return (
<li key={index}
data-value={context.value}
className='tag'
onClick={() => editTag(index)}>
{context.value}
<button onClick={(evt)=>{evt.stopPropagation(); submitTag(null, context.value, index)}}><i className='fa fa-times fa-fw'/></button>
</li>
);
};

const renderWriteTag = (context, index) => {
return (
<input type='text'
key={index}
defaultValue={context.value}
onKeyDown={(evt) => handleInputKeyDown({evt, value: context.value, index: index})}
autoFocus
/>
);
};

return (
<div className='field'>
<label>{props.label}</label>
<div className='value'>
<ul className='list'>
{tagList.map((context, index) => { return context.editing ? renderWriteTag(context, index) : renderReadTag(context, index); })}
</ul>

<input
type='text'
className='value'
placeholder={props.placeholder}
value={tempInputText}
onChange={(e) => setTempInputText(e.target.value)}
onKeyDown={(evt) => handleInputKeyDown({ evt, value: null, options: { clear: true } })}
Gazook89 marked this conversation as resolved.
Show resolved Hide resolved
/>
</div>
</div>
);
};

module.exports = TagInput;
Empty file.