@passui/react 0.1.39 → 0.1.41

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 (27) hide show
  1. package/dist/types/src/components/code-block/code-block.d.ts +5 -2
  2. package/dist/types/src/components/content-view/content-view.d.ts +5 -2
  3. package/dist/types/src/components/context-menu/context-menu-store.d.ts +7 -2
  4. package/dist/types/src/components/conversation/conversation.d.ts +7 -2
  5. package/dist/types/src/components/one-time-code/one-time-code.d.ts +4 -1
  6. package/dist/types/src/components/popover/popover.d.ts +3 -0
  7. package/dist/types/src/components/search-list/search-list.d.ts +4 -1
  8. package/dist/types/src/components/sidebar/sidebar.d.ts +8 -3
  9. package/dist/types/src/components/slosh-slider/slosh-slider.d.ts +4 -1
  10. package/dist/types/src/components/toast/toast-store.d.ts +5 -0
  11. package/dist/types/src/components/tooltip/tooltip-host.d.ts +1 -0
  12. package/package.json +1 -1
  13. package/src/components/code-block/code-block.tsx +7 -1
  14. package/src/components/content-view/content-view.tsx +60 -8
  15. package/src/components/context-menu/context-menu-host.tsx +44 -15
  16. package/src/components/context-menu/context-menu-store.ts +10 -1
  17. package/src/components/conversation/conversation.tsx +93 -19
  18. package/src/components/one-time-code/one-time-code.tsx +22 -2
  19. package/src/components/popover/popover.tsx +15 -2
  20. package/src/components/search-list/search-list.tsx +27 -9
  21. package/src/components/sidebar/sidebar.tsx +38 -11
  22. package/src/components/slosh-slider/slosh-slider.tsx +6 -0
  23. package/src/components/toast/toast-store.ts +7 -0
  24. package/src/components/toast/toaster.tsx +56 -23
  25. package/src/components/tooltip/tooltip-host.tsx +14 -1
  26. package/src/styles/code-block.css +12 -0
  27. package/src/styles/motion-controls.css +8 -0
@@ -7,12 +7,39 @@ import {
7
7
  getToasts,
8
8
  subscribeToasts,
9
9
  type Toast,
10
+ type ToastSize,
10
11
  type ToastTone,
11
12
  toast as toastApi,
12
13
  toastLimits,
13
14
  } from "./toast-store"
14
15
 
15
- const PILL = 40
16
+ const PILL: Record<ToastSize, number> = { sm: 32, md: 40, lg: 48 }
17
+
18
+ const contentClasses: Record<ToastSize, string> = {
19
+ sm: "absolute top-0 left-0 flex w-max max-w-[min(420px,calc(100vw-24px))] items-start gap-2 py-2 pr-3 pl-2 text-on-inverse",
20
+ md: "absolute top-0 left-0 flex w-max max-w-[min(420px,calc(100vw-24px))] items-start gap-2.5 py-[10px] pr-4 pl-[10px] text-on-inverse",
21
+ lg: "absolute top-0 left-0 flex w-max max-w-[min(420px,calc(100vw-24px))] items-start gap-3 py-3 pr-5 pl-3 text-on-inverse",
22
+ }
23
+ const titleClasses: Record<ToastSize, string> = {
24
+ sm: "text-footnote leading-4 font-semibold",
25
+ md: "text-subheadline leading-5 font-semibold",
26
+ lg: "text-body leading-6 font-semibold",
27
+ }
28
+ const descriptionClasses: Record<ToastSize, string> = {
29
+ sm: "text-caption2 leading-snug text-on-inverse/70",
30
+ md: "text-footnote leading-snug text-on-inverse/70",
31
+ lg: "text-subheadline leading-snug text-on-inverse/70",
32
+ }
33
+ const actionClasses: Record<ToastSize, string> = {
34
+ sm: "ml-1 shrink-0 self-center rounded-full bg-on-inverse/12 px-2.5 py-0.5 text-caption2 font-semibold text-on-inverse transition-colors hover:bg-on-inverse/20",
35
+ md: "ml-1 shrink-0 self-center rounded-full bg-on-inverse/12 px-3 py-1 text-footnote font-semibold text-on-inverse transition-colors hover:bg-on-inverse/20",
36
+ lg: "ml-1 shrink-0 self-center rounded-full bg-on-inverse/12 px-3.5 py-1.5 text-subheadline font-semibold text-on-inverse transition-colors hover:bg-on-inverse/20",
37
+ }
38
+ const toneIconClasses: Record<ToastSize, string> = {
39
+ sm: "flex size-4 shrink-0 items-center justify-center rounded-full",
40
+ md: "flex size-5 shrink-0 items-center justify-center rounded-full",
41
+ lg: "flex size-6 shrink-0 items-center justify-center rounded-full",
42
+ }
16
43
  const EMPTY: Toast[] = []
17
44
  /** A little loose, so the body overshoots and settles like a drop of honey. */
18
45
  const BODY_SPRING = {
@@ -110,10 +137,11 @@ function ToastItem({
110
137
  filterId: string
111
138
  reducedMotion: boolean
112
139
  }) {
140
+ const pill = PILL[toast.size]
113
141
  const content = useRef<HTMLDivElement>(null)
114
142
  const [size, setSize] = useState({
115
- width: PILL,
116
- height: PILL,
143
+ width: pill,
144
+ height: pill,
117
145
  })
118
146
  const [open, setOpen] = useState(false)
119
147
 
@@ -143,8 +171,8 @@ function ToastItem({
143
171
  return () => clearTimeout(timer)
144
172
  }, [toast.id, toast.duration, paused])
145
173
 
146
- const width = open ? size.width : PILL
147
- const height = open ? size.height : PILL
174
+ const width = open ? size.width : pill
175
+ const height = open ? size.height : pill
148
176
  // Older toasts tuck in behind the newest, and fan out on hover.
149
177
  const stacked = !expanded && index > 0
150
178
 
@@ -156,7 +184,7 @@ function ToastItem({
156
184
  initial={reducedMotion ? false : { opacity: 0, y: -24, scale: 0.6 }}
157
185
  animate={{
158
186
  opacity: stacked ? 1 - index * 0.25 : 1,
159
- y: stacked ? index * 9 : index * (PILL + 14) + index * 6,
187
+ y: stacked ? index * 9 : index * (pill + 14) + index * 6,
160
188
  scale: stacked ? 1 - index * 0.05 : 1,
161
189
  }}
162
190
  exit={
@@ -180,7 +208,7 @@ function ToastItem({
180
208
  <motion.div
181
209
  role="status"
182
210
  className="pointer-events-auto relative"
183
- initial={{ width: PILL, height: PILL }}
211
+ initial={{ width: pill, height: pill }}
184
212
  animate={{ width, height }}
185
213
  transition={reducedMotion ? { duration: 0 } : BODY_SPRING}
186
214
  >
@@ -194,21 +222,21 @@ function ToastItem({
194
222
  >
195
223
  <motion.span
196
224
  className="absolute top-3 left-3 rounded-full bg-inverse shadow-xl"
197
- style={{ width: PILL, height: PILL }}
225
+ style={{ width: pill, height: pill }}
198
226
  animate={{ scale: open ? 1 : 1.06 }}
199
227
  transition={reducedMotion ? { duration: 0 } : spring.bouncy}
200
228
  />
201
229
  <motion.span
202
230
  className="absolute top-3 left-3 bg-inverse"
203
231
  initial={{
204
- width: PILL,
205
- height: PILL,
206
- borderRadius: PILL / 2,
232
+ width: pill,
233
+ height: pill,
234
+ borderRadius: pill / 2,
207
235
  }}
208
236
  animate={{
209
237
  width,
210
238
  height,
211
- borderRadius: Math.min(PILL / 2, height / 2, 22),
239
+ borderRadius: Math.min(pill / 2, height / 2, 22),
212
240
  }}
213
241
  transition={reducedMotion ? { duration: 0 } : BODY_SPRING}
214
242
  />
@@ -217,8 +245,8 @@ function ToastItem({
217
245
  {/* Content, crisp above the shapes. */}
218
246
  <motion.div
219
247
  ref={content}
220
- className="absolute top-0 left-0 flex w-max max-w-[min(420px,calc(100vw-24px))] items-start gap-2.5 py-[10px] pr-4 pl-[10px] text-on-inverse"
221
- style={{ minHeight: PILL }}
248
+ className={contentClasses[toast.size]}
249
+ style={{ minHeight: pill }}
222
250
  animate={{ opacity: open ? 1 : 0 }}
223
251
  transition={
224
252
  reducedMotion
@@ -229,12 +257,12 @@ function ToastItem({
229
257
  }
230
258
  }
231
259
  >
232
- <ToneIcon tone={toast.tone} reducedMotion={reducedMotion} />
260
+ <ToneIcon tone={toast.tone} size={toast.size} reducedMotion={reducedMotion} />
233
261
  <div className="flex min-w-0 flex-col py-px">
234
262
  <AnimatePresence mode="popLayout" initial={false}>
235
263
  <motion.span
236
264
  key={toast.title}
237
- className="text-subheadline leading-5 font-semibold"
265
+ className={titleClasses[toast.size]}
238
266
  initial={
239
267
  reducedMotion
240
268
  ? false
@@ -264,7 +292,7 @@ function ToastItem({
264
292
  </motion.span>
265
293
  </AnimatePresence>
266
294
  {toast.description && (
267
- <span className="text-footnote leading-snug text-on-inverse/70">
295
+ <span className={descriptionClasses[toast.size]}>
268
296
  {toast.description}
269
297
  </span>
270
298
  )}
@@ -276,7 +304,7 @@ function ToastItem({
276
304
  toast.action?.onClick()
277
305
  toastApi.dismiss(toast.id)
278
306
  }}
279
- className="ml-1 shrink-0 self-center rounded-full bg-on-inverse/12 px-3 py-1 text-footnote font-semibold text-on-inverse transition-colors hover:bg-on-inverse/20"
307
+ className={actionClasses[toast.size]}
280
308
  >
281
309
  {toast.action.label}
282
310
  </button>
@@ -294,15 +322,20 @@ const toneColors: Record<ToastTone, string> = {
294
322
  loading: "bg-on-inverse/15 text-on-inverse",
295
323
  }
296
324
 
297
- function ToneIcon({ tone, reducedMotion }: { tone: ToastTone; reducedMotion: boolean }) {
325
+ function ToneIcon({
326
+ tone,
327
+ size,
328
+ reducedMotion,
329
+ }: {
330
+ tone: ToastTone
331
+ size: ToastSize
332
+ reducedMotion: boolean
333
+ }) {
298
334
  return (
299
335
  <AnimatePresence mode="popLayout" initial={false}>
300
336
  <motion.span
301
337
  key={tone}
302
- className={cn(
303
- "flex size-5 shrink-0 items-center justify-center rounded-full",
304
- toneColors[tone]
305
- )}
338
+ className={cn(toneIconClasses[size], toneColors[tone])}
306
339
  initial={
307
340
  reducedMotion
308
341
  ? false
@@ -1,6 +1,15 @@
1
1
  import { AnimatePresence, motion, useReducedMotion } from "motion/react"
2
2
  import { spring } from "../../motion/transitions"
3
3
  import { useEffect, useId, useState } from "react"
4
+ import { cn } from "../../lib/class-names"
5
+
6
+ export type TooltipSize = "sm" | "md" | "lg"
7
+
8
+ const tooltipClasses: Record<TooltipSize, string> = {
9
+ sm: "pointer-events-none fixed z-[80] max-w-[200px] rounded-md bg-inverse px-2 py-1 text-center text-caption2 leading-tight font-medium text-on-inverse shadow-lg",
10
+ md: "pointer-events-none fixed z-[80] max-w-[240px] rounded-md bg-inverse px-2.5 py-1.5 text-center text-caption leading-tight font-medium text-on-inverse shadow-lg",
11
+ lg: "pointer-events-none fixed z-[80] max-w-[280px] rounded-md bg-inverse px-3 py-2 text-center text-footnote leading-tight font-medium text-on-inverse shadow-lg",
12
+ }
4
13
 
5
14
  interface Tip {
6
15
  text: string
@@ -8,6 +17,7 @@ interface Tip {
8
17
  y: number
9
18
  /** Shown above the element when there's no room below. */
10
19
  above: boolean
20
+ size: TooltipSize
11
21
  }
12
22
 
13
23
  const DELAY = 450
@@ -45,11 +55,14 @@ export function TooltipHost() {
45
55
  if (!text) return
46
56
  const rect = element.getBoundingClientRect()
47
57
  const above = rect.bottom + 40 > window.innerHeight
58
+ const sizeAttr = element.getAttribute("data-tooltip-size")
59
+ const size: TooltipSize = sizeAttr === "sm" || sizeAttr === "lg" ? sizeAttr : "md"
48
60
  setTip({
49
61
  text,
50
62
  x: rect.left + rect.width / 2,
51
63
  y: above ? rect.top - MARGIN : rect.bottom + MARGIN,
52
64
  above,
65
+ size,
53
66
  })
54
67
  lastShown = Date.now()
55
68
  }
@@ -121,7 +134,7 @@ export function TooltipHost() {
121
134
  id={tooltipId}
122
135
  key={tip.text + tip.x}
123
136
  role="tooltip"
124
- className="pointer-events-none fixed z-[80] max-w-[240px] rounded-md bg-inverse px-2.5 py-1.5 text-center text-caption leading-tight font-medium text-on-inverse shadow-lg"
137
+ className={cn(tooltipClasses[tip.size])}
125
138
  style={{
126
139
  left: Math.min(Math.max(tip.x, 128), window.innerWidth - 128),
127
140
  top: tip.y,
@@ -24,6 +24,18 @@
24
24
  user-select: none;
25
25
  }
26
26
  .passui-code-block:not([data-line-numbers]) pre { padding-inline-start: 1rem; }
27
+ .passui-code-block[data-size="sm"] pre {
28
+ padding-block: 0.75rem;
29
+ font-size: var(--text-caption);
30
+ line-height: 1.6;
31
+ }
32
+ .passui-code-block[data-size="sm"][data-line-numbers] .line::before { width: 2.5rem; margin-inline-end: 1rem; }
33
+ .passui-code-block[data-size="lg"] pre {
34
+ padding-block: 1.25rem;
35
+ font-size: var(--text-subheadline);
36
+ line-height: 1.8;
37
+ }
38
+ .passui-code-block[data-size="lg"][data-line-numbers] .line::before { width: 3.5rem; margin-inline-end: 1.5rem; }
27
39
  .passui-code-block .shiki, .passui-code-block .shiki span { color: var(--shiki-light); background: transparent !important; }
28
40
  @media (prefers-color-scheme: dark) {
29
41
  .passui-code-block .shiki, .passui-code-block .shiki span { color: var(--shiki-dark); }
@@ -80,6 +80,10 @@
80
80
  .passui-one-time-code:is([data-status="verifying"], [data-status="success"]) .passui-otp-capsule { opacity: 1; transform: translateX(-50%) scaleX(1); }
81
81
  .passui-one-time-code:is([data-status="verifying"], [data-status="success"]) .passui-otp-digits { opacity: 0; }
82
82
  .passui-otp-check { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); color: var(--color-on-accent); opacity: 0; pointer-events: none; }
83
+ .passui-one-time-code[data-size="sm"] .passui-otp-row { height: 2.5rem; }
84
+ .passui-one-time-code[data-size="sm"] .passui-otp-digit { font-size: var(--text-title); }
85
+ .passui-one-time-code[data-size="lg"] .passui-otp-row { height: 3.5rem; }
86
+ .passui-one-time-code[data-size="lg"] .passui-otp-digit { font-size: var(--text-title2); }
83
87
  .passui-one-time-code[data-status="success"] .passui-otp-check { opacity: 1; transition: opacity 160ms 240ms; }
84
88
  /* A wrong code shivers, then drains right to left. Selecting the single
85
89
  input afterward lets the next keystroke replace it without a trap. */
@@ -129,6 +133,10 @@
129
133
  calc(100% - var(--slosh-lean, 0px)) 100%, 0 100%);
130
134
  }
131
135
  .passui-slosh-track:dir(rtl) .passui-slosh-fill { transform: scaleX(-1); }
136
+ .passui-slosh-slider[data-size="sm"] .passui-slosh-track { height: 2rem; }
137
+ .passui-slosh-slider[data-size="sm"] .passui-slosh-knob { top: 4px; bottom: 4px; }
138
+ .passui-slosh-slider[data-size="lg"] .passui-slosh-track { height: 3rem; }
139
+ .passui-slosh-slider[data-size="lg"] .passui-slosh-knob { top: 8px; bottom: 8px; width: 4px; margin-inline-start: -2px; }
132
140
  .passui-slosh-knob {
133
141
  position: absolute;
134
142
  top: 6px;