@moloodev/ui 0.0.0-stage → 0.1.0

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 (93) hide show
  1. package/package.json +139 -3
  2. package/registry/components.ts +135 -0
  3. package/registry/fonts.css +81 -0
  4. package/registry/suit-OFL.txt +94 -0
  5. package/registry/wanted-sans-OFL.txt +93 -0
  6. package/src/components/ui/accordion.tsx +74 -0
  7. package/src/components/ui/alert-dialog.tsx +187 -0
  8. package/src/components/ui/alert.tsx +83 -0
  9. package/src/components/ui/aspect-ratio.tsx +22 -0
  10. package/src/components/ui/attachment.tsx +207 -0
  11. package/src/components/ui/avatar.tsx +109 -0
  12. package/src/components/ui/badge.tsx +54 -0
  13. package/src/components/ui/breadcrumb.tsx +126 -0
  14. package/src/components/ui/bubble.tsx +128 -0
  15. package/src/components/ui/button-group.tsx +103 -0
  16. package/src/components/ui/button.tsx +68 -0
  17. package/src/components/ui/calendar.tsx +222 -0
  18. package/src/components/ui/card.tsx +103 -0
  19. package/src/components/ui/carousel.tsx +242 -0
  20. package/src/components/ui/chart.tsx +398 -0
  21. package/src/components/ui/checkbox.tsx +28 -0
  22. package/src/components/ui/collapsible.tsx +40 -0
  23. package/src/components/ui/combobox.tsx +299 -0
  24. package/src/components/ui/command.tsx +218 -0
  25. package/src/components/ui/context-menu.tsx +271 -0
  26. package/src/components/ui/data-table.tsx +919 -0
  27. package/src/components/ui/date-picker.tsx +65 -0
  28. package/src/components/ui/dialog.tsx +224 -0
  29. package/src/components/ui/drawer.tsx +231 -0
  30. package/src/components/ui/dropdown-menu.tsx +268 -0
  31. package/src/components/ui/dropzone.tsx +186 -0
  32. package/src/components/ui/empty.tsx +101 -0
  33. package/src/components/ui/field.tsx +238 -0
  34. package/src/components/ui/form.tsx +83 -0
  35. package/src/components/ui/highlighted-source.tsx +200 -0
  36. package/src/components/ui/hover-card.tsx +51 -0
  37. package/src/components/ui/image-field.tsx +796 -0
  38. package/src/components/ui/input-group.tsx +149 -0
  39. package/src/components/ui/input-otp.tsx +86 -0
  40. package/src/components/ui/input-step.ts +19 -0
  41. package/src/components/ui/input.tsx +100 -0
  42. package/src/components/ui/item.tsx +201 -0
  43. package/src/components/ui/kbd.tsx +91 -0
  44. package/src/components/ui/kind-icon-glob.ts +24 -0
  45. package/src/components/ui/kind-icon-load.ts +52 -0
  46. package/src/components/ui/kind-icon-static.ts +46 -0
  47. package/src/components/ui/kind-icon.tsx +84 -0
  48. package/src/components/ui/kind-mark.tsx +65 -0
  49. package/src/components/ui/label.tsx +29 -0
  50. package/src/components/ui/marker.tsx +71 -0
  51. package/src/components/ui/menubar.tsx +277 -0
  52. package/src/components/ui/message-scroller.tsx +137 -0
  53. package/src/components/ui/message.tsx +92 -0
  54. package/src/components/ui/moloo-logo.tsx +45 -0
  55. package/src/components/ui/native-select.tsx +61 -0
  56. package/src/components/ui/navigation-menu.tsx +168 -0
  57. package/src/components/ui/outline-list.tsx +73 -0
  58. package/src/components/ui/pagination.tsx +130 -0
  59. package/src/components/ui/panel.tsx +108 -0
  60. package/src/components/ui/popover.tsx +90 -0
  61. package/src/components/ui/progress.tsx +117 -0
  62. package/src/components/ui/radio-group.tsx +38 -0
  63. package/src/components/ui/resizable.tsx +52 -0
  64. package/src/components/ui/scroll-area.tsx +54 -0
  65. package/src/components/ui/select.tsx +205 -0
  66. package/src/components/ui/separator.tsx +25 -0
  67. package/src/components/ui/sheet.tsx +140 -0
  68. package/src/components/ui/sidebar.tsx +746 -0
  69. package/src/components/ui/skeleton.tsx +13 -0
  70. package/src/components/ui/slider.tsx +52 -0
  71. package/src/components/ui/spinner.tsx +18 -0
  72. package/src/components/ui/switch.tsx +32 -0
  73. package/src/components/ui/table.tsx +116 -0
  74. package/src/components/ui/tabs.tsx +83 -0
  75. package/src/components/ui/tag-list-input.tsx +194 -0
  76. package/src/components/ui/textarea.tsx +18 -0
  77. package/src/components/ui/timeline.tsx +265 -0
  78. package/src/components/ui/toast.tsx +237 -0
  79. package/src/components/ui/toggle-group.tsx +90 -0
  80. package/src/components/ui/toggle.tsx +45 -0
  81. package/src/components/ui/tooltip.tsx +66 -0
  82. package/src/css.d.ts +1 -0
  83. package/src/hooks/use-clipboard.ts +82 -0
  84. package/src/hooks/use-mobile.ts +21 -0
  85. package/src/index.ts +106 -0
  86. package/src/kind-icon.ts +4 -0
  87. package/src/lib/format-relative-time.ts +41 -0
  88. package/src/lib/list-selection.ts +61 -0
  89. package/src/lib/utils.ts +6 -0
  90. package/src/moloo.css +360 -0
  91. package/src/styles.css +36 -0
  92. package/src/typeset.css +518 -0
  93. package/README.md +0 -3
@@ -0,0 +1,238 @@
1
+ "use client"
2
+
3
+ import { useMemo } from "react"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "../../lib/utils"
7
+ import { Label } from "./label"
8
+ import { Separator } from "./separator"
9
+
10
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
11
+ return (
12
+ <fieldset
13
+ data-slot="field-set"
14
+ className={cn(
15
+ "flex flex-col gap-4 has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function FieldLegend({
24
+ className,
25
+ variant = "legend",
26
+ ...props
27
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
28
+ return (
29
+ <legend
30
+ data-slot="field-legend"
31
+ data-variant={variant}
32
+ className={cn(
33
+ "mb-2.5 font-medium data-[variant=label]:text-sm data-[variant=legend]:text-base",
34
+ className
35
+ )}
36
+ {...props}
37
+ />
38
+ )
39
+ }
40
+
41
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="field-group"
45
+ className={cn(
46
+ "group/field-group @container/field-group flex w-full flex-col gap-5 data-[slot=checkbox-group]:gap-3 *:data-[slot=field-group]:gap-4",
47
+ className
48
+ )}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+
54
+ const fieldVariants = cva(
55
+ "group/field flex w-full gap-2 data-[invalid=true]:text-destructive",
56
+ {
57
+ variants: {
58
+ orientation: {
59
+ vertical: "flex-col *:w-full [&>.sr-only]:w-auto",
60
+ horizontal:
61
+ "flex-row items-center has-[>[data-slot=field-content]]:items-start *:data-[slot=field-label]:flex-auto has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
62
+ responsive:
63
+ "flex-col *:w-full @md/field-group:flex-row @md/field-group:items-center @md/field-group:*:w-auto @md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:*:data-[slot=field-label]:flex-auto [&>.sr-only]:w-auto @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
64
+ },
65
+ },
66
+ defaultVariants: {
67
+ orientation: "vertical",
68
+ },
69
+ }
70
+ )
71
+
72
+ function Field({
73
+ className,
74
+ orientation = "vertical",
75
+ ...props
76
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
77
+ return (
78
+ <div
79
+ role="group"
80
+ data-slot="field"
81
+ data-orientation={orientation}
82
+ className={cn(fieldVariants({ orientation }), className)}
83
+ {...props}
84
+ />
85
+ )
86
+ }
87
+
88
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
89
+ return (
90
+ <div
91
+ data-slot="field-content"
92
+ className={cn(
93
+ "group/field-content flex flex-1 flex-col gap-0.5 leading-snug",
94
+ className
95
+ )}
96
+ {...props}
97
+ />
98
+ )
99
+ }
100
+
101
+ function FieldLabel({
102
+ className,
103
+ ...props
104
+ }: React.ComponentProps<typeof Label>) {
105
+ return (
106
+ <Label
107
+ data-slot="field-label"
108
+ className={cn(
109
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50 has-data-checked:border-primary/30 has-data-checked:bg-primary/5 has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border-2 *:data-[slot=field]:p-2 dark:has-data-checked:border-primary/20 dark:has-data-checked:bg-primary/10",
110
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col",
111
+ className
112
+ )}
113
+ {...props}
114
+ />
115
+ )
116
+ }
117
+
118
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
119
+ return (
120
+ <div
121
+ data-slot="field-label"
122
+ className={cn(
123
+ "flex w-fit items-center gap-2 text-sm/relaxed group-data-[disabled=true]/field:opacity-50",
124
+ className
125
+ )}
126
+ {...props}
127
+ />
128
+ )
129
+ }
130
+
131
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
132
+ return (
133
+ <p
134
+ data-slot="field-description"
135
+ className={cn(
136
+ "text-left text-sm/relaxed leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
137
+ "last:mt-0 nth-last-2:-mt-1",
138
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
139
+ className
140
+ )}
141
+ {...props}
142
+ />
143
+ )
144
+ }
145
+
146
+ function FieldSeparator({
147
+ children,
148
+ className,
149
+ ...props
150
+ }: React.ComponentProps<"div"> & {
151
+ children?: React.ReactNode
152
+ }) {
153
+ return (
154
+ <div
155
+ data-slot="field-separator"
156
+ data-content={!!children}
157
+ className={cn(
158
+ "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
159
+ className
160
+ )}
161
+ {...props}
162
+ >
163
+ <Separator className="absolute inset-0 top-1/2" />
164
+ {children && (
165
+ <span
166
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
167
+ data-slot="field-separator-content"
168
+ >
169
+ {children}
170
+ </span>
171
+ )}
172
+ </div>
173
+ )
174
+ }
175
+
176
+ function FieldError({
177
+ className,
178
+ children,
179
+ errors,
180
+ ...props
181
+ }: React.ComponentProps<"div"> & {
182
+ errors?: Array<{ message?: string } | undefined>
183
+ }) {
184
+ const content = useMemo(() => {
185
+ if (children) {
186
+ return children
187
+ }
188
+
189
+ if (!errors?.length) {
190
+ return null
191
+ }
192
+
193
+ const uniqueErrors = [
194
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
195
+ ]
196
+
197
+ if (uniqueErrors?.length == 1) {
198
+ return uniqueErrors[0]?.message
199
+ }
200
+
201
+ return (
202
+ <ul className="ml-4 flex list-disc flex-col gap-1">
203
+ {uniqueErrors.map(
204
+ (error, index) =>
205
+ error?.message && <li key={index}>{error.message}</li>
206
+ )}
207
+ </ul>
208
+ )
209
+ }, [children, errors])
210
+
211
+ if (!content) {
212
+ return null
213
+ }
214
+
215
+ return (
216
+ <div
217
+ role="alert"
218
+ data-slot="field-error"
219
+ className={cn("text-sm font-normal text-destructive", className)}
220
+ {...props}
221
+ >
222
+ {content}
223
+ </div>
224
+ )
225
+ }
226
+
227
+ export {
228
+ Field,
229
+ FieldLabel,
230
+ FieldDescription,
231
+ FieldError,
232
+ FieldGroup,
233
+ FieldLegend,
234
+ FieldSeparator,
235
+ FieldSet,
236
+ FieldContent,
237
+ FieldTitle,
238
+ }
@@ -0,0 +1,83 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { cva, type VariantProps } from "class-variance-authority"
5
+
6
+ import { cn } from "../../lib/utils"
7
+ import { Label } from "./label"
8
+
9
+ /**
10
+ * Lightweight form composition helpers matching common shadcn Form patterns
11
+ * without requiring react-hook-form. Pair with Field for denser layouts.
12
+ */
13
+
14
+ const formItemVariants = cva("grid gap-2")
15
+
16
+ function Form({ className, ...props }: React.ComponentProps<"form">) {
17
+ return (
18
+ <form
19
+ data-slot="form"
20
+ className={cn("grid gap-6", className)}
21
+ {...props}
22
+ />
23
+ )
24
+ }
25
+
26
+ function FormItem({
27
+ className,
28
+ ...props
29
+ }: React.ComponentProps<"div"> & VariantProps<typeof formItemVariants>) {
30
+ return (
31
+ <div
32
+ data-slot="form-item"
33
+ className={cn(formItemVariants(), className)}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ function FormLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
40
+ return (
41
+ <Label
42
+ data-slot="form-label"
43
+ className={cn("font-display text-sm font-bold tracking-[-0.02em]", className)}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function FormDescription({ className, ...props }: React.ComponentProps<"p">) {
50
+ return (
51
+ <p
52
+ data-slot="form-description"
53
+ className={cn("text-sm text-muted-foreground", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ function FormMessage({ className, ...props }: React.ComponentProps<"p">) {
60
+ return (
61
+ <p
62
+ data-slot="form-message"
63
+ className={cn("text-sm font-medium text-destructive", className)}
64
+ {...props}
65
+ />
66
+ )
67
+ }
68
+
69
+ function FormControl({ className, ...props }: React.ComponentProps<"div">) {
70
+ return (
71
+ <div data-slot="form-control" className={cn(className)} {...props} />
72
+ )
73
+ }
74
+
75
+ export {
76
+ Form,
77
+ FormItem,
78
+ FormLabel,
79
+ FormControl,
80
+ FormDescription,
81
+ FormMessage,
82
+ formItemVariants,
83
+ }
@@ -0,0 +1,200 @@
1
+ import * as React from "react"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "../../lib/utils"
5
+
6
+ const shellVariants = cva(
7
+ "source-highlight relative min-h-0 w-full min-w-0 flex-1 overflow-hidden",
8
+ {
9
+ variants: {
10
+ variant: {
11
+ field:
12
+ "moloo-field moloo-focus-within has-[[aria-invalid=true]]:border-destructive has-[[aria-invalid=true]]:shadow-[var(--moloo-invalid-shadow)]",
13
+ plain: "border-0 bg-background shadow-none",
14
+ },
15
+ },
16
+ defaultVariants: {
17
+ variant: "field",
18
+ },
19
+ }
20
+ )
21
+
22
+ const layerVariants = cva(
23
+ "absolute inset-0 m-0 box-border h-full w-full overflow-auto font-mono text-sm leading-relaxed whitespace-pre-wrap break-words [overflow-wrap:anywhere] [tab-size:2] [font-variant-ligatures:none] [font-synthesis:none]",
24
+ {
25
+ variants: {
26
+ variant: {
27
+ field: "px-3 py-3",
28
+ plain: "px-6 pt-8 pb-32",
29
+ },
30
+ },
31
+ defaultVariants: {
32
+ variant: "field",
33
+ },
34
+ }
35
+ )
36
+
37
+ const MIRROR_STYLES = [
38
+ "box-sizing",
39
+ "width",
40
+ "border-top-width",
41
+ "border-right-width",
42
+ "border-bottom-width",
43
+ "border-left-width",
44
+ "padding-top",
45
+ "padding-right",
46
+ "padding-bottom",
47
+ "padding-left",
48
+ "font",
49
+ "font-family",
50
+ "font-size",
51
+ "font-style",
52
+ "font-variant",
53
+ "font-weight",
54
+ "letter-spacing",
55
+ "line-height",
56
+ "tab-size",
57
+ "text-indent",
58
+ "text-transform",
59
+ "white-space",
60
+ "word-break",
61
+ "overflow-wrap",
62
+ "word-wrap",
63
+ ] as const
64
+
65
+ const CARET_GAP = 96
66
+
67
+ export type HighlightedSourceProps = {
68
+ value: string
69
+ onValueChange: (value: string) => void
70
+ highlight: (value: string) => string
71
+ textareaRef?: React.Ref<HTMLTextAreaElement | null>
72
+ } & Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, "value" | "onChange"> &
73
+ VariantProps<typeof shellVariants>
74
+
75
+ function assignTextareaRef(
76
+ textareaRef: HighlightedSourceProps["textareaRef"],
77
+ node: HTMLTextAreaElement | null
78
+ ) {
79
+ if (typeof textareaRef === "function") textareaRef(node)
80
+ else if (textareaRef) textareaRef.current = node
81
+ }
82
+
83
+ function caretTop(node: HTMLTextAreaElement) {
84
+ const mirror = document.createElement("div")
85
+ const style = getComputedStyle(node)
86
+ for (const name of MIRROR_STYLES) {
87
+ mirror.style.setProperty(name, style.getPropertyValue(name))
88
+ }
89
+ mirror.style.position = "absolute"
90
+ mirror.style.visibility = "hidden"
91
+ mirror.style.pointerEvents = "none"
92
+ mirror.style.top = "0"
93
+ mirror.style.left = "-9999px"
94
+ mirror.style.height = "auto"
95
+ mirror.style.width = `${node.clientWidth}px`
96
+ mirror.style.overflow = "hidden"
97
+ mirror.textContent = node.value.slice(0, node.selectionEnd)
98
+ const mark = document.createElement("span")
99
+ mark.textContent = "\u200b"
100
+ mirror.append(mark)
101
+ document.body.append(mirror)
102
+ const top = mark.offsetTop
103
+ mirror.remove()
104
+ return top
105
+ }
106
+
107
+ function revealCaret(node: HTMLTextAreaElement, gap = CARET_GAP) {
108
+ const style = getComputedStyle(node)
109
+ const lineHeight = parseFloat(style.lineHeight) || parseFloat(style.fontSize) * 1.625
110
+ const bottom = caretTop(node) + lineHeight
111
+ const limit = node.scrollTop + node.clientHeight - gap
112
+ if (bottom <= limit) return
113
+ const max = Math.max(0, node.scrollHeight - node.clientHeight)
114
+ node.scrollTop = Math.min(max, node.scrollTop + (bottom - limit))
115
+ }
116
+
117
+ function HighlightedSource({
118
+ value,
119
+ onValueChange,
120
+ highlight,
121
+ textareaRef,
122
+ className,
123
+ onScroll,
124
+ variant = "field",
125
+ ...textareaProps
126
+ }: HighlightedSourceProps) {
127
+ const layerClass = layerVariants({ variant })
128
+ const preRef = React.useRef<HTMLPreElement>(null)
129
+ const nodeRef = React.useRef<HTMLTextAreaElement | null>(null)
130
+ const scrollRef = React.useRef({ top: 0, left: 0 })
131
+ const valueRef = React.useRef(value)
132
+ const html = `${highlight(value)}\n`
133
+
134
+ function bindTextarea(node: HTMLTextAreaElement | null) {
135
+ nodeRef.current = node
136
+ assignTextareaRef(textareaRef, node)
137
+ }
138
+
139
+ React.useLayoutEffect(() => {
140
+ const node = nodeRef.current
141
+ if (!node) return
142
+ const run = () => {
143
+ node.scrollTop = scrollRef.current.top
144
+ node.scrollLeft = scrollRef.current.left
145
+ if (document.activeElement === node) {
146
+ revealCaret(node)
147
+ scrollRef.current = { top: node.scrollTop, left: node.scrollLeft }
148
+ }
149
+ if (preRef.current) {
150
+ preRef.current.scrollTop = node.scrollTop
151
+ preRef.current.scrollLeft = node.scrollLeft
152
+ }
153
+ valueRef.current = value
154
+ }
155
+ run()
156
+ const frame = requestAnimationFrame(run)
157
+ return () => cancelAnimationFrame(frame)
158
+ }, [value])
159
+
160
+ return (
161
+ <div
162
+ data-slot="highlighted-source"
163
+ data-variant={variant}
164
+ className={cn(shellVariants({ variant }), className)}
165
+ >
166
+ <pre
167
+ ref={preRef}
168
+ aria-hidden
169
+ className={cn(layerClass, "pointer-events-none text-foreground")}
170
+ dangerouslySetInnerHTML={{ __html: html }}
171
+ />
172
+ <textarea
173
+ {...textareaProps}
174
+ ref={bindTextarea}
175
+ value={value}
176
+ spellCheck={false}
177
+ onChange={(event) => {
178
+ onValueChange(event.target.value)
179
+ }}
180
+ onScroll={(event) => {
181
+ if (valueRef.current !== value) return
182
+ const node = event.currentTarget
183
+ scrollRef.current = { top: node.scrollTop, left: node.scrollLeft }
184
+ if (preRef.current) {
185
+ preRef.current.scrollTop = node.scrollTop
186
+ preRef.current.scrollLeft = node.scrollLeft
187
+ }
188
+ onScroll?.(event)
189
+ }}
190
+ className={cn(
191
+ layerClass,
192
+ "z-1 resize-none appearance-none rounded-none border-0 bg-transparent text-transparent caret-foreground shadow-none outline-none",
193
+ "[field-sizing:fixed] [-webkit-text-fill-color:transparent] selection:bg-primary/25"
194
+ )}
195
+ />
196
+ </div>
197
+ )
198
+ }
199
+
200
+ export { HighlightedSource }
@@ -0,0 +1,51 @@
1
+ "use client"
2
+
3
+ import { PreviewCard as PreviewCardPrimitive } from "@base-ui/react/preview-card"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ function HoverCard({ ...props }: PreviewCardPrimitive.Root.Props) {
8
+ return <PreviewCardPrimitive.Root data-slot="hover-card" {...props} />
9
+ }
10
+
11
+ function HoverCardTrigger({ ...props }: PreviewCardPrimitive.Trigger.Props) {
12
+ return (
13
+ <PreviewCardPrimitive.Trigger data-slot="hover-card-trigger" {...props} />
14
+ )
15
+ }
16
+
17
+ function HoverCardContent({
18
+ className,
19
+ side = "bottom",
20
+ sideOffset = 4,
21
+ align = "center",
22
+ alignOffset = 4,
23
+ ...props
24
+ }: PreviewCardPrimitive.Popup.Props &
25
+ Pick<
26
+ PreviewCardPrimitive.Positioner.Props,
27
+ "align" | "alignOffset" | "side" | "sideOffset"
28
+ >) {
29
+ return (
30
+ <PreviewCardPrimitive.Portal data-slot="hover-card-portal">
31
+ <PreviewCardPrimitive.Positioner
32
+ align={align}
33
+ alignOffset={alignOffset}
34
+ side={side}
35
+ sideOffset={sideOffset}
36
+ className="isolate z-50"
37
+ >
38
+ <PreviewCardPrimitive.Popup
39
+ data-slot="hover-card-content"
40
+ className={cn(
41
+ "z-50 w-64 origin-(--transform-origin) rounded-md moloo-surface bg-popover p-2.5 text-sm/relaxed text-popover-foreground shadow-moloo-lg outline-hidden duration-100 data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
42
+ className
43
+ )}
44
+ {...props}
45
+ />
46
+ </PreviewCardPrimitive.Positioner>
47
+ </PreviewCardPrimitive.Portal>
48
+ )
49
+ }
50
+
51
+ export { HoverCard, HoverCardTrigger, HoverCardContent }