@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,23 +1,23 @@
1
1
  /** The feedback kinds the systems share, from a selection tick to an error. */
2
2
  export type HapticKind =
3
- | "selection"
4
- | "light"
5
- | "medium"
6
- | "heavy"
7
- | "success"
8
- | "warning"
9
- | "error"
3
+ | "selection"
4
+ | "light"
5
+ | "medium"
6
+ | "heavy"
7
+ | "success"
8
+ | "warning"
9
+ | "error";
10
10
 
11
- let player: (kind: HapticKind) => void = () => {}
11
+ let player: (kind: HapticKind) => void = () => {};
12
12
 
13
13
  /**
14
14
  * Components only ask for feedback; the app decides how it plays on this
15
15
  * device (system haptics, a vibration, or nothing) and whether it's on.
16
16
  */
17
17
  export function setHapticPlayer(next: (kind: HapticKind) => void): void {
18
- player = next
18
+ player = next;
19
19
  }
20
20
 
21
21
  export function haptic(kind: HapticKind): void {
22
- player(kind)
22
+ player(kind);
23
23
  }
@@ -1,16 +1,16 @@
1
1
  /** A language's name in itself, capitalized: "Français", "中文". */
2
2
  export function getNativeLanguageName(tag: string) {
3
- const name = new Intl.DisplayNames([tag], {
4
- type: "language",
5
- }).of(tag)
6
- return name ? name.charAt(0).toUpperCase() + name.slice(1) : tag
3
+ const name = new Intl.DisplayNames([tag], {
4
+ type: "language",
5
+ }).of(tag);
6
+ return name ? name.charAt(0).toUpperCase() + name.slice(1) : tag;
7
7
  }
8
8
 
9
9
  /** A language's name as read in `displayLocale`: "French" in English. */
10
10
  export function getLocalizedLanguageName(tag: string, displayLocale: string) {
11
- return (
12
- new Intl.DisplayNames([displayLocale], {
13
- type: "language",
14
- }).of(tag) ?? tag
15
- )
11
+ return (
12
+ new Intl.DisplayNames([displayLocale], {
13
+ type: "language",
14
+ }).of(tag) ?? tag
15
+ );
16
16
  }
@@ -1,32 +1,38 @@
1
1
  /** Keep controlled numeric inputs within finite bounds without floating-point drift. */
2
2
  export function clampRange(value: number, min: number, max: number): number {
3
- if (!Number.isFinite(value)) return min
4
- return Math.min(max, Math.max(min, value))
3
+ if (!Number.isFinite(value)) return min;
4
+ return Math.min(max, Math.max(min, value));
5
5
  }
6
6
 
7
7
  /** The nearest reachable step from min, optionally allowing an exact maximum. */
8
8
  export function stepRange(
9
- value: number,
10
- direction: -1 | 1,
11
- min: number,
12
- max: number,
13
- step: number
9
+ value: number,
10
+ direction: -1 | 1,
11
+ min: number,
12
+ max: number,
13
+ step: number,
14
14
  ): number {
15
- const precision = Math.min(12, Math.max(0, ...[min, max, step].map(decimalPlaces)))
16
- const factor = 10 ** precision
17
- const low = Math.round(min * factor)
18
- const high = Math.round(max * factor)
19
- const increment = Math.max(1, Math.round(step * factor))
20
- const current = Math.round(clampRange(value, min, max) * factor)
21
- const next =
22
- direction > 0
23
- ? low + (Math.floor((current - low) / increment) + 1) * increment
24
- : low + (Math.ceil((current - low) / increment) - 1) * increment
25
- return clampRange(Math.min(high, Math.max(low, next)) / factor, min, max)
15
+ const precision = Math.min(
16
+ 12,
17
+ Math.max(0, ...[min, max, step].map(decimalPlaces)),
18
+ );
19
+ const factor = 10 ** precision;
20
+ const low = Math.round(min * factor);
21
+ const high = Math.round(max * factor);
22
+ const increment = Math.max(1, Math.round(step * factor));
23
+ const current = Math.round(clampRange(value, min, max) * factor);
24
+ const next =
25
+ direction > 0
26
+ ? low + (Math.floor((current - low) / increment) + 1) * increment
27
+ : low + (Math.ceil((current - low) / increment) - 1) * increment;
28
+ return clampRange(Math.min(high, Math.max(low, next)) / factor, min, max);
26
29
  }
27
30
 
28
31
  function decimalPlaces(value: number): number {
29
- const text = String(value)
30
- const [mantissa, exponent] = text.toLowerCase().split("e")
31
- return Math.max(0, (mantissa?.split(".")[1]?.length ?? 0) - Number(exponent ?? 0))
32
+ const text = String(value);
33
+ const [mantissa, exponent] = text.toLowerCase().split("e");
34
+ return Math.max(
35
+ 0,
36
+ (mantissa?.split(".")[1]?.length ?? 0) - Number(exponent ?? 0),
37
+ );
32
38
  }
@@ -1,11 +1,11 @@
1
1
  export interface ScreenEdges {
2
- top: number
3
- right: number
4
- bottom: number
5
- left: number
2
+ top: number;
3
+ right: number;
4
+ bottom: number;
5
+ left: number;
6
6
  }
7
7
 
8
- let probe: HTMLDivElement | undefined
8
+ let probe: HTMLDivElement | undefined;
9
9
 
10
10
  /**
11
11
  * How far menus and popovers keep from each side of the screen: clear of
@@ -13,29 +13,32 @@ let probe: HTMLDivElement | undefined
13
13
  * further from the glass, as the systems' own menus are.
14
14
  */
15
15
  export function screenEdges(): ScreenEdges {
16
- const margin =
17
- typeof window !== "undefined" && window.matchMedia("(pointer: coarse)").matches ? 12 : 8
18
- if (typeof document === "undefined") {
19
- return {
20
- top: margin,
21
- right: margin,
22
- bottom: margin,
23
- left: margin,
24
- }
25
- }
26
- if (!probe) {
27
- probe = document.createElement("div")
28
- probe.setAttribute("aria-hidden", "true")
29
- probe.style.cssText =
30
- "position:fixed;inset:0;visibility:hidden;pointer-events:none;" +
31
- "padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)"
32
- }
33
- if (!probe.isConnected) document.body.appendChild(probe)
34
- const style = getComputedStyle(probe)
35
- return {
36
- top: Number.parseFloat(style.paddingTop) + margin,
37
- right: Number.parseFloat(style.paddingRight) + margin,
38
- bottom: Number.parseFloat(style.paddingBottom) + margin,
39
- left: Number.parseFloat(style.paddingLeft) + margin,
40
- }
16
+ const margin =
17
+ typeof window !== "undefined" &&
18
+ window.matchMedia("(pointer: coarse)").matches
19
+ ? 12
20
+ : 8;
21
+ if (typeof document === "undefined") {
22
+ return {
23
+ top: margin,
24
+ right: margin,
25
+ bottom: margin,
26
+ left: margin,
27
+ };
28
+ }
29
+ if (!probe) {
30
+ probe = document.createElement("div");
31
+ probe.setAttribute("aria-hidden", "true");
32
+ probe.style.cssText =
33
+ "position:fixed;inset:0;visibility:hidden;pointer-events:none;" +
34
+ "padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)";
35
+ }
36
+ if (!probe.isConnected) document.body.appendChild(probe);
37
+ const style = getComputedStyle(probe);
38
+ return {
39
+ top: Number.parseFloat(style.paddingTop) + margin,
40
+ right: Number.parseFloat(style.paddingRight) + margin,
41
+ bottom: Number.parseFloat(style.paddingBottom) + margin,
42
+ left: Number.parseFloat(style.paddingLeft) + margin,
43
+ };
41
44
  }
package/src/lib/shapes.ts CHANGED
@@ -1,28 +1,31 @@
1
1
  /** Material 3 expressive outlines: a circle and the scalloped "cookie", "clover", and "sunny" shapes. */
2
2
  export const shapes = [
3
- "circle",
4
- "cookie4",
5
- "cookie6",
6
- "cookie9",
7
- "clover",
8
- "sunny",
9
- /** Reserved for the Pherus mark itself, so it never matches a project's icon. */
10
- "cookie12",
11
- ] as const
3
+ "circle",
4
+ "cookie4",
5
+ "cookie6",
6
+ "cookie9",
7
+ "clover",
8
+ "sunny",
9
+ /** Reserved for the Pherus mark itself, so it never matches a project's icon. */
10
+ "cookie12",
11
+ ] as const;
12
12
 
13
- export type Shape = (typeof shapes)[number]
13
+ export type Shape = (typeof shapes)[number];
14
14
 
15
15
  /** Lobe count and wave depth (as a fraction of the radius) for each wavy shape. */
16
- const waves: Record<Exclude<Shape, "circle">, { lobes: number; depth: number }> = {
17
- cookie4: { lobes: 4, depth: 0.08 },
18
- cookie6: { lobes: 6, depth: 0.07 },
19
- cookie9: { lobes: 9, depth: 0.055 },
20
- clover: { lobes: 4, depth: 0.16 },
21
- sunny: { lobes: 8, depth: 0.045 },
22
- cookie12: { lobes: 12, depth: 0.04 },
23
- }
16
+ const waves: Record<
17
+ Exclude<Shape, "circle">,
18
+ { lobes: number; depth: number }
19
+ > = {
20
+ cookie4: { lobes: 4, depth: 0.08 },
21
+ cookie6: { lobes: 6, depth: 0.07 },
22
+ cookie9: { lobes: 9, depth: 0.055 },
23
+ clover: { lobes: 4, depth: 0.16 },
24
+ sunny: { lobes: 8, depth: 0.045 },
25
+ cookie12: { lobes: 12, depth: 0.04 },
26
+ };
24
27
 
25
- const STEPS = 144
28
+ const STEPS = 144;
26
29
 
27
30
  /**
28
31
  * An SVG path for a Material 3 expressive shape centred on (cx, cy): a circle
@@ -31,25 +34,26 @@ const STEPS = 144
31
34
  * the same `r`, keeping icons optically the same size.
32
35
  */
33
36
  export function shapePath(shape: Shape, cx: number, cy: number, r: number) {
34
- if (shape === "circle") {
35
- return `M ${cx - r} ${cy} a ${r} ${r} 0 1 0 ${r * 2} 0 a ${r} ${r} 0 1 0 ${-r * 2} 0 Z`
36
- }
37
+ if (shape === "circle") {
38
+ return `M ${cx - r} ${cy} a ${r} ${r} 0 1 0 ${r * 2} 0 a ${r} ${r} 0 1 0 ${-r * 2} 0 Z`;
39
+ }
37
40
 
38
- const { lobes, depth } = waves[shape]
39
- const base = r / (1 + depth)
40
- const points: string[] = []
41
- for (let i = 0; i < STEPS; i++) {
42
- const theta = (i / STEPS) * Math.PI * 2 - Math.PI / 2
43
- const radius = base * (1 + depth * Math.cos(lobes * (theta + Math.PI / 2)))
44
- const x = cx + radius * Math.cos(theta)
45
- const y = cy + radius * Math.sin(theta)
46
- points.push(`${i === 0 ? "M" : "L"} ${x.toFixed(2)} ${y.toFixed(2)}`)
47
- }
48
- return `${points.join(" ")} Z`
41
+ const { lobes, depth } = waves[shape];
42
+ const base = r / (1 + depth);
43
+ const points: string[] = [];
44
+ for (let i = 0; i < STEPS; i++) {
45
+ const theta = (i / STEPS) * Math.PI * 2 - Math.PI / 2;
46
+ const radius = base * (1 + depth * Math.cos(lobes * (theta + Math.PI / 2)));
47
+ const x = cx + radius * Math.cos(theta);
48
+ const y = cy + radius * Math.sin(theta);
49
+ points.push(`${i === 0 ? "M" : "L"} ${x.toFixed(2)} ${y.toFixed(2)}`);
50
+ }
51
+ return `${points.join(" ")} Z`;
49
52
  }
50
53
 
51
54
  /**
52
55
  * The app wide `dualTone` rule dims every svg path for two tone icons; a
53
56
  * drawn shape or illustration opts out with this so its colors stay true.
54
57
  */
55
- export const plainSvg = "cursor-[inherit] [&_path]:opacity-100 [&_path]:transform-none"
58
+ export const plainSvg =
59
+ "cursor-[inherit] [&_path]:opacity-100 [&_path]:transform-none";
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Popover state machine — pure function, no React, no DOM.
3
+ *
4
+ * Translates directly to a Rust enum with match arms.
5
+ *
6
+ * States:
7
+ * closed → nothing visible
8
+ * opening → surface morphing from trigger to content
9
+ * open → content fully visible
10
+ * closing → surface morphing back to trigger
11
+ *
12
+ * The OPENED/CLOSED events are fired by onAnimationComplete,
13
+ * so the machine stays in "opening"/"closing" until the animation
14
+ * actually finishes — enabling interruptibility (close during open).
15
+ */
16
+ export type PopoverState = "closed" | "opening" | "open" | "closing";
17
+
18
+ export type PopoverEvent = "OPEN" | "OPENED" | "CLOSE" | "CLOSED" | "TOGGLE";
19
+
20
+ export function popoverMachine(
21
+ state: PopoverState,
22
+ event: PopoverEvent,
23
+ ): PopoverState {
24
+ switch (state) {
25
+ case "closed":
26
+ return event === "OPEN" || event === "TOGGLE" ? "opening" : "closed";
27
+ case "opening":
28
+ if (event === "CLOSE" || event === "TOGGLE") return "closing";
29
+ if (event === "OPENED") return "open";
30
+ return "opening";
31
+ case "open":
32
+ return event === "CLOSE" || event === "TOGGLE" ? "closing" : "open";
33
+ case "closing":
34
+ if (event === "OPEN" || event === "TOGGLE") return "opening";
35
+ if (event === "CLOSED") return "closed";
36
+ return "closing";
37
+ }
38
+ }
39
+
40
+ export type PopoverContextValue = {
41
+ state: PopoverState;
42
+ send: (event: PopoverEvent) => void;
43
+ /** The trigger element — set by PopoverTrigger, read by PopoverContent for positioning. */
44
+ triggerRef: React.RefObject<HTMLElement | null>;
45
+ /** Unique ID for aria-controls + layoutId morphing. */
46
+ contentId: string;
47
+ /** Shared layoutId for motion's matched-geometry transition. */
48
+ surfaceId: string;
49
+ /** Whether the popover is open or transitioning (not closed). */
50
+ isPresent: boolean;
51
+ };
@@ -1,12 +1,12 @@
1
- import { springs } from "@passui/tokens/motion"
2
- import type { Transition } from "motion/react"
1
+ import { springs } from "@passui/tokens/motion";
2
+ import type { Transition } from "motion/react";
3
3
 
4
4
  /** Design-token springs as motion/react transitions. */
5
5
  export const spring = {
6
- smooth: { type: "spring", ...springs.smooth },
7
- snappy: { type: "spring", ...springs.snappy },
8
- bouncy: { type: "spring", ...springs.bouncy },
9
- } as const satisfies Record<keyof typeof springs, Transition>
6
+ smooth: { type: "spring", ...springs.smooth },
7
+ snappy: { type: "spring", ...springs.snappy },
8
+ bouncy: { type: "spring", ...springs.bouncy },
9
+ } as const satisfies Record<keyof typeof springs, Transition>;
10
10
 
11
11
  /**
12
12
  * Apple's fluid overshoot: cubic-bezier(0.34, 1.56, 0.1, 1).
@@ -16,9 +16,9 @@ export const spring = {
16
16
  * from the trigger and snaps into place with a physical bounce.
17
17
  */
18
18
  export const fluidOvershoot = {
19
- duration: 0.4,
20
- ease: [0.34, 1.56, 0.1, 1] as [number, number, number, number],
21
- } as const
19
+ duration: 0.4,
20
+ ease: [0.34, 1.56, 0.1, 1] as [number, number, number, number],
21
+ } as const;
22
22
 
23
23
  /**
24
24
  * Exit motion: content collapses back toward its source.
@@ -26,11 +26,11 @@ export const fluidOvershoot = {
26
26
  * like a water drop absorbing back into its origin.
27
27
  */
28
28
  export const fluidCollapse = {
29
- duration: 0.28,
30
- ease: [0.4, 0, 0.6, 1] as [number, number, number, number],
31
- } as const
29
+ duration: 0.28,
30
+ ease: [0.4, 0, 0.6, 1] as [number, number, number, number],
31
+ } as const;
32
32
 
33
- export type FloatingPanelSide = "above" | "below"
33
+ export type FloatingPanelSide = "above" | "below";
34
34
 
35
35
  /**
36
36
  * Anchored panels settle toward their source without shrinking their text.
@@ -39,20 +39,23 @@ export type FloatingPanelSide = "above" | "below"
39
39
  * crossfade, rather than a plain slide.
40
40
  */
41
41
  export function floatingPanelMotion(side: FloatingPanelSide = "below") {
42
- const direction = side === "above" ? 1 : -1
43
- return {
44
- initial: { opacity: 0, scale: 0.92, y: direction * 6 },
45
- animate: { opacity: 1, scale: 1, y: 0 },
46
- exit: { opacity: 0, scale: 0.95, y: direction * 4 },
47
- transition: {
48
- ...fluidOvershoot,
49
- opacity: { duration: 0.16, ease: [0.4, 0, 0.2, 1] as [number, number, number, number] },
50
- },
51
- } as const
42
+ const direction = side === "above" ? 1 : -1;
43
+ return {
44
+ initial: { opacity: 0, scale: 0.92, y: direction * 6 },
45
+ animate: { opacity: 1, scale: 1, y: 0 },
46
+ exit: { opacity: 0, scale: 0.95, y: direction * 4 },
47
+ transition: {
48
+ ...fluidOvershoot,
49
+ opacity: {
50
+ duration: 0.16,
51
+ ease: [0.4, 0, 0.2, 1] as [number, number, number, number],
52
+ },
53
+ },
54
+ } as const;
52
55
  }
53
56
 
54
57
  /** Default below-anchor presentation, retained for existing consumers. */
55
- export const popoverMotion = floatingPanelMotion()
58
+ export const popoverMotion = floatingPanelMotion();
56
59
 
57
60
  /**
58
61
  * Zoom/matched-geometry transition for dialogs and center-placed sheets.
@@ -62,11 +65,11 @@ export const popoverMotion = floatingPanelMotion()
62
65
  * asynchronously — not a harsh switch.
63
66
  */
64
67
  export const zoomReveal = {
65
- initial: { opacity: 0, scale: 0.88 },
66
- animate: { opacity: 1, scale: 1 },
67
- exit: { opacity: 0, scale: 0.92 },
68
- transition: fluidOvershoot,
69
- } as const
68
+ initial: { opacity: 0, scale: 0.88 },
69
+ animate: { opacity: 1, scale: 1 },
70
+ exit: { opacity: 0, scale: 0.92 },
71
+ transition: fluidOvershoot,
72
+ } as const;
70
73
 
71
74
  /**
72
75
  * Content crossfade for elements inside an overlay.
@@ -75,20 +78,20 @@ export const zoomReveal = {
75
78
  * and popovers — the container arrives first, then content fades in.
76
79
  */
77
80
  export const contentCrossfade = {
78
- initial: { opacity: 0, scale: 0.96 },
79
- animate: { opacity: 1, scale: 1 },
80
- exit: { opacity: 0, scale: 0.98 },
81
- transition: {
82
- duration: 0.24,
83
- ease: [0.4, 0, 0.2, 1] as [number, number, number, number],
84
- delay: 0.04,
85
- },
86
- } as const
81
+ initial: { opacity: 0, scale: 0.96 },
82
+ animate: { opacity: 1, scale: 1 },
83
+ exit: { opacity: 0, scale: 0.98 },
84
+ transition: {
85
+ duration: 0.24,
86
+ ease: [0.4, 0, 0.2, 1] as [number, number, number, number],
87
+ delay: 0.04,
88
+ },
89
+ } as const;
87
90
 
88
91
  /** Floating bars that rise from the bottom edge. */
89
92
  export const riseMotion = {
90
- initial: { opacity: 0, y: 32 },
91
- animate: { opacity: 1, y: 0 },
92
- exit: { opacity: 0, y: 24 },
93
- transition: spring.smooth,
94
- } as const
93
+ initial: { opacity: 0, y: 32 },
94
+ animate: { opacity: 1, y: 0 },
95
+ exit: { opacity: 0, y: 24 },
96
+ transition: spring.smooth,
97
+ } as const;
@@ -1,45 +1,71 @@
1
1
  @layer components {
2
- .passui-code-block pre {
3
- margin: 0;
4
- padding-block: 1rem;
5
- padding-inline-end: 1.5rem;
6
- overflow-x: auto;
7
- font-family: var(--font-mono);
8
- font-size: var(--text-footnote);
9
- line-height: 1.75;
10
- color: var(--color-label);
11
- background: transparent !important;
12
- counter-reset: code-line;
13
- tab-size: 4;
14
- }
15
- .passui-code-block .line { display: inline-block; min-width: 100%; }
16
- .passui-code-block[data-line-numbers] .line::before {
17
- counter-increment: code-line;
18
- content: counter(code-line);
19
- display: inline-block;
20
- width: 3rem;
21
- margin-inline-end: 1.25rem;
22
- color: var(--color-label-tertiary);
23
- text-align: end;
24
- user-select: none;
25
- }
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; }
39
- .passui-code-block .shiki, .passui-code-block .shiki span { color: var(--shiki-light); background: transparent !important; }
40
- @media (prefers-color-scheme: dark) {
41
- .passui-code-block .shiki, .passui-code-block .shiki span { color: var(--shiki-dark); }
42
- }
43
- [data-theme="light"] .passui-code-block .shiki, [data-theme="light"] .passui-code-block .shiki span { color: var(--shiki-light); }
44
- [data-theme="dark"] .passui-code-block .shiki, [data-theme="dark"] .passui-code-block .shiki span { color: var(--shiki-dark); }
2
+ .passui-code-block pre {
3
+ margin: 0;
4
+ padding-block: 1rem;
5
+ padding-inline-end: 1.5rem;
6
+ overflow-x: auto;
7
+ font-family: var(--font-mono);
8
+ font-size: var(--text-footnote);
9
+ line-height: 1.75;
10
+ color: var(--color-label);
11
+ /* biome-ignore lint/complexity/noImportantStyles: CSS utility override */
12
+ background: transparent !important;
13
+ counter-reset: code-line;
14
+ tab-size: 4;
15
+ }
16
+ .passui-code-block .line {
17
+ display: inline-block;
18
+ min-width: 100%;
19
+ }
20
+ .passui-code-block[data-line-numbers] .line::before {
21
+ counter-increment: code-line;
22
+ content: counter(code-line);
23
+ display: inline-block;
24
+ width: 3rem;
25
+ margin-inline-end: 1.25rem;
26
+ color: var(--color-label-tertiary);
27
+ text-align: end;
28
+ user-select: none;
29
+ }
30
+ .passui-code-block:not([data-line-numbers]) pre {
31
+ padding-inline-start: 1rem;
32
+ }
33
+ .passui-code-block[data-size="sm"] pre {
34
+ padding-block: 0.75rem;
35
+ font-size: var(--text-caption);
36
+ line-height: 1.6;
37
+ }
38
+ .passui-code-block[data-size="sm"][data-line-numbers] .line::before {
39
+ width: 2.5rem;
40
+ margin-inline-end: 1rem;
41
+ }
42
+ .passui-code-block[data-size="lg"] pre {
43
+ padding-block: 1.25rem;
44
+ font-size: var(--text-subheadline);
45
+ line-height: 1.8;
46
+ }
47
+ .passui-code-block[data-size="lg"][data-line-numbers] .line::before {
48
+ width: 3.5rem;
49
+ margin-inline-end: 1.5rem;
50
+ }
51
+ .passui-code-block .shiki,
52
+ .passui-code-block .shiki span {
53
+ color: var(--shiki-light);
54
+ /* biome-ignore lint/complexity/noImportantStyles: CSS utility override */
55
+ background: transparent !important;
56
+ }
57
+ @media (prefers-color-scheme: dark) {
58
+ .passui-code-block .shiki,
59
+ .passui-code-block .shiki span {
60
+ color: var(--shiki-dark);
61
+ }
62
+ }
63
+ [data-theme="light"] .passui-code-block .shiki,
64
+ [data-theme="light"] .passui-code-block .shiki span {
65
+ color: var(--shiki-light);
66
+ }
67
+ [data-theme="dark"] .passui-code-block .shiki,
68
+ [data-theme="dark"] .passui-code-block .shiki span {
69
+ color: var(--shiki-dark);
70
+ }
45
71
  }