|
1 | 1 | ---
|
2 | 2 | id: pin_indicator
|
3 |
| -title: Pin Indicator and Permissions |
| 3 | +title: Pin Indicator |
4 | 4 | ---
|
5 | 5 |
|
6 |
| -import CustomPinMessage from '../../assets/CustomPinMessage.png'; |
7 |
| -import CustomPinMessagePinned from '../../assets/CustomPinMessagePinned.png'; |
8 |
| -import CustomPinMessagePinnedHover from '../../assets/CustomPinMessagePinnedHover.png'; |
| 6 | +import Tabs from '@theme/Tabs'; |
| 7 | +import TabItem from '@theme/TabItem'; |
9 | 8 |
|
10 |
| -In this example, we will demonstrate how to create a custom pin indicator component for pinned messages, as well as set custom permissions for pinning. |
| 9 | +In this example, we will demonstrate how to create a custom pin indicator for pinned messages. |
11 | 10 |
|
12 |
| -## Pin Permissions |
| 11 | +Let's start with the component based solution displaying the name of the user who pinned the message. We'll pass our custom component to the component context from which it'll be picked up by the [`MessageSimple`](../../components/message-components/message-ui.mdx) component to render. |
13 | 12 |
|
14 |
| -Stream provides a default set of pin permissions for each user type within each channel type: |
| 13 | +<Tabs groupId="example"> |
| 14 | +<TabItem value="js" label="React"> |
15 | 15 |
|
16 |
| -```tsx |
17 |
| -export const defaultPinPermissions: PinPermissions = { |
18 |
| - commerce: { |
19 |
| - admin: true, |
20 |
| - anonymous: false, |
21 |
| - channel_member: false, |
22 |
| - channel_moderator: true, |
23 |
| - guest: false, |
24 |
| - member: false, |
25 |
| - moderator: true, |
26 |
| - owner: true, |
27 |
| - user: false, |
28 |
| - }, |
29 |
| - gaming: { |
30 |
| - admin: true, |
31 |
| - anonymous: false, |
32 |
| - channel_member: false, |
33 |
| - channel_moderator: true, |
34 |
| - guest: false, |
35 |
| - member: false, |
36 |
| - moderator: true, |
37 |
| - owner: false, |
38 |
| - user: false, |
39 |
| - }, |
40 |
| - livestream: { |
41 |
| - admin: true, |
42 |
| - anonymous: false, |
43 |
| - channel_member: false, |
44 |
| - channel_moderator: true, |
45 |
| - guest: false, |
46 |
| - member: false, |
47 |
| - moderator: true, |
48 |
| - owner: true, |
49 |
| - user: false, |
50 |
| - }, |
51 |
| - messaging: { |
52 |
| - admin: true, |
53 |
| - anonymous: false, |
54 |
| - channel_member: true, |
55 |
| - channel_moderator: true, |
56 |
| - guest: false, |
57 |
| - member: true, |
58 |
| - moderator: true, |
59 |
| - owner: true, |
60 |
| - user: false, |
61 |
| - }, |
62 |
| - team: { |
63 |
| - admin: true, |
64 |
| - anonymous: false, |
65 |
| - channel_member: true, |
66 |
| - channel_moderator: true, |
67 |
| - guest: false, |
68 |
| - member: true, |
69 |
| - moderator: true, |
70 |
| - owner: true, |
71 |
| - user: false, |
72 |
| - }, |
73 |
| -}; |
74 |
| -``` |
75 |
| - |
76 |
| -:::important |
77 |
| -When using a custom channel type, these permissions must be set for each user role. |
78 |
| -::: |
| 16 | +```jsx |
| 17 | +import { Channel } from 'stream-chat-react'; |
79 | 18 |
|
80 |
| -If you want to use different user permissions or create a custom channel type, the new permissions would need to be passed to `MessageList` using the `pinPermissions` prop. |
| 19 | +const CustomPinIndicator = () => { |
| 20 | + const { message } = useMessageContext('CustomPinIndicator'); |
81 | 21 |
|
82 |
| -```tsx |
83 |
| -const CustomPermissions = { |
84 |
| - customChannelType: { |
85 |
| - admin: true, |
86 |
| - anonymous: false, |
87 |
| - channel_member: false, |
88 |
| - channel_moderator: true, |
89 |
| - guest: false, |
90 |
| - member: false, |
91 |
| - moderator: true, |
92 |
| - owner: true, |
93 |
| - user: false, |
94 |
| - }, |
95 |
| -}; |
96 |
| -``` |
| 22 | + const pinnedBy = message.pinned_by?.name || message.pinned_by?.id; |
97 | 23 |
|
98 |
| -```tsx |
99 |
| -<MessageList pinPermissions={CustomPermissions} /> |
100 |
| -``` |
| 24 | + if (!pinnedBy) return null; |
101 | 25 |
|
102 |
| -## Custom Pin Indicator |
103 |
| - |
104 |
| -To utilize pinned messages, we will create a basic message component as well as a custom pin indicator to display when a message has been pinned. The custom pin indicator component will simply display the text 'pinned'. This is where you could get creative with custom icons and styles. |
105 |
| - |
106 |
| -```tsx |
107 |
| -export const CustomPinIndicator = () => { |
108 |
| - return ( |
109 |
| - <div> |
110 |
| - <div className='pin-text'>pinned</div> |
111 |
| - </div> |
112 |
| - ); |
| 26 | + return <div className='pin-indicator'>📌 Pinned by {pinnedBy}</div>; |
113 | 27 | };
|
114 |
| -``` |
115 |
| - |
116 |
| -```css |
117 |
| -.pin-text { |
118 |
| - font-style: italic; |
119 |
| - color: grey; |
120 |
| -} |
121 |
| -``` |
122 |
| - |
123 |
| -This component can now be passed to `Channel` and later pulled from the `ComponentContext` when used. |
124 |
| - |
125 |
| -```tsx |
126 |
| -<Channel PinIndicator={CustomPinIndicator}>{/* children of Channel component */}</Channel> |
127 |
| -``` |
128 |
| - |
129 |
| -Now we will build a message component that changes its background from turquoise to yellow when a message is pinned. Our custom pin indicator will also replace the `MessageOptions` component unless the message is currently being hovered. |
130 | 28 |
|
131 |
| -```tsx |
132 |
| -export const CustomMessage = () => { |
133 |
| - const [hovering, setHovering] = useState(false); |
134 |
| - |
135 |
| - const { message } = useMessageContext(); |
136 |
| - const { PinIndicator = DefaultPinIndicator } = useComponentContext(); |
137 |
| - |
138 |
| - const messageWrapperRef = useRef<HTMLDivElement>(null); |
139 |
| - |
140 |
| - const { pinned } = message; |
141 |
| - |
142 |
| - return ( |
143 |
| - <div |
144 |
| - className={pinned ? 'pinned-custom-message-wrapper' : 'custom-message-wrapper'} |
145 |
| - onMouseEnter={() => setHovering(true)} |
146 |
| - onMouseLeave={() => setHovering(false)} |
147 |
| - > |
148 |
| - <div className='custom-message-wrapper-content'> |
149 |
| - <div className='custom-message-header'> |
150 |
| - <div className='custom-message-header-name'>{message.user?.name || message.user?.id}</div> |
151 |
| - </div> |
152 |
| - <MessageText /> |
153 |
| - <MessageRepliesCountButton reply_count={message.reply_count} /> |
154 |
| - </div> |
155 |
| - <div className='custom-message-right-wrapper'> |
156 |
| - {hovering ? ( |
157 |
| - <MessageOptions messageWrapperRef={messageWrapperRef} /> |
158 |
| - ) : ( |
159 |
| - pinned && <PinIndicator /> |
160 |
| - )} |
161 |
| - </div> |
162 |
| - </div> |
163 |
| - ); |
| 29 | +export const ChannelWrapper = ({ children }) => { |
| 30 | + return <Channel PinIndicator={CustomPinIndicator}>{children}</Channel>; |
164 | 31 | };
|
165 | 32 | ```
|
166 | 33 |
|
167 |
| -```css |
168 |
| -.custom-message-wrapper { |
169 |
| - display: flex; |
170 |
| - padding: 12px; |
171 |
| - width: fit-content; |
172 |
| - border-radius: 16px; |
173 |
| -} |
174 |
| - |
175 |
| -.custom-message-wrapper-content { |
176 |
| - display: flex; |
177 |
| - flex-direction: column; |
178 |
| - width: 100%; |
179 |
| -} |
| 34 | +</TabItem> |
180 | 35 |
|
181 |
| -.right-wrapper { |
182 |
| - margin-left: 80px; |
183 |
| - margin-right: 16px; |
184 |
| - width: 48px; |
185 |
| -} |
| 36 | +<TabItem value="css" label="CSS"> |
186 | 37 |
|
187 |
| -.custom-message-header-name { |
188 |
| - color: black; |
189 |
| - font-weight: bold; |
190 |
| - font-size: 14; |
| 38 | +```css |
| 39 | +.pin-indicator { |
| 40 | + grid-area: pin; |
191 | 41 | }
|
192 | 42 |
|
193 |
| -.str-chat__message-text-inner { |
194 |
| - color: grey; |
195 |
| - background: white; |
196 |
| - border: none; |
197 |
| - padding: 0; |
| 43 | +.str-chat__message.str-chat__message--other, |
| 44 | +.str-chat__message.str-chat__quoted-message-preview { |
| 45 | + grid-template-areas: |
| 46 | + '. pin' |
| 47 | + 'avatar message' |
| 48 | + '. replies' |
| 49 | + '. translation-notice' |
| 50 | + '. custom-metadata' |
| 51 | + '. metadata'; |
198 | 52 | }
|
199 | 53 |
|
200 |
| -.str-chat__message-simple__actions { |
201 |
| - display: flex; |
| 54 | +.str-chat__message.str-chat__message--me { |
| 55 | + grid-template-areas: |
| 56 | + 'pin' |
| 57 | + 'message' |
| 58 | + 'replies' |
| 59 | + 'translation-notice' |
| 60 | + 'custom-metadata' |
| 61 | + 'metadata'; |
202 | 62 | }
|
| 63 | +``` |
203 | 64 |
|
204 |
| -.custom-message-wrapper { |
205 |
| - background: turquoise; |
206 |
| - box-shadow: 0 2px 1px 0 rgba(0, 0, 0, 0.07); |
207 |
| -} |
| 65 | +</TabItem> |
208 | 66 |
|
209 |
| -.custom-message-wrapper:hover .str-chat__message-text-inner { |
210 |
| - background: turquoise; |
211 |
| -} |
| 67 | +</Tabs> |
212 | 68 |
|
213 |
| -.str-chat__message-replies-count-button { |
214 |
| - align-self: flex-start; |
215 |
| -} |
| 69 | + |
216 | 70 |
|
217 |
| -/** Pinned Message */ |
| 71 | +You can omit the component based solution altogether, all the class names you need are in place and toggled appropriately, here's solution based purely on styles; we'll add `::before` to our message bubble with a 📌 icon to display whenever message has been pinned. |
218 | 72 |
|
219 |
| -.pinned-custom-message-wrapper { |
| 73 | +```css |
| 74 | +.str-chat__message--pinned .str-chat__message-bubble::before { |
| 75 | + content: '📌'; |
220 | 76 | display: flex;
|
221 |
| - padding: 12px; |
222 |
| - width: fit-content; |
223 |
| - border-radius: 16px; |
224 |
| -} |
225 |
| - |
226 |
| -.pinned-custom-message-wrapper { |
227 |
| - background: papayawhip; |
228 |
| - box-shadow: 0 2px 1px 0 rgba(0, 0, 0, 0.07); |
| 77 | + align-items: center; |
| 78 | + justify-content: center; |
| 79 | + position: absolute; |
| 80 | + background-color: papayawhip; |
| 81 | + font-size: 0.6rem; |
| 82 | + width: 1.4rem; |
| 83 | + height: 1.4rem; |
| 84 | + border-radius: 9999px; |
| 85 | + z-index: 1; |
| 86 | + top: -10px; |
229 | 87 | }
|
230 | 88 |
|
231 |
| -.pinned-custom-message-wrapper:hover .str-chat__message-text-inner { |
232 |
| - background: papayawhip; |
| 89 | +.str-chat__message--other.str-chat__message--pinned .str-chat__message-bubble::before { |
| 90 | + right: -10px; |
233 | 91 | }
|
234 | 92 |
|
235 |
| -.pinned-custom-message-wrapper .str-chat__message-text-inner { |
236 |
| - background: papayawhip; |
| 93 | +.str-chat__message--me.str-chat__message--pinned .str-chat__message-bubble::before { |
| 94 | + left: -10px; |
237 | 95 | }
|
238 | 96 | ```
|
239 | 97 |
|
240 |
| -From here all we need to do is override the default component in `Channel` at the `App.tsx` level: |
241 |
| - |
242 |
| -```tsx |
243 |
| -<Channel Message={CustomMessage}>{/* children of Channel component */}</Channel> |
244 |
| -``` |
245 |
| - |
246 |
| -## The Result |
247 |
| - |
248 |
| -Unpinned Message: |
249 |
| - |
250 |
| -<img src={CustomPinMessage} alt='Custom Pin Indicator UI Component for Chat' width='650' /> |
251 |
| - |
252 |
| -Pinned Message (not hovered): |
253 |
| - |
254 |
| -<img src={CustomPinMessagePinned} alt='Custom Pin Indicator UI Component for Chat' width='650' /> |
| 98 | + |
255 | 99 |
|
256 |
| -Pinned Message (hovered): |
| 100 | +### Read More |
257 | 101 |
|
258 |
| -<img |
259 |
| - src={CustomPinMessagePinnedHover} |
260 |
| - alt='Custom Pin Indicator UI Component for Chat' |
261 |
| - width='700' |
262 |
| -/> |
| 102 | +See more on permissions regarding message pinning in [_Permissions v2_](https://getstream.io/chat/docs/react/user_permissions/?language=javascript) section of our JS documentation. |
0 commit comments