@ermis-network/ermis-chat-react 2.0.0 → 2.0.1

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.
Files changed (72) hide show
  1. package/README.md +144 -0
  2. package/dist/index.cjs +5087 -11279
  3. package/dist/index.cjs.map +1 -1
  4. package/dist/index.css +632 -152
  5. package/dist/index.css.map +1 -1
  6. package/dist/index.d.mts +273 -9
  7. package/dist/index.d.ts +273 -9
  8. package/dist/index.mjs +5085 -11295
  9. package/dist/index.mjs.map +1 -1
  10. package/package.json +2 -2
  11. package/src/components/Channel.tsx +0 -3
  12. package/src/components/ChannelActions.tsx +6 -1
  13. package/src/components/ChannelHeader.tsx +8 -32
  14. package/src/components/ChannelInfo/AddMemberModal.tsx +7 -1
  15. package/src/components/ChannelInfo/ChannelInfo.tsx +82 -2
  16. package/src/components/ChannelInfo/EditChannelModal.tsx +2 -2
  17. package/src/components/ChannelInfo/MediaGridItem.tsx +215 -78
  18. package/src/components/ChannelInfo/useChannelInfoTabs.tsx +170 -129
  19. package/src/components/ChannelList.tsx +72 -13
  20. package/src/components/CreateChannelModal.tsx +131 -12
  21. package/src/components/FilesPreview.tsx +8 -12
  22. package/src/components/FlatTopicGroupItem.tsx +27 -16
  23. package/src/components/ForwardMessageModal.tsx +11 -3
  24. package/src/components/MediaLightbox.tsx +444 -304
  25. package/src/components/MessageActionsBox.tsx +2 -0
  26. package/src/components/MessageInput.tsx +41 -12
  27. package/src/components/MessageItem.tsx +70 -25
  28. package/src/components/MessageQuickReactions.tsx +131 -128
  29. package/src/components/MessageReactions.tsx +47 -2
  30. package/src/components/MessageRenderers.tsx +1030 -433
  31. package/src/components/PinnedMessages.tsx +40 -12
  32. package/src/components/QuotedMessagePreview.tsx +99 -8
  33. package/src/components/RecoveryPin/RecoveryPin.tsx +279 -0
  34. package/src/components/RecoveryPin/index.ts +19 -0
  35. package/src/components/TopicList.tsx +20 -5
  36. package/src/components/TypingIndicator.tsx +3 -3
  37. package/src/components/UserPicker.tsx +26 -25
  38. package/src/components/VirtualMessageList.tsx +345 -125
  39. package/src/context/ChatProvider.tsx +27 -1
  40. package/src/hooks/useChannelListUpdates.ts +22 -1
  41. package/src/hooks/useChannelMessages.ts +338 -51
  42. package/src/hooks/useChannelRowUpdates.ts +18 -6
  43. package/src/hooks/useChatUser.ts +9 -1
  44. package/src/hooks/useE2eeAttachmentRenderer.ts +204 -0
  45. package/src/hooks/useE2eeFileUpload.ts +38 -0
  46. package/src/hooks/useFileUpload.ts +25 -5
  47. package/src/hooks/useForwardMessage.ts +210 -13
  48. package/src/hooks/useLoadMessages.ts +16 -4
  49. package/src/hooks/useMentions.ts +60 -6
  50. package/src/hooks/useMessageActions.ts +14 -8
  51. package/src/hooks/useMessageSend.ts +64 -12
  52. package/src/hooks/usePendingE2eeSends.ts +29 -0
  53. package/src/hooks/useRecoveryPin.ts +287 -0
  54. package/src/hooks/useScrollToMessage.ts +29 -4
  55. package/src/hooks/useTopicGroupUpdates.ts +49 -11
  56. package/src/index.ts +23 -0
  57. package/src/messageTypeUtils.ts +14 -0
  58. package/src/styles/_channel-info.css +9 -0
  59. package/src/styles/_channel-list.css +37 -14
  60. package/src/styles/_media-lightbox.css +36 -3
  61. package/src/styles/_message-bubble.css +381 -41
  62. package/src/styles/_message-input.css +8 -0
  63. package/src/styles/_message-list.css +67 -10
  64. package/src/styles/_message-quick-reactions.css +101 -59
  65. package/src/styles/_message-reactions.css +18 -32
  66. package/src/styles/_recovery-pin.css +97 -0
  67. package/src/styles/_tokens.css +5 -5
  68. package/src/styles/_typing-indicator.css +23 -13
  69. package/src/styles/index.css +1 -0
  70. package/src/types.ts +115 -1
  71. package/src/utils/avatarColors.ts +1 -1
  72. package/src/utils.ts +38 -18
@@ -26,22 +26,30 @@
26
26
  display: flex;
27
27
  flex-direction: column;
28
28
  flex: 1;
29
+ min-height: 0;
30
+ overflow: hidden;
29
31
  font-family: var(--ermis-font-family);
30
- gap: var(--ermis-spacing-xs);
31
32
  position: relative;
32
33
  }
33
34
 
34
35
  .ermis-message-list__vlist {
35
36
  display: flex !important;
36
37
  flex-direction: column !important;
38
+ flex: 1;
39
+ min-height: 0;
40
+ overflow-anchor: none;
37
41
  padding-left: 1rem;
38
42
  padding-right: 1rem;
39
- padding-top: var(--ermis-spacing-lg);
40
- padding-bottom: var(--ermis-spacing-lg);
43
+ padding-top: 1rem;
44
+ padding-bottom: 0.5rem;
45
+ }
46
+
47
+ .ermis-message-list__vlist * {
48
+ overflow-anchor: none;
41
49
  }
42
50
 
43
51
  /* Push content to bottom when few messages, margin collapses when content overflows */
44
- .ermis-message-list__vlist>div {
52
+ .ermis-message-list__vlist > div {
45
53
  margin-top: auto;
46
54
  }
47
55
 
@@ -106,13 +114,30 @@
106
114
  .ermis-message-list__date-separator-label {
107
115
  font-size: 11px;
108
116
  color: #fff;
109
- background-color: rgba(0, 0, 0, 0.2);
117
+ background-color: rgba(0, 0, 0, 0.3);
110
118
  padding: 4px 12px;
111
119
  border-radius: 12px;
112
120
  white-space: nowrap;
113
121
  font-weight: 500;
114
122
  }
115
123
 
124
+ /* --- Time separator (same user, time gap > 5min) --- */
125
+ .ermis-message-list__time-separator {
126
+ display: flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ padding: var(--ermis-spacing-xs) 0;
130
+ margin: var(--ermis-spacing-xs) 0;
131
+ align-self: stretch;
132
+ }
133
+
134
+ .ermis-message-list__time-separator-label {
135
+ font-size: 10px;
136
+ color: var(--ermis-text-muted);
137
+ padding: 2px 10px;
138
+ white-space: nowrap;
139
+ font-weight: 400;
140
+ }
116
141
  .ermis-message-list__empty {
117
142
  display: flex;
118
143
  flex-direction: column;
@@ -143,21 +168,53 @@
143
168
  color: var(--ermis-text-muted);
144
169
  }
145
170
 
171
+ /* --- Message group (consecutive messages from same user) --- */
172
+ .ermis-message-group {
173
+ display: flex;
174
+ align-items: flex-end;
175
+ gap: var(--ermis-spacing-sm);
176
+ width: 100%;
177
+ padding-top: 4px;
178
+ }
179
+
180
+ .ermis-message-group--own {
181
+ flex-direction: row-reverse;
182
+ }
183
+
184
+ .ermis-message-group--other {
185
+ flex-direction: row;
186
+ }
187
+
188
+ .ermis-message-group__avatar-col {
189
+ flex-shrink: 0;
190
+ position: sticky;
191
+ bottom: 0;
192
+ align-self: flex-end;
193
+ z-index: 5;
194
+ }
195
+
196
+ .ermis-message-group__messages-col {
197
+ display: flex;
198
+ flex-direction: column;
199
+ min-width: 0;
200
+ max-width: 75%;
201
+ }
202
+
146
203
  /* --- Message item (other = left, own = right) --- */
147
204
  .ermis-message-list__item {
148
205
  display: flex;
149
- align-items: flex-start;
206
+ align-items: flex-end;
150
207
  gap: var(--ermis-spacing-sm);
151
208
  width: 100%;
152
209
  }
153
210
 
154
- /* First message in a group (or standalone): normal top margin */
211
+ /* First message in a group (or standalone): spacing handled by group wrapper */
155
212
  .ermis-message-list__item--group-single,
156
213
  .ermis-message-list__item--group-top {
157
- padding-top: var(--ermis-spacing-sm);
214
+ padding-top: 0;
158
215
  }
159
216
 
160
- /* Continuation messages in a group: tight spacing (increased slightly per request) */
217
+ /* Continuation messages in a group: tight spacing */
161
218
  .ermis-message-list__item--group-middle,
162
219
  .ermis-message-list__item--group-bottom {
163
220
  padding-top: 2px;
@@ -499,4 +556,4 @@
499
556
 
500
557
  .ermis-message-list__pending-invitee-content svg {
501
558
  color: var(--ermis-accent);
502
- }
559
+ }
@@ -1,98 +1,140 @@
1
1
  /* ============================================================
2
- Message Quick Reactions (Hover popup)
2
+ Quick Reactions – Horizontal Strip (In Hover Actions)
3
3
  ============================================================ */
4
4
 
5
- .ermis-message-quick-reactions {
5
+ /* The wrapper is now inline-flex inside the hover actions box */
6
+ .ermis-qr-wrapper {
7
+ position: relative;
8
+ display: inline-flex;
9
+ }
10
+
11
+ /* ---- Horizontal Strip ---- */
12
+ .ermis-qr__strip--horizontal {
6
13
  position: absolute;
7
- bottom: 100%; /* Sits exactly on top of bubble-wrapper, no gap */
14
+ bottom: calc(100% + 8px);
15
+ left: 50%;
8
16
  display: flex;
17
+ flex-direction: row;
9
18
  align-items: center;
10
19
  gap: 2px;
11
- padding: 4px;
12
- padding-bottom: 8px; /* 4px normal padding + 4px transparent gap */
20
+ background-color: var(--ermis-bg-primary, #ffffff);
21
+ border: 1px solid var(--ermis-border);
13
22
  border-radius: 20px;
23
+ padding: 4px;
24
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
25
+ z-index: 101;
26
+ width: max-content;
27
+
28
+ /* Animation */
14
29
  opacity: 0;
15
- visibility: hidden;
16
- transform: translateY(4px);
17
- /* Fast, snappy transition with NO delay to prevent stuttering */
18
- transition: opacity 0.1s ease, transform 0.1s ease, visibility 0.1s ease;
19
- z-index: 20;
30
+ transform: translateX(-50%) scale(0.95);
31
+ animation: ermis-qr-pop-centered 0.15s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
20
32
  }
21
33
 
22
- /* Visual background pushed up by the padding gap */
23
- .ermis-message-quick-reactions::before {
34
+ /* Invisible bridge to prevent hover loss when moving mouse from trigger to strip */
35
+ .ermis-qr__strip--horizontal::after {
24
36
  content: '';
25
37
  position: absolute;
26
- top: 0;
27
- left: 0;
28
- right: 0;
29
- bottom: 4px; /* Leave 4px transparent at the bottom */
30
- background-color: var(--ermis-bg-primary);
31
- border: 1px solid var(--ermis-border);
32
- border-radius: 20px;
33
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
34
- z-index: -1;
35
- }
36
-
37
-
38
-
39
- /* Align based on ownership */
40
- .ermis-message-quick-reactions {
38
+ top: 100%;
41
39
  left: 0;
42
- }
43
- .ermis-message-quick-reactions--own {
44
- left: auto;
45
- right: 0;
40
+ width: 100%;
41
+ height: 12px; /* Covers the 8px gap + some extra safe area */
46
42
  }
47
43
 
48
- .ermis-message-list__bubble-wrapper:hover {
49
- z-index: 100; /* Ensure the hovered wrapper is above adjacent messages */
44
+ @keyframes ermis-qr-pop-centered {
45
+ to {
46
+ opacity: 1;
47
+ transform: translateX(-50%) scale(1);
48
+ }
50
49
  }
51
50
 
52
- .ermis-message-list__bubble-wrapper:hover .ermis-message-quick-reactions,
53
- .ermis-message-quick-reactions:hover {
54
- opacity: 1;
55
- visibility: visible;
56
- transform: translateY(0);
51
+ @keyframes ermis-qr-pop {
52
+ to {
53
+ opacity: 1;
54
+ transform: scale(1);
55
+ }
57
56
  }
58
57
 
59
- .ermis-message-quick-reactions--expanded {
60
- opacity: 1;
61
- visibility: visible;
62
- transform: translateY(0);
63
- transition: none !important; /* Let framer-motion handle transitions */
64
- will-change: width, height, transform;
58
+ /* Positioning based on message owner */
59
+ .ermis-message-list__item--own .ermis-qr__strip--horizontal {
60
+ left: 50%; /* Center it relative to the wrapper */
65
61
  }
66
62
 
67
- .ermis-message-quick-reactions--expanded::before {
68
- display: none; /* Hide custom background when expanded, framer-motion takes over */
63
+ .ermis-message-list__item--other .ermis-qr__strip--horizontal {
64
+ left: 50%; /* Center it relative to the wrapper */
69
65
  }
70
66
 
71
- .ermis-message-list__bubble-wrapper:hover .ermis-message-quick-reactions--disabled {
72
- opacity: 0.5;
73
- pointer-events: none;
74
- }
75
-
76
- .ermis-message-quick-reactions__btn {
67
+ /* ---- Emoji Buttons ---- */
68
+ .ermis-qr__emoji {
77
69
  display: flex;
78
70
  align-items: center;
79
71
  justify-content: center;
80
- background: none;
72
+ background: transparent;
81
73
  border: none;
82
- font-size: 18px;
74
+ font-size: 16px;
83
75
  line-height: 1;
84
76
  width: 32px;
85
77
  height: 32px;
86
78
  border-radius: 50%;
87
79
  cursor: pointer;
88
- transition: transform 0.15s cubic-bezier(0.175, 0.885, 0.32, 1.275), background-color 0.15s;
80
+ transition: transform 0.15s cubic-bezier(0.175, 0.885, 0.32, 1.275),
81
+ background-color 0.15s;
82
+ padding: 0;
83
+ outline: none;
84
+ color: inherit;
85
+ flex-shrink: 0;
89
86
  }
90
87
 
91
- .ermis-message-quick-reactions__btn:hover {
88
+ .ermis-qr__emoji:hover {
92
89
  transform: scale(1.2);
93
- background-color: var(--ermis-bg-hover);
90
+ background-color: rgba(0, 0, 0, 0.05);
91
+ }
92
+
93
+ .ermis-qr__emoji--active {
94
+ background-color: rgba(99, 102, 241, 0.1);
94
95
  }
95
96
 
96
- .ermis-message-quick-reactions__btn--active {
97
- background-color: var(--ermis-bg-active, rgba(99, 102, 241, 0.12));
97
+ .ermis-qr__emoji--more {
98
+ color: var(--ermis-text-muted);
99
+ font-size: 14px;
98
100
  }
101
+
102
+ .ermis-qr__emoji--more:hover {
103
+ color: var(--ermis-text-primary);
104
+ }
105
+
106
+ /* ---- Full Emoji Picker ---- */
107
+ .ermis-qr__picker {
108
+ position: absolute;
109
+ width: 350px;
110
+ height: 368px;
111
+ border-radius: 16px;
112
+ overflow: hidden;
113
+ background-color: var(--ermis-bg-primary);
114
+ border: 1px solid var(--ermis-border);
115
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1),
116
+ 0 8px 10px -6px rgba(0, 0, 0, 0.1);
117
+ z-index: 102;
118
+
119
+ /* Animation */
120
+ opacity: 0;
121
+ transform: scale(0.95);
122
+ animation: ermis-qr-pop 0.15s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
123
+ }
124
+
125
+ .ermis-qr__picker--top {
126
+ bottom: calc(100% + 8px);
127
+ }
128
+
129
+ .ermis-qr__picker--bottom {
130
+ top: calc(100% + 8px);
131
+ }
132
+
133
+ .ermis-message-list__item--own .ermis-qr__picker {
134
+ right: -8px;
135
+ }
136
+
137
+ .ermis-message-list__item--other .ermis-qr__picker {
138
+ left: -8px;
139
+ }
140
+
@@ -2,12 +2,12 @@
2
2
  Message Reactions
3
3
  ============================================================ */
4
4
  .ermis-message-reactions {
5
- display: flex;
5
+ display: inline-flex;
6
6
  flex-wrap: wrap;
7
7
  gap: 4px;
8
- margin-bottom: 2px;
9
- margin-top: 2px;
10
- width: 100%;
8
+ margin-top: 4px;
9
+ max-width: 100%;
10
+ vertical-align: middle;
11
11
  }
12
12
 
13
13
  .ermis-message-reactions--disabled {
@@ -26,7 +26,7 @@
26
26
  gap: 4px;
27
27
  padding: 2px 6px;
28
28
  border-radius: 12px;
29
- background-color: var(--ermis-surface);
29
+ background-color: var(--ermis-bg-hover);
30
30
  border: 1px solid var(--ermis-border);
31
31
  font-size: 11px;
32
32
  line-height: 1.2;
@@ -36,39 +36,25 @@
36
36
  user-select: none;
37
37
  }
38
38
 
39
- /* Custom Tooltip */
40
- .ermis-message-reactions__item::after {
41
- content: attr(data-tooltip);
42
- position: absolute;
43
- bottom: 100%;
44
- left: 50%;
45
- transform: translateX(-50%);
46
- background-color: rgba(0, 0, 0, 0.75);
47
- color: #fff;
48
- padding: 4px 8px;
49
- border-radius: 6px;
50
- font-size: 11px;
51
- white-space: pre;
52
- text-align: left;
53
- pointer-events: none;
54
- opacity: 0;
55
- visibility: hidden;
56
- transition: opacity 0.2s, visibility 0.2s;
57
- margin-bottom: 6px;
58
- z-index: 100;
59
- }
60
-
61
- .ermis-message-reactions__item:hover::after {
62
- opacity: 1;
63
- visibility: visible;
64
- }
65
-
66
39
  .ermis-message-reactions__item--active {
67
40
  background-color: var(--ermis-bg-active, rgba(99, 102, 241, 0.12));
68
41
  border-color: var(--ermis-accent);
69
42
  color: var(--ermis-accent);
70
43
  }
71
44
 
45
+ /* Own bubble overrides — pills need contrast against accent bg */
46
+ .ermis-message-bubble--own .ermis-message-reactions__item {
47
+ background-color: rgba(255, 255, 255, 0.2);
48
+ border-color: rgba(255, 255, 255, 0.3);
49
+ color: rgba(255, 255, 255, 0.9);
50
+ }
51
+
52
+ .ermis-message-bubble--own .ermis-message-reactions__item--active {
53
+ background-color: rgba(255, 255, 255, 0.35);
54
+ border-color: rgba(255, 255, 255, 0.6);
55
+ color: #ffffff;
56
+ }
57
+
72
58
  .ermis-message-reactions__emoji {
73
59
  font-size: 14px;
74
60
  }
@@ -0,0 +1,97 @@
1
+ .ermis-recovery-pin {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 10px;
5
+ width: 100%;
6
+ }
7
+
8
+ .ermis-recovery-pin__header {
9
+ align-items: center;
10
+ display: flex;
11
+ gap: 8px;
12
+ justify-content: space-between;
13
+ }
14
+
15
+ .ermis-recovery-pin__header h3 {
16
+ font-size: 16px;
17
+ font-weight: 600;
18
+ line-height: 1.3;
19
+ margin: 0;
20
+ }
21
+
22
+ .ermis-recovery-pin__badge,
23
+ .ermis-recovery-status {
24
+ border: 1px solid var(--ermis-border-color, #d7dde5);
25
+ border-radius: 999px;
26
+ color: var(--ermis-text-muted, #596579);
27
+ display: inline-flex;
28
+ font-size: 12px;
29
+ font-weight: 600;
30
+ line-height: 1;
31
+ padding: 5px 8px;
32
+ white-space: nowrap;
33
+ }
34
+
35
+ .ermis-recovery-pin__input {
36
+ background: var(--ermis-input-bg, #fff);
37
+ border: 1px solid var(--ermis-border-color, #d7dde5);
38
+ border-radius: 8px;
39
+ color: var(--ermis-text-color, #111827);
40
+ font-size: 14px;
41
+ min-height: 38px;
42
+ padding: 8px 10px;
43
+ }
44
+
45
+ .ermis-recovery-pin__button {
46
+ align-items: center;
47
+ background: var(--ermis-primary-color, #2563eb);
48
+ border: 0;
49
+ border-radius: 8px;
50
+ color: #fff;
51
+ cursor: pointer;
52
+ display: inline-flex;
53
+ font-size: 14px;
54
+ font-weight: 600;
55
+ justify-content: center;
56
+ min-height: 38px;
57
+ padding: 8px 12px;
58
+ }
59
+
60
+ .ermis-recovery-pin__button:disabled {
61
+ cursor: not-allowed;
62
+ opacity: 0.55;
63
+ }
64
+
65
+ .ermis-recovery-pin__error,
66
+ .ermis-recovery-gap {
67
+ background: var(--ermis-danger-bg, #fff1f2);
68
+ border: 1px solid var(--ermis-danger-border, #fecdd3);
69
+ border-radius: 8px;
70
+ color: var(--ermis-danger-color, #be123c);
71
+ font-size: 13px;
72
+ line-height: 1.4;
73
+ padding: 8px 10px;
74
+ }
75
+
76
+ .ermis-recovery-status--ready {
77
+ background: var(--ermis-success-bg, #ecfdf5);
78
+ border-color: var(--ermis-success-border, #bbf7d0);
79
+ color: var(--ermis-success-color, #047857);
80
+ }
81
+
82
+ .ermis-recovery-status--working {
83
+ background: var(--ermis-info-bg, #eff6ff);
84
+ border-color: var(--ermis-info-border, #bfdbfe);
85
+ color: var(--ermis-info-color, #1d4ed8);
86
+ }
87
+
88
+ .ermis-recovery-status--locked,
89
+ .ermis-recovery-status--idle {
90
+ background: var(--ermis-muted-bg, #f8fafc);
91
+ }
92
+
93
+ .ermis-recovery-status--error {
94
+ background: var(--ermis-danger-bg, #fff1f2);
95
+ border-color: var(--ermis-danger-border, #fecdd3);
96
+ color: var(--ermis-danger-color, #be123c);
97
+ }
@@ -17,8 +17,8 @@
17
17
  --ermis-accent: #6366f1;
18
18
  --ermis-accent-hover: #818cf8;
19
19
  --ermis-text-primary: #e5e7eb;
20
- --ermis-text-secondary: #9ca3af;
21
- --ermis-text-muted: #6b7280;
20
+ --ermis-text-secondary: #abb4c6;
21
+ --ermis-text-muted: #abb4c6;
22
22
 
23
23
  /* Semantic Colors */
24
24
  --ermis-color-danger: #ef4444;
@@ -42,7 +42,7 @@
42
42
  --ermis-quote-own-text: rgba(255, 255, 255, 0.75);
43
43
 
44
44
  /* Typography */
45
- --ermis-font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
45
+ --ermis-font-family: 'SF Pro Display', 'SF Pro Text', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
46
46
  'Helvetica Neue', sans-serif;
47
47
  --ermis-font-size-xs: 0.75rem;
48
48
  --ermis-font-size-sm: 0.875rem;
@@ -96,7 +96,7 @@
96
96
  --ermis-accent-hover: #0d00ff;
97
97
  --ermis-text-primary: #111827;
98
98
  --ermis-text-secondary: #6b7280;
99
- --ermis-text-muted: #9ca3af;
99
+ --ermis-text-muted: #545f71;
100
100
 
101
101
  /* Semantic Colors */
102
102
  --ermis-color-danger: #ef4444;
@@ -107,7 +107,7 @@
107
107
  /* Message bubbles */
108
108
  --ermis-bubble-own-bg: var(--ermis-accent);
109
109
  --ermis-bubble-own-text: #ffffff;
110
- --ermis-bubble-other-bg: #f3f4f6;
110
+ --ermis-bubble-other-bg: #f0f2f5;
111
111
  --ermis-bubble-other-text: var(--ermis-text-primary);
112
112
 
113
113
  /* Quote message */
@@ -2,37 +2,47 @@
2
2
  Typing Indicator
3
3
  ============================================================ */
4
4
 
5
- /* Container always rendered with fixed height to prevent layout shifts */
5
+ /* Floats above the input area without taking layout space.
6
+ Height is 0 so messages/input don't jump; content overflows upward. */
7
+ .ermis-typing-indicator-wrapper {
8
+ height: 0;
9
+ overflow: visible;
10
+ padding-left: 16px;
11
+ position: relative;
12
+ z-index: 10;
13
+ }
14
+
6
15
  .ermis-typing-indicator {
7
- display: flex;
16
+ display: inline-flex;
8
17
  align-items: center;
9
18
  gap: 6px;
10
- padding: 0 16px;
11
- height: 24px;
12
19
  font-size: 12px;
13
20
  color: var(--ermis-text-secondary);
14
- overflow: hidden;
15
- position: absolute;
16
- bottom: 0;
17
- left: 0;
18
- right: 0;
21
+ background-color: var(--ermis-bg-primary, #fff);
22
+ padding: 4px 12px;
23
+ border-radius: 999px;
24
+ border: 1px solid var(--ermis-border, rgba(0, 0, 0, 0.08));
25
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
26
+ transform: translateY(-100%);
27
+ margin-bottom: 8px;
28
+ animation: ermis-typing-appear 0.2s ease-out forwards;
19
29
  }
20
30
 
21
31
  .ermis-typing-indicator__dots {
22
32
  display: flex;
23
33
  align-items: center;
24
34
  gap: 3px;
25
- animation: ermis-typing-appear 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
26
35
  }
27
36
 
28
37
  @keyframes ermis-typing-appear {
29
38
  0% {
30
39
  opacity: 0;
31
- transform: translateY(8px);
40
+ transform: translateY(calc(-100% + 4px));
32
41
  }
42
+
33
43
  100% {
34
44
  opacity: 1;
35
- transform: translateY(0);
45
+ transform: translateY(-100%);
36
46
  }
37
47
  }
38
48
 
@@ -63,6 +73,7 @@
63
73
  transform: scale(0.6);
64
74
  opacity: 0.4;
65
75
  }
76
+
66
77
  40% {
67
78
  transform: scale(1);
68
79
  opacity: 1;
@@ -74,5 +85,4 @@
74
85
  white-space: nowrap;
75
86
  overflow: hidden;
76
87
  text-overflow: ellipsis;
77
- animation: ermis-typing-appear 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
78
88
  }
@@ -28,3 +28,4 @@
28
28
  @import './_topic-modal.css';
29
29
  @import './_media-lightbox.css';
30
30
  @import './_preview-overlay.css';
31
+ @import './_recovery-pin.css';