diff --git a/package/src/components/Message/MessageSimple/MessageContent.tsx b/package/src/components/Message/MessageSimple/MessageContent.tsx index 86317a3700..9489e4953f 100644 --- a/package/src/components/Message/MessageSimple/MessageContent.tsx +++ b/package/src/components/Message/MessageSimple/MessageContent.tsx @@ -78,9 +78,8 @@ const styles = StyleSheet.create({ position: 'absolute', }, replyContainer: { - flexDirection: 'row', - paddingHorizontal: 8, - paddingTop: 8, + paddingHorizontal: primitives.spacingXs, + paddingTop: primitives.spacingXs, }, rightAlignContent: { justifyContent: 'flex-end', diff --git a/package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap b/package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap index 0f384672ee..99fa039df3 100644 --- a/package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap +++ b/package/src/components/MessageInput/__tests__/__snapshots__/AttachButton.test.js.snap @@ -32,6 +32,7 @@ exports[`AttachButton should call handleAttachButtonPress when the button is cli "height": 48, "width": 48, }, + undefined, ] } > @@ -385,6 +386,7 @@ exports[`AttachButton should render a enabled AttachButton 1`] = ` "height": 48, "width": 48, }, + undefined, ] } > @@ -738,6 +740,7 @@ exports[`AttachButton should render an disabled AttachButton 1`] = ` "height": 48, "width": 48, }, + undefined, ] } > diff --git a/package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap b/package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap index 364869e599..a954e7caff 100644 --- a/package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap +++ b/package/src/components/MessageInput/__tests__/__snapshots__/SendButton.test.js.snap @@ -31,6 +31,7 @@ exports[`SendButton should render a SendButton 1`] = ` "height": 32, "width": 32, }, + undefined, ] } > @@ -381,6 +382,7 @@ exports[`SendButton should render a disabled SendButton 1`] = ` "height": 32, "width": 32, }, + undefined, ] } > diff --git a/package/src/components/MessageList/__tests__/__snapshots__/ScrollToBottomButton.test.js.snap b/package/src/components/MessageList/__tests__/__snapshots__/ScrollToBottomButton.test.js.snap index 565c589967..e0d306f6eb 100644 --- a/package/src/components/MessageList/__tests__/__snapshots__/ScrollToBottomButton.test.js.snap +++ b/package/src/components/MessageList/__tests__/__snapshots__/ScrollToBottomButton.test.js.snap @@ -45,6 +45,7 @@ exports[`ScrollToBottomButton should render the message notification and match s "height": 40, "width": 40, }, + undefined, ] } > diff --git a/package/src/components/Poll/Poll.tsx b/package/src/components/Poll/Poll.tsx index dc64810994..6493d069c5 100644 --- a/package/src/components/Poll/Poll.tsx +++ b/package/src/components/Poll/Poll.tsx @@ -15,6 +15,8 @@ import { useTranslationContext, } from '../../contexts'; +import { primitives } from '../../theme'; + export type PollProps = Pick & Pick; @@ -24,8 +26,10 @@ export type PollContentProps = { }; export const PollHeader = () => { + const styles = useStyles(); const { t } = useTranslationContext(); const { enforceUniqueVote, isClosed, maxVotesAllowed, name } = usePollState(); + const subtitle = useMemo(() => { if (isClosed) { return t('Vote ended'); @@ -41,7 +45,6 @@ export const PollHeader = () => { const { theme: { - colors: { text_high_emphasis, text_low_emphasis }, poll: { message: { header }, }, @@ -49,12 +52,10 @@ export const PollHeader = () => { } = useTheme(); return ( - <> - {name} - - {subtitle} - - + + {name} + {subtitle} + ); }; @@ -63,6 +64,7 @@ export const PollContent = ({ PollHeader: PollHeaderOverride, }: PollContentProps) => { const { options } = usePollState(); + const styles = useStyles(); const { theme: { @@ -98,9 +100,33 @@ export const Poll = ({ message, poll, PollContent: PollContentOverride }: PollPr ); -const styles = StyleSheet.create({ - container: { padding: 15, width: 270 }, - headerSubtitle: { fontSize: 12, marginTop: 4 }, - headerTitle: { fontSize: 16, fontWeight: '500' }, - optionsWrapper: { marginTop: 12 }, -}); +const useStyles = () => { + const { + theme: { semantics }, + } = useTheme(); + return useMemo(() => { + return StyleSheet.create({ + container: { + width: 256, // TODO: Fix this + padding: primitives.spacingMd, + gap: primitives.spacingLg, + }, + headerContainer: { gap: primitives.spacingXxs }, + headerSubtitle: { + color: semantics.chatTextIncoming, + fontSize: primitives.typographyFontSizeSm, + fontWeight: primitives.typographyFontWeightRegular, + lineHeight: primitives.typographyLineHeightTight, + }, + headerTitle: { + color: semantics.chatTextIncoming, + fontSize: primitives.typographyFontSizeMd, + fontWeight: primitives.typographyFontWeightSemiBold, + lineHeight: primitives.typographyLineHeightNormal, + }, + optionsWrapper: { + gap: primitives.spacingMd, + }, + }); + }, [semantics]); +}; diff --git a/package/src/components/Poll/components/Button.tsx b/package/src/components/Poll/components/Button.tsx index 533cc4dd61..b7a9a102bd 100644 --- a/package/src/components/Poll/components/Button.tsx +++ b/package/src/components/Poll/components/Button.tsx @@ -1,9 +1,9 @@ import React from 'react'; -import { Pressable, StyleSheet, Text } from 'react-native'; +import { StyleProp, ViewStyle } from 'react-native'; import { LocalMessage, Poll, PollOption } from 'stream-chat'; -import { useTheme } from '../../../contexts'; +import { Button, ButtonProps } from '../../ui'; export type PollButtonProps = { onPress?: ({ message, poll }: { message: LocalMessage; poll: Poll }) => void; @@ -11,33 +11,29 @@ export type PollButtonProps = { export type PollVoteButtonProps = { option: PollOption; + style?: StyleProp; } & Pick; -export const GenericPollButton = ({ onPress, title }: { onPress?: () => void; title?: string }) => { - const { - theme: { - colors: { accent_dark_blue }, - poll: { - button: { container, text }, - }, - }, - } = useTheme(); +export type GenericPollButtonProps = Partial; +export const GenericPollButton = ({ + variant = 'secondary', + type = 'ghost', + onPress, + label, + style, + size = 'sm', + ...rest +}: GenericPollButtonProps) => { return ( - [{ opacity: pressed ? 0.5 : 1 }, styles.container, container]} - > - {title} - + size={size} + style={style} + {...rest} + /> ); }; - -const styles = StyleSheet.create({ - container: { - alignItems: 'center', - marginHorizontal: 16, - paddingVertical: 11, - }, - text: { fontSize: 16 }, -}); diff --git a/package/src/components/Poll/components/PollButtons.tsx b/package/src/components/Poll/components/PollButtons.tsx index be1903b966..20b3dfbbb3 100644 --- a/package/src/components/Poll/components/PollButtons.tsx +++ b/package/src/components/Poll/components/PollButtons.tsx @@ -1,5 +1,5 @@ -import React, { useCallback, useState } from 'react'; -import { Modal } from 'react-native'; +import React, { useCallback, useMemo, useState } from 'react'; +import { Modal, StyleSheet, View } from 'react-native'; import { GenericPollButton, PollButtonProps } from './Button'; import { PollAnswersList } from './PollAnswersList'; @@ -9,7 +9,9 @@ import { PollAllOptions } from './PollOption'; import { PollResults } from './PollResults'; import { useChatContext, usePollContext, useTheme, useTranslationContext } from '../../../contexts'; +import { primitives } from '../../../theme'; import { SafeAreaViewWrapper } from '../../UIComponents/SafeAreaViewWrapper'; +import { useIsPollCreatedByCurrentUser } from '../hook/useIsPollCreatedByCurrentUser'; import { usePollState } from '../hooks/usePollState'; export const ViewResultsButton = (props: PollButtonProps) => { @@ -32,6 +34,7 @@ export const ViewResultsButton = (props: PollButtonProps) => { colors: { white }, }, } = useTheme(); + const styles = useStyles(); const onRequestClose = useCallback(() => { setShowResults(false); @@ -39,7 +42,12 @@ export const ViewResultsButton = (props: PollButtonProps) => { return ( <> - + {showResults ? ( @@ -83,7 +91,7 @@ export const ShowAllOptionsButton = (props: PollButtonProps) => { {options && options.length > 10 ? ( ) : null} {showAllOptions ? ( @@ -129,7 +137,7 @@ export const ShowAllCommentsButton = (props: PollButtonProps) => { {answersCount && answersCount > 0 ? ( ) : null} {showAnswers ? ( @@ -167,7 +175,7 @@ export const SuggestOptionButton = (props: PollButtonProps) => { return ( <> {!isClosed && allowUserSuggestedOptions ? ( - + ) : null} {showAddOptionDialog ? ( { return ( <> {!isClosed && allowAnswers ? ( - + ) : null} {showAddCommentDialog ? ( { const { t } = useTranslationContext(); const { createdBy, endVote, isClosed } = usePollState(); const { client } = useChatContext(); + const styles = useStyles(); return !isClosed && createdBy?.id === client.userID ? ( - + ) : null; }; -export const PollButtons = () => ( - <> - - - - - - - -); +export const PollButtons = () => { + const styles = useStyles(); + return ( + + + + + + + + + ); +}; + +const useStyles = () => { + const { + theme: { semantics }, + } = useTheme(); + const isPollCreatedByClient = useIsPollCreatedByCurrentUser(); + return useMemo(() => { + return StyleSheet.create({ + buttonsContainer: { gap: primitives.spacingXs }, + endVoteButton: { + borderColor: isPollCreatedByClient + ? semantics.chatBorderOnChatOutgoing + : semantics.chatBorderOnChatIncoming, + }, + viewResultsButton: { + borderColor: isPollCreatedByClient + ? semantics.chatBorderOnChatOutgoing + : semantics.chatBorderOnChatIncoming, + }, + }); + }, [semantics, isPollCreatedByClient]); +}; diff --git a/package/src/components/Poll/components/PollOption.tsx b/package/src/components/Poll/components/PollOption.tsx index 3a6ffc6f2a..884d91edae 100644 --- a/package/src/components/Poll/components/PollOption.tsx +++ b/package/src/components/Poll/components/PollOption.tsx @@ -4,7 +4,7 @@ import { Pressable, ScrollViewProps, StyleSheet, Text, View } from 'react-native import { ScrollView } from 'react-native-gesture-handler'; -import { PollOption as PollOptionClass, PollVote } from 'stream-chat'; +import { PollOption as PollOptionClass, PollVote, UserResponse } from 'stream-chat'; import { PollVoteButtonProps } from './Button'; @@ -17,7 +17,10 @@ import { } from '../../../contexts'; import { Check } from '../../../icons'; -import { UserAvatar } from '../../ui/Avatar/UserAvatar'; +import { primitives } from '../../../theme'; +import { ProgressBar } from '../../ProgressControl/ProgressBar'; +import { UserAvatarStack } from '../../ui/Avatar/AvatarStack'; +import { useIsPollCreatedByCurrentUser } from '../hook/useIsPollCreatedByCurrentUser'; import { usePollState } from '../hooks/usePollState'; export type PollOptionProps = { @@ -80,10 +83,11 @@ export const PollAllOptions = ({ ); export const PollOption = ({ option, showProgressBar = true }: PollOptionProps) => { - const { isClosed, latestVotesByOption, maxVotedOptionIds, voteCountsByOption } = usePollState(); + const { latestVotesByOption, maxVotedOptionIds, voteCountsByOption } = usePollState(); + const styles = useStyles(); const relevantVotes = useMemo( - () => latestVotesByOption?.[option.id]?.slice(0, 2) || [], + () => latestVotesByOption?.[option.id] || [], [latestVotesByOption, option.id], ); const maxVotes = useMemo( @@ -95,55 +99,51 @@ export const PollOption = ({ option, showProgressBar = true }: PollOptionProps) const { theme: { - colors: { accent_dark_blue, accent_info, black, grey }, poll: { message: { - option: { - container, - progressBar, - progressBarEmptyFill, - progressBarVotedFill, - progressBarWinnerFill, - text, - votesContainer, - wrapper, - }, + option: { text, votesContainer, container, info, header, votesText }, }, }, + semantics, }, } = useTheme(); + const isPollCreatedByClient = useIsPollCreatedByCurrentUser(); + + const unFilledColor = isPollCreatedByClient + ? semantics.chatPollProgressFillOutgoing + : semantics.chatPollProgressFillIncoming; + + const filledColor = isPollCreatedByClient + ? semantics.chatPollProgressTrackOutgoing + : semantics.chatPollProgressTrackIncoming; return ( - - - - {option.text} - - {relevantVotes.map((vote: PollVote) => - vote.user ? : null, - )} - {voteCountsByOption[option.id] || 0} + + + + + {option.text} + + vote.user as UserResponse)} + overlap={0.2} + maxVisible={3} + avatarSize='xs' + /> + + {voteCountsByOption[option.id] || 0} + - - {showProgressBar ? ( - - 0 ? votes / maxVotes : 0, - }} - /> - 0 ? (maxVotes - votes) / maxVotes : 1, - }} - /> + + {showProgressBar ? ( + + ) : null} - ) : null} + ); }; @@ -152,13 +152,17 @@ export const VoteButton = ({ onPress, option }: PollVoteButtonProps) => { const { message, poll } = usePollContext(); const { isClosed, ownVotesByOptionId } = usePollState(); const ownCapabilities = useOwnCapabilitiesContext(); + const { + theme: { semantics }, + } = useTheme(); + const isPollCreatedByClient = useIsPollCreatedByCurrentUser(); + const styles = useStyles(); const { theme: { - colors: { accent_dark_blue, disabled }, poll: { message: { - option: { voteButtonActive, voteButtonContainer, voteButtonInactive }, + option: { voteButtonContainer }, }, }, }, @@ -190,10 +194,11 @@ export const VoteButton = ({ onPress, option }: PollVoteButtonProps) => { { opacity: pressed ? 0.5 : 1 }, styles.voteContainer, { - backgroundColor: hasVote ? voteButtonActive || accent_dark_blue : 'transparent', - borderColor: hasVote - ? voteButtonActive || accent_dark_blue - : voteButtonInactive || disabled, + borderWidth: hasVote ? 0 : 1, + backgroundColor: hasVote ? semantics.controlRadiocheckBgSelected : 'transparent', + borderColor: isPollCreatedByClient + ? semantics.chatBorderOnChatOutgoing + : semantics.chatBorderOnChatIncoming, }, voteButtonContainer, ]} @@ -203,6 +208,52 @@ export const VoteButton = ({ onPress, option }: PollVoteButtonProps) => { ) : null; }; +const useStyles = () => { + const { + theme: { semantics }, + } = useTheme(); + return useMemo(() => { + return StyleSheet.create({ + container: { + flexDirection: 'row', + gap: primitives.spacingXs, + alignItems: 'center', + }, + header: { + flexDirection: 'row', + justifyContent: 'space-between', + }, + text: { + color: semantics.chatTextIncoming, + fontSize: primitives.typographyFontSizeSm, + fontWeight: primitives.typographyFontWeightRegular, + lineHeight: primitives.typographyLineHeightTight, + }, + info: { + flexGrow: 1, + gap: primitives.spacingXs, + }, + votesContainer: { flexDirection: 'row', gap: primitives.spacingXs, alignItems: 'center' }, + votesText: { + color: semantics.chatTextIncoming, + fontSize: primitives.typographyFontSizeXs, + fontWeight: primitives.typographyFontWeightRegular, + lineHeight: primitives.typographyLineHeightTight, + }, + progressBarContainer: { + flex: 1, + }, + voteContainer: { + borderRadius: primitives.radiusMax, + height: 24, + justifyContent: 'center', + alignItems: 'center', + width: 24, + }, + }); + }, [semantics]); +}; + const styles = StyleSheet.create({ allOptionsListContainer: { borderRadius: 12, @@ -217,21 +268,4 @@ const styles = StyleSheet.create({ }, allOptionsTitleText: { fontSize: 16, fontWeight: '500' }, allOptionsWrapper: { flex: 1, marginBottom: 16, padding: 16 }, - container: { flexDirection: 'row' }, - progressBar: { borderRadius: 4, flex: 1, flexDirection: 'row', height: 4, marginTop: 2 }, - text: { - flex: 1, - fontSize: 16, - marginLeft: 4, - }, - voteContainer: { - alignItems: 'center', - borderRadius: 18, - borderWidth: 1, - height: 18, - justifyContent: 'center', - width: 18, - }, - votesContainer: { flexDirection: 'row', marginLeft: 4 }, - wrapper: { marginTop: 8, paddingVertical: 8 }, }); diff --git a/package/src/components/Poll/components/PollResults/PollResultItem.tsx b/package/src/components/Poll/components/PollResults/PollResultItem.tsx index 63827928d6..994ece5de8 100644 --- a/package/src/components/Poll/components/PollResults/PollResultItem.tsx +++ b/package/src/components/Poll/components/PollResults/PollResultItem.tsx @@ -59,7 +59,7 @@ export const ShowAllVotesButton = (props: ShowAllVotesButtonProps) => { {ownCapabilities.queryPollVotes && voteCountsByOption && voteCountsByOption?.[option.id] > 5 ? ( - + ) : null} {showAllVotes ? ( { + const { createdBy } = usePollState(); + const { client } = useChatContext(); + + return createdBy?.id === client.userID; +}; diff --git a/package/src/components/ProgressControl/ProgressBar.tsx b/package/src/components/ProgressControl/ProgressBar.tsx new file mode 100644 index 0000000000..347d868dfd --- /dev/null +++ b/package/src/components/ProgressControl/ProgressBar.tsx @@ -0,0 +1,58 @@ +import React, { useMemo } from 'react'; +import { ColorValue, StyleSheet, View } from 'react-native'; + +import { useTheme } from '../../contexts'; +import { primitives } from '../../theme'; + +const TRACK_HEIGHT = 8; + +export type ProgressBarProps = { + progress: number; + filledColor: ColorValue; + emptyColor: ColorValue; +}; + +export const ProgressBar = ({ progress, filledColor, emptyColor }: ProgressBarProps) => { + const styles = useStyles(); + + // clamp for safety + const value = Math.max(0, Math.min(progress, 1)); + const unfilledValue = 1 - value; + + return ( + + + + + ); +}; + +const useStyles = () => { + const { + theme: { semantics }, + } = useTheme(); + return useMemo(() => { + return StyleSheet.create({ + container: { + borderRadius: primitives.radiusMax, + height: TRACK_HEIGHT, + alignItems: 'center', + flexDirection: 'row', + backgroundColor: semantics.chatWaveformBar, + }, + filledStyle: { + backgroundColor: semantics.chatWaveformBarPlaying, + height: TRACK_HEIGHT, + borderRadius: primitives.radiusMax, + }, + }); + }, [semantics]); +}; diff --git a/package/src/components/ProgressControl/ProgressControl.tsx b/package/src/components/ProgressControl/ProgressControl.tsx index aedf14f996..c9dba29a05 100644 --- a/package/src/components/ProgressControl/ProgressControl.tsx +++ b/package/src/components/ProgressControl/ProgressControl.tsx @@ -132,7 +132,7 @@ const useStyles = () => { return useMemo(() => { return StyleSheet.create({ container: { - borderRadius: primitives.radiusXxs, + borderRadius: primitives.radiusMax, height: TRACK_HEIGHT, alignItems: 'center', flexDirection: 'row', @@ -140,7 +140,7 @@ const useStyles = () => { }, filledStyle: { alignSelf: 'center', - borderRadius: 2, + borderRadius: primitives.radiusMax, height: TRACK_HEIGHT, backgroundColor: semantics.chatWaveformBarPlaying, }, diff --git a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap index f8132a3a27..e51202cccb 100644 --- a/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap +++ b/package/src/components/Thread/__tests__/__snapshots__/Thread.test.js.snap @@ -1943,6 +1943,7 @@ exports[`Thread should match thread snapshot 1`] = ` "height": 48, "width": 48, }, + undefined, ] } > @@ -2192,6 +2193,7 @@ exports[`Thread should match thread snapshot 1`] = ` "height": 32, "width": 32, }, + undefined, ] } > diff --git a/package/src/components/ui/Avatar/AvatarStack.tsx b/package/src/components/ui/Avatar/AvatarStack.tsx index 391e4058a8..1d7fee02f0 100644 --- a/package/src/components/ui/Avatar/AvatarStack.tsx +++ b/package/src/components/ui/Avatar/AvatarStack.tsx @@ -25,7 +25,7 @@ export const AvatarStack = (props: AvatarStackProps) => { const extraCount = items.length - visibleItems.length; if (extraCount > 0) { - visibleItems.push(); + visibleItems.push(); } const totalWidth = diameter + (visibleItems.length - 1) * visiblePortion; diff --git a/package/src/components/ui/Button/Button.tsx b/package/src/components/ui/Button/Button.tsx index 11f0d40d68..24b7e64174 100644 --- a/package/src/components/ui/Button/Button.tsx +++ b/package/src/components/ui/Button/Button.tsx @@ -1,5 +1,14 @@ import React, { useMemo } from 'react'; -import { I18nManager, Pressable, PressableProps, StyleSheet, Text, View } from 'react-native'; +import { + I18nManager, + Pressable, + PressableProps, + StyleProp, + StyleSheet, + ViewStyle, + Text, + View, +} from 'react-native'; import { buttonPadding, buttonSizes } from './constants'; import { useButtonStyles } from './hooks/useButtonStyles'; @@ -43,6 +52,11 @@ export type ButtonProps = PressableProps & { * Whether the button is only an icon. */ iconOnly?: boolean; + + /** + * The style of the button. + */ + style?: StyleProp; }; export const Button = ({ @@ -56,6 +70,7 @@ export const Button = ({ label, onLayout, disabled = false, + style, ...rest }: ButtonProps) => { const { @@ -82,6 +97,7 @@ export const Button = ({ height: buttonSizes[size].height, width: iconOnly ? buttonSizes[size].width : undefined, }, + style, ]} onLayout={onLayout} > diff --git a/package/src/contexts/themeContext/utils/theme.ts b/package/src/contexts/themeContext/utils/theme.ts index 6da4ec9a92..ea49e2ae35 100644 --- a/package/src/contexts/themeContext/utils/theme.ts +++ b/package/src/contexts/themeContext/utils/theme.ts @@ -815,14 +815,13 @@ export type Theme = { }; option: { container: ViewStyle; + info: ViewStyle; + header: ViewStyle; + votesText: TextStyle; + progressBarContainer: ViewStyle; progressBar: ViewStyle; - progressBarEmptyFill: string; - progressBarVotedFill: string; - progressBarWinnerFill: string; text: TextStyle; - voteButtonActive: string; voteButtonContainer: ViewStyle; - voteButtonInactive: string; votesContainer: ViewStyle; wrapper: ViewStyle; }; @@ -1646,16 +1645,15 @@ export const defaultTheme: Theme = { }, option: { container: {}, - progressBar: {}, - progressBarEmptyFill: '', - progressBarVotedFill: '', - progressBarWinnerFill: '', + info: {}, + header: {}, text: {}, - voteButtonActive: '', - voteButtonContainer: {}, - voteButtonInactive: '', + progressBarContainer: {}, + progressBar: {}, votesContainer: {}, + votesText: {}, wrapper: {}, + voteButtonContainer: {}, }, optionsWrapper: {}, },