Lists modals: TypeScript, FC
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
1d2c07f0a9
commit
4ba2d08162
23 changed files with 442 additions and 170 deletions
Binary file not shown.
31
app/soapbox/features/list_adder/components/account.tsx
Normal file
31
app/soapbox/features/list_adder/components/account.tsx
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import DisplayName from 'soapbox/components/display-name';
|
||||||
|
import { Avatar } from 'soapbox/components/ui';
|
||||||
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
import { makeGetAccount } from 'soapbox/selectors';
|
||||||
|
|
||||||
|
const getAccount = makeGetAccount();
|
||||||
|
|
||||||
|
interface IAccount {
|
||||||
|
accountId: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Account: React.FC<IAccount> = ({ accountId }) => {
|
||||||
|
const account = useAppSelector((state) => getAccount(state, accountId));
|
||||||
|
|
||||||
|
if (!account) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='account'>
|
||||||
|
<div className='account__wrapper'>
|
||||||
|
<div className='account__display-name'>
|
||||||
|
<div className='account__avatar-wrapper'><Avatar src={account.avatar} size={36} /></div>
|
||||||
|
<DisplayName account={account} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Account;
|
Binary file not shown.
49
app/soapbox/features/list_adder/components/list.tsx
Normal file
49
app/soapbox/features/list_adder/components/list.tsx
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { removeFromListAdder, addToListAdder } from 'soapbox/actions/lists';
|
||||||
|
import Icon from 'soapbox/components/icon';
|
||||||
|
import IconButton from 'soapbox/components/icon_button';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
|
||||||
|
add: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IList {
|
||||||
|
listId: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const List: React.FC<IList> = ({ listId }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const list = useAppSelector((state) => state.lists.get(listId));
|
||||||
|
const added = useAppSelector((state) => state.listAdder.lists.items.includes(listId));
|
||||||
|
|
||||||
|
const onRemove = () => dispatch(removeFromListAdder(listId));
|
||||||
|
const onAdd = () => dispatch(addToListAdder(listId));
|
||||||
|
|
||||||
|
if (!list) return null;
|
||||||
|
|
||||||
|
let button;
|
||||||
|
|
||||||
|
if (added) {
|
||||||
|
button = <IconButton iconClassName='h-5 w-5' src={require('@tabler/icons/icons/x.svg')} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
|
||||||
|
} else {
|
||||||
|
button = <IconButton iconClassName='h-5 w-5' src={require('@tabler/icons/icons/plus.svg')} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='flex items-center gap-1.5 px-2 py-4 text-black dark:text-white'>
|
||||||
|
<Icon src={require('@tabler/icons/icons/list.svg')} fixedWidth />
|
||||||
|
<span className='flex-grow'>
|
||||||
|
{list.title}
|
||||||
|
</span>
|
||||||
|
{button}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default List;
|
Binary file not shown.
83
app/soapbox/features/list_adder/index.tsx
Normal file
83
app/soapbox/features/list_adder/index.tsx
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
|
||||||
|
import { setupListAdder, resetListAdder } from 'soapbox/actions/lists';
|
||||||
|
import { CardHeader, CardTitle, Modal } from 'soapbox/components/ui';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
import NewListForm from '../lists/components/new_list_form';
|
||||||
|
|
||||||
|
import Account from './components/account';
|
||||||
|
import List from './components/list';
|
||||||
|
|
||||||
|
import type { List as ImmutableList } from 'immutable';
|
||||||
|
import type { RootState } from 'soapbox/store';
|
||||||
|
import type { List as ListEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||||
|
subheading: { id: 'lists.subheading', defaultMessage: 'Your lists' },
|
||||||
|
add: { id: 'lists.new.create', defaultMessage: 'Add List' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// hack
|
||||||
|
const getOrderedLists = createSelector([(state: RootState) => state.lists], lists => {
|
||||||
|
if (!lists) {
|
||||||
|
return lists;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lists.toList().filter(item => !!item).sort((a, b) => (a as ListEntity).title.localeCompare((b as ListEntity).title)) as ImmutableList<ListEntity>;
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IListAdder {
|
||||||
|
accountId: string,
|
||||||
|
onClose: (type: string) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListAdder: React.FC<IListAdder> = ({ accountId, onClose }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const listIds = useAppSelector((state) => getOrderedLists(state).map(list => list.id));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(setupListAdder(accountId));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dispatch(resetListAdder());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onClickClose = () => {
|
||||||
|
onClose('LIST_ADDER');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={<FormattedMessage id='list_adder.header_title' defaultMessage='Add or Remove from Lists' />}
|
||||||
|
onClose={onClickClose}
|
||||||
|
>
|
||||||
|
<Account accountId={accountId} />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle title={intl.formatMessage(messages.add)} />
|
||||||
|
</CardHeader>
|
||||||
|
<NewListForm />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle title={intl.formatMessage(messages.subheading)} />
|
||||||
|
</CardHeader>
|
||||||
|
<div>
|
||||||
|
{listIds.map(ListId => <List key={ListId} listId={ListId} />)}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListAdder;
|
Binary file not shown.
58
app/soapbox/features/list_editor/components/account.tsx
Normal file
58
app/soapbox/features/list_editor/components/account.tsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { removeFromListEditor, addToListEditor } from 'soapbox/actions/lists';
|
||||||
|
import DisplayName from 'soapbox/components/display-name';
|
||||||
|
import IconButton from 'soapbox/components/icon_button';
|
||||||
|
import { Avatar } from 'soapbox/components/ui';
|
||||||
|
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||||
|
import { makeGetAccount } from 'soapbox/selectors';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
|
||||||
|
add: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const getAccount = makeGetAccount();
|
||||||
|
|
||||||
|
interface IAccount {
|
||||||
|
accountId: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const Account: React.FC<IAccount> = ({ accountId }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const account = useAppSelector((state) => getAccount(state, accountId));
|
||||||
|
const isAdded = useAppSelector((state) => state.listEditor.accounts.items.includes(accountId));
|
||||||
|
|
||||||
|
const onRemove = () => dispatch(removeFromListEditor(accountId));
|
||||||
|
const onAdd = () => dispatch(addToListEditor(accountId));
|
||||||
|
|
||||||
|
if (!account) return null;
|
||||||
|
|
||||||
|
let button;
|
||||||
|
|
||||||
|
if (isAdded) {
|
||||||
|
button = <IconButton src={require('@tabler/icons/icons/x.svg')} title={intl.formatMessage(messages.remove)} onClick={onRemove} />;
|
||||||
|
} else {
|
||||||
|
button = <IconButton src={require('@tabler/icons/icons/plus.svg')} title={intl.formatMessage(messages.add)} onClick={onAdd} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='account'>
|
||||||
|
<div className='account__wrapper'>
|
||||||
|
<div className='account__display-name'>
|
||||||
|
<div className='account__avatar-wrapper'><Avatar src={account.avatar} size={36} /></div>
|
||||||
|
<DisplayName account={account} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='account__relationship'>
|
||||||
|
{button}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Account;
|
Binary file not shown.
|
@ -0,0 +1,53 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { changeListEditorTitle, submitListEditor } from 'soapbox/actions/lists';
|
||||||
|
import { Button, Form, HStack, Input } from 'soapbox/components/ui';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
title: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
|
||||||
|
save: { id: 'lists.new.save_title', defaultMessage: 'Save Title' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const ListForm = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const value = useAppSelector((state) => state.listEditor.title);
|
||||||
|
const disabled = useAppSelector((state) => !state.listEditor.isChanged);
|
||||||
|
|
||||||
|
const handleChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
||||||
|
dispatch(changeListEditorTitle(e.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit: React.FormEventHandler<Element> = e => {
|
||||||
|
e.preventDefault();
|
||||||
|
dispatch(submitListEditor(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
dispatch(submitListEditor(false));
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = intl.formatMessage(messages.save);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<HStack space={2}>
|
||||||
|
<Input
|
||||||
|
outerClassName='flex-grow'
|
||||||
|
type='text'
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button onClick={handleClick} disabled={disabled}>
|
||||||
|
{save}
|
||||||
|
</Button>
|
||||||
|
</HStack>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListForm;
|
Binary file not shown.
58
app/soapbox/features/list_editor/components/search.tsx
Normal file
58
app/soapbox/features/list_editor/components/search.tsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import React from 'react';
|
||||||
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from 'soapbox/actions/lists';
|
||||||
|
import Icon from 'soapbox/components/icon';
|
||||||
|
import { Button, Form, HStack, Input } from 'soapbox/components/ui';
|
||||||
|
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
search: { id: 'lists.search', defaultMessage: 'Search among people you follow' },
|
||||||
|
searchTitle: { id: 'tabs_bar.search', defaultMessage: 'Search' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const Search = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const value = useAppSelector((state) => state.listEditor.suggestions.value);
|
||||||
|
|
||||||
|
const handleChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
||||||
|
dispatch(changeListSuggestions(e.target.value));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
dispatch(fetchListSuggestions(value));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClear = () => {
|
||||||
|
dispatch(clearListSuggestions());
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasValue = value.length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<HStack space={2}>
|
||||||
|
<label className='flex-grow relative'>
|
||||||
|
<span style={{ display: 'none' }}>{intl.formatMessage(messages.search)}</span>
|
||||||
|
|
||||||
|
<Input
|
||||||
|
type='text'
|
||||||
|
value={value}
|
||||||
|
onChange={handleChange}
|
||||||
|
placeholder={intl.formatMessage(messages.search)}
|
||||||
|
/>
|
||||||
|
<div role='button' tabIndex={0} className='search__icon' onClick={handleClear}>
|
||||||
|
<Icon src={require('@tabler/icons/icons/backspace.svg')} aria-label={intl.formatMessage(messages.search)} className={classNames('svg-icon--backspace', { active: hasValue })} />
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<Button onClick={handleSubmit}>{intl.formatMessage(messages.searchTitle)}</Button>
|
||||||
|
</HStack>
|
||||||
|
</Form>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Search;
|
Binary file not shown.
79
app/soapbox/features/list_editor/index.tsx
Normal file
79
app/soapbox/features/list_editor/index.tsx
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
|
||||||
|
import { setupListEditor, resetListEditor } from 'soapbox/actions/lists';
|
||||||
|
import { CardHeader, CardTitle, Modal } from 'soapbox/components/ui';
|
||||||
|
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
import Account from './components/account';
|
||||||
|
import EditListForm from './components/edit_list_form';
|
||||||
|
import Search from './components/search';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||||
|
changeTitle: { id: 'lists.edit.submit', defaultMessage: 'Change title' },
|
||||||
|
addToList: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
||||||
|
removeFromList: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
|
||||||
|
editList: { id: 'lists.edit', defaultMessage: 'Edit list' },
|
||||||
|
});
|
||||||
|
|
||||||
|
interface IListEditor {
|
||||||
|
listId: string,
|
||||||
|
onClose: (type: string) => void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const ListEditor: React.FC<IListEditor> = ({ listId, onClose }) => {
|
||||||
|
const intl = useIntl();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const accountIds = useAppSelector((state) => state.listEditor.accounts.items);
|
||||||
|
const searchAccountIds = useAppSelector((state) => state.listEditor.suggestions.items);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(setupListEditor(listId));
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
dispatch(resetListEditor());
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onClickClose = () => {
|
||||||
|
onClose('LIST_ADDER');
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
title={<FormattedMessage id='lists.edit' defaultMessage='Edit list' />}
|
||||||
|
onClose={onClickClose}
|
||||||
|
>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle title={intl.formatMessage(messages.changeTitle)} />
|
||||||
|
</CardHeader>
|
||||||
|
<EditListForm />
|
||||||
|
<br />
|
||||||
|
|
||||||
|
{accountIds.size > 0 && (
|
||||||
|
<div>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle title={intl.formatMessage(messages.removeFromList)} />
|
||||||
|
</CardHeader>
|
||||||
|
<div className='max-h-48 overflow-y-auto'>
|
||||||
|
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle title={intl.formatMessage(messages.addToList)} />
|
||||||
|
</CardHeader>
|
||||||
|
<Search />
|
||||||
|
<div className='max-h-48 overflow-y-auto'>
|
||||||
|
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ListEditor;
|
|
@ -65,7 +65,7 @@ const ListTimeline: React.FC = () => {
|
||||||
// }));
|
// }));
|
||||||
// };
|
// };
|
||||||
|
|
||||||
const title = list ? list.get('title') : id;
|
const title = list ? list.title : id;
|
||||||
|
|
||||||
if (typeof list === 'undefined') {
|
if (typeof list === 'undefined') {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { defineMessages, useIntl } from 'react-intl';
|
||||||
import { useDispatch } from 'react-redux';
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
import { changeListEditorTitle, submitListEditor } from 'soapbox/actions/lists';
|
import { changeListEditorTitle, submitListEditor } from 'soapbox/actions/lists';
|
||||||
import { Button } from 'soapbox/components/ui';
|
import { Button, Form, HStack, Input } from 'soapbox/components/ui';
|
||||||
import { useAppSelector } from 'soapbox/hooks';
|
import { useAppSelector } from 'soapbox/hooks';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
|
@ -23,7 +23,7 @@ const NewListForm: React.FC = () => {
|
||||||
dispatch(changeListEditorTitle(e.target.value));
|
dispatch(changeListEditorTitle(e.target.value));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement | HTMLButtonElement>) => {
|
const handleSubmit = (e: React.FormEvent<Element>) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
dispatch(submitListEditor(true));
|
dispatch(submitListEditor(true));
|
||||||
};
|
};
|
||||||
|
@ -32,12 +32,13 @@ const NewListForm: React.FC = () => {
|
||||||
const create = intl.formatMessage(messages.create);
|
const create = intl.formatMessage(messages.create);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className='column-inline-form' method='post' onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<label>
|
<HStack space={2}>
|
||||||
|
<label className='flex-grow'>
|
||||||
<span style={{ display: 'none' }}>{label}</span>
|
<span style={{ display: 'none' }}>{label}</span>
|
||||||
|
|
||||||
<input
|
<Input
|
||||||
className='setting-text new-list-form__input'
|
type='text'
|
||||||
value={value}
|
value={value}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
|
@ -46,13 +47,13 @@ const NewListForm: React.FC = () => {
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
// className='new-list-form__btn'
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onClick={handleSubmit}
|
onClick={handleSubmit}
|
||||||
>
|
>
|
||||||
{create}
|
{create}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</HStack>
|
||||||
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -93,13 +93,13 @@ const Lists: React.FC = () => {
|
||||||
itemClassName='py-2'
|
itemClassName='py-2'
|
||||||
>
|
>
|
||||||
{lists.map((list: any) => (
|
{lists.map((list: any) => (
|
||||||
<Link key={list.get('id')} to={`/list/${list.get('id')}`} className='flex items-center gap-1.5 p-2 text-black dark:text-white hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg'>
|
<Link key={list.id} to={`/list/${list.id}`} className='flex items-center gap-1.5 p-2 text-black dark:text-white hover:bg-gray-100 dark:hover:bg-slate-700 rounded-lg'>
|
||||||
<Icon src={require('@tabler/icons/icons/list.svg')} fixedWidth />
|
<Icon src={require('@tabler/icons/icons/list.svg')} fixedWidth />
|
||||||
<span className='flex-grow'>
|
<span className='flex-grow'>
|
||||||
{list.get('title')}
|
{list.title}
|
||||||
</span>
|
</span>
|
||||||
<IconButton iconClassName='h-5 w-5' src={require('@tabler/icons/icons/pencil.svg')} onClick={handleEditClick(list.get('id'))} title={intl.formatMessage(messages.editList)} />
|
<IconButton iconClassName='h-5 w-5' src={require('@tabler/icons/icons/pencil.svg')} onClick={handleEditClick(list.id)} title={intl.formatMessage(messages.editList)} />
|
||||||
<IconButton iconClassName='h-5 w-5' src={require('@tabler/icons/icons/trash.svg')} onClick={handleDeleteClick(list.get('id'))} title={intl.formatMessage(messages.deleteList)} />
|
<IconButton iconClassName='h-5 w-5' src={require('@tabler/icons/icons/trash.svg')} onClick={handleDeleteClick(list.id)} title={intl.formatMessage(messages.deleteList)} />
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</ScrollableList>
|
</ScrollableList>
|
||||||
|
|
|
@ -30,7 +30,7 @@ const ColorPicker: React.FC<IColorPicker> = ({ style, value, onClose, onChange }
|
||||||
document.removeEventListener('click', handleDocumentClick, false);
|
document.removeEventListener('click', handleDocumentClick, false);
|
||||||
document.removeEventListener('touchend', handleDocumentClick);
|
document.removeEventListener('touchend', handleDocumentClick);
|
||||||
};
|
};
|
||||||
});
|
}, []);
|
||||||
|
|
||||||
const pickerStyle: React.CSSProperties = {
|
const pickerStyle: React.CSSProperties = {
|
||||||
...style,
|
...style,
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
EmojiRecord,
|
EmojiRecord,
|
||||||
FieldRecord,
|
FieldRecord,
|
||||||
InstanceRecord,
|
InstanceRecord,
|
||||||
|
ListRecord,
|
||||||
MentionRecord,
|
MentionRecord,
|
||||||
NotificationRecord,
|
NotificationRecord,
|
||||||
PollRecord,
|
PollRecord,
|
||||||
|
@ -28,6 +29,7 @@ type ChatMessage = ReturnType<typeof ChatMessageRecord>;
|
||||||
type Emoji = ReturnType<typeof EmojiRecord>;
|
type Emoji = ReturnType<typeof EmojiRecord>;
|
||||||
type Field = ReturnType<typeof FieldRecord>;
|
type Field = ReturnType<typeof FieldRecord>;
|
||||||
type Instance = ReturnType<typeof InstanceRecord>;
|
type Instance = ReturnType<typeof InstanceRecord>;
|
||||||
|
type List = ReturnType<typeof ListRecord>;
|
||||||
type Mention = ReturnType<typeof MentionRecord>;
|
type Mention = ReturnType<typeof MentionRecord>;
|
||||||
type Notification = ReturnType<typeof NotificationRecord>;
|
type Notification = ReturnType<typeof NotificationRecord>;
|
||||||
type Poll = ReturnType<typeof PollRecord>;
|
type Poll = ReturnType<typeof PollRecord>;
|
||||||
|
@ -61,6 +63,7 @@ export {
|
||||||
Emoji,
|
Emoji,
|
||||||
Field,
|
Field,
|
||||||
Instance,
|
Instance,
|
||||||
|
List,
|
||||||
Mention,
|
Mention,
|
||||||
Notification,
|
Notification,
|
||||||
Poll,
|
Poll,
|
||||||
|
|
|
@ -44,7 +44,6 @@
|
||||||
@import 'components/status';
|
@import 'components/status';
|
||||||
@import 'components/reply-mentions';
|
@import 'components/reply-mentions';
|
||||||
@import 'components/detailed-status';
|
@import 'components/detailed-status';
|
||||||
@import 'components/list-forms';
|
|
||||||
@import 'components/media-gallery';
|
@import 'components/media-gallery';
|
||||||
@import 'components/notification';
|
@import 'components/notification';
|
||||||
@import 'components/display-name';
|
@import 'components/display-name';
|
||||||
|
|
|
@ -1,105 +0,0 @@
|
||||||
.list-editor {
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
padding: 15px 0;
|
|
||||||
background: var(--background-color);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 16px;
|
|
||||||
text-align: center;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__content {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__accounts {
|
|
||||||
background: var(--background-color);
|
|
||||||
overflow-y: auto;
|
|
||||||
max-height: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.account__display-name {
|
|
||||||
&:hover strong {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.account__avatar {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.search {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
margin: 10px;
|
|
||||||
|
|
||||||
> label {
|
|
||||||
flex: 1 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .search__icon {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .button {
|
|
||||||
width: 80px;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-adder {
|
|
||||||
flex-direction: column;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
|
||||||
|
|
||||||
&__account {
|
|
||||||
background: var(--background-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__lists {
|
|
||||||
background: var(--background-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list {
|
|
||||||
padding: 4px;
|
|
||||||
border-bottom: 1px solid var(--brand-color--med);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list__wrapper {
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
.account__relationship {
|
|
||||||
padding: 8px 5px 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list__display-name {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
overflow: hidden;
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 16px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.new-list-form,
|
|
||||||
.edit-list-form {
|
|
||||||
&__btn {
|
|
||||||
margin-left: 6px;
|
|
||||||
width: 112px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__input {
|
|
||||||
height: 36px;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -339,33 +339,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.compose-modal__content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: hidden;
|
|
||||||
|
|
||||||
&--scroll {
|
|
||||||
display: block;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timeline-compose-block {
|
|
||||||
background: transparent !important;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
|
|
||||||
.compose-form {
|
|
||||||
max-height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.reply-mentions-modal__accounts {
|
.reply-mentions-modal__accounts {
|
||||||
display: block;
|
display: block;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|
|
@ -118,13 +118,3 @@ input.search__input {
|
||||||
right: 24px;
|
right: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-editor__search {
|
|
||||||
.search__icon {
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.svg-icon {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue