@passui/react 0.1.45 → 0.1.47
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/README.md +57 -46
- package/dist/types/src/components/accordion/accordion.d.ts +1 -1
- package/dist/types/src/components/breadcrumb/breadcrumb.d.ts +1 -1
- package/dist/types/src/components/inline-disclosure/inline-disclosure.d.ts +1 -1
- package/dist/types/src/components/popover/anchored-surface.d.ts +5 -1
- package/dist/types/src/components/popover/index.d.ts +41 -0
- package/dist/types/src/components/popover/popover-content.d.ts +38 -0
- package/dist/types/src/components/popover/popover-root.d.ts +22 -0
- package/dist/types/src/components/popover/popover-trigger.d.ts +23 -0
- package/dist/types/src/components/stepper/rolling-digit.d.ts +1 -1
- package/dist/types/src/components/table/table.d.ts +1 -1
- package/dist/types/src/components/view-controller/view-controller.d.ts +2 -2
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/machines/popover.machine.d.ts +30 -0
- package/package.json +307 -307
- package/src/appearance/index.ts +2 -2
- package/src/appearance/platform.tsx +76 -60
- package/src/appearance/theme.tsx +409 -377
- package/src/components/accordion/accordion.tsx +58 -49
- package/src/components/ambient/ambient.tsx +25 -25
- package/src/components/ambient/index.ts +1 -1
- package/src/components/aspect-ratio/aspect-ratio.tsx +21 -16
- package/src/components/breadcrumb/breadcrumb.tsx +63 -57
- package/src/components/button/button-styles.ts +39 -32
- package/src/components/button/button.tsx +91 -84
- package/src/components/button/icon-button.tsx +27 -25
- package/src/components/button/pending-spinner.tsx +8 -8
- package/src/components/button/use-pending-indicator.ts +11 -11
- package/src/components/chip/chip.tsx +35 -30
- package/src/components/code-block/code-block.tsx +40 -39
- package/src/components/combo-box/combo-box.tsx +5 -5
- package/src/components/command/command.tsx +124 -94
- package/src/components/compact-date-picker/calendar-month.ts +75 -53
- package/src/components/compact-date-picker/compact-date-picker.tsx +308 -289
- package/src/components/compact-time-picker/compact-time-picker.tsx +288 -281
- package/src/components/compact-time-picker/time-value.ts +32 -29
- package/src/components/content-unavailable-view/content-unavailable-view.tsx +64 -48
- package/src/components/content-view/content-view.tsx +168 -159
- package/src/components/content-view/index.ts +1 -1
- package/src/components/context-menu/conceal-menu-source.ts +25 -25
- package/src/components/context-menu/context-menu-host.tsx +439 -402
- package/src/components/context-menu/context-menu-store.ts +125 -123
- package/src/components/context-menu/menu-navigation.ts +25 -18
- package/src/components/context-menu/menu-position.ts +33 -29
- package/src/components/context-menu/menu-reveal.ts +32 -32
- package/src/components/context-menu/use-context-menu.ts +63 -56
- package/src/components/conversation/conversation.tsx +265 -263
- package/src/components/conversation/index.ts +1 -1
- package/src/components/date-picker/date-picker.tsx +95 -94
- package/src/components/date-picker/date-value.ts +20 -10
- package/src/components/dialog/dialog-host.tsx +105 -103
- package/src/components/dialog/dialog-store.ts +37 -36
- package/src/components/dialog/dialog.tsx +192 -179
- package/src/components/dialog/index.ts +1 -1
- package/src/components/disclosure-group/disclosure-group.tsx +93 -86
- package/src/components/divider/divider.tsx +24 -22
- package/src/components/inline-disclosure/inline-disclosure.tsx +40 -36
- package/src/components/keyboard-shortcut/keyboard-shortcut.tsx +23 -19
- package/src/components/labeled-content/labeled-content.tsx +68 -68
- package/src/components/locale-switcher/index.ts +1 -1
- package/src/components/locale-switcher/locale-switcher.tsx +278 -251
- package/src/components/menu/menu.tsx +75 -58
- package/src/components/morph/index.ts +2 -2
- package/src/components/morph/morph.tsx +227 -201
- package/src/components/morph/use-morph-disclosure.ts +19 -19
- package/src/components/one-time-code/one-time-code.tsx +216 -200
- package/src/components/picker/picker-trigger.ts +16 -13
- package/src/components/picker/picker.tsx +371 -345
- package/src/components/popover/anchored-surface.tsx +253 -229
- package/src/components/popover/index.ts +50 -0
- package/src/components/popover/popover-content.tsx +374 -0
- package/src/components/popover/popover-root.tsx +97 -0
- package/src/components/popover/popover-trigger.tsx +71 -0
- package/src/components/profile-image/profile-image.tsx +80 -64
- package/src/components/progress-view/progress-view.tsx +61 -59
- package/src/components/progress-view/use-liquid-progress.ts +38 -38
- package/src/components/radio-group/radio-group.tsx +71 -68
- package/src/components/search-field/expanding-search-field.tsx +191 -177
- package/src/components/search-field/search-field.tsx +59 -50
- package/src/components/search-list/search-list.tsx +246 -236
- package/src/components/section/section.tsx +58 -58
- package/src/components/secure-field/secure-field.tsx +3 -3
- package/src/components/segmented-control/segmented-control.tsx +136 -136
- package/src/components/sheet/modal-environment.ts +71 -64
- package/src/components/sheet/sheet-portal.tsx +58 -53
- package/src/components/sheet/sheet.tsx +146 -141
- package/src/components/sheet/use-sheet-focus.ts +97 -82
- package/src/components/sidebar/sidebar.tsx +78 -78
- package/src/components/skeleton/skeleton.tsx +30 -21
- package/src/components/slider/slider.tsx +113 -104
- package/src/components/slosh-slider/liquid-step.ts +32 -31
- package/src/components/slosh-slider/slosh-slider.tsx +265 -228
- package/src/components/stat-tile/index.ts +1 -1
- package/src/components/stat-tile/stat-tile.tsx +79 -68
- package/src/components/status-indicator/status-indicator.tsx +40 -37
- package/src/components/stepper/rolling-digit.tsx +82 -68
- package/src/components/stepper/stepper.tsx +122 -113
- package/src/components/swatch-picker/swatch-picker.tsx +120 -120
- package/src/components/tab-bar/tab-bar.tsx +149 -148
- package/src/components/tab-view/tab-view.tsx +140 -137
- package/src/components/table/table.tsx +117 -98
- package/src/components/text-editor/text-editor.tsx +34 -29
- package/src/components/text-field/text-field.tsx +182 -180
- package/src/components/toast/toast-store.ts +127 -115
- package/src/components/toast/toaster.tsx +361 -347
- package/src/components/toggle/toggle.tsx +297 -282
- package/src/components/tooltip/tooltip-host.tsx +157 -150
- package/src/components/view-controller/index.ts +1 -1
- package/src/components/view-controller/view-controller.tsx +64 -53
- package/src/components/wake-slider/wake-slider.tsx +220 -205
- package/src/hooks/scroll/minimize-decision.ts +11 -7
- package/src/hooks/use-anchored-disclosure.ts +19 -19
- package/src/hooks/use-anchored-placement.ts +29 -25
- package/src/hooks/use-dismiss.ts +33 -29
- package/src/hooks/use-floating-dismiss.ts +24 -22
- package/src/hooks/use-media-query.ts +67 -60
- package/src/hooks/use-minimize-on-scroll.ts +69 -60
- package/src/hooks/use-visual-viewport.ts +63 -63
- package/src/hooks/viewport/types.ts +16 -16
- package/src/hooks/viewport/viewport-metrics.ts +16 -10
- package/src/icons/icons.tsx +440 -328
- package/src/index.ts +91 -91
- package/src/lib/anchored-placement.ts +70 -65
- package/src/lib/class-names.ts +14 -13
- package/src/lib/clipboard.ts +8 -7
- package/src/lib/date.ts +61 -60
- package/src/lib/floating-layers.ts +39 -24
- package/src/lib/haptics.ts +10 -10
- package/src/lib/language.ts +9 -9
- package/src/lib/range-value.ts +27 -21
- package/src/lib/screen-edges.ts +33 -30
- package/src/lib/shapes.ts +38 -34
- package/src/machines/popover.machine.ts +51 -0
- package/src/motion/transitions.ts +46 -43
- package/src/styles/code-block.css +69 -43
- package/src/styles/motion-controls.css +393 -191
- package/src/tokens.ts +1 -1
- package/dist/types/src/components/popover/popover-fit.d.ts +0 -21
- package/dist/types/src/components/popover/popover.d.ts +0 -21
- package/src/components/popover/popover-fit.ts +0 -69
- package/src/components/popover/popover.tsx +0 -334
|
@@ -1,116 +1,116 @@
|
|
|
1
|
-
import { IconButton } from "../button/icon-button"
|
|
2
|
-
import { SendIcon } from "../../icons/icons"
|
|
3
|
-
import type { ReactNode } from "react"
|
|
4
|
-
import { useState } from "react"
|
|
5
|
-
import { formatTime } from "../../lib/date"
|
|
6
|
-
import { cn } from "../../lib/class-names"
|
|
1
|
+
import { IconButton } from "../button/icon-button";
|
|
2
|
+
import { SendIcon } from "../../icons/icons";
|
|
3
|
+
import type { ReactNode } from "react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { formatTime } from "../../lib/date";
|
|
6
|
+
import { cn } from "../../lib/class-names";
|
|
7
7
|
|
|
8
8
|
export type ConversationMessage = {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
9
|
+
id: string;
|
|
10
|
+
body: string;
|
|
11
|
+
/** Written by whoever is looking: drawn on the right. */
|
|
12
|
+
mine: boolean;
|
|
13
|
+
author?: string;
|
|
14
|
+
/** Shown beside the other side's messages, like their avatar. */
|
|
15
|
+
avatar?: ReactNode;
|
|
16
|
+
createdAt: string;
|
|
17
|
+
};
|
|
18
18
|
|
|
19
|
-
export type ConversationSize = "sm" | "md" | "lg"
|
|
19
|
+
export type ConversationSize = "sm" | "md" | "lg";
|
|
20
20
|
|
|
21
21
|
const avatarSizes: Record<ConversationSize, string> = {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
22
|
+
sm: "size-6",
|
|
23
|
+
md: "size-7",
|
|
24
|
+
lg: "size-8",
|
|
25
|
+
};
|
|
26
26
|
const bubbleClasses: Record<ConversationSize, string> = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
27
|
+
sm: "rounded-3xl px-3 py-1.5 text-footnote leading-snug",
|
|
28
|
+
md: "rounded-4xl px-3.5 py-2 text-subheadline leading-snug",
|
|
29
|
+
lg: "rounded-5xl px-4 py-2.5 text-body leading-snug",
|
|
30
|
+
};
|
|
31
31
|
const bubbleTails: Record<ConversationSize, string> = {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
32
|
+
sm: "rounded-br-sm",
|
|
33
|
+
md: "rounded-br-md",
|
|
34
|
+
lg: "rounded-br-lg",
|
|
35
|
+
};
|
|
36
36
|
const bubbleTailsOther: Record<ConversationSize, string> = {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
37
|
+
sm: "rounded-bl-sm",
|
|
38
|
+
md: "rounded-bl-md",
|
|
39
|
+
lg: "rounded-bl-lg",
|
|
40
|
+
};
|
|
41
41
|
const authorText: Record<ConversationSize, string> = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
42
|
+
sm: "px-3 text-caption2 text-label-secondary",
|
|
43
|
+
md: "px-3 text-caption2 text-label-secondary",
|
|
44
|
+
lg: "px-3.5 text-caption text-label-secondary",
|
|
45
|
+
};
|
|
46
46
|
const timeText: Record<ConversationSize, string> = {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
47
|
+
sm: "px-3 text-caption2 text-label-secondary tabular-nums",
|
|
48
|
+
md: "px-3 text-caption2 text-label-secondary tabular-nums",
|
|
49
|
+
lg: "px-3.5 text-caption text-label-secondary tabular-nums",
|
|
50
|
+
};
|
|
51
51
|
const dayMargins: Record<ConversationSize, string> = {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
52
|
+
sm: "my-3",
|
|
53
|
+
md: "my-4",
|
|
54
|
+
lg: "my-5",
|
|
55
|
+
};
|
|
56
56
|
|
|
57
57
|
// Composer size records
|
|
58
58
|
const composerSurfaces: Record<ConversationSize, string> = {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
59
|
+
sm: "flex items-end gap-2 rounded-4xl border border-separator bg-elevated py-1 pr-1 pl-3 transition-colors focus-within:border-accent-text/60",
|
|
60
|
+
md: "flex items-end gap-2 rounded-5xl border border-separator bg-elevated py-1.5 pr-1.5 pl-4 transition-colors focus-within:border-accent-text/60",
|
|
61
|
+
lg: "flex items-end gap-2 rounded-5xl border border-separator bg-elevated py-2 pr-2 pl-5 transition-colors focus-within:border-accent-text/60",
|
|
62
|
+
};
|
|
63
63
|
const textareaClasses: Record<ConversationSize, string> = {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
}
|
|
64
|
+
sm: "field-sizing-content max-h-48 min-h-8 flex-1 resize-none bg-transparent py-1 text-footnote text-label outline-none placeholder:text-label-secondary",
|
|
65
|
+
md: "field-sizing-content max-h-48 min-h-9 flex-1 resize-none bg-transparent py-1.5 text-subheadline text-label outline-none placeholder:text-label-secondary",
|
|
66
|
+
lg: "field-sizing-content max-h-48 min-h-10 flex-1 resize-none bg-transparent py-2 text-body text-label outline-none placeholder:text-label-secondary",
|
|
67
|
+
};
|
|
68
68
|
const sendButtonClasses: Record<ConversationSize, string> = {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
69
|
+
sm: "size-7 shrink-0 rounded-full",
|
|
70
|
+
md: "size-8 shrink-0 rounded-full",
|
|
71
|
+
lg: "size-9 shrink-0 rounded-full",
|
|
72
|
+
};
|
|
73
73
|
const sendIconSizes: Record<ConversationSize, number> = {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
74
|
+
sm: 16,
|
|
75
|
+
md: 18,
|
|
76
|
+
lg: 20,
|
|
77
|
+
};
|
|
78
78
|
const counterText: Record<ConversationSize, string> = {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
79
|
+
sm: "text-caption2 text-label-secondary tabular-nums",
|
|
80
|
+
md: "text-caption2 text-label-secondary tabular-nums",
|
|
81
|
+
lg: "text-caption text-label-secondary tabular-nums",
|
|
82
|
+
};
|
|
83
83
|
|
|
84
|
-
const GROUP_GAP_MS = 5 * 60 * 1000
|
|
84
|
+
const GROUP_GAP_MS = 5 * 60 * 1000;
|
|
85
85
|
|
|
86
86
|
function dayLabel(iso: string, locale?: string) {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
87
|
+
const date = new Date(iso);
|
|
88
|
+
const today = new Date();
|
|
89
|
+
const yesterday = new Date();
|
|
90
|
+
yesterday.setDate(today.getDate() - 1);
|
|
91
|
+
const offset =
|
|
92
|
+
date.toDateString() === today.toDateString()
|
|
93
|
+
? 0
|
|
94
|
+
: date.toDateString() === yesterday.toDateString()
|
|
95
|
+
? -1
|
|
96
|
+
: null;
|
|
97
|
+
if (offset === null)
|
|
98
|
+
return date.toLocaleDateString(locale, {
|
|
99
|
+
weekday: "long",
|
|
100
|
+
month: "long",
|
|
101
|
+
day: "numeric",
|
|
102
|
+
});
|
|
103
|
+
const word = new Intl.RelativeTimeFormat(locale, {
|
|
104
|
+
numeric: "auto",
|
|
105
|
+
}).format(offset, "day");
|
|
106
|
+
return word.charAt(0).toLocaleUpperCase(locale) + word.slice(1);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
const clock = (iso: string, locale?: string) =>
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
new Date(iso).toLocaleTimeString(locale, {
|
|
111
|
+
hour: "numeric",
|
|
112
|
+
minute: "2-digit",
|
|
113
|
+
});
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
116
|
* A thread of plain-text messages like Messages on iPhone: a day line when
|
|
@@ -118,190 +118,192 @@ const clock = (iso: string, locale?: string) =>
|
|
|
118
118
|
* name, theirs on the left with an avatar, yours on the right.
|
|
119
119
|
*/
|
|
120
120
|
function Messages({
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
121
|
+
messages,
|
|
122
|
+
locale,
|
|
123
|
+
size = "md",
|
|
124
124
|
}: {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
125
|
+
messages: ConversationMessage[];
|
|
126
|
+
/** For day names and times; the reader's own by default. */
|
|
127
|
+
locale?: string;
|
|
128
|
+
/** Bubble, avatar, author and time scale. @default "md" */
|
|
129
|
+
size?: ConversationSize;
|
|
130
130
|
}) {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
131
|
+
return (
|
|
132
|
+
<ol data-not-typeset className="flex flex-col">
|
|
133
|
+
{messages.map((message, index) => {
|
|
134
|
+
const prev = messages[index - 1];
|
|
135
|
+
const next = messages[index + 1];
|
|
136
|
+
const newDay =
|
|
137
|
+
!prev ||
|
|
138
|
+
new Date(prev.createdAt).toDateString() !==
|
|
139
|
+
new Date(message.createdAt).toDateString();
|
|
140
|
+
const startsGroup =
|
|
141
|
+
newDay ||
|
|
142
|
+
prev.mine !== message.mine ||
|
|
143
|
+
new Date(message.createdAt).getTime() -
|
|
144
|
+
new Date(prev.createdAt).getTime() >
|
|
145
|
+
GROUP_GAP_MS;
|
|
146
|
+
const endsGroup =
|
|
147
|
+
!next ||
|
|
148
|
+
next.mine !== message.mine ||
|
|
149
|
+
new Date(next.createdAt).getTime() -
|
|
150
|
+
new Date(message.createdAt).getTime() >
|
|
151
|
+
GROUP_GAP_MS;
|
|
150
152
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
153
|
+
return (
|
|
154
|
+
<li key={message.id} className="flex flex-col">
|
|
155
|
+
{newDay && (
|
|
156
|
+
<span
|
|
157
|
+
suppressHydrationWarning
|
|
158
|
+
className={cn(
|
|
159
|
+
dayMargins[size],
|
|
160
|
+
"text-caption2 text-label-secondary text-center font-medium",
|
|
161
|
+
)}
|
|
162
|
+
>
|
|
163
|
+
{dayLabel(message.createdAt, locale)}
|
|
164
|
+
</span>
|
|
165
|
+
)}
|
|
166
|
+
<div
|
|
167
|
+
className={cn(
|
|
168
|
+
"flex items-end gap-2",
|
|
169
|
+
message.mine ? "justify-end" : "justify-start",
|
|
170
|
+
startsGroup ? "mt-3" : "mt-0.5",
|
|
171
|
+
)}
|
|
172
|
+
>
|
|
173
|
+
{!message.mine && (
|
|
174
|
+
<span
|
|
175
|
+
className={cn(
|
|
176
|
+
"flex shrink-0 items-center justify-center",
|
|
177
|
+
avatarSizes[size],
|
|
178
|
+
)}
|
|
179
|
+
>
|
|
180
|
+
{endsGroup && message.avatar}
|
|
181
|
+
</span>
|
|
182
|
+
)}
|
|
183
|
+
<div
|
|
184
|
+
className={cn(
|
|
185
|
+
"flex max-w-[78%] flex-col gap-1",
|
|
186
|
+
message.mine ? "items-end" : "items-start",
|
|
187
|
+
)}
|
|
188
|
+
>
|
|
189
|
+
{startsGroup && message.author && (
|
|
190
|
+
<span className={authorText[size]}>{message.author}</span>
|
|
191
|
+
)}
|
|
192
|
+
<p
|
|
193
|
+
suppressHydrationWarning
|
|
194
|
+
title={formatTime(message.createdAt)}
|
|
195
|
+
className={cn(
|
|
196
|
+
bubbleClasses[size],
|
|
197
|
+
"wrap-break-word whitespace-pre-wrap",
|
|
198
|
+
message.mine
|
|
199
|
+
? "bg-inverse text-on-inverse"
|
|
200
|
+
: "bg-fill text-label",
|
|
201
|
+
endsGroup &&
|
|
202
|
+
(message.mine
|
|
203
|
+
? bubbleTails[size]
|
|
204
|
+
: bubbleTailsOther[size]),
|
|
205
|
+
)}
|
|
206
|
+
>
|
|
207
|
+
{message.body}
|
|
208
|
+
</p>
|
|
209
|
+
{endsGroup && (
|
|
210
|
+
<time
|
|
211
|
+
suppressHydrationWarning
|
|
212
|
+
dateTime={message.createdAt}
|
|
213
|
+
className={timeText[size]}
|
|
214
|
+
>
|
|
215
|
+
{clock(message.createdAt, locale)}
|
|
216
|
+
</time>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
</li>
|
|
221
|
+
);
|
|
222
|
+
})}
|
|
223
|
+
</ol>
|
|
224
|
+
);
|
|
223
225
|
}
|
|
224
226
|
|
|
225
227
|
type ComposerProps = {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
228
|
+
onSend: (body: string) => Promise<unknown>;
|
|
229
|
+
pending?: boolean;
|
|
230
|
+
maxLength: number;
|
|
231
|
+
placeholder: string;
|
|
232
|
+
/** Extra controls under the field, like a status picker. */
|
|
233
|
+
actions?: ReactNode;
|
|
234
|
+
/** Names the round Send button for screen readers. */
|
|
235
|
+
sendLabel: string;
|
|
236
|
+
/**
|
|
237
|
+
* Words for the characters left, shown near the limit, such as
|
|
238
|
+
* (count) => `${count} left`. Without it, just the number shows.
|
|
239
|
+
*/
|
|
240
|
+
remaining?: (count: number) => string;
|
|
241
|
+
/** Surface, textarea, send button and counter scale. @default "md" */
|
|
242
|
+
size?: ConversationSize;
|
|
243
|
+
};
|
|
242
244
|
|
|
243
245
|
/** A reply field with a round send button; Ctrl/Cmd+Enter sends, and it clears only once the send worked. */
|
|
244
246
|
function Composer({
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
247
|
+
onSend,
|
|
248
|
+
pending = false,
|
|
249
|
+
maxLength,
|
|
250
|
+
placeholder,
|
|
251
|
+
actions,
|
|
252
|
+
sendLabel,
|
|
253
|
+
remaining,
|
|
254
|
+
size = "md",
|
|
253
255
|
}: ComposerProps) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
256
|
+
const [body, setBody] = useState("");
|
|
257
|
+
const ready = body.trim().length > 0 && !pending;
|
|
258
|
+
const send = async () => {
|
|
259
|
+
if (!ready) return;
|
|
260
|
+
await onSend(body.trim());
|
|
261
|
+
setBody("");
|
|
262
|
+
};
|
|
261
263
|
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
264
|
+
return (
|
|
265
|
+
<form
|
|
266
|
+
data-not-typeset
|
|
267
|
+
className="flex flex-col gap-2"
|
|
268
|
+
onSubmit={(event) => {
|
|
269
|
+
event.preventDefault();
|
|
270
|
+
void send();
|
|
271
|
+
}}
|
|
272
|
+
>
|
|
273
|
+
<div className={composerSurfaces[size]}>
|
|
274
|
+
<textarea
|
|
275
|
+
value={body}
|
|
276
|
+
onChange={(event) => setBody(event.target.value)}
|
|
277
|
+
onKeyDown={(event) => {
|
|
278
|
+
if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
|
|
279
|
+
event.preventDefault();
|
|
280
|
+
void send();
|
|
281
|
+
}
|
|
282
|
+
}}
|
|
283
|
+
maxLength={maxLength}
|
|
284
|
+
placeholder={placeholder}
|
|
285
|
+
rows={1}
|
|
286
|
+
className={textareaClasses[size]}
|
|
287
|
+
/>
|
|
288
|
+
<IconButton
|
|
289
|
+
type="submit"
|
|
290
|
+
size="icon-xs"
|
|
291
|
+
disabled={!ready}
|
|
292
|
+
label={sendLabel}
|
|
293
|
+
className={sendButtonClasses[size]}
|
|
294
|
+
>
|
|
295
|
+
<SendIcon size={sendIconSizes[size]} strokeWidth={2.5} />
|
|
296
|
+
</IconButton>
|
|
297
|
+
</div>
|
|
298
|
+
<div className="flex min-h-6 items-center gap-2 px-1">
|
|
299
|
+
{actions}
|
|
300
|
+
<span className={cn("ml-auto", counterText[size])}>
|
|
301
|
+
{body.length > maxLength * 0.8 &&
|
|
302
|
+
(remaining?.(maxLength - body.length) ?? maxLength - body.length)}
|
|
303
|
+
</span>
|
|
304
|
+
</div>
|
|
305
|
+
</form>
|
|
306
|
+
);
|
|
305
307
|
}
|
|
306
308
|
|
|
307
|
-
export const Conversation = { Messages, Composer }
|
|
309
|
+
export const Conversation = { Messages, Composer };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./conversation"
|
|
1
|
+
export * from "./conversation";
|