@passui/react 0.1.45 → 0.1.47

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 (141) 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 +38 -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 +278 -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 +374 -0
  72. package/src/components/popover/popover-root.tsx +97 -0
  73. package/src/components/popover/popover-trigger.tsx +71 -0
  74. package/src/components/profile-image/profile-image.tsx +80 -64
  75. package/src/components/progress-view/progress-view.tsx +61 -59
  76. package/src/components/progress-view/use-liquid-progress.ts +38 -38
  77. package/src/components/radio-group/radio-group.tsx +71 -68
  78. package/src/components/search-field/expanding-search-field.tsx +191 -177
  79. package/src/components/search-field/search-field.tsx +59 -50
  80. package/src/components/search-list/search-list.tsx +246 -236
  81. package/src/components/section/section.tsx +58 -58
  82. package/src/components/secure-field/secure-field.tsx +3 -3
  83. package/src/components/segmented-control/segmented-control.tsx +136 -136
  84. package/src/components/sheet/modal-environment.ts +71 -64
  85. package/src/components/sheet/sheet-portal.tsx +58 -53
  86. package/src/components/sheet/sheet.tsx +146 -141
  87. package/src/components/sheet/use-sheet-focus.ts +97 -82
  88. package/src/components/sidebar/sidebar.tsx +78 -78
  89. package/src/components/skeleton/skeleton.tsx +30 -21
  90. package/src/components/slider/slider.tsx +113 -104
  91. package/src/components/slosh-slider/liquid-step.ts +32 -31
  92. package/src/components/slosh-slider/slosh-slider.tsx +265 -228
  93. package/src/components/stat-tile/index.ts +1 -1
  94. package/src/components/stat-tile/stat-tile.tsx +79 -68
  95. package/src/components/status-indicator/status-indicator.tsx +40 -37
  96. package/src/components/stepper/rolling-digit.tsx +82 -68
  97. package/src/components/stepper/stepper.tsx +122 -113
  98. package/src/components/swatch-picker/swatch-picker.tsx +120 -120
  99. package/src/components/tab-bar/tab-bar.tsx +149 -148
  100. package/src/components/tab-view/tab-view.tsx +140 -137
  101. package/src/components/table/table.tsx +117 -98
  102. package/src/components/text-editor/text-editor.tsx +34 -29
  103. package/src/components/text-field/text-field.tsx +182 -180
  104. package/src/components/toast/toast-store.ts +127 -115
  105. package/src/components/toast/toaster.tsx +361 -347
  106. package/src/components/toggle/toggle.tsx +297 -282
  107. package/src/components/tooltip/tooltip-host.tsx +157 -150
  108. package/src/components/view-controller/index.ts +1 -1
  109. package/src/components/view-controller/view-controller.tsx +64 -53
  110. package/src/components/wake-slider/wake-slider.tsx +220 -205
  111. package/src/hooks/scroll/minimize-decision.ts +11 -7
  112. package/src/hooks/use-anchored-disclosure.ts +19 -19
  113. package/src/hooks/use-anchored-placement.ts +29 -25
  114. package/src/hooks/use-dismiss.ts +33 -29
  115. package/src/hooks/use-floating-dismiss.ts +24 -22
  116. package/src/hooks/use-media-query.ts +67 -60
  117. package/src/hooks/use-minimize-on-scroll.ts +69 -60
  118. package/src/hooks/use-visual-viewport.ts +63 -63
  119. package/src/hooks/viewport/types.ts +16 -16
  120. package/src/hooks/viewport/viewport-metrics.ts +16 -10
  121. package/src/icons/icons.tsx +440 -328
  122. package/src/index.ts +91 -91
  123. package/src/lib/anchored-placement.ts +70 -65
  124. package/src/lib/class-names.ts +14 -13
  125. package/src/lib/clipboard.ts +8 -7
  126. package/src/lib/date.ts +61 -60
  127. package/src/lib/floating-layers.ts +39 -24
  128. package/src/lib/haptics.ts +10 -10
  129. package/src/lib/language.ts +9 -9
  130. package/src/lib/range-value.ts +27 -21
  131. package/src/lib/screen-edges.ts +33 -30
  132. package/src/lib/shapes.ts +38 -34
  133. package/src/machines/popover.machine.ts +51 -0
  134. package/src/motion/transitions.ts +46 -43
  135. package/src/styles/code-block.css +69 -43
  136. package/src/styles/motion-controls.css +393 -191
  137. package/src/tokens.ts +1 -1
  138. package/dist/types/src/components/popover/popover-fit.d.ts +0 -21
  139. package/dist/types/src/components/popover/popover.d.ts +0 -21
  140. package/src/components/popover/popover-fit.ts +0 -69
  141. package/src/components/popover/popover.tsx +0 -334
package/src/index.ts CHANGED
@@ -1,107 +1,107 @@
1
- export * from "./components/button/button"
2
- export * from "./components/accordion/accordion"
3
- export * from "./components/text-field/text-field"
4
- export * from "./components/secure-field/secure-field"
5
- export * from "./components/text-editor/text-editor"
6
- export * from "./components/tab-view/tab-view"
7
- export * from "./components/profile-image/profile-image"
8
- export * from "./components/search-list/search-list"
9
- export * from "./components/table/table"
10
- export * from "./components/button/icon-button"
11
- export * from "./components/chip/chip"
12
- export * from "./components/context-menu/context-menu-host"
1
+ export * from "./components/button/button";
2
+ export * from "./components/accordion/accordion";
3
+ export * from "./components/text-field/text-field";
4
+ export * from "./components/secure-field/secure-field";
5
+ export * from "./components/text-editor/text-editor";
6
+ export * from "./components/tab-view/tab-view";
7
+ export * from "./components/profile-image/profile-image";
8
+ export * from "./components/search-list/search-list";
9
+ export * from "./components/table/table";
10
+ export * from "./components/button/icon-button";
11
+ export * from "./components/chip/chip";
12
+ export * from "./components/context-menu/context-menu-host";
13
13
  export {
14
- type ContextMenuItem,
15
- type ContextMenuAlignX,
16
- type ContextMenuAlignY,
17
- type ContextMenuEdge,
18
- closeContextMenu,
19
- openContextMenu,
20
- openFollowUpMenu,
21
- openMenu,
22
- } from "./components/context-menu/context-menu-store"
23
- export * from "./components/context-menu/use-context-menu"
24
- export * from "./components/dialog/dialog-host"
14
+ type ContextMenuItem,
15
+ type ContextMenuAlignX,
16
+ type ContextMenuAlignY,
17
+ type ContextMenuEdge,
18
+ closeContextMenu,
19
+ openContextMenu,
20
+ openFollowUpMenu,
21
+ openMenu,
22
+ } from "./components/context-menu/context-menu-store";
23
+ export * from "./components/context-menu/use-context-menu";
24
+ export * from "./components/dialog/dialog-host";
25
25
  export {
26
- type ConfirmOptions,
27
- confirmDialog,
28
- } from "./components/dialog/dialog-store"
29
- export * from "./components/labeled-content/labeled-content"
30
- export * from "./components/picker/picker"
31
- export * from "./components/popover/popover"
32
- export * from "./components/search-field/search-field"
33
- export * from "./components/section/section"
34
- export * from "./components/inline-disclosure/inline-disclosure"
35
- export * from "./components/disclosure-group/disclosure-group"
36
- export * from "./components/menu/menu"
37
- export * from "./components/segmented-control/segmented-control"
38
- export * from "./components/sheet/sheet"
39
- export * from "./components/sidebar/sidebar"
40
- export * from "./components/status-indicator/status-indicator"
41
- export * from "./components/swatch-picker/swatch-picker"
42
- export * from "./components/tab-bar/tab-bar"
26
+ type ConfirmOptions,
27
+ confirmDialog,
28
+ } from "./components/dialog/dialog-store";
29
+ export * from "./components/labeled-content/labeled-content";
30
+ export * from "./components/picker/picker";
31
+ export * from "./components/popover";
32
+ export * from "./components/search-field/search-field";
33
+ export * from "./components/section/section";
34
+ export * from "./components/inline-disclosure/inline-disclosure";
35
+ export * from "./components/disclosure-group/disclosure-group";
36
+ export * from "./components/menu/menu";
37
+ export * from "./components/segmented-control/segmented-control";
38
+ export * from "./components/sheet/sheet";
39
+ export * from "./components/sidebar/sidebar";
40
+ export * from "./components/status-indicator/status-indicator";
41
+ export * from "./components/swatch-picker/swatch-picker";
42
+ export * from "./components/tab-bar/tab-bar";
43
43
  export {
44
- type ToastOptions,
45
- type ToastTone,
46
- toast,
47
- } from "./components/toast/toast-store"
48
- export * from "./components/toast/toaster"
49
- export * from "./components/toggle/toggle"
50
- export * from "./components/slider/slider"
51
- export * from "./components/stepper/stepper"
52
- export * from "./components/progress-view/progress-view"
53
- export * from "./components/divider/divider"
54
- export * from "./components/tooltip/tooltip-host"
55
- export * from "./hooks/use-anchored-placement"
56
- export * from "./hooks/use-dismiss"
57
- export * from "./hooks/use-media-query"
58
- export * from "./icons/icons"
59
- export * from "./lib/anchored-placement"
60
- export * from "./lib/class-names"
61
- export * from "./lib/haptics"
62
- export * from "./lib/clipboard"
63
- export * from "./lib/screen-edges"
64
- export * from "./motion/transitions"
65
- export { AnimatePresence, motion } from "motion/react"
66
- export * from "./appearance"
44
+ type ToastOptions,
45
+ type ToastTone,
46
+ toast,
47
+ } from "./components/toast/toast-store";
48
+ export * from "./components/toast/toaster";
49
+ export * from "./components/toggle/toggle";
50
+ export * from "./components/slider/slider";
51
+ export * from "./components/stepper/stepper";
52
+ export * from "./components/progress-view/progress-view";
53
+ export * from "./components/divider/divider";
54
+ export * from "./components/tooltip/tooltip-host";
55
+ export * from "./hooks/use-anchored-placement";
56
+ export * from "./hooks/use-dismiss";
57
+ export * from "./hooks/use-media-query";
58
+ export * from "./icons/icons";
59
+ export * from "./lib/anchored-placement";
60
+ export * from "./lib/class-names";
61
+ export * from "./lib/haptics";
62
+ export * from "./lib/clipboard";
63
+ export * from "./lib/screen-edges";
64
+ export * from "./motion/transitions";
65
+ export { AnimatePresence, motion } from "motion/react";
66
+ export * from "./appearance";
67
67
 
68
- export * from "./components/date-picker/date-picker"
69
- export * from "./components/compact-date-picker/compact-date-picker"
70
- export * from "./components/compact-time-picker/compact-time-picker"
68
+ export * from "./components/date-picker/date-picker";
69
+ export * from "./components/compact-date-picker/compact-date-picker";
70
+ export * from "./components/compact-time-picker/compact-time-picker";
71
71
 
72
- export { Dialog } from "./components/dialog"
72
+ export { Dialog } from "./components/dialog";
73
73
  export type {
74
- DialogProps,
75
- DialogPresentation,
76
- DialogTone,
77
- } from "./components/dialog/dialog"
74
+ DialogProps,
75
+ DialogPresentation,
76
+ DialogTone,
77
+ } from "./components/dialog/dialog";
78
78
  export type {
79
- ContentViewHeadingLevel,
80
- ContentViewVariant,
81
- } from "./components/content-view/content-view"
82
- export type { StatTileTone } from "./components/stat-tile/stat-tile"
79
+ ContentViewHeadingLevel,
80
+ ContentViewVariant,
81
+ } from "./components/content-view/content-view";
82
+ export type { StatTileTone } from "./components/stat-tile/stat-tile";
83
83
 
84
- export * from "./hooks/use-visual-viewport"
84
+ export * from "./hooks/use-visual-viewport";
85
85
 
86
- export { registerFloatingLayer } from "./lib/floating-layers"
86
+ export { registerFloatingLayer } from "./lib/floating-layers";
87
87
 
88
- export * from "./hooks/use-minimize-on-scroll"
88
+ export * from "./hooks/use-minimize-on-scroll";
89
89
 
90
- export * from "./components/code-block/code-block"
91
- export * from "./components/keyboard-shortcut/keyboard-shortcut"
92
- export * from "./components/content-unavailable-view/content-unavailable-view"
93
- export * from "./components/combo-box/combo-box"
94
- export * from "./components/radio-group/radio-group"
95
- export * from "./components/breadcrumb/breadcrumb"
96
- export * from "./components/skeleton/skeleton"
90
+ export * from "./components/code-block/code-block";
91
+ export * from "./components/keyboard-shortcut/keyboard-shortcut";
92
+ export * from "./components/content-unavailable-view/content-unavailable-view";
93
+ export * from "./components/combo-box/combo-box";
94
+ export * from "./components/radio-group/radio-group";
95
+ export * from "./components/breadcrumb/breadcrumb";
96
+ export * from "./components/skeleton/skeleton";
97
97
 
98
- export * from "./components/command/command"
98
+ export * from "./components/command/command";
99
99
 
100
- export * from "./components/slosh-slider/slosh-slider"
101
- export * from "./components/wake-slider/wake-slider"
100
+ export * from "./components/slosh-slider/slosh-slider";
101
+ export * from "./components/wake-slider/wake-slider";
102
102
 
103
- export * from "./components/one-time-code/one-time-code"
103
+ export * from "./components/one-time-code/one-time-code";
104
104
 
105
- export * from "./components/aspect-ratio/aspect-ratio"
105
+ export * from "./components/aspect-ratio/aspect-ratio";
106
106
 
107
- export * from "./components/morph"
107
+ export * from "./components/morph";
@@ -1,29 +1,29 @@
1
- import { screenEdges } from "./screen-edges"
1
+ import { screenEdges } from "./screen-edges";
2
2
 
3
3
  export interface AnchoredPlacement {
4
- left: number
5
- width: number
6
- top?: number
7
- bottom?: number
8
- /** Opens upward, so motion should grow from the bottom edge. */
9
- above: boolean
10
- /**
11
- * Set when the chosen side has less room than the panel needs: the
12
- * panel scrolls inside this height instead of crossing the edge.
13
- */
14
- maxHeight?: number
4
+ left: number;
5
+ width: number;
6
+ top?: number;
7
+ bottom?: number;
8
+ /** Opens upward, so motion should grow from the bottom edge. */
9
+ above: boolean;
10
+ /**
11
+ * Set when the chosen side has less room than the panel needs: the
12
+ * panel scrolls inside this height instead of crossing the edge.
13
+ */
14
+ maxHeight?: number;
15
15
  }
16
16
 
17
17
  /** The measurements placement decides on, so the math stays testable. */
18
18
  export interface PlacementFrame {
19
- anchor: { top: number; bottom: number; left: number }
20
- viewport: { width: number; height: number }
21
- edges: {
22
- top: number
23
- right: number
24
- bottom: number
25
- left: number
26
- }
19
+ anchor: { top: number; bottom: number; left: number };
20
+ viewport: { width: number; height: number };
21
+ edges: {
22
+ top: number;
23
+ right: number;
24
+ bottom: number;
25
+ left: number;
26
+ };
27
27
  }
28
28
 
29
29
  /**
@@ -32,55 +32,60 @@ export interface PlacementFrame {
32
32
  * screen's safe edges.
33
33
  */
34
34
  export function placeInViewport(
35
- { anchor, viewport, edges }: PlacementFrame,
36
- width: number,
37
- needed: number,
38
- gap = 6,
39
- overlap = false
35
+ { anchor, viewport, edges }: PlacementFrame,
36
+ width: number,
37
+ needed: number,
38
+ gap = 6,
39
+ overlap = false,
40
40
  ): AnchoredPlacement {
41
- if (overlap) gap = anchor.top - anchor.bottom
42
- const fitted = Math.min(width, viewport.width - edges.left - edges.right)
43
- const left = Math.min(Math.max(edges.left, anchor.left), viewport.width - fitted - edges.right)
44
- const roomBelow = viewport.height - anchor.bottom - gap - edges.bottom
45
- const roomAbove = anchor.top - gap - edges.top
46
- const above = roomBelow < needed && roomAbove > roomBelow
47
- const room = above ? roomAbove : roomBelow
48
- const placement: AnchoredPlacement = {
49
- left,
50
- width: fitted,
51
- above,
52
- ...(above ? { bottom: viewport.height - anchor.top + gap } : { top: anchor.bottom + gap }),
53
- }
54
- if (room < needed) placement.maxHeight = room
55
- return placement
41
+ if (overlap) gap = anchor.top - anchor.bottom;
42
+ const fitted = Math.min(width, viewport.width - edges.left - edges.right);
43
+ const left = Math.min(
44
+ Math.max(edges.left, anchor.left),
45
+ viewport.width - fitted - edges.right,
46
+ );
47
+ const roomBelow = viewport.height - anchor.bottom - gap - edges.bottom;
48
+ const roomAbove = anchor.top - gap - edges.top;
49
+ const above = roomBelow < needed && roomAbove > roomBelow;
50
+ const room = above ? roomAbove : roomBelow;
51
+ const placement: AnchoredPlacement = {
52
+ left,
53
+ width: fitted,
54
+ above,
55
+ ...(above
56
+ ? { bottom: viewport.height - anchor.top + gap }
57
+ : { top: anchor.bottom + gap }),
58
+ };
59
+ if (room < needed) placement.maxHeight = room;
60
+ return placement;
56
61
  }
57
62
 
58
63
  /** `placeInViewport` from live DOM measurements. */
59
64
  export function placeAnchored(
60
- anchor: HTMLElement,
61
- width: number,
62
- needed: number,
63
- gap = 6,
64
- overlap = false
65
+ anchor: HTMLElement,
66
+ width: number,
67
+ needed: number,
68
+ gap = 6,
69
+ overlap = false,
65
70
  ): AnchoredPlacement {
66
- const rect = anchor.getBoundingClientRect()
67
- const viewport = window.visualViewport
68
- return placeInViewport(
69
- {
70
- anchor: {
71
- top: rect.top,
72
- bottom: rect.bottom,
73
- left: rect.left,
74
- },
75
- viewport: {
76
- width: viewport?.width ?? window.innerWidth,
77
- height: viewport?.height ?? window.innerHeight,
78
- },
79
- edges: screenEdges(),
80
- },
81
- width,
82
- needed,
83
- gap,
84
- overlap
85
- )
71
+ const rect = anchor.getBoundingClientRect();
72
+ const viewport = window.visualViewport;
73
+ return placeInViewport(
74
+ {
75
+ anchor: {
76
+ top: rect.top,
77
+ bottom: rect.bottom,
78
+ left: rect.left,
79
+ },
80
+ viewport: {
81
+ width: viewport?.width ?? window.innerWidth,
82
+ height: viewport?.height ?? window.innerHeight,
83
+ },
84
+ edges: screenEdges(),
85
+ },
86
+ width,
87
+ needed,
88
+ gap,
89
+ overlap,
90
+ );
86
91
  }
@@ -1,20 +1,21 @@
1
- import { clsx } from "clsx"
2
- import type { ClassValue } from "clsx"
3
- import { extendTailwindMerge } from "tailwind-merge"
4
- import { textStyles } from "@passui/tokens/typography"
1
+ import { clsx } from "clsx";
2
+ import type { ClassValue } from "clsx";
3
+ import { extendTailwindMerge } from "tailwind-merge";
4
+ import { textStyles } from "@passui/tokens/typography";
5
5
 
6
6
  const textStyleClasses = Object.keys(textStyles).map(
7
- (name) => `text-${name.replace(/[A-Z]/g, (character) => `-${character.toLowerCase()}`)}`
8
- )
7
+ (name) =>
8
+ `text-${name.replace(/[A-Z]/g, (character) => `-${character.toLowerCase()}`)}`,
9
+ );
9
10
 
10
11
  const twMerge = extendTailwindMerge({
11
- extend: {
12
- classGroups: {
13
- "font-size": textStyleClasses,
14
- },
15
- },
16
- })
12
+ extend: {
13
+ classGroups: {
14
+ "font-size": textStyleClasses,
15
+ },
16
+ },
17
+ });
17
18
 
18
19
  export function cn(...inputs: ClassValue[]) {
19
- return twMerge(clsx(inputs))
20
+ return twMerge(clsx(inputs));
20
21
  }
@@ -4,11 +4,12 @@
4
4
  * manual copying. This helper never reads the clipboard or adds feedback.
5
5
  */
6
6
  export async function copyText(text: string): Promise<boolean> {
7
- try {
8
- if (typeof navigator === "undefined" || !navigator.clipboard?.writeText) return false
9
- await navigator.clipboard.writeText(text)
10
- return true
11
- } catch {
12
- return false
13
- }
7
+ try {
8
+ if (typeof navigator === "undefined" || !navigator.clipboard?.writeText)
9
+ return false;
10
+ await navigator.clipboard.writeText(text);
11
+ return true;
12
+ } catch {
13
+ return false;
14
+ }
14
15
  }
package/src/lib/date.ts CHANGED
@@ -1,85 +1,86 @@
1
1
  // Dates through the platform's own Intl, no date library in the bundle.
2
2
  // en-US on purpose: the server renders first, so a viewer-specific locale
3
3
  // would make server and browser disagree on hydration.
4
- const LOCALE = "en-US"
4
+ const LOCALE = "en-US";
5
5
 
6
6
  const PRESETS = {
7
- /** Aug 30, 2026 */
8
- PP: { dateStyle: "medium" },
9
- /** August 30, 2026 */
10
- PPP: { dateStyle: "long" },
11
- /** Aug 30, 2026, 2:15 PM */
12
- PPp: { dateStyle: "medium", timeStyle: "short" },
13
- /** August 30, 2026 at 2:15 PM */
14
- PPPp: { dateStyle: "long", timeStyle: "short" },
15
- /** Aug 30 */
16
- "MMM d": { month: "short", day: "numeric" },
17
- /** Aug 30, 14:15 */
18
- "MMM d, HH:mm": {
19
- month: "short",
20
- day: "numeric",
21
- hour: "2-digit",
22
- minute: "2-digit",
23
- hourCycle: "h23",
24
- },
25
- /** Aug 30, 14:15:09 */
26
- "MMM d, HH:mm:ss": {
27
- month: "short",
28
- day: "numeric",
29
- hour: "2-digit",
30
- minute: "2-digit",
31
- second: "2-digit",
32
- hourCycle: "h23",
33
- },
34
- } satisfies Record<string, Intl.DateTimeFormatOptions>
7
+ /** Aug 30, 2026 */
8
+ PP: { dateStyle: "medium" },
9
+ /** August 30, 2026 */
10
+ PPP: { dateStyle: "long" },
11
+ /** Aug 30, 2026, 2:15 PM */
12
+ PPp: { dateStyle: "medium", timeStyle: "short" },
13
+ /** August 30, 2026 at 2:15 PM */
14
+ PPPp: { dateStyle: "long", timeStyle: "short" },
15
+ /** Aug 30 */
16
+ "MMM d": { month: "short", day: "numeric" },
17
+ /** Aug 30, 14:15 */
18
+ "MMM d, HH:mm": {
19
+ month: "short",
20
+ day: "numeric",
21
+ hour: "2-digit",
22
+ minute: "2-digit",
23
+ hourCycle: "h23",
24
+ },
25
+ /** Aug 30, 14:15:09 */
26
+ "MMM d, HH:mm:ss": {
27
+ month: "short",
28
+ day: "numeric",
29
+ hour: "2-digit",
30
+ minute: "2-digit",
31
+ second: "2-digit",
32
+ hourCycle: "h23",
33
+ },
34
+ } satisfies Record<string, Intl.DateTimeFormatOptions>;
35
35
 
36
- export type DatePreset = keyof typeof PRESETS
36
+ export type DatePreset = keyof typeof PRESETS;
37
37
 
38
- const formatters = new Map<string, Intl.DateTimeFormat>()
38
+ const formatters = new Map<string, Intl.DateTimeFormat>();
39
39
 
40
40
  function formatter(key: string, options: Intl.DateTimeFormatOptions) {
41
- let cached = formatters.get(key)
42
- if (!cached) {
43
- cached = new Intl.DateTimeFormat(LOCALE, options)
44
- formatters.set(key, cached)
45
- }
46
- return cached
41
+ let cached = formatters.get(key);
42
+ if (!cached) {
43
+ cached = new Intl.DateTimeFormat(LOCALE, options);
44
+ formatters.set(key, cached);
45
+ }
46
+ return cached;
47
47
  }
48
48
 
49
49
  /** A stored UTC timestamp shown as its UTC wall-clock time, whatever the viewer's timezone. */
50
50
  export function formatUtc(date: Date | string | number, preset: DatePreset) {
51
- return formatter(`utc:${preset}`, {
52
- ...PRESETS[preset],
53
- timeZone: "UTC",
54
- }).format(new Date(date))
51
+ return formatter(`utc:${preset}`, {
52
+ ...PRESETS[preset],
53
+ timeZone: "UTC",
54
+ }).format(new Date(date));
55
55
  }
56
56
 
57
57
  /** The viewer's local time of day, like 2:15 PM. */
58
58
  export function formatTime(date: Date | string | number) {
59
- return formatter("local:time", {
60
- timeStyle: "short",
61
- }).format(new Date(date))
59
+ return formatter("local:time", {
60
+ timeStyle: "short",
61
+ }).format(new Date(date));
62
62
  }
63
63
 
64
64
  const UNITS: [Intl.RelativeTimeFormatUnit, number][] = [
65
- ["year", 31_536_000],
66
- ["month", 2_592_000],
67
- ["week", 604_800],
68
- ["day", 86_400],
69
- ["hour", 3_600],
70
- ["minute", 60],
71
- ]
65
+ ["year", 31_536_000],
66
+ ["month", 2_592_000],
67
+ ["week", 604_800],
68
+ ["day", 86_400],
69
+ ["hour", 3_600],
70
+ ["minute", 60],
71
+ ];
72
72
 
73
- let relative: Intl.RelativeTimeFormat | undefined
73
+ let relative: Intl.RelativeTimeFormat | undefined;
74
74
 
75
75
  /** "5 minutes ago", "in 30 days": the largest whole unit that fits. */
76
76
  export function fromNow(date: Date | string | number) {
77
- relative ??= new Intl.RelativeTimeFormat(LOCALE, {
78
- numeric: "auto",
79
- })
80
- const seconds = (new Date(date).getTime() - Date.now()) / 1000
81
- for (const [unit, size] of UNITS) {
82
- if (Math.abs(seconds) >= size) return relative.format(Math.round(seconds / size), unit)
83
- }
84
- return relative.format(0, "second")
77
+ relative ??= new Intl.RelativeTimeFormat(LOCALE, {
78
+ numeric: "auto",
79
+ });
80
+ const seconds = (new Date(date).getTime() - Date.now()) / 1000;
81
+ for (const [unit, size] of UNITS) {
82
+ if (Math.abs(seconds) >= size)
83
+ return relative.format(Math.round(seconds / size), unit);
84
+ }
85
+ return relative.format(0, "second");
85
86
  }
@@ -1,38 +1,53 @@
1
- export const FLOATING_LAYER = "data-floating-layer"
1
+ export const FLOATING_LAYER = "data-floating-layer";
2
2
 
3
- const anchors = new WeakMap<Element, Element>()
3
+ const anchors = new WeakMap<Element, Element>();
4
4
 
5
5
  /** Portals retain the ownership of the control that opened them. */
6
- export function registerFloatingLayer(layer: Element, anchor: Element): () => void {
7
- anchors.set(layer, anchor)
8
- return () => anchors.delete(layer)
6
+ export function registerFloatingLayer(
7
+ layer: Element,
8
+ anchor: Element,
9
+ ): () => void {
10
+ anchors.set(layer, anchor);
11
+ return () => anchors.delete(layer);
9
12
  }
10
13
 
11
- export function ownsFloatingLayer(container: Element, target: Element | null): boolean {
12
- let layer = target?.closest(`[${FLOATING_LAYER}]`)
13
- const visited = new Set<Element>()
14
- while (layer && !visited.has(layer)) {
15
- visited.add(layer)
16
- const anchor = anchors.get(layer)
17
- if (!anchor) return false
18
- if (container.contains(anchor)) return true
19
- layer = anchor.closest(`[${FLOATING_LAYER}]`)
20
- }
21
- return false
14
+ export function ownsFloatingLayer(
15
+ container: Element,
16
+ target: Element | null,
17
+ ): boolean {
18
+ let layer = target?.closest(`[${FLOATING_LAYER}]`);
19
+ const visited = new Set<Element>();
20
+ while (layer && !visited.has(layer)) {
21
+ visited.add(layer);
22
+ const anchor = anchors.get(layer);
23
+ if (!anchor) return false;
24
+ if (container.contains(anchor)) return true;
25
+ layer = anchor.closest(`[${FLOATING_LAYER}]`);
26
+ }
27
+ return false;
22
28
  }
23
29
 
24
30
  /** An element belongs to a surface either directly or through a portaled child layer. */
25
- export function belongsToFloatingSurface(surface: Element, target: Element | null): boolean {
26
- return Boolean(target && (surface.contains(target) || ownsFloatingLayer(surface, target)))
31
+ export function belongsToFloatingSurface(
32
+ surface: Element,
33
+ target: Element | null,
34
+ ): boolean {
35
+ return Boolean(
36
+ target && (surface.contains(target) || ownsFloatingLayer(surface, target)),
37
+ );
27
38
  }
28
39
 
29
40
  /** Whether a pointer target is outside both an anchored surface and its nested portals. */
30
41
  export function isOutsideFloatingSurface(
31
- surface: Element | null,
32
- anchor: Element,
33
- target: EventTarget | null
42
+ surface: Element | null,
43
+ anchor: Element,
44
+ target: EventTarget | null,
34
45
  ): boolean {
35
- if (!target || !("closest" in target) || !("contains" in target)) return false
36
- const element = target as Element
37
- return !anchor.contains(element) && (!surface || !belongsToFloatingSurface(surface, element))
46
+ if (!target || !("closest" in target) || !("contains" in target))
47
+ return false;
48
+ const element = target as Element;
49
+ return (
50
+ !anchor.contains(element) &&
51
+ (!surface || !belongsToFloatingSurface(surface, element))
52
+ );
38
53
  }