@passui/react 0.1.45 → 0.1.46

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