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

Invite users to service rooms #42

Merged
merged 109 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
109 commits
Select commit Hold shift + click to select a range
6b41482
add icon and rudimentary component for inviting users
Aug 23, 2023
d040393
add logic to search for matrix users
Aug 23, 2023
43ba9b9
add invite user button
Aug 23, 2023
5851589
use react-modal package
Aug 23, 2023
5018104
add invitation logic and user feedback, close modal after sucessfull …
Aug 24, 2023
81f677a
add comments and TODO
Aug 24, 2023
cf3ec1e
improve object name
Aug 24, 2023
7369a8d
fix document undefined error
Aug 24, 2023
baf3db1
check for userId and displayName manually
Aug 24, 2023
73fceee
close modal after successful invitation
Aug 29, 2023
3f8bde8
merge main into invite-users
Aug 29, 2023
5b73822
merge main into invite-users
Aug 31, 2023
e1f40f2
remove unnecessary function and call function directly from matrixClient
Aug 31, 2023
77f8c79
use logger instead of console.log
Sep 5, 2023
1c935a0
replace `datalist` with `ServiceTable` and update imports
Sep 5, 2023
84cf241
remove unused imports
Sep 5, 2023
1036306
css clean up
Sep 5, 2023
3772fe4
clear search results on successful invitation
Sep 5, 2023
b7aa8ea
add translations and fix typos
Sep 5, 2023
882ad9a
remove duplicate
Sep 5, 2023
a44410a
add default modal component
Sep 5, 2023
5cb4e99
change modal style
Sep 6, 2023
67f7ac7
remove unused import
Sep 6, 2023
ef16180
change variable to from `name` to `roomName` and user user-add icon i…
Sep 6, 2023
626f47e
introduce and implement custom datalist component using ServiceTable
Sep 8, 2023
69bb68f
Create Datalist component and use when searching for users to invite
Sep 12, 2023
c52e8c9
Merge pull request #57 from medienhaus/invite-users-custom-datalist
andirueckel Sep 12, 2023
1df63a7
add translation
Sep 12, 2023
b0812bf
add translation
Sep 12, 2023
d6ca6a2
fix: disable stylelint for Modal custom styles to not trigger eslint
andirueckel Sep 12, 2023
12fa478
merge main into invite-users
Sep 13, 2023
f13dae0
Merge branch 'main' into invite-users
Sep 13, 2023
d9c00b4
refactor: close datalist when string empty
Sep 20, 2023
d8dc5bc
refactor: improve and shorten functions
Sep 20, 2023
5360bd6
merge main into invite-users
Sep 21, 2023
f42995c
chore: add comment and improve JSDoc
Sep 21, 2023
5b6d2c6
Merge branch 'main' into invite-users
Sep 21, 2023
9fca902
refactor: add InviteUsers
Sep 21, 2023
5226795
refactor: add InviteUsers to spacedeck
Sep 21, 2023
4b74202
Merge remote-tracking branch 'origin/invite-users' into invite-users
Sep 21, 2023
89357c5
Merge branch 'main' into invite-users
Sep 21, 2023
a17dfd6
Merge branch 'main' into invite-users
Sep 26, 2023
8e49701
fix: invitations not working and wrap user id in brackets
Sep 26, 2023
35dd409
refactor: remove unused import
Sep 26, 2023
1e2f1ec
merge main into invite-users
Oct 10, 2023
e4ce0c3
update package-lock.json
Oct 10, 2023
95c217a
refactor: use TextButton for svg
Oct 10, 2023
7a87e2a
chore: sort alphabetically after merge
Oct 10, 2023
79a597b
update package-lock
Oct 10, 2023
558f33b
refactor: move close icon and header inside DefaultModal
Oct 11, 2023
3414bc8
Merge branch 'main' into invite-users
Oct 17, 2023
f4a3d19
refactor: remove modal and display InviteUsersToMatrixRoom component …
Oct 24, 2023
98aa41b
refactor: uninstall react-modal
Oct 24, 2023
93cf51a
refactor: change button texts and headline
Oct 24, 2023
1a84ad0
Merge pull request #82 from medienhaus/invite-users-sans-modal
aofn Oct 24, 2023
eed8e0b
refactor: invite multiple users and display selected user below
Oct 24, 2023
6c9a543
small fixes and refactors
Oct 24, 2023
b4c3b49
merge main into invite-users
Oct 24, 2023
56041db
refactor: add onClick event to table row and add arrow indicating sel…
Oct 24, 2023
ba30b45
merge main into invite-users
Oct 25, 2023
5a11f66
Merge branch 'main' into invite-users
Oct 25, 2023
8d0ff23
refactor: use npm package for icons
Oct 25, 2023
c69e55d
refactor: remove unnecessary div
Oct 25, 2023
421b797
fix: comments
Oct 25, 2023
939b222
refactor: html structure
Oct 25, 2023
9bdc60a
WIP: align buttons on bottom
Oct 25, 2023
40d29f4
refactor: change structure to fit refactor of InviteUsersToMatrixRoom…
Oct 25, 2023
31c982b
refactor: remove unused parameter
Oct 25, 2023
0fa7757
style: introduce `--line-height` and `--icon-size` css variables
andirueckel Oct 25, 2023
810cc4c
Merge pull request #87 from medienhaus/invite-users-css-improvements
aofn Oct 25, 2023
fae3538
merge main into invite-users
Oct 31, 2023
b5dea4e
fix: remove unused imports and eslint problems
Oct 31, 2023
8d98fb6
merge main into invite-users
Oct 31, 2023
a913a6d
refactor: use checkboxes fir datalist
Oct 31, 2023
602cf45
merge main into invite-users
Nov 1, 2023
d751b01
Merge branch 'main' into invite-users
Nov 1, 2023
230baa2
Merge branch 'main' into invite-users
Nov 7, 2023
59f6d11
chore: make linter happy
Nov 7, 2023
e1aa86a
refactor update render of selected items only on input change so item…
Nov 8, 2023
5184844
refactor: move all state handling inside DataList.js
Nov 8, 2023
0fde3a4
refactor: add accessibility feature so arrow keys and tab serve the s…
Nov 8, 2023
8dd8458
refactor: only parse error messages instead of object, wrap success a…
Nov 8, 2023
b56b515
refactor: remove unnecessary code
Nov 8, 2023
fd01fdc
merge main into invite-users
Nov 8, 2023
ddbb984
fix: only display user feedback once
Nov 8, 2023
ede9bc1
Merge branch 'main' into invite-users
Nov 8, 2023
2073b33
chore: use newly introduced `Icon` component for svg icons
andirueckel Nov 8, 2023
e3cc50e
refactor: unfocus list elements when focusing text input
Nov 8, 2023
f476aa0
refactor: deselect element on mous press, when unchecking element
Nov 8, 2023
fdad060
fix: use onMouseDown instead of onClick
Nov 8, 2023
088c2c7
fix: use onMouseUp instead of onMouseDown
Nov 8, 2023
66abff3
Merge branch 'main' into invite-users
Nov 15, 2023
c62c2b6
refactor: use DataListRow for selected items, change variable name to…
Nov 15, 2023
101a3e4
fix: table breaking mobile width
Nov 15, 2023
73ec8ca
Merge branch 'main' into invite-users
fnwbr Nov 15, 2023
6112aa3
InviteUsersToMatrixRoom: Filter the user themselves from search results
fnwbr Nov 15, 2023
5b9e0a3
Use transient props with styled-components
fnwbr Nov 15, 2023
7c95e57
Remove jumpy rendering if there are no results / clean up JSX code
fnwbr Nov 15, 2023
b62f64a
refactor: use <Trans /> for complicated translation
Nov 16, 2023
f0dda56
chore: fix translations for invitations
Nov 16, 2023
7d71222
remove unused file
Nov 16, 2023
67150e7
create button component within InviteUsersToMatrixRoom component
Nov 16, 2023
eddff13
chore: add jsDoc for DataListRow
Nov 16, 2023
fefe201
refactor: use button component from InviteUsersToMatrixRoom
Nov 16, 2023
f0ef1a4
fix: success message not properly displaying after inviting users
Nov 17, 2023
b1e3486
Merge branch 'main' into invite-users
fnwbr Nov 28, 2023
b54cdd6
Allow checking a data list option by clicking it
fnwbr Nov 28, 2023
1d95be6
doc: Use React.ReactNode where possible
fnwbr Nov 28, 2023
522245d
Undo change that we might want, but let's not do it as part of this p…
fnwbr Nov 28, 2023
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
243 changes: 243 additions & 0 deletions components/UI/DataList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
import React, { useState, useRef, useEffect } from 'react';
import styled from 'styled-components';
import { useTranslation } from 'react-i18next';
import _ from 'lodash';

import { ServiceTable } from './ServiceTable';
import Form from './Form';

/**
* Datalist component that functions as an input with a datalist and supports keyboard navigation and mouse interaction.
*
* @component
* @param {Object[]} options - An array of Objects for the datalist.
* @param {function} onInputChange - Function to execute when input changes, receives string as the first parameter.
* @param {function} onSubmit - Function to execute when the form is submitted, receives an array of selected options.
* @param {Array} keysToDisplay - Array of strings of key values to be displayed as results.
*
* @returns {React.ReactNode}
*/

const Row = styled(ServiceTable.Row)`
text-decoration: ${props => props.$focused && 'underline' };
cursor: pointer;

&:hover,
&:focus {
text-decoration: underline;
}
`;

const InviteUserForm = styled(Form)`
display: grid;
height: 100%;

> :last-child {
align-self: end;
}
`;

const TableWrapper = styled.section`
overflow-x: auto;
`;

export default function DataList({ options, onInputChange, keysToDisplay, onSubmit }) {
const [value, setValue] = useState('');
const [isOpen, setIsOpen] = useState(false);
const [selectedIndex, setSelectedIndex] = useState(-1);
const [selected, setSelected] = useState([]);
const [checked, setChecked] = useState([]);
const [filteredOptions, setFilteredOptions] = useState([]);
const inputRef = useRef(null);

const { t } = useTranslation('invitationModal');

useEffect(() => {
setFilteredOptions(options.filter(option => {
return !selected.some(select => _.isEqual(select, option));
}));
}, [options, selected]);

const handleChange = async (e) => {
if (e.target.value !== '') setIsOpen(true);
else {
// if the input is empty we close the datalist
setIsOpen(false);
}
Fixed Show fixed Hide fixed

setSelectedIndex(-1);

if (!_.isEmpty(checked)) {
// if an option was checked we update the 'selected' array here.
// For the user this has the benefit that options don't immediately jump to the bottom when they are being selected,
// only when the input changes will a checked option jump to the bottom
setSelected(prevState => [...prevState, ...checked]);
setChecked([]);
}

setValue(e.target.value);
await onInputChange(e.target.value);
};

const handleKeyDown = (e) => {
// Handle keyboard navigation when options are available
if (!isOpen && !selected) return;

if (e.key === 'ArrowDown') {
const max = (filteredOptions.length + selected.length) - 1;
setSelectedIndex(Math.min(selectedIndex + 1, max));
} else if (e.key === 'ArrowUp') {
e.preventDefault();

const newIndex = Math.max(selectedIndex - 1, -1);
if (newIndex === -1) inputRef.current.focus();
setSelectedIndex(newIndex);
} else if (e.key === 'Enter' && selectedIndex !== -1) {
e.preventDefault();
handleSelect(filteredOptions[selectedIndex]);
}
};

const handleSelect = (selectedOption) => {
// if a user wants to uncheck an option which is inside the 'selected' array, selectedIndex will be bigger than the amount of entries within filteredOptions,
// since 'selected' options are rendered below filteredOptions.
if (selectedIndex > filteredOptions.length -1) {
setSelected(prevState => prevState.filter((option, index) => {
return selectedIndex - filteredOptions.length !== index;
}));

return;
}

setChecked(prevState => {
if (checked.includes(selectedOption)) {
// remove option if it is changing from checked to unchecked (therefore already inside the `checked` array
return prevState.filter(option => selectedOption !== option);
} else {
// otherwise add option to the array
return [...prevState, selectedOption];
}
});
// inputRef.current.focus();
};

const handleRemove = (option) => {
setChecked(prevState => prevState.filter(state => state !== option));
setSelected(prevState => prevState.filter(state => state !== option));
};

const handleSubmit = async (e) => {
e.preventDefault();
setChecked([]);
setSelected([]);
setIsOpen(false);
await onSubmit(checked.concat(selected));
};

return (
<InviteUserForm onSubmit={handleSubmit}>
<>
<input
type="text"
value={value}
onChange={handleChange}
onKeyDown={handleKeyDown}
onFocus={() => setSelectedIndex(-1)}
ref={inputRef}
/>

<TableWrapper>
<ServiceTable>
<ServiceTable.Body>
{ isOpen && filteredOptions.map((option, index) => <DataListRow
key={index}
option={option}
focus={selectedIndex === index}
index={index}
keysToDisplay={keysToDisplay}
handleSelect={handleSelect}
isChecked={checked.includes(option)}
handleKeyDown={handleKeyDown}
setSelectedIndex={setSelectedIndex}
/>) }
{ selected.map((option, index) => <DataListRow
key={index}
option={option}
// we need to add the number of options to the index to highlight the correct item in the list
// and not have multiple items show up as selected
focus={selectedIndex === index + filteredOptions.length}
index={index + filteredOptions.length}
keysToDisplay={keysToDisplay}
handleSelect={handleRemove}
isChecked={selected.includes(option)}
handleKeyDown={handleKeyDown}
setSelectedIndex={setSelectedIndex}
/>) }
</ServiceTable.Body>
</ServiceTable>
</TableWrapper>
<button disabled={selected.length === 0 && checked.length === 0}>{ t('invite') }</button>
</>
</InviteUserForm>
);
}
/**
* Row component for the Datalist that represents a selectable option.
*
* @component
* @param {Object} option - The option to display in the row.
* @param {Array} keysToDisplay - Array of strings of key values to be displayed as results.
* @param {function} handleSelect - Function to execute when an option is selected or deselected.
* @param {number} index - Index of the option in the list.
* @param {boolean} isChecked - Flag indicating whether the option is checked.
* @param {function} handleKeyDown - Function to handle keydown events.
* @param {boolean} focus - Flag indicating whether the row has focus.
* @param {function} setSelectedIndex - Function to set the selected index.
*
* @returns {React.ReactNode}
*/

const DataListRow = ({ option, keysToDisplay, handleSelect, index, isChecked, handleKeyDown, focus, setSelectedIndex }) => {
const ref = useRef(null);

useEffect(() => {
if (focus && ref.current) {
ref.current.focus();
}
}, [focus]);

return (
<Row
key={index}
$focused={focus}
onClick={() => { handleSelect(option); }}
onKeyDown={handleKeyDown} // Add onKeyDown event
>
<ServiceTable.Cell>
<input
id={index}
ref={ref}
tabIndex={0}
type="checkbox"
checked={isChecked}
onFocus={() => setSelectedIndex(index)}
onChange={() => { handleSelect(option); }}
onMouseUp={() => {
// make sure element gets deselected on mouse press
if (focus) setSelectedIndex(-1);
}}
/>
</ServiceTable.Cell>
{ keysToDisplay.map((key) => {
return (
<ServiceTable.Cell
htmlFor={index}
key={key}
>
{ option[key] }
</ServiceTable.Cell>
);
}) }
</Row>
);
};
4 changes: 2 additions & 2 deletions components/UI/Form.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import styled from 'styled-components';

const Form = styled.form`
/* if first child of parent element (i.e. wrapper), set margin-top */
/* if first child of parent element (i.e. wrapper), don't set margin-top */
&:not(:first-child) {
margin-top: calc(var(--margin) / 1.5);
}

/* for all children except last child, set margin-top */
/* for all children except first child, set margin-top */
& > * + * {
margin-top: calc(var(--margin) / 1.5);
}
Expand Down
Loading