@passui/react 0.1.39 → 0.1.40

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.
@@ -1,10 +1,13 @@
1
1
  import { type ComponentProps, type ReactNode } from "react";
2
- export interface CodeBlockProps extends Omit<ComponentProps<"div">, "children"> {
2
+ export type CodeBlockSize = "sm" | "md" | "lg";
3
+ export interface CodeBlockProps extends Omit<ComponentProps<"div">, "children" | "size"> {
3
4
  code: string;
4
5
  /** Supply a highlighter's rendered pre/code element; source remains the fallback. */
5
6
  children?: ReactNode;
6
7
  /** Show a nonselectable line gutter. @default true */
7
8
  lineNumbers?: boolean;
9
+ /** Code font size, padding and line gutter scale. @default "md" */
10
+ size?: CodeBlockSize;
8
11
  }
9
12
  /** Code presentation without shipping a highlighter or formatter to consumers. */
10
- export declare function CodeBlock({ code, children, lineNumbers, className, ...props }: CodeBlockProps): import("react").JSX.Element;
13
+ export declare function CodeBlock({ code, children, lineNumbers, size, className, ...props }: CodeBlockProps): import("react").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import { type InputHTMLAttributes, type Ref } from "react";
2
2
  export type OneTimeCodeStatus = "idle" | "verifying" | "success" | "error";
3
3
  export type OneTimeCodeFormat = "numeric" | "alphanumeric";
4
+ export type OneTimeCodeSize = "sm" | "md" | "lg";
4
5
  export interface OneTimeCodeProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "value" | "defaultValue" | "onChange" | "type" | "maxLength" | "size"> {
5
6
  label: string;
6
7
  value: string;
@@ -16,8 +17,10 @@ export interface OneTimeCodeProps extends Omit<InputHTMLAttributes<HTMLInputElem
16
17
  status?: OneTimeCodeStatus;
17
18
  /** Localized verification feedback announced without reading decorative cells. */
18
19
  statusLabel?: string;
20
+ /** Cell height, digit text scale and check icon size. @default "md" */
21
+ size?: OneTimeCodeSize;
19
22
  }
20
23
  /** Normalize common numeric keyboards and optional ASCII letters in one native field. */
21
24
  export declare function normalizeCode(value: string, length: number, format?: OneTimeCodeFormat): string;
22
25
  /** Six cells, one field: paste, autofill, selection and Backspace stay native. */
23
- export declare function OneTimeCode({ label, value, onChange, onComplete, length, format, ref, status, statusLabel, className, disabled, id: suppliedId, ...props }: OneTimeCodeProps): import("react").JSX.Element;
26
+ export declare function OneTimeCode({ label, value, onChange, onComplete, length, format, ref, status, statusLabel, size, className, disabled, id: suppliedId, ...props }: OneTimeCodeProps): import("react").JSX.Element;
@@ -1,6 +1,7 @@
1
1
  import { type HTMLMotionProps } from "motion/react";
2
2
  import { type ReactNode, type RefObject } from "react";
3
3
  export type PopoverOrigin = "top-left" | "top-right";
4
+ export type PopoverSize = "sm" | "md";
4
5
  export interface PopoverProps extends Omit<HTMLMotionProps<"div">, "children"> {
5
6
  open: boolean;
6
7
  children?: ReactNode;
@@ -8,6 +9,8 @@ export interface PopoverProps extends Omit<HTMLMotionProps<"div">, "children"> {
8
9
  origin?: PopoverOrigin;
9
10
  /** Trigger whose shape the popover uses during its opening and closing motion. */
10
11
  anchorRef?: RefObject<HTMLElement | null>;
12
+ /** Surface corner radius. @default "md" */
13
+ size?: PopoverSize;
11
14
  }
12
15
  /**
13
16
  * A glass surface that springs open from its anchor. Position it with
@@ -1,7 +1,10 @@
1
1
  import { type ComponentProps } from "react";
2
- export declare function SearchList({ loop, open, onKeyDown, className, ...props }: ComponentProps<"div"> & {
2
+ export type SearchListSize = "sm" | "md" | "lg";
3
+ export declare function SearchList({ loop, open, size, onKeyDown, className, ...props }: ComponentProps<"div"> & {
3
4
  loop?: boolean;
4
5
  open?: boolean;
6
+ /** Field, item and heading density. @default "md" */
7
+ size?: SearchListSize;
5
8
  }): import("react").JSX.Element;
6
9
  export declare function SearchListField({ value, onValueChange, className, ...props }: Omit<ComponentProps<"input">, "value" | "onChange"> & {
7
10
  value?: string;
@@ -12,8 +12,13 @@ export interface SidebarItemContentProps {
12
12
  active?: boolean;
13
13
  children: ReactNode;
14
14
  }
15
- export declare function SidebarSection({ title, children }: SidebarSectionProps): import("react").JSX.Element;
15
+ export declare function SidebarSection({ title, children, size, }: SidebarSectionProps & {
16
+ size?: SidebarSize;
17
+ }): import("react").JSX.Element;
16
18
  /** Classes for a sidebar row; apply to the app's router link. */
17
19
  export type SidebarDensity = "regular" | "compact";
18
- export declare function sidebarItemClass(active?: boolean, density?: SidebarDensity): string;
19
- export declare function SidebarItemContent({ icon, count, active, children }: SidebarItemContentProps): import("react").JSX.Element;
20
+ export type SidebarSize = "sm" | "md" | "lg";
21
+ export declare function sidebarItemClass(active?: boolean, density?: SidebarDensity, size?: SidebarSize): string;
22
+ export declare function SidebarItemContent({ icon, count, active, size, children, }: SidebarItemContentProps & {
23
+ size?: SidebarSize;
24
+ }): import("react").JSX.Element;
@@ -1,3 +1,4 @@
1
+ export type SloshSliderSize = "sm" | "md" | "lg";
1
2
  export interface SloshSliderProps {
2
3
  label: string;
3
4
  value?: number;
@@ -11,8 +12,10 @@ export interface SloshSliderProps {
11
12
  tilt?: number;
12
13
  /** Track corner, from square to a pill, 0–20. @default 20 */
13
14
  corner?: number;
15
+ /** Track height and knob scale. @default "md" */
16
+ size?: SloshSliderSize;
14
17
  disabled?: boolean;
15
18
  className?: string;
16
19
  formatValue?: (value: number) => string;
17
20
  }
18
- export declare function SloshSlider({ label, value, defaultValue, onChange, viscosity, momentum, tilt, corner, disabled, className, formatValue, }: SloshSliderProps): import("react").JSX.Element;
21
+ export declare function SloshSlider({ label, value, defaultValue, onChange, viscosity, momentum, tilt, corner, size, disabled, className, formatValue, }: SloshSliderProps): import("react").JSX.Element;
@@ -1,3 +1,4 @@
1
+ export type TooltipSize = "sm" | "md" | "lg";
1
2
  /**
2
3
  * Tooltips for any element with `data-tooltip`, on devices that can hover.
3
4
  * One listener for the whole app: a short delay before the first, then
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passui/react",
3
- "version": "0.1.39",
3
+ "version": "0.1.40",
4
4
  "description": "Passui: native React controls, design tokens, presentation and appearance.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,12 +1,16 @@
1
1
  import { Fragment, type ComponentProps, type ReactNode } from "react"
2
2
  import { cn } from "../../lib/class-names"
3
3
 
4
- export interface CodeBlockProps extends Omit<ComponentProps<"div">, "children"> {
4
+ export type CodeBlockSize = "sm" | "md" | "lg"
5
+
6
+ export interface CodeBlockProps extends Omit<ComponentProps<"div">, "children" | "size"> {
5
7
  code: string
6
8
  /** Supply a highlighter's rendered pre/code element; source remains the fallback. */
7
9
  children?: ReactNode
8
10
  /** Show a nonselectable line gutter. @default true */
9
11
  lineNumbers?: boolean
12
+ /** Code font size, padding and line gutter scale. @default "md" */
13
+ size?: CodeBlockSize
10
14
  }
11
15
 
12
16
  /** Code presentation without shipping a highlighter or formatter to consumers. */
@@ -14,6 +18,7 @@ export function CodeBlock({
14
18
  code,
15
19
  children,
16
20
  lineNumbers = true,
21
+ size = "md",
17
22
  className,
18
23
  ...props
19
24
  }: CodeBlockProps) {
@@ -23,6 +28,7 @@ export function CodeBlock({
23
28
  {...props}
24
29
  className={cn("passui-code-block", className)}
25
30
  data-line-numbers={lineNumbers || undefined}
31
+ data-size={size}
26
32
  >
27
33
  {children ?? (
28
34
  <pre>
@@ -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. */}
@@ -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;