sforkowany z mirror/soapbox
Merge branch 'develop' into 'chat_notifications'
Develop See merge request soapbox-pub/soapbox-fe!213better-alerts
commit
34a575482c
|
@ -34,20 +34,20 @@ export function fetchChats() {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchChatMessages(chatId) {
|
export function fetchChatMessages(chatId, maxId = null) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch({ type: CHAT_MESSAGES_FETCH_REQUEST, chatId });
|
dispatch({ type: CHAT_MESSAGES_FETCH_REQUEST, chatId, maxId });
|
||||||
return api(getState).get(`/api/v1/pleroma/chats/${chatId}/messages`).then(({ data }) => {
|
return api(getState).get(`/api/v1/pleroma/chats/${chatId}/messages`, { params: { max_id: maxId } }).then(({ data }) => {
|
||||||
dispatch({ type: CHAT_MESSAGES_FETCH_SUCCESS, chatId, chatMessages: data });
|
dispatch({ type: CHAT_MESSAGES_FETCH_SUCCESS, chatId, maxId, chatMessages: data });
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch({ type: CHAT_MESSAGES_FETCH_FAIL, chatId, error });
|
dispatch({ type: CHAT_MESSAGES_FETCH_FAIL, chatId, maxId, error });
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendChatMessage(chatId, params) {
|
export function sendChatMessage(chatId, params) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
const uuid = uuidv4();
|
const uuid = `末_${Date.now()}_${uuidv4()}`;
|
||||||
const me = getState().get('me');
|
const me = getState().get('me');
|
||||||
dispatch({ type: CHAT_MESSAGE_SEND_REQUEST, chatId, params, uuid, me });
|
dispatch({ type: CHAT_MESSAGE_SEND_REQUEST, chatId, params, uuid, me });
|
||||||
return api(getState).post(`/api/v1/pleroma/chats/${chatId}/messages`, params).then(({ data }) => {
|
return api(getState).post(`/api/v1/pleroma/chats/${chatId}/messages`, params).then(({ data }) => {
|
||||||
|
|
|
@ -58,11 +58,11 @@ class ChatRoom extends ImmutablePureComponent {
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const markReadConditions = [
|
const markReadConditions = [
|
||||||
() => this.props.chat !== undefined,
|
() => this.props.chat,
|
||||||
() => this.props.chat.get('unread') > 0,
|
() => this.props.chat.get('unread') > 0,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (markReadConditions.every(c => c() === true))
|
if (markReadConditions.every(c => c()))
|
||||||
this.markRead();
|
this.markRead();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { injectIntl, defineMessages } from 'react-intl';
|
import { injectIntl, defineMessages } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import {
|
import {
|
||||||
fetchChatMessages,
|
|
||||||
sendChatMessage,
|
sendChatMessage,
|
||||||
markChatRead,
|
markChatRead,
|
||||||
} from 'soapbox/actions/chats';
|
} from 'soapbox/actions/chats';
|
||||||
|
@ -81,11 +80,6 @@ class ChatBox extends ImmutablePureComponent {
|
||||||
onSetInputRef(el);
|
onSetInputRef(el);
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
const { dispatch, chatId } = this.props;
|
|
||||||
dispatch(fetchChatMessages(chatId));
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
const markReadConditions = [
|
const markReadConditions = [
|
||||||
() => this.props.chat !== undefined,
|
() => this.props.chat !== undefined,
|
||||||
|
@ -98,12 +92,12 @@ class ChatBox extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { chatMessageIds, intl } = this.props;
|
const { chatMessageIds, chatId, intl } = this.props;
|
||||||
if (!chatMessageIds) return null;
|
if (!chatMessageIds) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='chat-box' onMouseOver={this.handleHover}>
|
<div className='chat-box' onMouseOver={this.handleHover}>
|
||||||
<ChatMessageList chatMessageIds={chatMessageIds} />
|
<ChatMessageList chatMessageIds={chatMessageIds} chatId={chatId} />
|
||||||
<div className='chat-box__actions simple_form'>
|
<div className='chat-box__actions simple_form'>
|
||||||
<textarea
|
<textarea
|
||||||
rows={1}
|
rows={1}
|
||||||
|
|
|
@ -5,9 +5,12 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||||
import { injectIntl } from 'react-intl';
|
import { injectIntl } from 'react-intl';
|
||||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||||
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
|
||||||
|
import { fetchChatMessages } from 'soapbox/actions/chats';
|
||||||
import emojify from 'soapbox/features/emoji/emoji';
|
import emojify from 'soapbox/features/emoji/emoji';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { escape } from 'lodash';
|
import { escape, throttle } from 'lodash';
|
||||||
|
|
||||||
|
const scrollBottom = (elem) => elem.scrollHeight - elem.offsetHeight - elem.scrollTop;
|
||||||
|
|
||||||
const makeEmojiMap = record => record.get('emojis', ImmutableList()).reduce((map, emoji) => {
|
const makeEmojiMap = record => record.get('emojis', ImmutableList()).reduce((map, emoji) => {
|
||||||
return map.set(`:${emoji.get('shortcode')}:`, emoji);
|
return map.set(`:${emoji.get('shortcode')}:`, emoji);
|
||||||
|
@ -18,7 +21,7 @@ const mapStateToProps = (state, { chatMessageIds }) => ({
|
||||||
chatMessages: chatMessageIds.reduce((acc, curr) => {
|
chatMessages: chatMessageIds.reduce((acc, curr) => {
|
||||||
const chatMessage = state.getIn(['chat_messages', curr]);
|
const chatMessage = state.getIn(['chat_messages', curr]);
|
||||||
return chatMessage ? acc.push(chatMessage) : acc;
|
return chatMessage ? acc.push(chatMessage) : acc;
|
||||||
}, ImmutableList()).sort(),
|
}, ImmutableList()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export default @connect(mapStateToProps)
|
export default @connect(mapStateToProps)
|
||||||
|
@ -28,6 +31,7 @@ class ChatMessageList extends ImmutablePureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
dispatch: PropTypes.func.isRequired,
|
dispatch: PropTypes.func.isRequired,
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
|
chatId: PropTypes.string,
|
||||||
chatMessages: ImmutablePropTypes.list,
|
chatMessages: ImmutablePropTypes.list,
|
||||||
chatMessageIds: ImmutablePropTypes.orderedSet,
|
chatMessageIds: ImmutablePropTypes.orderedSet,
|
||||||
me: PropTypes.node,
|
me: PropTypes.node,
|
||||||
|
@ -37,6 +41,10 @@ class ChatMessageList extends ImmutablePureComponent {
|
||||||
chatMessages: ImmutableList(),
|
chatMessages: ImmutableList(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state = {
|
||||||
|
isLoading: false,
|
||||||
|
}
|
||||||
|
|
||||||
scrollToBottom = () => {
|
scrollToBottom = () => {
|
||||||
if (!this.messagesEnd) return;
|
if (!this.messagesEnd) return;
|
||||||
this.messagesEnd.scrollIntoView();
|
this.messagesEnd.scrollIntoView();
|
||||||
|
@ -44,7 +52,6 @@ class ChatMessageList extends ImmutablePureComponent {
|
||||||
|
|
||||||
setMessageEndRef = (el) => {
|
setMessageEndRef = (el) => {
|
||||||
this.messagesEnd = el;
|
this.messagesEnd = el;
|
||||||
this.scrollToBottom();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
getFormattedTimestamp = (chatMessage) => {
|
getFormattedTimestamp = (chatMessage) => {
|
||||||
|
@ -61,7 +68,7 @@ class ChatMessageList extends ImmutablePureComponent {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
setRef = (c) => {
|
setBubbleRef = (c) => {
|
||||||
if (!c) return;
|
if (!c) return;
|
||||||
const links = c.querySelectorAll('a[rel="ugc"]');
|
const links = c.querySelectorAll('a[rel="ugc"]');
|
||||||
|
|
||||||
|
@ -72,11 +79,42 @@ class ChatMessageList extends ImmutablePureComponent {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidMount() {
|
||||||
if (prevProps.chatMessages !== this.props.chatMessages)
|
const { dispatch, chatId } = this.props;
|
||||||
this.scrollToBottom();
|
dispatch(fetchChatMessages(chatId));
|
||||||
|
|
||||||
|
this.node.addEventListener('scroll', this.handleScroll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const oldCount = prevProps.chatMessages.count();
|
||||||
|
const newCount = this.props.chatMessages.count();
|
||||||
|
const isNearBottom = scrollBottom(this.node) < 150;
|
||||||
|
const historyAdded = prevProps.chatMessages.getIn([-1, 'id']) !== this.props.chatMessages.getIn([-1, 'id']);
|
||||||
|
|
||||||
|
if (oldCount !== newCount) {
|
||||||
|
if (isNearBottom) this.scrollToBottom();
|
||||||
|
if (historyAdded) this.setState({ isLoading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.node.removeEventListener('scroll', this.handleScroll);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleLoadMore = () => {
|
||||||
|
const { dispatch, chatId, chatMessages } = this.props;
|
||||||
|
const maxId = chatMessages.getIn([-1, 'id']);
|
||||||
|
dispatch(fetchChatMessages(chatId, maxId));
|
||||||
|
this.setState({ isLoading: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleScroll = throttle(() => {
|
||||||
|
if (this.node.scrollTop < 150 && !this.state.isLoading) this.handleLoadMore();
|
||||||
|
}, 150, {
|
||||||
|
trailing: true,
|
||||||
|
});
|
||||||
|
|
||||||
parsePendingContent = content => {
|
parsePendingContent = content => {
|
||||||
return escape(content).replace(/(?:\r\n|\r|\n)/g, '<br>');
|
return escape(content).replace(/(?:\r\n|\r|\n)/g, '<br>');
|
||||||
}
|
}
|
||||||
|
@ -89,11 +127,16 @@ class ChatMessageList extends ImmutablePureComponent {
|
||||||
return emojify(formatted, emojiMap.toJS());
|
return emojify(formatted, emojiMap.toJS());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setRef = (c) => {
|
||||||
|
this.node = c;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { chatMessages, me } = this.props;
|
const { chatMessages, me } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='chat-messages'>
|
<div className='chat-messages' ref={this.setRef}>
|
||||||
|
<div style={{ float: 'left', clear: 'both' }} ref={this.setMessageEndRef} />
|
||||||
{chatMessages.map(chatMessage => (
|
{chatMessages.map(chatMessage => (
|
||||||
<div
|
<div
|
||||||
className={classNames('chat-message', {
|
className={classNames('chat-message', {
|
||||||
|
@ -106,11 +149,10 @@ class ChatMessageList extends ImmutablePureComponent {
|
||||||
title={this.getFormattedTimestamp(chatMessage)}
|
title={this.getFormattedTimestamp(chatMessage)}
|
||||||
className='chat-message__bubble'
|
className='chat-message__bubble'
|
||||||
dangerouslySetInnerHTML={{ __html: this.parseContent(chatMessage) }}
|
dangerouslySetInnerHTML={{ __html: this.parseContent(chatMessage) }}
|
||||||
ref={this.setRef}
|
ref={this.setBubbleRef}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
<div style={{ float: 'left', clear: 'both' }} ref={this.setMessageEndRef} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -173,7 +173,7 @@ class PrivacyDropdown extends React.PureComponent {
|
||||||
const { intl: { formatMessage } } = props;
|
const { intl: { formatMessage } } = props;
|
||||||
|
|
||||||
this.options = [
|
this.options = [
|
||||||
{ icon: 'globe', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
{ icon: 'globe-w', value: 'public', text: formatMessage(messages.public_short), meta: formatMessage(messages.public_long) },
|
||||||
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
||||||
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||||
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
{ icon: 'envelope', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||||
|
|
|
@ -52,7 +52,7 @@ class UploadButton extends ImmutablePureComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl, resetFileKey, unavailable, disabled, acceptContentTypes } = this.props;
|
const { intl, resetFileKey, unavailable, disabled } = this.props;
|
||||||
|
|
||||||
if (unavailable) {
|
if (unavailable) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -60,7 +60,7 @@ class UploadButton extends ImmutablePureComponent {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='compose-form__upload-button'>
|
<div className='compose-form__upload-button'>
|
||||||
<IconButton icon='upload' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
|
<IconButton icon='paperclip' title={intl.formatMessage(messages.upload)} disabled={disabled} onClick={this.handleClick} className='compose-form__upload-button-icon' size={18} inverted style={iconStyle} />
|
||||||
<label>
|
<label>
|
||||||
<span style={{ display: 'none' }}>{intl.formatMessage(messages.upload)}</span>
|
<span style={{ display: 'none' }}>{intl.formatMessage(messages.upload)}</span>
|
||||||
<input
|
<input
|
||||||
|
@ -68,7 +68,8 @@ class UploadButton extends ImmutablePureComponent {
|
||||||
ref={this.setRef}
|
ref={this.setRef}
|
||||||
type='file'
|
type='file'
|
||||||
multiple
|
multiple
|
||||||
accept={acceptContentTypes.toArray().join(',')}
|
// Accept all types for now.
|
||||||
|
// accept={acceptContentTypes.toArray().join(',')}
|
||||||
onChange={this.handleChange}
|
onChange={this.handleChange}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
style={{ display: 'none' }}
|
style={{ display: 'none' }}
|
||||||
|
|
|
@ -88,7 +88,7 @@ class Compose extends React.PureComponent {
|
||||||
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
|
<Link to='/timelines/public/local' className='drawer__tab' title={intl.formatMessage(messages.community)} aria-label={intl.formatMessage(messages.community)}><Icon id='users' fixedWidth /></Link>
|
||||||
)}
|
)}
|
||||||
{!columns.some(column => column.get('id') === 'PUBLIC') && (
|
{!columns.some(column => column.get('id') === 'PUBLIC') && (
|
||||||
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe' fixedWidth /></Link>
|
<Link to='/timelines/public' className='drawer__tab' title={intl.formatMessage(messages.public)} aria-label={intl.formatMessage(messages.public)}><Icon id='globe-w' fixedWidth /></Link>
|
||||||
)}
|
)}
|
||||||
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
|
<a href='/settings/preferences' className='drawer__tab' title={intl.formatMessage(messages.preferences)} aria-label={intl.formatMessage(messages.preferences)}><Icon id='cog' fixedWidth /></a>
|
||||||
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><Icon id='sign-out' fixedWidth /></a>
|
<a href='/auth/sign_out' className='drawer__tab' data-method='delete' title={intl.formatMessage(messages.logout)} aria-label={intl.formatMessage(messages.logout)}><Icon id='sign-out' fixedWidth /></a>
|
||||||
|
|
|
@ -9,9 +9,15 @@ import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutabl
|
||||||
|
|
||||||
const initialState = ImmutableMap();
|
const initialState = ImmutableMap();
|
||||||
|
|
||||||
|
const idComparator = (a, b) => {
|
||||||
|
if (a < b) return 1;
|
||||||
|
if (a > b) return -1;
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
const updateList = (state, chatId, messageIds) => {
|
const updateList = (state, chatId, messageIds) => {
|
||||||
const ids = state.get(chatId, ImmutableOrderedSet());
|
const ids = state.get(chatId, ImmutableOrderedSet());
|
||||||
const newIds = ids.union(messageIds);
|
const newIds = ids.union(messageIds).sort(idComparator);
|
||||||
return state.set(chatId, newIds);
|
return state.set(chatId, newIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,22 +37,28 @@ const importLastMessages = (state, chats) =>
|
||||||
if (chat.last_message) importMessage(mutable, chat.last_message);
|
if (chat.last_message) importMessage(mutable, chat.last_message);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const replaceMessage = (state, chatId, oldId, newId) => {
|
||||||
|
const ids = state.get(chatId, ImmutableOrderedSet());
|
||||||
|
const newIds = ids.delete(oldId).add(newId).sort(idComparator);
|
||||||
|
return state.set(chatId, newIds);
|
||||||
|
};
|
||||||
|
|
||||||
export default function chatMessageLists(state = initialState, action) {
|
export default function chatMessageLists(state = initialState, action) {
|
||||||
switch(action.type) {
|
switch(action.type) {
|
||||||
case CHAT_MESSAGE_SEND_REQUEST:
|
case CHAT_MESSAGE_SEND_REQUEST:
|
||||||
return updateList(state, action.chatId, [action.uuid]).sort();
|
return updateList(state, action.chatId, [action.uuid]);
|
||||||
case CHATS_FETCH_SUCCESS:
|
case CHATS_FETCH_SUCCESS:
|
||||||
return importLastMessages(state, action.chats).sort();
|
return importLastMessages(state, action.chats);
|
||||||
case STREAMING_CHAT_UPDATE:
|
case STREAMING_CHAT_UPDATE:
|
||||||
if (action.chat.last_message &&
|
if (action.chat.last_message &&
|
||||||
action.chat.last_message.account_id !== action.me)
|
action.chat.last_message.account_id !== action.me)
|
||||||
return importMessages(state, [action.chat.last_message]).sort();
|
return importMessages(state, [action.chat.last_message]);
|
||||||
else
|
else
|
||||||
return state;
|
return state;
|
||||||
case CHAT_MESSAGES_FETCH_SUCCESS:
|
case CHAT_MESSAGES_FETCH_SUCCESS:
|
||||||
return updateList(state, action.chatId, action.chatMessages.map(chat => chat.id).reverse()).sort();
|
return updateList(state, action.chatId, action.chatMessages.map(chat => chat.id));
|
||||||
case CHAT_MESSAGE_SEND_SUCCESS:
|
case CHAT_MESSAGE_SEND_SUCCESS:
|
||||||
return updateList(state, action.chatId, [action.chatMessage.id]).sort();
|
return replaceMessage(state, action.chatId, action.uuid, action.chatMessage.id);
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,12 +99,18 @@
|
||||||
.chat-messages {
|
.chat-messages {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-message {
|
.chat-message {
|
||||||
margin: 14px 10px;
|
padding: 7px 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
padding-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
&__bubble {
|
&__bubble {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
padding: 4px 10px;
|
padding: 4px 10px;
|
||||||
|
|
|
@ -143,6 +143,10 @@
|
||||||
.setting-toggle {
|
.setting-toggle {
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
|
||||||
|
.react-toggle-track {
|
||||||
|
background-color: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
.react-toggle--checked {
|
.react-toggle--checked {
|
||||||
.react-toggle-track {
|
.react-toggle-track {
|
||||||
background-color: var(--accent-color);
|
background-color: var(--accent-color);
|
||||||
|
|
Ładowanie…
Reference in New Issue