stream-chat-react 12.4.1 → 12.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/icons/stream-chat-icons.eot +0 -0
- package/dist/assets/icons/stream-chat-icons.svg +4 -0
- package/dist/assets/icons/stream-chat-icons.ttf +0 -0
- package/dist/assets/icons/stream-chat-icons.woff +0 -0
- package/dist/assets/icons/stream-chat-icons.woff2 +0 -0
- package/dist/components/Attachment/components/ProgressBar.d.ts +2 -2
- package/dist/components/Attachment/components/ProgressBar.js +2 -1
- package/dist/components/Channel/Channel.d.ts +1 -1
- package/dist/components/Channel/Channel.js +36 -15
- package/dist/components/Channel/constants.d.ts +1 -0
- package/dist/components/Channel/constants.js +1 -0
- package/dist/components/Channel/hooks/useChannelContainerClasses.d.ts +2 -0
- package/dist/components/Channel/hooks/useChannelContainerClasses.js +10 -5
- package/dist/components/ChannelPreview/utils.js +35 -0
- package/dist/components/Chat/hooks/useChat.js +2 -0
- package/dist/components/Dialog/DialogAnchor.d.ts +1 -2
- package/dist/components/Dialog/DialogMenu.d.ts +3 -0
- package/dist/components/Dialog/DialogMenu.js +5 -0
- package/dist/components/Dialog/DialogPortal.d.ts +1 -1
- package/dist/components/Dialog/DialogPortal.js +4 -12
- package/dist/components/Dialog/FormDialog.d.ts +23 -0
- package/dist/components/Dialog/FormDialog.js +72 -0
- package/dist/components/Dialog/PromptDialog.d.ts +8 -0
- package/dist/components/Dialog/PromptDialog.js +7 -0
- package/dist/components/DragAndDrop/DragAndDropContainer.d.ts +7 -0
- package/dist/components/DragAndDrop/DragAndDropContainer.js +93 -0
- package/dist/components/Form/FieldError.d.ts +6 -0
- package/dist/components/Form/FieldError.js +3 -0
- package/dist/components/Form/SwitchField.d.ts +7 -0
- package/dist/components/Form/SwitchField.js +21 -0
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.d.ts +10 -0
- package/dist/components/InfiniteScrollPaginator/InfiniteScroll.js +10 -0
- package/dist/components/InfiniteScrollPaginator/InfiniteScrollPaginator.d.ts +10 -0
- package/dist/components/InfiniteScrollPaginator/InfiniteScrollPaginator.js +68 -0
- package/dist/components/InfiniteScrollPaginator/hooks/useCursorPaginator.d.ts +18 -0
- package/dist/components/InfiniteScrollPaginator/hooks/useCursorPaginator.js +41 -0
- package/dist/components/Message/MessageSimple.js +5 -1
- package/dist/components/Message/QuotedMessage.js +8 -4
- package/dist/components/Message/hooks/useUserRole.js +3 -2
- package/dist/components/Message/index.d.ts +1 -0
- package/dist/components/MessageInput/AttachmentSelector.d.ts +25 -0
- package/dist/components/MessageInput/AttachmentSelector.js +125 -0
- package/dist/components/MessageInput/EditMessageForm.js +1 -1
- package/dist/components/MessageInput/MessageInput.d.ts +2 -0
- package/dist/components/MessageInput/MessageInput.js +9 -4
- package/dist/components/MessageInput/MessageInputFlat.js +4 -10
- package/dist/components/MessageInput/QuotedMessagePreview.js +7 -3
- package/dist/components/MessageInput/hooks/useCreateMessageInputContext.js +3 -1
- package/dist/components/MessageInput/hooks/useSubmitHandler.js +4 -1
- package/dist/components/MessageInput/index.d.ts +1 -0
- package/dist/components/MessageInput/index.js +1 -0
- package/dist/components/Modal/ModalHeader.d.ts +8 -0
- package/dist/components/Modal/ModalHeader.js +6 -0
- package/dist/components/Poll/Poll.d.ts +7 -0
- package/dist/components/Poll/Poll.js +8 -0
- package/dist/components/Poll/PollActions/AddCommentForm.d.ts +7 -0
- package/dist/components/Poll/PollActions/AddCommentForm.js +24 -0
- package/dist/components/Poll/PollActions/EndPollDialog.d.ts +6 -0
- package/dist/components/Poll/PollActions/EndPollDialog.js +19 -0
- package/dist/components/Poll/PollActions/PollAction.d.ts +9 -0
- package/dist/components/Poll/PollActions/PollAction.js +5 -0
- package/dist/components/Poll/PollActions/PollActions.d.ts +17 -0
- package/dist/components/Poll/PollActions/PollActions.js +46 -0
- package/dist/components/Poll/PollActions/PollAnswerList.d.ts +7 -0
- package/dist/components/Poll/PollActions/PollAnswerList.js +28 -0
- package/dist/components/Poll/PollActions/PollOptionsFullList.d.ts +6 -0
- package/dist/components/Poll/PollActions/PollOptionsFullList.js +16 -0
- package/dist/components/Poll/PollActions/PollResults/PollOptionVotesList.d.ts +7 -0
- package/dist/components/Poll/PollActions/PollResults/PollOptionVotesList.js +18 -0
- package/dist/components/Poll/PollActions/PollResults/PollOptionWithLatestVotes.d.ts +9 -0
- package/dist/components/Poll/PollActions/PollResults/PollOptionWithLatestVotes.js +19 -0
- package/dist/components/Poll/PollActions/PollResults/PollOptionWithVotesHeader.d.ts +11 -0
- package/dist/components/Poll/PollActions/PollResults/PollOptionWithVotesHeader.js +18 -0
- package/dist/components/Poll/PollActions/PollResults/PollResults.d.ts +6 -0
- package/dist/components/Poll/PollActions/PollResults/PollResults.js +33 -0
- package/dist/components/Poll/PollActions/PollResults/index.d.ts +1 -0
- package/dist/components/Poll/PollActions/PollResults/index.js +1 -0
- package/dist/components/Poll/PollActions/SuggestPollOptionForm.d.ts +7 -0
- package/dist/components/Poll/PollActions/SuggestPollOptionForm.js +37 -0
- package/dist/components/Poll/PollActions/index.d.ts +7 -0
- package/dist/components/Poll/PollActions/index.js +7 -0
- package/dist/components/Poll/PollContent.d.ts +3 -0
- package/dist/components/Poll/PollContent.js +18 -0
- package/dist/components/Poll/PollCreationDialog/OptionFieldSet.d.ts +9 -0
- package/dist/components/Poll/PollCreationDialog/OptionFieldSet.js +70 -0
- package/dist/components/Poll/PollCreationDialog/PollCreationDialog.d.ts +5 -0
- package/dist/components/Poll/PollCreationDialog/PollCreationDialog.js +87 -0
- package/dist/components/Poll/PollCreationDialog/PollCreationDialogControls.d.ts +8 -0
- package/dist/components/Poll/PollCreationDialog/PollCreationDialogControls.js +44 -0
- package/dist/components/Poll/PollCreationDialog/index.d.ts +1 -0
- package/dist/components/Poll/PollCreationDialog/index.js +1 -0
- package/dist/components/Poll/PollCreationDialog/types.d.ts +21 -0
- package/dist/components/Poll/PollCreationDialog/types.js +1 -0
- package/dist/components/Poll/PollHeader.d.ts +3 -0
- package/dist/components/Poll/PollHeader.js +31 -0
- package/dist/components/Poll/PollOptionList.d.ts +6 -0
- package/dist/components/Poll/PollOptionList.js +14 -0
- package/dist/components/Poll/PollOptionSelector.d.ts +19 -0
- package/dist/components/Poll/PollOptionSelector.js +53 -0
- package/dist/components/Poll/PollVote.d.ts +12 -0
- package/dist/components/Poll/PollVote.js +31 -0
- package/dist/components/Poll/QuotedPoll.d.ts +3 -0
- package/dist/components/Poll/QuotedPoll.js +17 -0
- package/dist/components/Poll/constants.d.ts +3 -0
- package/dist/components/Poll/constants.js +3 -0
- package/dist/components/Poll/hooks/index.d.ts +2 -0
- package/dist/components/Poll/hooks/index.js +2 -0
- package/dist/components/Poll/hooks/useManagePollVotesRealtime.d.ts +4 -0
- package/dist/components/Poll/hooks/useManagePollVotesRealtime.js +36 -0
- package/dist/components/Poll/hooks/usePollAnswerPagination.d.ts +13 -0
- package/dist/components/Poll/hooks/usePollAnswerPagination.js +27 -0
- package/dist/components/Poll/hooks/usePollOptionVotesPagination.d.ts +13 -0
- package/dist/components/Poll/hooks/usePollOptionVotesPagination.js +27 -0
- package/dist/components/Poll/index.d.ts +10 -0
- package/dist/components/Poll/index.js +10 -0
- package/dist/components/Portal/Portal.d.ts +6 -0
- package/dist/components/Portal/Portal.js +14 -0
- package/dist/components/ReactFileUtilities/UploadButton.d.ts +11 -1
- package/dist/components/ReactFileUtilities/UploadButton.js +22 -4
- package/dist/components/Thread/Thread.js +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +1 -0
- package/dist/context/AttachmentSelectorContext.d.ts +8 -0
- package/dist/context/AttachmentSelectorContext.js +6 -0
- package/dist/context/ComponentContext.d.ts +21 -5
- package/dist/context/PollContext.d.ts +11 -0
- package/dist/context/PollContext.js +7 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +1 -0
- package/dist/css/v2/index.css +2 -2
- package/dist/css/v2/index.layout.css +2 -2
- package/dist/experimental/index.browser.cjs +129 -117
- package/dist/experimental/index.browser.cjs.map +4 -4
- package/dist/experimental/index.node.cjs +129 -117
- package/dist/experimental/index.node.cjs.map +4 -4
- package/dist/i18n/Streami18n.d.ts +45 -0
- package/dist/i18n/de.json +70 -25
- package/dist/i18n/en.json +46 -1
- package/dist/i18n/es.json +74 -25
- package/dist/i18n/fr.json +83 -34
- package/dist/i18n/hi.json +54 -9
- package/dist/i18n/it.json +75 -26
- package/dist/i18n/ja.json +46 -5
- package/dist/i18n/ko.json +46 -5
- package/dist/i18n/nl.json +59 -14
- package/dist/i18n/pt.json +66 -17
- package/dist/i18n/ru.json +66 -13
- package/dist/i18n/tr.json +77 -32
- package/dist/index.browser.cjs +4226 -1857
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +4166 -1770
- package/dist/index.node.cjs.map +4 -4
- package/dist/scss/v2/AttachmentPreviewList/AttachmentPreviewList-layout.scss +2 -2
- package/dist/scss/v2/AudioRecorder/AudioRecorder-layout.scss +64 -14
- package/dist/scss/v2/AudioRecorder/AudioRecorder-theme.scss +11 -1
- package/dist/scss/v2/Avatar/Avatar-layout.scss +4 -0
- package/dist/scss/v2/ChannelList/ChannelList-layout.scss +15 -0
- package/dist/scss/v2/Dialog/Dialog-layout.scss +54 -0
- package/dist/scss/v2/Dialog/Dialog-theme.scss +103 -0
- package/dist/scss/v2/DragAndDropContainer/DragAmdDropContainer-layout.scss +5 -0
- package/dist/scss/v2/DragAndDropContainer/DragAndDropContainer-theme.scss +47 -0
- package/dist/scss/v2/Form/Form-layout.scss +9 -0
- package/dist/scss/v2/Form/Form-theme.scss +17 -0
- package/dist/scss/v2/Icon/Icon-layout.scss +6 -1
- package/dist/scss/v2/InfiniteScrollPaginator/InfiniteScrollPaginator-layout.scss +4 -0
- package/dist/scss/v2/MessageActionsBox/MessageActionsBox-layout.scss +0 -9
- package/dist/scss/v2/MessageInput/MessageInput-layout.scss +29 -4
- package/dist/scss/v2/MessageInput/MessageInput-theme.scss +61 -0
- package/dist/scss/v2/Modal/Modal-layout.scss +31 -0
- package/dist/scss/v2/Modal/Modal-theme.scss +6 -0
- package/dist/scss/v2/Notification/MessageNotification-layout.scss +1 -1
- package/dist/scss/v2/Poll/Poll-layout.scss +488 -0
- package/dist/scss/v2/Poll/Poll-theme.scss +206 -0
- package/dist/scss/v2/_base.scss +4 -0
- package/dist/scss/v2/_global-theme-variables.scss +1 -1
- package/dist/scss/v2/_icons.scss +7 -0
- package/dist/scss/v2/index.layout.scss +4 -0
- package/dist/scss/v2/index.scss +4 -0
- package/dist/types/types.d.ts +6 -0
- package/package.json +4 -4
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { nanoid } from 'nanoid';
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { FieldError } from '../../Form/FieldError';
|
|
5
|
+
import { OptionFieldSet } from './OptionFieldSet';
|
|
6
|
+
import { PollCreationDialogControls } from './PollCreationDialogControls';
|
|
7
|
+
import { VALID_MAX_VOTES_VALUE_REGEX } from '../constants';
|
|
8
|
+
import { ModalHeader } from '../../Modal/ModalHeader';
|
|
9
|
+
import { SimpleSwitchField } from '../../Form/SwitchField';
|
|
10
|
+
import { useChatContext, useTranslationContext } from '../../../context';
|
|
11
|
+
export const PollCreationDialog = ({ close }) => {
|
|
12
|
+
const { client } = useChatContext();
|
|
13
|
+
const { t } = useTranslationContext();
|
|
14
|
+
const [nameError, setNameError] = useState();
|
|
15
|
+
const [optionsErrors, setOptionsErrors] = useState({});
|
|
16
|
+
const [multipleAnswerCountError, setMultipleAnswerCountError] = useState();
|
|
17
|
+
const [state, setState] = useState(() => ({
|
|
18
|
+
allow_answers: false,
|
|
19
|
+
allow_user_suggested_options: false,
|
|
20
|
+
description: '',
|
|
21
|
+
enforce_unique_vote: true,
|
|
22
|
+
id: nanoid(),
|
|
23
|
+
max_votes_allowed: '',
|
|
24
|
+
name: '',
|
|
25
|
+
options: [{ id: nanoid(), text: '' }],
|
|
26
|
+
user_id: client.user?.id,
|
|
27
|
+
voting_visibility: 'public',
|
|
28
|
+
}));
|
|
29
|
+
return (React.createElement("div", { className: 'str-chat__dialog str-chat__poll-creation-dialog', "data-testid": 'poll-creation-dialog' },
|
|
30
|
+
React.createElement(ModalHeader, { close: close, title: t('Create poll') }),
|
|
31
|
+
React.createElement("div", { className: 'str-chat__dialog__body' },
|
|
32
|
+
React.createElement("form", { autoComplete: 'off' },
|
|
33
|
+
React.createElement("div", { className: clsx('str-chat__form__field str-chat__form__input-field str-chat__form__input-field--with-label', {
|
|
34
|
+
'str-chat__form__input-field--has-error': nameError,
|
|
35
|
+
}) },
|
|
36
|
+
React.createElement("label", { className: 'str-chat__form__field-label', htmlFor: 'name' }, t('Question')),
|
|
37
|
+
React.createElement("div", { className: clsx('str-chat__form__input-field__value') },
|
|
38
|
+
React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-name-input-field-error', text: nameError }),
|
|
39
|
+
React.createElement("input", { id: 'name', onBlur: (e) => {
|
|
40
|
+
if (!e.target.value) {
|
|
41
|
+
setNameError('The field is required');
|
|
42
|
+
}
|
|
43
|
+
}, onChange: (e) => {
|
|
44
|
+
setState((prev) => ({ ...prev, name: e.target.value }));
|
|
45
|
+
if (nameError && e.target.value) {
|
|
46
|
+
setNameError(undefined);
|
|
47
|
+
}
|
|
48
|
+
}, placeholder: t('Ask a question'), type: 'text', value: state.name }))),
|
|
49
|
+
React.createElement(OptionFieldSet, { errors: optionsErrors, options: state.options, setErrors: setOptionsErrors, setState: setState }),
|
|
50
|
+
React.createElement("div", { className: clsx('str-chat__form__expandable-field', {
|
|
51
|
+
'str-chat__form__expandable-field--expanded': !state.enforce_unique_vote,
|
|
52
|
+
}) },
|
|
53
|
+
React.createElement(SimpleSwitchField, { checked: !state.enforce_unique_vote, id: 'enforce_unique_vote', labelText: t('Multiple answers'), onChange: (e) => {
|
|
54
|
+
setState((prev) => ({
|
|
55
|
+
...prev,
|
|
56
|
+
enforce_unique_vote: !e.target.checked,
|
|
57
|
+
max_votes_allowed: '',
|
|
58
|
+
}));
|
|
59
|
+
setMultipleAnswerCountError(undefined);
|
|
60
|
+
} }),
|
|
61
|
+
!state.enforce_unique_vote && (React.createElement("div", { className: clsx('str-chat__form__input-field', {
|
|
62
|
+
'str-chat__form__input-field--has-error': multipleAnswerCountError,
|
|
63
|
+
}) },
|
|
64
|
+
React.createElement("div", { className: clsx('str-chat__form__input-field__value') },
|
|
65
|
+
React.createElement(FieldError, { className: 'str-chat__form__input-field__error', "data-testid": 'poll-max-votes-allowed-input-field-error', text: multipleAnswerCountError }),
|
|
66
|
+
React.createElement("input", { id: 'max_votes_allowed', onChange: (e) => {
|
|
67
|
+
const isValidValue = !e.target.value || e.target.value.match(VALID_MAX_VOTES_VALUE_REGEX);
|
|
68
|
+
if (!isValidValue) {
|
|
69
|
+
setMultipleAnswerCountError(t('Type a number from 2 to 10'));
|
|
70
|
+
}
|
|
71
|
+
else if (multipleAnswerCountError) {
|
|
72
|
+
setMultipleAnswerCountError(undefined);
|
|
73
|
+
}
|
|
74
|
+
setState((prev) => ({ ...prev, max_votes_allowed: e.target.value }));
|
|
75
|
+
}, placeholder: t('Maximum number of votes (from 2 to 10)'), type: 'number', value: state.max_votes_allowed }))))),
|
|
76
|
+
React.createElement(SimpleSwitchField, { checked: state.voting_visibility === 'anonymous', id: 'voting_visibility', labelText: t('Anonymous poll'), onChange: (e) => setState((prev) => ({
|
|
77
|
+
...prev,
|
|
78
|
+
voting_visibility: (e.target.checked ? 'anonymous' : 'public'),
|
|
79
|
+
})) }),
|
|
80
|
+
React.createElement(SimpleSwitchField, { checked: state.allow_user_suggested_options, id: 'allow_user_suggested_options', labelText: t('Allow option suggestion'), onChange: (e) => setState((prev) => ({ ...prev, allow_user_suggested_options: e.target.checked })) }),
|
|
81
|
+
React.createElement(SimpleSwitchField, { checked: state.allow_answers, id: 'allow_answers', labelText: t('Allow comments'), onChange: (e) => setState((prev) => ({ ...prev, allow_answers: e.target.checked })) }))),
|
|
82
|
+
React.createElement(PollCreationDialogControls, { close: close, errors: [
|
|
83
|
+
...(nameError ?? []),
|
|
84
|
+
...(multipleAnswerCountError ?? []),
|
|
85
|
+
...Object.keys(optionsErrors),
|
|
86
|
+
], state: state })));
|
|
87
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { PollFormState } from './types';
|
|
3
|
+
export type PollCreationDialogControlsProps = {
|
|
4
|
+
close: () => void;
|
|
5
|
+
errors: string[];
|
|
6
|
+
state: PollFormState;
|
|
7
|
+
};
|
|
8
|
+
export declare const PollCreationDialogControls: ({ close, errors, state, }: PollCreationDialogControlsProps) => React.JSX.Element;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { VALID_MAX_VOTES_VALUE_REGEX } from '../constants';
|
|
3
|
+
import { useChatContext, useMessageInputContext, useTranslationContext } from '../../../context';
|
|
4
|
+
export const PollCreationDialogControls = ({ close, errors, state, }) => {
|
|
5
|
+
const { client } = useChatContext();
|
|
6
|
+
const { t } = useTranslationContext('PollCreationDialogControls');
|
|
7
|
+
const { handleSubmit: handleSubmitMessage } = useMessageInputContext('PollCreationDialogControls');
|
|
8
|
+
const canSubmit = () => {
|
|
9
|
+
const hasAtLeastOneOption = state.options.filter((o) => !!o.text).length > 0;
|
|
10
|
+
const hasName = !!state.name;
|
|
11
|
+
const maxVotesAllowedNumber = parseInt(state.max_votes_allowed?.match(VALID_MAX_VOTES_VALUE_REGEX)?.[0] || '');
|
|
12
|
+
const validMaxVotesAllowed = state.max_votes_allowed === '' ||
|
|
13
|
+
(!!maxVotesAllowedNumber && (2 <= maxVotesAllowedNumber || maxVotesAllowedNumber <= 10));
|
|
14
|
+
const noErrors = errors.length === 0;
|
|
15
|
+
return hasAtLeastOneOption && hasName && validMaxVotesAllowed && noErrors;
|
|
16
|
+
};
|
|
17
|
+
return (React.createElement("div", { className: 'str-chat__dialog__controls' },
|
|
18
|
+
React.createElement("button", { className: 'str-chat__dialog__controls-button str-chat__dialog__controls-button--cancel', onClick: close }, t('Cancel')),
|
|
19
|
+
React.createElement("button", { className: 'str-chat__dialog__controls-button str-chat__dialog__controls-button--submit', disabled: !canSubmit(), onClick: async (e) => {
|
|
20
|
+
let pollId;
|
|
21
|
+
try {
|
|
22
|
+
const { poll } = await client.createPoll({
|
|
23
|
+
...state,
|
|
24
|
+
max_votes_allowed: state.max_votes_allowed
|
|
25
|
+
? parseInt(state.max_votes_allowed)
|
|
26
|
+
: undefined,
|
|
27
|
+
options: state.options?.filter((o) => o.text).map((o) => ({ text: o.text })),
|
|
28
|
+
});
|
|
29
|
+
pollId = poll.id;
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
// todo: add notification
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
await handleSubmitMessage(e, { poll_id: pollId });
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// todo: add notification
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
close();
|
|
43
|
+
}, type: 'submit' }, t('Create'))));
|
|
44
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PollCreationDialog';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './PollCreationDialog';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { VotingVisibility } from 'stream-chat';
|
|
2
|
+
type Id = string;
|
|
3
|
+
export type PollOptionFormData = {
|
|
4
|
+
id: Id;
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
export type PollFormState = {
|
|
8
|
+
id: Id;
|
|
9
|
+
max_votes_allowed: string;
|
|
10
|
+
name: string;
|
|
11
|
+
options: PollOptionFormData[];
|
|
12
|
+
allow_answers?: boolean;
|
|
13
|
+
allow_user_suggested_options?: boolean;
|
|
14
|
+
description?: string;
|
|
15
|
+
enforce_unique_vote?: boolean;
|
|
16
|
+
is_closed?: boolean;
|
|
17
|
+
user_id?: string;
|
|
18
|
+
voting_visibility?: VotingVisibility;
|
|
19
|
+
};
|
|
20
|
+
export type OptionErrors = Record<Id, string>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { usePollContext, useTranslationContext } from '../../context';
|
|
3
|
+
import { useStateStore } from '../../store';
|
|
4
|
+
const pollStateSelector = (nextValue) => ({
|
|
5
|
+
enforce_unique_vote: nextValue.enforce_unique_vote,
|
|
6
|
+
is_closed: nextValue.is_closed,
|
|
7
|
+
max_votes_allowed: nextValue.max_votes_allowed,
|
|
8
|
+
name: nextValue.name,
|
|
9
|
+
options: nextValue.options,
|
|
10
|
+
});
|
|
11
|
+
export const PollHeader = () => {
|
|
12
|
+
const { t } = useTranslationContext('PollHeader');
|
|
13
|
+
const { poll } = usePollContext();
|
|
14
|
+
const { enforce_unique_vote, is_closed, max_votes_allowed, name, options } = useStateStore(poll.state, pollStateSelector);
|
|
15
|
+
const selectionInstructions = useMemo(() => {
|
|
16
|
+
if (is_closed)
|
|
17
|
+
return t('Vote ended');
|
|
18
|
+
if (enforce_unique_vote)
|
|
19
|
+
return t('Select one');
|
|
20
|
+
if (max_votes_allowed)
|
|
21
|
+
return t('Select up to {{count}}', {
|
|
22
|
+
count: max_votes_allowed > options.length ? options.length : max_votes_allowed,
|
|
23
|
+
});
|
|
24
|
+
return t('Select one or more');
|
|
25
|
+
}, [is_closed, enforce_unique_vote, max_votes_allowed, options.length, t]);
|
|
26
|
+
if (!name)
|
|
27
|
+
return;
|
|
28
|
+
return (React.createElement("div", { className: 'str-chat__poll-header' },
|
|
29
|
+
React.createElement("div", { className: 'str-chat__poll-title' }, name),
|
|
30
|
+
React.createElement("div", { className: 'str-chat__poll-subtitle' }, selectionInstructions)));
|
|
31
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { DefaultStreamChatGenerics } from '../../types';
|
|
3
|
+
export type PollOptionListProps = {
|
|
4
|
+
optionsDisplayCount?: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const PollOptionList: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ optionsDisplayCount, }: PollOptionListProps) => React.JSX.Element;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { PollOptionSelector as DefaultPollOptionSelector } from './PollOptionSelector';
|
|
4
|
+
import { useStateStore } from '../../store';
|
|
5
|
+
import { useComponentContext, usePollContext } from '../../context';
|
|
6
|
+
const pollStateSelector = (nextValue) => ({ options: nextValue.options });
|
|
7
|
+
export const PollOptionList = ({ optionsDisplayCount, }) => {
|
|
8
|
+
const { PollOptionSelector = DefaultPollOptionSelector, } = useComponentContext();
|
|
9
|
+
const { poll } = usePollContext();
|
|
10
|
+
const { options } = useStateStore(poll.state, pollStateSelector);
|
|
11
|
+
return (React.createElement("div", { className: clsx('str-chat__poll-option-list', {
|
|
12
|
+
'str-chat__poll-option-list--full': typeof optionsDisplayCount === 'undefined',
|
|
13
|
+
}) }, options.slice(0, optionsDisplayCount ?? options.length).map((option) => (React.createElement(PollOptionSelector, { displayAvatarCount: 3, key: `poll-option-${option.id}`, option: option })))));
|
|
14
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { PollOption } from 'stream-chat';
|
|
3
|
+
import type { DefaultStreamChatGenerics } from '../../types';
|
|
4
|
+
type AmountBarProps = {
|
|
5
|
+
amount: number;
|
|
6
|
+
className?: string;
|
|
7
|
+
};
|
|
8
|
+
export declare const AmountBar: ({ amount, className }: AmountBarProps) => React.JSX.Element;
|
|
9
|
+
export type CheckmarkProps = {
|
|
10
|
+
checked?: boolean;
|
|
11
|
+
};
|
|
12
|
+
export declare const Checkmark: ({ checked }: CheckmarkProps) => React.JSX.Element;
|
|
13
|
+
export type PollOptionSelectorProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
14
|
+
option: PollOption<StreamChatGenerics>;
|
|
15
|
+
displayAvatarCount?: number;
|
|
16
|
+
voteCountVerbose?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare const PollOptionSelector: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ displayAvatarCount, option, voteCountVerbose, }: PollOptionSelectorProps<StreamChatGenerics>) => React.JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import debounce from 'lodash.debounce';
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
|
+
import { isVoteAnswer } from 'stream-chat';
|
|
5
|
+
import { Avatar } from '../Avatar';
|
|
6
|
+
import { useChannelStateContext, useMessageContext, usePollContext, useTranslationContext, } from '../../context';
|
|
7
|
+
import { useStateStore } from '../../store';
|
|
8
|
+
export const AmountBar = ({ amount, className }) => (React.createElement("div", { className: clsx('str-chat__amount-bar', className), "data-testid": 'amount-bar', role: 'progressbar', style: {
|
|
9
|
+
'--str-chat__amount-bar-fulfillment': amount + '%',
|
|
10
|
+
} }));
|
|
11
|
+
export const Checkmark = ({ checked }) => (React.createElement("div", { className: clsx('str-chat__checkmark', { 'str-chat__checkmark--checked': checked }) }));
|
|
12
|
+
const pollStateSelector = (nextValue) => ({
|
|
13
|
+
is_closed: nextValue.is_closed,
|
|
14
|
+
latest_votes_by_option: nextValue.latest_votes_by_option,
|
|
15
|
+
maxVotedOptionIds: nextValue.maxVotedOptionIds,
|
|
16
|
+
ownVotesByOptionId: nextValue.ownVotesByOptionId,
|
|
17
|
+
vote_counts_by_option: nextValue.vote_counts_by_option,
|
|
18
|
+
voting_visibility: nextValue.voting_visibility,
|
|
19
|
+
});
|
|
20
|
+
export const PollOptionSelector = ({ displayAvatarCount, option, voteCountVerbose, }) => {
|
|
21
|
+
const { t } = useTranslationContext();
|
|
22
|
+
const { channelCapabilities = {} } = useChannelStateContext('PollOptionsShortlist');
|
|
23
|
+
const { message } = useMessageContext();
|
|
24
|
+
const { poll } = usePollContext();
|
|
25
|
+
const { is_closed, latest_votes_by_option, maxVotedOptionIds, ownVotesByOptionId, vote_counts_by_option, voting_visibility, } = useStateStore(poll.state, pollStateSelector);
|
|
26
|
+
const canCastVote = channelCapabilities['cast-poll-vote'] && !is_closed;
|
|
27
|
+
const winningOptionCount = maxVotedOptionIds[0] ? vote_counts_by_option[maxVotedOptionIds[0]] : 0;
|
|
28
|
+
const toggleVote = useMemo(() => debounce(() => {
|
|
29
|
+
if (!canCastVote)
|
|
30
|
+
return;
|
|
31
|
+
const haveVotedForTheOption = !!ownVotesByOptionId[option.id];
|
|
32
|
+
return haveVotedForTheOption
|
|
33
|
+
? poll.removeVote(ownVotesByOptionId[option.id].id, message.id)
|
|
34
|
+
: poll.castVote(option.id, message.id);
|
|
35
|
+
}, 100), [canCastVote, message.id, option.id, ownVotesByOptionId, poll]);
|
|
36
|
+
return (React.createElement("div", { className: clsx('str-chat__poll-option', {
|
|
37
|
+
'str-chat__poll-option--votable': canCastVote,
|
|
38
|
+
}), key: `base-poll-option-${option.id}`, onClick: toggleVote },
|
|
39
|
+
canCastVote && React.createElement(Checkmark, { checked: !!ownVotesByOptionId[option.id] }),
|
|
40
|
+
React.createElement("div", { className: 'str-chat__poll-option-data' },
|
|
41
|
+
React.createElement("p", { className: 'str-chat__poll-option-text' }, option.text),
|
|
42
|
+
displayAvatarCount && voting_visibility === 'public' && (React.createElement("div", { className: 'str-chat__poll-option-voters' }, latest_votes_by_option?.[option.id] &&
|
|
43
|
+
latest_votes_by_option[option.id]
|
|
44
|
+
.filter((vote) => !!vote.user && !isVoteAnswer(vote))
|
|
45
|
+
.slice(0, displayAvatarCount)
|
|
46
|
+
.map(({ user }) => (React.createElement(Avatar, { image: user?.image, key: `poll-option-${option.id}-avatar-${user?.id}`, name: user?.name }))))),
|
|
47
|
+
React.createElement("div", { className: 'str-chat__poll-option-vote-count' }, voteCountVerbose
|
|
48
|
+
? t('{{count}} votes', { count: vote_counts_by_option[option.id] ?? 0 })
|
|
49
|
+
: vote_counts_by_option[option.id] ?? 0)),
|
|
50
|
+
React.createElement(AmountBar, { amount: (winningOptionCount && (vote_counts_by_option[option.id] ?? 0) / winningOptionCount) * 100, className: clsx('str-chat__poll-option__votes-bar', {
|
|
51
|
+
'str-chat__poll-option__votes-bar--winner': is_closed && maxVotedOptionIds.length === 1 && maxVotedOptionIds[0] === option.id,
|
|
52
|
+
}) })));
|
|
53
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { PollVote as PollVoteType } from 'stream-chat';
|
|
3
|
+
import type { DefaultStreamChatGenerics } from '../../types';
|
|
4
|
+
type PollVoteProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
5
|
+
vote: PollVoteType<StreamChatGenerics>;
|
|
6
|
+
};
|
|
7
|
+
export declare const PollVote: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ vote, }: PollVoteProps<StreamChatGenerics>) => React.JSX.Element;
|
|
8
|
+
export type PollVoteListingProps<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics> = {
|
|
9
|
+
votes: PollVoteType<StreamChatGenerics>[];
|
|
10
|
+
};
|
|
11
|
+
export declare const PollVoteListing: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ votes, }: PollVoteListingProps<StreamChatGenerics>) => React.JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Avatar } from '../Avatar';
|
|
3
|
+
import { PopperTooltip } from '../Tooltip';
|
|
4
|
+
import { useEnterLeaveHandlers } from '../Tooltip/hooks';
|
|
5
|
+
import { useChatContext, useTranslationContext } from '../../context';
|
|
6
|
+
const PollVoteTimestamp = ({ timestamp }) => {
|
|
7
|
+
const { t } = useTranslationContext();
|
|
8
|
+
const { handleEnter, handleLeave, tooltipVisible } = useEnterLeaveHandlers();
|
|
9
|
+
const [referenceElement, setReferenceElement] = useState(null);
|
|
10
|
+
const timestampDate = new Date(timestamp);
|
|
11
|
+
return (React.createElement("div", { className: 'str-chat__poll-vote__timestamp', onMouseEnter: handleEnter, onMouseLeave: handleLeave, ref: setReferenceElement },
|
|
12
|
+
t('timestamp/PollVote', { timestamp: timestampDate }),
|
|
13
|
+
React.createElement(PopperTooltip, { offset: [0, 5], placement: 'bottom', referenceElement: referenceElement, visible: tooltipVisible }, t('timestamp/PollVoteTooltip', { timestamp: timestampDate }))));
|
|
14
|
+
};
|
|
15
|
+
const PollVoteAuthor = ({ vote, }) => {
|
|
16
|
+
const { t } = useTranslationContext();
|
|
17
|
+
const { client } = useChatContext();
|
|
18
|
+
const { handleEnter, handleLeave, tooltipVisible } = useEnterLeaveHandlers();
|
|
19
|
+
const [referenceElement, setReferenceElement] = useState(null);
|
|
20
|
+
const displayName = client.user?.id && client.user.id === vote.user?.id
|
|
21
|
+
? t('You')
|
|
22
|
+
: vote.user?.name || t('Anonymous');
|
|
23
|
+
return (React.createElement("div", { className: 'str-chat__poll-vote__author', onMouseEnter: handleEnter, onMouseLeave: handleLeave, ref: setReferenceElement },
|
|
24
|
+
vote.user && (React.createElement(Avatar, { className: 'str-chat__avatar--poll-vote-author', image: vote.user.image, key: `poll-vote-${vote.id}-avatar-${vote.user.id}`, name: vote.user.name })),
|
|
25
|
+
React.createElement("div", { className: 'str-chat__poll-vote__author__name' }, displayName),
|
|
26
|
+
React.createElement(PopperTooltip, { offset: [0, 5], placement: 'bottom', referenceElement: referenceElement, visible: tooltipVisible }, displayName)));
|
|
27
|
+
};
|
|
28
|
+
export const PollVote = ({ vote, }) => (React.createElement("div", { className: 'str-chat__poll-vote' },
|
|
29
|
+
React.createElement(PollVoteAuthor, { vote: vote }),
|
|
30
|
+
React.createElement(PollVoteTimestamp, { timestamp: vote.created_at })));
|
|
31
|
+
export const PollVoteListing = ({ votes, }) => (React.createElement("div", { className: 'str-chat__poll-vote-listing' }, votes.map((vote) => (React.createElement(PollVote, { key: `poll-vote-${vote.id}`, vote: vote })))));
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { usePollContext } from '../../context';
|
|
4
|
+
import { useStateStore } from '../../store';
|
|
5
|
+
const pollStateSelectorQuotedPoll = (nextValue) => ({
|
|
6
|
+
is_closed: nextValue.is_closed,
|
|
7
|
+
name: nextValue.name,
|
|
8
|
+
});
|
|
9
|
+
export const QuotedPoll = () => {
|
|
10
|
+
const { poll } = usePollContext();
|
|
11
|
+
const { is_closed, name } = useStateStore(poll.state, pollStateSelectorQuotedPoll);
|
|
12
|
+
return (React.createElement("div", { className: clsx('str-chat__quoted-poll-preview', {
|
|
13
|
+
'str-chat__quoted-poll-preview--closed': is_closed,
|
|
14
|
+
}) },
|
|
15
|
+
React.createElement("div", { className: 'str-chat__quoted-poll-preview__icon' }, "\uD83D\uDCCA"),
|
|
16
|
+
React.createElement("div", { className: 'str-chat__quoted-poll-preview__name' }, name)));
|
|
17
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PollAnswer, PollVote } from 'stream-chat';
|
|
2
|
+
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
3
|
+
import { CursorPaginatorStateStore } from '../../InfiniteScrollPaginator/hooks/useCursorPaginator';
|
|
4
|
+
export declare function useManagePollVotesRealtime<StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics, T extends PollVote<StreamChatGenerics> | PollAnswer<StreamChatGenerics> = PollVote<StreamChatGenerics>>(managedVoteType: 'answer' | 'vote', cursorPaginatorState?: CursorPaginatorStateStore<T>, optionId?: string): T[];
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { isVoteAnswer } from 'stream-chat';
|
|
3
|
+
import { useChatContext } from '../../../context';
|
|
4
|
+
export function useManagePollVotesRealtime(managedVoteType, cursorPaginatorState, optionId) {
|
|
5
|
+
const { client } = useChatContext();
|
|
6
|
+
const [votesInRealtime, setVotesInRealtime] = useState(cursorPaginatorState?.getLatestValue().items ?? []);
|
|
7
|
+
useEffect(() => cursorPaginatorState?.subscribeWithSelector((state) => [state.latestPageItems], ([latestPageItems]) => setVotesInRealtime((prev) => [...prev, ...latestPageItems])), [cursorPaginatorState]);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const handleVoteEvent = (event) => {
|
|
10
|
+
if (!event.poll_vote)
|
|
11
|
+
return;
|
|
12
|
+
const isAnswer = isVoteAnswer(event.poll_vote);
|
|
13
|
+
if ((managedVoteType === 'answer' && !isAnswer) ||
|
|
14
|
+
(managedVoteType === 'vote' && (isAnswer || event.poll_vote.option_id !== optionId)))
|
|
15
|
+
return;
|
|
16
|
+
if (event.type === 'poll.vote_removed') {
|
|
17
|
+
setVotesInRealtime((prev) => event.poll_vote ? prev.filter((vote) => vote.id !== event.poll_vote.id) : prev);
|
|
18
|
+
}
|
|
19
|
+
if (event.type === 'poll.vote_changed') {
|
|
20
|
+
setVotesInRealtime((prev) => event.poll_vote ? prev.filter((vote) => vote.id !== event.poll_vote.id) : prev);
|
|
21
|
+
}
|
|
22
|
+
if (['poll.vote_casted', 'poll.vote_changed'].includes(event.type)) {
|
|
23
|
+
setVotesInRealtime((prev) => (event.poll_vote ? [event.poll_vote, ...prev] : prev));
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const voteCastedSubscription = client.on('poll.vote_casted', handleVoteEvent);
|
|
27
|
+
const voteRemovedSubscription = client.on('poll.vote_removed', handleVoteEvent);
|
|
28
|
+
const voteChangedSubscription = client.on('poll.vote_changed', handleVoteEvent);
|
|
29
|
+
return () => {
|
|
30
|
+
voteCastedSubscription.unsubscribe();
|
|
31
|
+
voteRemovedSubscription.unsubscribe();
|
|
32
|
+
voteChangedSubscription.unsubscribe();
|
|
33
|
+
};
|
|
34
|
+
}, [client, optionId, managedVoteType]);
|
|
35
|
+
return votesInRealtime;
|
|
36
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PollAnswer, PollAnswersQueryParams } from 'stream-chat';
|
|
2
|
+
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
3
|
+
type UsePollAnswerPaginationParams = {
|
|
4
|
+
paginationParams?: PollAnswersQueryParams;
|
|
5
|
+
};
|
|
6
|
+
export declare const usePollAnswerPagination: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ paginationParams }?: UsePollAnswerPaginationParams) => {
|
|
7
|
+
answers: PollAnswer<StreamChatGenerics>[];
|
|
8
|
+
error: Error | undefined;
|
|
9
|
+
hasNextPage: boolean;
|
|
10
|
+
loading: boolean;
|
|
11
|
+
loadMore: () => Promise<void>;
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useManagePollVotesRealtime } from './useManagePollVotesRealtime';
|
|
3
|
+
import { useCursorPaginator, } from '../../InfiniteScrollPaginator/hooks/useCursorPaginator';
|
|
4
|
+
import { usePollContext } from '../../../context';
|
|
5
|
+
import { useStateStore } from '../../../store';
|
|
6
|
+
const paginationStateSelector = (state) => [state.error, state.hasNextPage, state.loading];
|
|
7
|
+
export const usePollAnswerPagination = ({ paginationParams } = {}) => {
|
|
8
|
+
const { poll } = usePollContext();
|
|
9
|
+
const paginationFn = useCallback(async (next) => {
|
|
10
|
+
const { next: newNext, votes } = await poll.queryAnswers({
|
|
11
|
+
filter: paginationParams?.filter,
|
|
12
|
+
options: !next ? paginationParams?.options : { ...paginationParams?.options, next },
|
|
13
|
+
sort: { created_at: -1, ...paginationParams?.sort },
|
|
14
|
+
});
|
|
15
|
+
return { items: votes, next: newNext };
|
|
16
|
+
}, [paginationParams, poll]);
|
|
17
|
+
const { cursorPaginatorState, loadMore } = useCursorPaginator(paginationFn, true);
|
|
18
|
+
const answers = useManagePollVotesRealtime('answer', cursorPaginatorState);
|
|
19
|
+
const [error, hasNextPage, loading] = useStateStore(cursorPaginatorState, paginationStateSelector);
|
|
20
|
+
return {
|
|
21
|
+
answers,
|
|
22
|
+
error,
|
|
23
|
+
hasNextPage,
|
|
24
|
+
loading,
|
|
25
|
+
loadMore,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { DefaultStreamChatGenerics } from '../../../types';
|
|
2
|
+
import type { PollOptionVotesQueryParams, PollVote } from 'stream-chat';
|
|
3
|
+
type UsePollOptionVotesPaginationParams = {
|
|
4
|
+
paginationParams: PollOptionVotesQueryParams;
|
|
5
|
+
};
|
|
6
|
+
export declare const usePollOptionVotesPagination: <StreamChatGenerics extends DefaultStreamChatGenerics = DefaultStreamChatGenerics>({ paginationParams, }: UsePollOptionVotesPaginationParams) => {
|
|
7
|
+
error: Error | undefined;
|
|
8
|
+
hasNextPage: boolean;
|
|
9
|
+
loading: boolean;
|
|
10
|
+
loadMore: () => Promise<void>;
|
|
11
|
+
votes: PollVote<StreamChatGenerics>[];
|
|
12
|
+
};
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { useManagePollVotesRealtime } from './useManagePollVotesRealtime';
|
|
3
|
+
import { useCursorPaginator, } from '../../InfiniteScrollPaginator/hooks/useCursorPaginator';
|
|
4
|
+
import { useStateStore } from '../../../store';
|
|
5
|
+
import { usePollContext } from '../../../context';
|
|
6
|
+
const paginationStateSelector = (state) => [state.error, state.hasNextPage, state.loading];
|
|
7
|
+
export const usePollOptionVotesPagination = ({ paginationParams, }) => {
|
|
8
|
+
const { poll } = usePollContext();
|
|
9
|
+
const paginationFn = useCallback(async (next) => {
|
|
10
|
+
const { next: newNext, votes } = await poll.queryOptionVotes({
|
|
11
|
+
filter: paginationParams.filter,
|
|
12
|
+
options: !next ? paginationParams?.options : { ...paginationParams?.options, next },
|
|
13
|
+
sort: { created_at: -1, ...paginationParams?.sort },
|
|
14
|
+
});
|
|
15
|
+
return { items: votes, next: newNext };
|
|
16
|
+
}, [paginationParams, poll]);
|
|
17
|
+
const { cursorPaginatorState, loadMore } = useCursorPaginator(paginationFn, true);
|
|
18
|
+
const votes = useManagePollVotesRealtime('vote', cursorPaginatorState, paginationParams.filter.option_id);
|
|
19
|
+
const [error, hasNextPage, loading] = useStateStore(cursorPaginatorState, paginationStateSelector);
|
|
20
|
+
return {
|
|
21
|
+
error,
|
|
22
|
+
hasNextPage,
|
|
23
|
+
loading,
|
|
24
|
+
loadMore,
|
|
25
|
+
votes,
|
|
26
|
+
};
|
|
27
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './Poll';
|
|
2
|
+
export * from './PollActions';
|
|
3
|
+
export * from './PollContent';
|
|
4
|
+
export * from './PollCreationDialog';
|
|
5
|
+
export * from './PollHeader';
|
|
6
|
+
export * from './PollOptionList';
|
|
7
|
+
export * from './PollOptionSelector';
|
|
8
|
+
export * from './PollVote';
|
|
9
|
+
export * from './QuotedPoll';
|
|
10
|
+
export * from './hooks';
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './Poll';
|
|
2
|
+
export * from './PollActions';
|
|
3
|
+
export * from './PollContent';
|
|
4
|
+
export * from './PollCreationDialog';
|
|
5
|
+
export * from './PollHeader';
|
|
6
|
+
export * from './PollOptionList';
|
|
7
|
+
export * from './PollOptionSelector';
|
|
8
|
+
export * from './PollVote';
|
|
9
|
+
export * from './QuotedPoll';
|
|
10
|
+
export * from './hooks';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { PropsWithChildren, ReactPortal } from 'react';
|
|
2
|
+
export type PortalProps = {
|
|
3
|
+
getPortalDestination: () => Element | null;
|
|
4
|
+
isOpen?: boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare const Portal: ({ children, getPortalDestination, isOpen, }: PropsWithChildren<PortalProps>) => ReactPortal | null;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useLayoutEffect, useState } from 'react';
|
|
2
|
+
import { createPortal } from 'react-dom';
|
|
3
|
+
export const Portal = ({ children, getPortalDestination, isOpen, }) => {
|
|
4
|
+
const [portalDestination, setPortalDestination] = useState(null);
|
|
5
|
+
useLayoutEffect(() => {
|
|
6
|
+
const destination = getPortalDestination();
|
|
7
|
+
if (!destination || !isOpen)
|
|
8
|
+
return;
|
|
9
|
+
setPortalDestination(destination);
|
|
10
|
+
}, [getPortalDestination, isOpen]);
|
|
11
|
+
if (!portalDestination)
|
|
12
|
+
return null;
|
|
13
|
+
return createPortal(children, portalDestination);
|
|
14
|
+
};
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import React, { ComponentProps } from 'react';
|
|
2
|
+
import { PartialSelected } from '../../types/types';
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Use FileInputProps instead.
|
|
5
|
+
*/
|
|
2
6
|
export type UploadButtonProps = {
|
|
3
7
|
onFileChange: (files: Array<File>) => void;
|
|
4
8
|
resetOnChange?: boolean;
|
|
5
9
|
} & Omit<ComponentProps<'input'>, 'type' | 'onChange'>;
|
|
6
|
-
|
|
10
|
+
/**
|
|
11
|
+
* @deprecated Use FileInput instead
|
|
12
|
+
*/
|
|
13
|
+
export declare const UploadButton: React.ForwardRefExoticComponent<Omit<UploadButtonProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
|
|
14
|
+
export type FileInputProps = UploadButtonProps;
|
|
15
|
+
export declare const FileInput: React.ForwardRefExoticComponent<Omit<UploadButtonProps, "ref"> & React.RefAttributes<HTMLInputElement>>;
|
|
16
|
+
export declare const UploadFileInput: React.ForwardRefExoticComponent<Omit<PartialSelected<UploadButtonProps, "onFileChange">, "ref"> & React.RefAttributes<HTMLInputElement>>;
|
|
@@ -1,6 +1,24 @@
|
|
|
1
|
-
import
|
|
1
|
+
import clsx from 'clsx';
|
|
2
|
+
import { nanoid } from 'nanoid';
|
|
3
|
+
import React, { forwardRef, useCallback, useMemo } from 'react';
|
|
2
4
|
import { useHandleFileChangeWrapper } from './utils';
|
|
3
|
-
|
|
5
|
+
import { useChannelStateContext, useMessageInputContext, useTranslationContext, } from '../../context';
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Use FileInput instead
|
|
8
|
+
*/
|
|
9
|
+
export const UploadButton = forwardRef(function UploadButton({ onFileChange, resetOnChange = true, ...rest }, ref) {
|
|
4
10
|
const handleInputChange = useHandleFileChangeWrapper(resetOnChange, onFileChange);
|
|
5
|
-
return React.createElement("input", { onChange: handleInputChange, type: 'file', ...rest });
|
|
6
|
-
};
|
|
11
|
+
return React.createElement("input", { onChange: handleInputChange, ref: ref, type: 'file', ...rest });
|
|
12
|
+
});
|
|
13
|
+
export const FileInput = UploadButton;
|
|
14
|
+
export const UploadFileInput = forwardRef(function UploadFileInput({ className, onFileChange: onFileChangeCustom, ...props }, ref) {
|
|
15
|
+
const { t } = useTranslationContext('UploadFileInput');
|
|
16
|
+
const { acceptedFiles = [], multipleUploads } = useChannelStateContext('UploadFileInput');
|
|
17
|
+
const { isUploadEnabled, maxFilesLeft, uploadNewFiles, } = useMessageInputContext('UploadFileInput');
|
|
18
|
+
const id = useMemo(() => nanoid(), []);
|
|
19
|
+
const onFileChange = useCallback((files) => {
|
|
20
|
+
uploadNewFiles(files);
|
|
21
|
+
onFileChangeCustom?.(files);
|
|
22
|
+
}, [onFileChangeCustom, uploadNewFiles]);
|
|
23
|
+
return (React.createElement(FileInput, { accept: acceptedFiles?.join(','), "aria-label": t('aria/File upload'), "data-testid": 'file-input', disabled: !isUploadEnabled || maxFilesLeft === 0, id: id, multiple: multipleUploads, ...props, className: clsx('str-chat__file-input', className), onFileChange: onFileChange, ref: ref }));
|
|
24
|
+
});
|