feat: Version 2

Closes #627, closes #1047
This commit is contained in:
Maksim Eltyshev
2025-05-10 02:09:06 +02:00
parent ad7fb51cfa
commit 2ee1166747
1557 changed files with 76832 additions and 47042 deletions
@@ -0,0 +1,62 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { useSelector } from 'react-redux';
import { Menu } from 'semantic-ui-react';
import selectors from '../../../selectors';
import UserAvatar from '../../users/UserAvatar';
import styles from './Item.module.scss';
const Item = React.memo(({ id, isActive, onUserSelect, onUserDeselect }) => {
const selectBoardMembershipById = useMemo(() => selectors.makeSelectBoardMembershipById(), []);
const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);
const boardMembership = useSelector((state) => selectBoardMembershipById(state, id));
const user = useSelector((state) => selectUserById(state, boardMembership.userId));
const handleToggleClick = useCallback(() => {
if (isActive) {
if (onUserDeselect) {
onUserDeselect(boardMembership.userId);
}
} else {
onUserSelect(boardMembership.userId);
}
}, [isActive, onUserSelect, onUserDeselect, boardMembership.userId]);
return (
<Menu.Item
active={isActive}
disabled={!boardMembership.isPersisted}
className={classNames(styles.menuItem, isActive && styles.menuItemActive)}
onClick={handleToggleClick}
>
<span className={styles.user}>
<UserAvatar id={boardMembership.userId} />
</span>
<div className={classNames(styles.menuItemText, isActive && styles.menuItemTextActive)}>
{user.name}
</div>
</Menu.Item>
);
});
Item.propTypes = {
id: PropTypes.string.isRequired,
isActive: PropTypes.bool.isRequired,
onUserSelect: PropTypes.func.isRequired,
onUserDeselect: PropTypes.func,
};
Item.defaultProps = {
onUserDeselect: undefined,
};
export default Item;
@@ -0,0 +1,48 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
:global(#app) {
.menuItem {
display: block;
margin: 0;
overflow: hidden;
padding: 4px;
}
.menuItemActive {
background: transparent;
&:hover {
background: rgba(0, 0, 0, 0.05);
}
}
.menuItemText {
display: inline-block;
line-height: 32px;
width: calc(100% - 40px);
}
.menuItemTextActive:before {
bottom: 2px;
color: #798d99;
content: "Г";
font-size: 18px;
font-weight: normal;
line-height: 36px;
position: absolute;
right: 2px;
text-align: center;
transform: rotate(-135deg);
width: 36px;
}
.user {
display: inline-block;
line-height: 32px;
padding-right: 8px;
width: 40px;
}
}
@@ -0,0 +1,116 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import React, { useEffect, useMemo } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { Button, Menu } from 'semantic-ui-react';
import { Input, Popup } from '../../../lib/custom-ui';
import { useField, useNestedRef } from '../../../hooks';
import Item from './Item';
import styles from './PureBoardMembershipsStep.module.scss';
const PureBoardMembershipsStep = React.memo(
({
items,
currentUserIds,
title,
clearButtonContent,
onUserSelect,
onUserDeselect,
onClear,
onBack,
}) => {
const [t] = useTranslation();
const [search, handleSearchChange] = useField('');
const cleanSearch = useMemo(() => search.trim().toLowerCase(), [search]);
const filteredItems = useMemo(
() =>
items.filter(
({ user }) =>
user.name.toLowerCase().includes(cleanSearch) ||
(user.username && user.username.includes(cleanSearch)),
),
[items, cleanSearch],
);
const [searchFieldRef, handleSearchFieldRef] = useNestedRef('inputRef');
useEffect(() => {
searchFieldRef.current.focus({
preventScroll: true,
});
}, [searchFieldRef]);
return (
<>
<Popup.Header onBack={onBack}>
{t(title, {
context: 'title',
})}
</Popup.Header>
<Popup.Content>
<Input
fluid
ref={handleSearchFieldRef}
value={search}
placeholder={t('common.searchMembers')}
maxLength={128}
icon="search"
onChange={handleSearchChange}
/>
{filteredItems.length > 0 && (
<Menu secondary vertical className={styles.menu}>
{filteredItems.map((boardMembership) => (
<Item
key={boardMembership.id}
id={boardMembership.id}
isActive={currentUserIds.includes(boardMembership.user.id)}
onUserSelect={onUserSelect}
onUserDeselect={onUserDeselect}
/>
))}
</Menu>
)}
{currentUserIds.length > 0 && onClear && (
<Button
fluid
content={t(clearButtonContent)}
className={styles.clearButton}
onClick={onClear}
/>
)}
</Popup.Content>
</>
);
},
);
PureBoardMembershipsStep.propTypes = {
/* eslint-disable react/forbid-prop-types */
items: PropTypes.array.isRequired,
currentUserIds: PropTypes.array,
/* eslint-enable react/forbid-prop-types */
title: PropTypes.string,
clearButtonContent: PropTypes.string,
onUserSelect: PropTypes.func.isRequired,
onUserDeselect: PropTypes.func,
onClear: PropTypes.func,
onBack: PropTypes.func,
};
PureBoardMembershipsStep.defaultProps = {
currentUserIds: [],
title: 'common.members',
clearButtonContent: 'action.clear',
onUserDeselect: undefined,
onClear: undefined,
onBack: undefined,
};
export default PureBoardMembershipsStep;
@@ -0,0 +1,49 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
:global(#app) {
.clearButton {
background: transparent;
box-shadow: none;
color: #6b808c;
font-weight: normal;
margin-top: 8px;
padding: 6px 11px;
text-align: left;
text-decoration: underline;
transition: background 0.3s ease;
&:hover {
background: #e9e9e9;
}
}
.menu {
margin: 8px auto 0;
max-height: 60vh;
overflow-x: hidden;
overflow-y: auto;
width: 100%;
@supports (-moz-appearance: none) {
scrollbar-color: rgba(0, 0, 0, 0.32) transparent;
scrollbar-width: thin;
}
&::-webkit-scrollbar {
width: 9px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background-clip: padding-box;
border-left: 0.25em transparent solid;
border-radius: 3px;
}
}
}
@@ -0,0 +1,8 @@
/*!
* Copyright (c) 2024 PLANKA Software GmbH
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
*/
import PureBoardMembershipsStep from './PureBoardMembershipsStep';
export default PureBoardMembershipsStep;