@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
@@ -16,12 +16,73 @@ export type ConversationMessage = {
16
16
  createdAt: string
17
17
  }
18
18
 
19
+ export type ConversationSize = "sm" | "md" | "lg"
20
+
21
+ const avatarSizes: Record<ConversationSize, string> = {
22
+ sm: "size-6",
23
+ md: "size-7",
24
+ lg: "size-8",
25
+ }
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
+ }
31
+ const bubbleTails: Record<ConversationSize, string> = {
32
+ sm: "rounded-br-sm",
33
+ md: "rounded-br-md",
34
+ lg: "rounded-br-lg",
35
+ }
36
+ const bubbleTailsOther: Record<ConversationSize, string> = {
37
+ sm: "rounded-bl-sm",
38
+ md: "rounded-bl-md",
39
+ lg: "rounded-bl-lg",
40
+ }
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
+ }
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
+ }
51
+ const dayMargins: Record<ConversationSize, string> = {
52
+ sm: "my-3",
53
+ md: "my-4",
54
+ lg: "my-5",
55
+ }
56
+
57
+ // Composer size records
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
+ }
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
+ }
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
+ }
73
+ const sendIconSizes: Record<ConversationSize, number> = {
74
+ sm: 16,
75
+ md: 18,
76
+ lg: 20,
77
+ }
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
+ }
83
+
19
84
  const GROUP_GAP_MS = 5 * 60 * 1000
20
85
 
21
- /**
22
- * The day line: today and yesterday in the reader's own words, from
23
- * Intl rather than built-in English, else the full date.
24
- */
25
86
  function dayLabel(iso: string, locale?: string) {
26
87
  const date = new Date(iso)
27
88
  const today = new Date()
@@ -59,10 +120,13 @@ const clock = (iso: string, locale?: string) =>
59
120
  function Messages({
60
121
  messages,
61
122
  locale,
123
+ size = "md",
62
124
  }: {
63
125
  messages: ConversationMessage[]
64
126
  /** For day names and times; the reader's own by default. */
65
127
  locale?: string
128
+ /** Bubble, avatar, author and time scale. @default "md" */
129
+ size?: ConversationSize
66
130
  }) {
67
131
  return (
68
132
  <ol data-not-typeset className="flex flex-col">
@@ -87,10 +151,12 @@ function Messages({
87
151
  return (
88
152
  <li key={message.id} className="flex flex-col">
89
153
  {newDay && (
90
- // the reader's own day and clock, which a server can't know
91
154
  <span
92
155
  suppressHydrationWarning
93
- className="my-4 text-center text-caption2 font-medium text-label-secondary"
156
+ className={cn(
157
+ dayMargins[size],
158
+ "text-center text-caption2 font-medium text-label-secondary"
159
+ )}
94
160
  >
95
161
  {dayLabel(message.createdAt, locale)}
96
162
  </span>
@@ -103,7 +169,12 @@ function Messages({
103
169
  )}
104
170
  >
105
171
  {!message.mine && (
106
- <span className="flex size-7 shrink-0 items-center justify-center">
172
+ <span
173
+ className={cn(
174
+ "flex shrink-0 items-center justify-center",
175
+ avatarSizes[size]
176
+ )}
177
+ >
107
178
  {endsGroup && message.avatar}
108
179
  </span>
109
180
  )}
@@ -114,21 +185,21 @@ function Messages({
114
185
  )}
115
186
  >
116
187
  {startsGroup && message.author && (
117
- <span className="px-3 text-caption2 text-label-secondary">
118
- {message.author}
119
- </span>
188
+ <span className={authorText[size]}>{message.author}</span>
120
189
  )}
121
- {/* text only: a message can never become markup */}
122
190
  <p
123
191
  suppressHydrationWarning
124
192
  title={formatTime(message.createdAt)}
125
193
  className={cn(
126
- "rounded-4xl px-3.5 py-2 text-subheadline leading-snug whitespace-pre-wrap wrap-break-word",
194
+ bubbleClasses[size],
195
+ "whitespace-pre-wrap wrap-break-word",
127
196
  message.mine
128
197
  ? "bg-inverse text-on-inverse"
129
198
  : "bg-fill text-label",
130
199
  endsGroup &&
131
- (message.mine ? "rounded-br-md" : "rounded-bl-md")
200
+ (message.mine
201
+ ? bubbleTails[size]
202
+ : bubbleTailsOther[size])
132
203
  )}
133
204
  >
134
205
  {message.body}
@@ -137,7 +208,7 @@ function Messages({
137
208
  <time
138
209
  suppressHydrationWarning
139
210
  dateTime={message.createdAt}
140
- className="px-3 text-caption2 text-label-secondary tabular-nums"
211
+ className={timeText[size]}
141
212
  >
142
213
  {clock(message.createdAt, locale)}
143
214
  </time>
@@ -165,6 +236,8 @@ type ComposerProps = {
165
236
  * (count) => `${count} left`. Without it, just the number shows.
166
237
  */
167
238
  remaining?: (count: number) => string
239
+ /** Surface, textarea, send button and counter scale. @default "md" */
240
+ size?: ConversationSize
168
241
  }
169
242
 
170
243
  /** A reply field with a round send button; Ctrl/Cmd+Enter sends, and it clears only once the send worked. */
@@ -176,6 +249,7 @@ function Composer({
176
249
  actions,
177
250
  sendLabel,
178
251
  remaining,
252
+ size = "md",
179
253
  }: ComposerProps) {
180
254
  const [body, setBody] = useState("")
181
255
  const ready = body.trim().length > 0 && !pending
@@ -194,7 +268,7 @@ function Composer({
194
268
  void send()
195
269
  }}
196
270
  >
197
- <div className="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">
271
+ <div className={composerSurfaces[size]}>
198
272
  <textarea
199
273
  value={body}
200
274
  onChange={(event) => setBody(event.target.value)}
@@ -207,21 +281,21 @@ function Composer({
207
281
  maxLength={maxLength}
208
282
  placeholder={placeholder}
209
283
  rows={1}
210
- className="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"
284
+ className={textareaClasses[size]}
211
285
  />
212
286
  <IconButton
213
287
  type="submit"
214
288
  size="icon-xs"
215
289
  disabled={!ready}
216
290
  label={sendLabel}
217
- className="size-8 shrink-0 rounded-full"
291
+ className={sendButtonClasses[size]}
218
292
  >
219
- <SendIcon size={18} strokeWidth={2.5} />
293
+ <SendIcon size={sendIconSizes[size]} strokeWidth={2.5} />
220
294
  </IconButton>
221
295
  </div>
222
296
  <div className="flex min-h-6 items-center gap-2 px-1">
223
297
  {actions}
224
- <span className="ml-auto text-caption2 text-label-secondary tabular-nums">
298
+ <span className={cn("ml-auto", counterText[size])}>
225
299
  {body.length > maxLength * 0.8 &&
226
300
  (remaining?.(maxLength - body.length) ?? maxLength - body.length)}
227
301
  </span>
@@ -11,6 +11,18 @@ import { cn } from "../../lib/class-names"
11
11
 
12
12
  export type OneTimeCodeStatus = "idle" | "verifying" | "success" | "error"
13
13
  export type OneTimeCodeFormat = "numeric" | "alphanumeric"
14
+ export type OneTimeCodeSize = "sm" | "md" | "lg"
15
+
16
+ const checkSizes: Record<OneTimeCodeSize, number> = {
17
+ sm: 20,
18
+ md: 24,
19
+ lg: 28,
20
+ }
21
+ const statusText: Record<OneTimeCodeSize, string> = {
22
+ sm: "mt-1.5 text-caption2",
23
+ md: "mt-2 text-footnote",
24
+ lg: "mt-2.5 text-subheadline",
25
+ }
14
26
  export interface OneTimeCodeProps
15
27
  extends Omit<
16
28
  InputHTMLAttributes<HTMLInputElement>,
@@ -30,6 +42,8 @@ export interface OneTimeCodeProps
30
42
  status?: OneTimeCodeStatus
31
43
  /** Localized verification feedback announced without reading decorative cells. */
32
44
  statusLabel?: string
45
+ /** Cell height, digit text scale and check icon size. @default "md" */
46
+ size?: OneTimeCodeSize
33
47
  }
34
48
 
35
49
  /** Normalize common numeric keyboards and optional ASCII letters in one native field. */
@@ -60,6 +74,7 @@ export function OneTimeCode({
60
74
  ref,
61
75
  status = "idle",
62
76
  statusLabel,
77
+ size = "md",
63
78
  className,
64
79
  disabled,
65
80
  id: suppliedId,
@@ -85,6 +100,7 @@ export function OneTimeCode({
85
100
  <div
86
101
  className={cn("passui-one-time-code not-typeset", className)}
87
102
  data-status={status}
103
+ data-size={size}
88
104
  style={{ "--otp-length": count, "--otp-middle": (count - 1) / 2 } as CSSProperties}
89
105
  >
90
106
  <svg aria-hidden="true" width="0" height="0" className="absolute">
@@ -139,7 +155,11 @@ export function OneTimeCode({
139
155
  </span>
140
156
  ))}
141
157
  </div>
142
- <CheckIcon aria-hidden="true" className="passui-otp-check" size={24} />
158
+ <CheckIcon
159
+ aria-hidden="true"
160
+ className="passui-otp-check"
161
+ size={checkSizes[size]}
162
+ />
143
163
  <input
144
164
  {...props}
145
165
  ref={(node) => {
@@ -186,7 +206,7 @@ export function OneTimeCode({
186
206
  id={`${id}-status`}
187
207
  role="status"
188
208
  className={cn(
189
- "mt-2 text-footnote",
209
+ statusText[size],
190
210
  status === "error" ? "text-danger" : "text-label-secondary"
191
211
  )}
192
212
  >
@@ -19,6 +19,12 @@ import {
19
19
  import { fitPopover, type PopoverFit } from "./popover-fit"
20
20
 
21
21
  export type PopoverOrigin = "top-left" | "top-right"
22
+ export type PopoverSize = "sm" | "md"
23
+
24
+ const surfaceRadius: Record<PopoverSize, string> = {
25
+ sm: "rounded-3xl",
26
+ md: "rounded-5xl",
27
+ }
22
28
 
23
29
  export interface PopoverProps extends Omit<HTMLMotionProps<"div">, "children"> {
24
30
  open: boolean
@@ -27,6 +33,8 @@ export interface PopoverProps extends Omit<HTMLMotionProps<"div">, "children"> {
27
33
  origin?: PopoverOrigin
28
34
  /** Trigger whose shape the popover uses during its opening and closing motion. */
29
35
  anchorRef?: RefObject<HTMLElement | null>
36
+ /** Surface corner radius. @default "md" */
37
+ size?: PopoverSize
30
38
  }
31
39
 
32
40
  /**
@@ -42,6 +50,7 @@ export function Popover({ open, ...props }: PopoverProps) {
42
50
  function OpenPopover({
43
51
  origin = "top-left",
44
52
  anchorRef,
53
+ size = "md",
45
54
  className,
46
55
  style,
47
56
  children,
@@ -259,7 +268,8 @@ function OpenPopover({
259
268
  anchorRef
260
269
  ? "isolate [&>:not([data-popover-surface])]:opacity-[var(--popover-content-opacity)]"
261
270
  : "glass-menu",
262
- "absolute z-40 rounded-5xl outline-none",
271
+ "absolute z-40 outline-none",
272
+ surfaceRadius[size],
263
273
  className
264
274
  )}
265
275
  {...props}
@@ -272,7 +282,10 @@ function OpenPopover({
272
282
  onAnimationComplete={() => {
273
283
  if (reveal) setSettled(true)
274
284
  }}
275
- className="glass-menu pointer-events-none absolute top-0 left-0 -z-10 rounded-5xl"
285
+ className={cn(
286
+ "glass-menu pointer-events-none absolute top-0 left-0 -z-10",
287
+ surfaceRadius[size]
288
+ )}
276
289
  style={{ width: "100%", height: "100%" }}
277
290
  initial={reducedMotion || !reveal ? false : reveal}
278
291
  animate={
@@ -10,6 +10,24 @@ import {
10
10
  } from "react"
11
11
  import { cn } from "../../lib/class-names"
12
12
 
13
+ export type SearchListSize = "sm" | "md" | "lg"
14
+
15
+ const fieldSizes: Record<SearchListSize, string> = {
16
+ sm: "min-h-8 w-full rounded-full border border-separator bg-fill/60 px-3 text-caption text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
17
+ md: "min-h-11 w-full rounded-full border border-separator bg-fill/60 px-4 text-subheadline text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
18
+ lg: "min-h-13 w-full rounded-full border border-separator bg-fill/60 px-5 text-body text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
19
+ }
20
+ const headingSizes: Record<SearchListSize, string> = {
21
+ sm: "px-1.5 py-1.5 text-caption2 font-medium text-label-tertiary",
22
+ md: "px-2 py-2 text-caption font-medium text-label-tertiary",
23
+ lg: "px-2.5 py-2.5 text-subheadline font-medium text-label-tertiary",
24
+ }
25
+ const itemSizes: Record<SearchListSize, string> = {
26
+ sm: "flex min-h-8 w-full items-center gap-1.5 rounded-md px-2 py-1 text-left text-footnote text-label hover:bg-fill aria-selected:bg-fill [@media(pointer:coarse)]:min-h-10",
27
+ md: "flex min-h-9 w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-subheadline text-label hover:bg-fill aria-selected:bg-fill [@media(pointer:coarse)]:min-h-11",
28
+ lg: "flex min-h-11 w-full items-center gap-2.5 rounded-lg px-3 py-2 text-left text-body text-label hover:bg-fill aria-selected:bg-fill [@media(pointer:coarse)]:min-h-13",
29
+ }
30
+
13
31
  type SearchState = {
14
32
  query: string
15
33
  setQuery: (query: string) => void
@@ -18,6 +36,7 @@ type SearchState = {
18
36
  open: boolean
19
37
  visible: number
20
38
  register: (id: string, visible: boolean | null) => void
39
+ size: SearchListSize
21
40
  }
22
41
  const SearchContext = createContext<SearchState | null>(null)
23
42
  function useSearch() {
@@ -29,12 +48,15 @@ function useSearch() {
29
48
  export function SearchList({
30
49
  loop = true,
31
50
  open = true,
51
+ size = "md",
32
52
  onKeyDown,
33
53
  className,
34
54
  ...props
35
55
  }: ComponentProps<"div"> & {
36
56
  loop?: boolean
37
57
  open?: boolean
58
+ /** Field, item and heading density. @default "md" */
59
+ size?: SearchListSize
38
60
  }) {
39
61
  const [query, setQuery] = useState("")
40
62
  const updateQuery = useCallback((next: string) => {
@@ -73,6 +95,7 @@ export function SearchList({
73
95
  open,
74
96
  visible: Object.values(results).filter(Boolean).length,
75
97
  register,
98
+ size,
76
99
  }}
77
100
  >
78
101
  <div
@@ -148,10 +171,7 @@ export function SearchListField({
148
171
  search.setQuery(event.target.value)
149
172
  onValueChange?.(event.target.value)
150
173
  }}
151
- className={cn(
152
- "min-h-11 w-full rounded-full border border-separator bg-fill/60 px-4 text-subheadline text-label outline-none focus-visible:ring-2 focus-visible:ring-accent-text/50",
153
- className
154
- )}
174
+ className={cn(fieldSizes[search.size], className)}
155
175
  />
156
176
  )
157
177
  }
@@ -179,6 +199,7 @@ export function SearchListGroup({
179
199
  }: ComponentProps<"div"> & {
180
200
  heading?: React.ReactNode
181
201
  }) {
202
+ const search = useSearch()
182
203
  const id = useId()
183
204
  return (
184
205
  // biome-ignore lint/a11y/useSemanticElements: Listbox option groups are not form fieldsets.
@@ -189,7 +210,7 @@ export function SearchListGroup({
189
210
  aria-labelledby={heading ? id : undefined}
190
211
  >
191
212
  {heading && (
192
- <div id={id} className="px-2 py-2 text-caption font-medium text-label-tertiary">
213
+ <div id={id} className={headingSizes[search.size]}>
193
214
  {heading}
194
215
  </div>
195
216
  )}
@@ -231,10 +252,7 @@ export function SearchListItem({
231
252
  data-search-result=""
232
253
  aria-selected={search.active === id}
233
254
  data-selected={search.active === id ? "true" : undefined}
234
- className={cn(
235
- "flex min-h-9 w-full items-center gap-2 rounded-lg px-2.5 py-1.5 text-left text-subheadline text-label hover:bg-fill aria-selected:bg-fill [@media(pointer:coarse)]:min-h-11",
236
- className
237
- )}
255
+ className={cn(itemSizes[search.size], className)}
238
256
  onMouseDown={(event) => {
239
257
  props.onMouseDown?.(event)
240
258
  if (!event.defaultPrevented) event.preventDefault()
@@ -16,14 +16,15 @@ export interface SidebarItemContentProps {
16
16
  children: ReactNode
17
17
  }
18
18
 
19
- export function SidebarSection({ title, children }: SidebarSectionProps) {
19
+ export function SidebarSection({
20
+ title,
21
+ children,
22
+ size = "md",
23
+ }: SidebarSectionProps & { size?: SidebarSize }) {
20
24
  const headingId = useId()
21
25
  return (
22
26
  <section aria-labelledby={headingId} className="flex flex-col gap-0.5">
23
- <h2
24
- id={headingId}
25
- className="px-2.5 pb-1.5 text-caption font-semibold text-label-tertiary"
26
- >
27
+ <h2 id={headingId} className={sidebarHeadingSizes[size]}>
27
28
  {title}
28
29
  </h2>
29
30
  {children}
@@ -33,20 +34,46 @@ export function SidebarSection({ title, children }: SidebarSectionProps) {
33
34
 
34
35
  /** Classes for a sidebar row; apply to the app's router link. */
35
36
  export type SidebarDensity = "regular" | "compact"
37
+ export type SidebarSize = "sm" | "md" | "lg"
36
38
 
37
- export function sidebarItemClass(active?: boolean, density: SidebarDensity = "regular"): string {
39
+ const sidebarItemSizes: Record<SidebarSize, string> = {
40
+ sm: "min-h-8 rounded-lg px-2 py-1 text-footnote [@media(pointer:coarse)]:min-h-10",
41
+ md: "rounded-2xl px-2.5 py-[7px] text-subheadline",
42
+ lg: "rounded-2xl px-3 py-2.5 text-body [@media(pointer:coarse)]:min-h-13",
43
+ }
44
+ const sidebarHeadingSizes: Record<SidebarSize, string> = {
45
+ sm: "px-2 pb-1 text-caption2 font-semibold text-label-tertiary",
46
+ md: "px-2.5 pb-1.5 text-caption font-semibold text-label-tertiary",
47
+ lg: "px-3 pb-2 text-subheadline font-semibold text-label-tertiary",
48
+ }
49
+ const sidebarCountSizes: Record<SidebarSize, string> = {
50
+ sm: "text-caption2 tabular-nums",
51
+ md: "text-footnote tabular-nums",
52
+ lg: "text-subheadline tabular-nums",
53
+ }
54
+
55
+ export function sidebarItemClass(
56
+ active?: boolean,
57
+ density: SidebarDensity = "regular",
58
+ size?: SidebarSize
59
+ ): string {
60
+ const resolved = size ?? (density === "compact" ? "sm" : "md")
38
61
  return cn(
39
62
  "group flex w-full items-center gap-2.5 text-label no-underline transition-colors duration-fast focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-accent-text",
40
- density === "compact"
41
- ? "min-h-8 rounded-lg px-2 py-1 text-footnote [@media(pointer:coarse)]:min-h-10"
42
- : "rounded-2xl px-2.5 py-[7px] text-subheadline",
63
+ sidebarItemSizes[resolved],
43
64
  active
44
65
  ? "bg-fill font-medium"
45
66
  : "font-normal text-label-secondary hover:bg-fill/70 hover:text-label"
46
67
  )
47
68
  }
48
69
 
49
- export function SidebarItemContent({ icon, count, active, children }: SidebarItemContentProps) {
70
+ export function SidebarItemContent({
71
+ icon,
72
+ count,
73
+ active,
74
+ size = "md",
75
+ children,
76
+ }: SidebarItemContentProps & { size?: SidebarSize }) {
50
77
  return (
51
78
  <>
52
79
  {icon && (
@@ -63,7 +90,7 @@ export function SidebarItemContent({ icon, count, active, children }: SidebarIte
63
90
  {count !== undefined && (
64
91
  <span
65
92
  className={cn(
66
- "text-footnote tabular-nums",
93
+ sidebarCountSizes[size],
67
94
  active ? "text-accent-text" : "text-label-tertiary"
68
95
  )}
69
96
  >
@@ -5,6 +5,8 @@ import { cn } from "../../lib/class-names"
5
5
  import { clampRange as clamp } from "../../lib/range-value"
6
6
  import { stepLiquid } from "./liquid-step"
7
7
 
8
+ export type SloshSliderSize = "sm" | "md" | "lg"
9
+
8
10
  export interface SloshSliderProps {
9
11
  label: string
10
12
  value?: number
@@ -18,6 +20,8 @@ export interface SloshSliderProps {
18
20
  tilt?: number
19
21
  /** Track corner, from square to a pill, 0–20. @default 20 */
20
22
  corner?: number
23
+ /** Track height and knob scale. @default "md" */
24
+ size?: SloshSliderSize
21
25
  disabled?: boolean
22
26
  className?: string
23
27
  formatValue?: (value: number) => string
@@ -44,6 +48,7 @@ export function SloshSlider({
44
48
  momentum = 55,
45
49
  tilt = 45,
46
50
  corner = 20,
51
+ size = "md",
47
52
  disabled = false,
48
53
  className,
49
54
  formatValue,
@@ -169,6 +174,7 @@ export function SloshSlider({
169
174
  <div
170
175
  className={cn("passui-slosh-slider not-typeset", disabled && "opacity-40", className)}
171
176
  style={{ "--slosh-radius": `${clamp(corner, 0, 20)}px` } as CSSProperties}
177
+ data-size={size}
172
178
  >
173
179
  {/* Nothing but the track: the liquid is the value's motion, not a
174
180
  second numeric readout that lags and overshoots beside it. */}
@@ -3,6 +3,7 @@
3
3
  * a failure. Call `toast` from anywhere; render one `<Toaster />`.
4
4
  */
5
5
  export type ToastTone = "neutral" | "success" | "error" | "loading"
6
+ export type ToastSize = "sm" | "md" | "lg"
6
7
 
7
8
  export interface ToastAction {
8
9
  label: string
@@ -18,12 +19,16 @@ export interface Toast {
18
19
  /** Milliseconds before it leaves on its own; `null` stays until dismissed. */
19
20
  duration: number | null
20
21
  createdAt: number
22
+ /** Pill size, content padding and title/description text scale. @default "md" */
23
+ size: ToastSize
21
24
  }
22
25
 
23
26
  export interface ToastOptions {
24
27
  description?: string
25
28
  action?: ToastAction
26
29
  duration?: number | null
30
+ /** Pill size, content padding and title/description text scale. @default "md" */
31
+ size?: ToastSize
27
32
  }
28
33
 
29
34
  const DEFAULT_DURATION = 4200
@@ -54,6 +59,7 @@ function show(tone: ToastTone, title: string, options: ToastOptions = {}): numbe
54
59
  action: options.action,
55
60
  duration,
56
61
  createdAt: Date.now(),
62
+ size: options.size ?? "md",
57
63
  },
58
64
  ...toasts,
59
65
  ].slice(0, MAX_VISIBLE + 2)
@@ -79,6 +85,7 @@ function update(id: number, tone: ToastTone, title: string, options: ToastOption
79
85
  ? null
80
86
  : DEFAULT_DURATION,
81
87
  createdAt: Date.now(),
88
+ size: options.size ?? t.size,
82
89
  }
83
90
  : t
84
91
  )