@hilum/ui 3.2.3 → 3.2.4

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.
@@ -4,6 +4,12 @@ import * as React from "react";
4
4
  import { DropdownMenu } from "radix-ui";
5
5
  import { Check, ChevronRight, Circle } from "lucide-react";
6
6
  import { cn } from "../lib/utils";
7
+ import {
8
+ mobilePopperSheetMotionClassName,
9
+ mobilePopperSheetPositionClassName,
10
+ mobilePopperSheetStyle,
11
+ mobilePopperSheetSurfaceClassName,
12
+ } from "../lib/mobile-popper-sheet";
7
13
 
8
14
  const DropdownMenuRoot = DropdownMenu.Root;
9
15
  const DropdownMenuTrigger = DropdownMenu.Trigger;
@@ -16,22 +22,30 @@ const DropdownMenuContent = React.forwardRef<
16
22
  React.ComponentRef<typeof DropdownMenu.Content>,
17
23
  React.ComponentPropsWithoutRef<typeof DropdownMenu.Content>
18
24
  >(({ className, sideOffset = 4, ...props }, ref) => (
19
- <DropdownMenu.Portal>
20
- <DropdownMenu.Content
21
- ref={ref}
22
- sideOffset={sideOffset}
23
- className={cn(
24
- "z-50 min-w-[8rem] bg-card rounded-xl shadow-natural p-1",
25
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
26
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
27
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
28
- "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
29
- "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
30
- className,
31
- )}
32
- {...props}
33
- />
34
- </DropdownMenu.Portal>
25
+ <>
26
+ <style>{mobilePopperSheetStyle}</style>
27
+ <DropdownMenu.Portal>
28
+ <DropdownMenu.Content
29
+ ref={ref}
30
+ data-hilum-mobile-sheet="true"
31
+ sideOffset={sideOffset}
32
+ className={cn(
33
+ "z-50 min-w-[8rem] bg-card rounded-xl shadow-natural p-1",
34
+ mobilePopperSheetPositionClassName,
35
+ mobilePopperSheetSurfaceClassName,
36
+ "max-sm:overflow-y-auto max-sm:px-2 max-sm:pb-2 max-sm:pt-5",
37
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
38
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
39
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
40
+ mobilePopperSheetMotionClassName,
41
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
42
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
43
+ className,
44
+ )}
45
+ {...props}
46
+ />
47
+ </DropdownMenu.Portal>
48
+ </>
35
49
  ));
36
50
  DropdownMenuContent.displayName = "DropdownMenuContent";
37
51
 
@@ -162,18 +176,26 @@ const DropdownMenuSubContent = React.forwardRef<
162
176
  React.ComponentRef<typeof DropdownMenu.SubContent>,
163
177
  React.ComponentPropsWithoutRef<typeof DropdownMenu.SubContent>
164
178
  >(({ className, ...props }, ref) => (
165
- <DropdownMenu.SubContent
166
- ref={ref}
167
- className={cn(
168
- "z-50 min-w-[8rem] bg-card rounded-xl border border-border shadow-natural p-1",
169
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
170
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
171
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
172
- "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
173
- className,
174
- )}
175
- {...props}
176
- />
179
+ <>
180
+ <style>{mobilePopperSheetStyle}</style>
181
+ <DropdownMenu.SubContent
182
+ ref={ref}
183
+ data-hilum-mobile-sheet="true"
184
+ className={cn(
185
+ "z-50 min-w-[8rem] bg-card rounded-xl border border-border shadow-natural p-1",
186
+ mobilePopperSheetPositionClassName,
187
+ mobilePopperSheetSurfaceClassName,
188
+ "max-sm:overflow-y-auto max-sm:px-2 max-sm:pb-2 max-sm:pt-5",
189
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
190
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
191
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
192
+ mobilePopperSheetMotionClassName,
193
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
194
+ className,
195
+ )}
196
+ {...props}
197
+ />
198
+ </>
177
199
  ));
178
200
  DropdownMenuSubContent.displayName = "DropdownMenuSubContent";
179
201
 
@@ -0,0 +1,171 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { File as FileIcon, Upload } from "lucide-react";
5
+ import { Spinner } from "./spinner";
6
+ import { cn } from "../lib/utils";
7
+
8
+ type FileDropzoneSelectedFile = Pick<File, "name" | "size">;
9
+
10
+ interface FileDropzoneProps extends Omit<
11
+ React.HTMLAttributes<HTMLLabelElement>,
12
+ "children" | "onChange" | "onDrop" | "onDragOver" | "onDragLeave"
13
+ > {
14
+ accept?: string;
15
+ multiple?: boolean;
16
+ disabled?: boolean;
17
+ loading?: boolean;
18
+ loadingText?: React.ReactNode;
19
+ label?: React.ReactNode;
20
+ activeLabel?: React.ReactNode;
21
+ description?: React.ReactNode;
22
+ icon?: React.ReactNode;
23
+ selectedFiles?: readonly FileDropzoneSelectedFile[] | null;
24
+ inputRef?: React.Ref<HTMLInputElement>;
25
+ inputClassName?: string;
26
+ inputName?: string;
27
+ onFilesSelected?: (files: File[]) => void;
28
+ }
29
+
30
+ function formatFileSize(bytes: number) {
31
+ if (!Number.isFinite(bytes) || bytes <= 0) return "0 B";
32
+
33
+ const units = ["B", "KB", "MB", "GB"];
34
+ const unitIndex = Math.min(Math.floor(Math.log(bytes) / Math.log(1024)), units.length - 1);
35
+ const value = bytes / Math.pow(1024, unitIndex);
36
+
37
+ return `${value.toFixed(value >= 10 || unitIndex === 0 ? 0 : 1)} ${units[unitIndex]}`;
38
+ }
39
+
40
+ function getFileSummary(files: readonly FileDropzoneSelectedFile[]) {
41
+ if (files.length === 0) return null;
42
+ if (files.length === 1) return `${files[0].name} (${formatFileSize(files[0].size)})`;
43
+ const totalSize = files.reduce((sum, file) => sum + file.size, 0);
44
+ return `${files.length} files selected (${formatFileSize(totalSize)})`;
45
+ }
46
+
47
+ function FileDropzone({
48
+ accept,
49
+ multiple,
50
+ disabled,
51
+ loading,
52
+ loadingText = "Uploading...",
53
+ label = "Drag files here or click to upload",
54
+ activeLabel = "Drop files to upload",
55
+ description,
56
+ icon,
57
+ selectedFiles,
58
+ inputRef,
59
+ inputClassName,
60
+ inputName,
61
+ onFilesSelected,
62
+ className,
63
+ id,
64
+ tabIndex,
65
+ onKeyDown,
66
+ onClick,
67
+ ...props
68
+ }: FileDropzoneProps) {
69
+ const [isDragging, setIsDragging] = React.useState(false);
70
+ const inputId = React.useId();
71
+ const resolvedId = id ?? inputId;
72
+ const fileSummary = getFileSummary(selectedFiles ?? []);
73
+ const isUnavailable = Boolean(disabled || loading);
74
+
75
+ const emitFiles = React.useCallback(
76
+ (fileList: FileList | null) => {
77
+ if (!fileList || isUnavailable) return;
78
+ onFilesSelected?.(Array.from(fileList));
79
+ },
80
+ [isUnavailable, onFilesSelected],
81
+ );
82
+
83
+ return (
84
+ <label
85
+ htmlFor={resolvedId}
86
+ role="button"
87
+ tabIndex={isUnavailable ? -1 : (tabIndex ?? 0)}
88
+ aria-disabled={isUnavailable || undefined}
89
+ data-dragging={isDragging ? "true" : "false"}
90
+ className={cn(
91
+ "group flex min-h-36 cursor-pointer flex-col items-center justify-center rounded-xl border border-dashed border-border bg-card p-6 text-center shadow-natural",
92
+ "transition-[background-color,border-color,box-shadow,scale] duration-150",
93
+ "hover:border-brand-primary/50 hover:bg-muted/40 active:scale-[0.96]",
94
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
95
+ "data-[dragging=true]:border-brand-primary data-[dragging=true]:bg-brand-secondary/25",
96
+ isUnavailable && "pointer-events-none cursor-not-allowed opacity-60 active:scale-100",
97
+ className,
98
+ )}
99
+ onDragOver={(event) => {
100
+ event.preventDefault();
101
+ if (!isUnavailable) setIsDragging(true);
102
+ }}
103
+ onDragLeave={(event) => {
104
+ event.preventDefault();
105
+ setIsDragging(false);
106
+ }}
107
+ onDrop={(event) => {
108
+ event.preventDefault();
109
+ setIsDragging(false);
110
+ emitFiles(event.dataTransfer.files);
111
+ }}
112
+ onKeyDown={(event) => {
113
+ onKeyDown?.(event);
114
+ if (event.defaultPrevented || isUnavailable) return;
115
+ if (event.key === "Enter" || event.key === " ") {
116
+ event.preventDefault();
117
+ document.getElementById(resolvedId)?.click();
118
+ }
119
+ }}
120
+ onClick={(event) => {
121
+ if (isUnavailable) {
122
+ event.preventDefault();
123
+ return;
124
+ }
125
+ onClick?.(event);
126
+ }}
127
+ {...props}
128
+ >
129
+ <input
130
+ ref={inputRef}
131
+ id={resolvedId}
132
+ name={inputName}
133
+ type="file"
134
+ accept={accept}
135
+ multiple={multiple}
136
+ disabled={isUnavailable}
137
+ className={cn("sr-only", inputClassName)}
138
+ onChange={(event) => emitFiles(event.currentTarget.files)}
139
+ />
140
+ <span
141
+ className={cn(
142
+ "mb-3 flex size-12 items-center justify-center rounded-xl bg-muted text-muted-foreground",
143
+ "transition-[background-color,color,scale] duration-150 group-hover:bg-brand-secondary/35 group-hover:text-foreground",
144
+ "group-data-[dragging=true]:bg-brand-secondary group-data-[dragging=true]:text-foreground",
145
+ )}
146
+ aria-hidden="true"
147
+ >
148
+ {loading ? <Spinner size="sm" /> : (icon ?? <Upload className="size-5" />)}
149
+ </span>
150
+ <span className="body-sm font-medium text-foreground text-balance">
151
+ {loading ? loadingText : isDragging ? activeLabel : fileSummary ? fileSummary : label}
152
+ </span>
153
+ {description && (
154
+ <span className="caption mt-1 max-w-md text-pretty text-muted-foreground">
155
+ {description}
156
+ </span>
157
+ )}
158
+ {fileSummary && !loading && (
159
+ <span className="caption mt-2 inline-flex min-h-7 max-w-full items-center gap-1 rounded-full bg-muted px-2.5 text-muted-foreground">
160
+ <FileIcon className="size-3.5 shrink-0" aria-hidden="true" />
161
+ <span className="min-w-0 truncate">{multiple ? "Ready to upload" : "Selected"}</span>
162
+ </span>
163
+ )}
164
+ </label>
165
+ );
166
+ }
167
+
168
+ FileDropzone.displayName = "FileDropzone";
169
+
170
+ export { FileDropzone, formatFileSize };
171
+ export type { FileDropzoneProps, FileDropzoneSelectedFile };
@@ -0,0 +1,90 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { CircleHelp } from "lucide-react";
5
+ import { cn } from "../lib/utils";
6
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./tooltip";
7
+
8
+ interface HelpTooltipProps {
9
+ text: string;
10
+ learnMoreUrl?: string;
11
+ learnMoreURL?: string;
12
+ placement?: "top" | "right" | "bottom" | "left";
13
+ className?: string;
14
+ contentClassName?: string;
15
+ }
16
+
17
+ function HelpTooltip({
18
+ text,
19
+ learnMoreUrl,
20
+ learnMoreURL,
21
+ placement = "top",
22
+ className,
23
+ contentClassName,
24
+ }: HelpTooltipProps) {
25
+ const [open, setOpen] = React.useState(false);
26
+ const rootRef = React.useRef<HTMLSpanElement>(null);
27
+ const url = learnMoreUrl ?? learnMoreURL;
28
+ const body = text.length > 120 ? `${text.slice(0, 117)}...` : text;
29
+
30
+ React.useEffect(() => {
31
+ if (!open) return;
32
+
33
+ const onPointerDown = (event: PointerEvent) => {
34
+ if (!rootRef.current?.contains(event.target as Node)) {
35
+ setOpen(false);
36
+ }
37
+ };
38
+
39
+ document.addEventListener("pointerdown", onPointerDown);
40
+ return () => document.removeEventListener("pointerdown", onPointerDown);
41
+ }, [open]);
42
+
43
+ return (
44
+ <TooltipProvider delayDuration={150}>
45
+ <Tooltip open={open} onOpenChange={setOpen}>
46
+ <span ref={rootRef} className={cn("inline-flex align-middle", className)}>
47
+ <TooltipTrigger asChild>
48
+ <button
49
+ type="button"
50
+ className={cn(
51
+ "relative ml-1 inline-flex size-5 items-center justify-center rounded-full text-muted-foreground",
52
+ "transition-[color,scale] duration-150 hover:text-foreground active:scale-[0.96]",
53
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
54
+ "before:absolute before:-inset-2.5 before:content-['']",
55
+ )}
56
+ aria-label="Help"
57
+ onClick={(event) => {
58
+ event.preventDefault();
59
+ setOpen((current) => !current);
60
+ }}
61
+ >
62
+ <CircleHelp className="size-4" aria-hidden="true" />
63
+ </button>
64
+ </TooltipTrigger>
65
+ <TooltipContent
66
+ side={placement}
67
+ collisionPadding={12}
68
+ className={cn("max-w-xs text-sm", contentClassName)}
69
+ >
70
+ <p className="text-pretty">{body}</p>
71
+ {url && (
72
+ <a
73
+ href={url}
74
+ target="_blank"
75
+ rel="noopener noreferrer"
76
+ className="mt-1 block text-xs font-medium text-brand-primary underline-offset-4 hover:underline"
77
+ >
78
+ Learn more
79
+ </a>
80
+ )}
81
+ </TooltipContent>
82
+ </span>
83
+ </Tooltip>
84
+ </TooltipProvider>
85
+ );
86
+ }
87
+
88
+ HelpTooltip.displayName = "HelpTooltip";
89
+
90
+ export { HelpTooltip, type HelpTooltipProps };
@@ -0,0 +1,118 @@
1
+ "use client";
2
+
3
+ import * as React from "react";
4
+ import { File, Image } from "lucide-react";
5
+ import { cn } from "../lib/utils";
6
+
7
+ interface MediaAssetCardProps extends React.HTMLAttributes<HTMLDivElement> {
8
+ name: React.ReactNode;
9
+ src?: string | null;
10
+ alt?: string;
11
+ meta?: React.ReactNode;
12
+ details?: React.ReactNode;
13
+ actions?: React.ReactNode;
14
+ selected?: boolean;
15
+ mediaType?: "image" | "file" | "video" | "unknown";
16
+ orientation?: "responsive" | "grid" | "list";
17
+ onSelect?: () => void;
18
+ }
19
+
20
+ function MediaAssetCard({
21
+ name,
22
+ src,
23
+ alt = "",
24
+ meta,
25
+ details,
26
+ actions,
27
+ selected,
28
+ mediaType = src ? "image" : "file",
29
+ orientation = "responsive",
30
+ onSelect,
31
+ className,
32
+ onClick,
33
+ ...props
34
+ }: MediaAssetCardProps) {
35
+ const isInteractive = Boolean(onSelect || onClick);
36
+ const Wrapper = isInteractive ? "button" : "div";
37
+ const isList = orientation === "list";
38
+ const isGrid = orientation === "grid";
39
+ const showImage = Boolean(src && mediaType === "image");
40
+
41
+ const fallbackIcon =
42
+ mediaType === "image" ? (
43
+ <Image className="size-6 text-muted-foreground" aria-hidden="true" />
44
+ ) : (
45
+ <File className="size-6 text-muted-foreground" aria-hidden="true" />
46
+ );
47
+
48
+ return (
49
+ <div
50
+ className={cn(
51
+ "group min-w-0",
52
+ isGrid && "overflow-hidden rounded-xl border border-border bg-card shadow-natural",
53
+ isList && "flex gap-3 border-b border-border py-3 last:border-b-0",
54
+ orientation === "responsive" &&
55
+ "flex gap-3 border-b border-border py-3 last:border-b-0 sm:block sm:overflow-hidden sm:rounded-xl sm:border sm:bg-card sm:py-0 sm:shadow-natural",
56
+ selected && "ring-2 ring-brand-primary/35",
57
+ className,
58
+ )}
59
+ data-slot="media-asset-card"
60
+ data-selected={selected ? "true" : "false"}
61
+ {...props}
62
+ >
63
+ <Wrapper
64
+ type={Wrapper === "button" ? "button" : undefined}
65
+ className={cn(
66
+ "relative flex shrink-0 items-center justify-center overflow-hidden bg-muted text-left",
67
+ "transition-[background-color,box-shadow,scale] duration-150",
68
+ isInteractive &&
69
+ "cursor-pointer active:scale-[0.96] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-primary/30",
70
+ isList && "size-16 rounded-lg",
71
+ isGrid && "aspect-square w-full",
72
+ orientation === "responsive" &&
73
+ "size-16 rounded-lg sm:aspect-square sm:size-auto sm:w-full sm:rounded-none",
74
+ )}
75
+ onClick={(event) => {
76
+ onClick?.(event as React.MouseEvent<HTMLDivElement>);
77
+ if (!event.defaultPrevented) onSelect?.();
78
+ }}
79
+ aria-label={isInteractive && typeof name === "string" ? name : undefined}
80
+ >
81
+ {showImage ? (
82
+ <img
83
+ src={src ?? undefined}
84
+ alt={alt}
85
+ className="size-full object-cover shadow-[inset_0_0_0_1px_rgba(0,0,0,0.1)] dark:shadow-[inset_0_0_0_1px_rgba(255,255,255,0.1)]"
86
+ />
87
+ ) : (
88
+ fallbackIcon
89
+ )}
90
+ </Wrapper>
91
+
92
+ <div
93
+ className={cn(
94
+ "min-w-0 flex-1",
95
+ isGrid && "p-3",
96
+ isList && "py-0.5",
97
+ orientation === "responsive" && "py-0.5 sm:p-3",
98
+ )}
99
+ >
100
+ <div className="flex min-w-0 items-start justify-between gap-2">
101
+ <div className="min-w-0">
102
+ <p className="line-clamp-2 break-words text-sm font-medium leading-5 text-foreground sm:truncate">
103
+ {name}
104
+ </p>
105
+ {meta && <p className="caption mt-0.5 truncate text-muted-foreground">{meta}</p>}
106
+ </div>
107
+ {actions && <div className="shrink-0">{actions}</div>}
108
+ </div>
109
+ {details && <div className="caption mt-2 text-pretty text-muted-foreground">{details}</div>}
110
+ </div>
111
+ </div>
112
+ );
113
+ }
114
+
115
+ MediaAssetCard.displayName = "MediaAssetCard";
116
+
117
+ export { MediaAssetCard };
118
+ export type { MediaAssetCardProps };
@@ -4,6 +4,12 @@ import * as React from "react";
4
4
  import { Menubar } from "radix-ui";
5
5
  import { Check, ChevronRight, Circle } from "lucide-react";
6
6
  import { cn } from "../lib/utils";
7
+ import {
8
+ mobilePopperSheetMotionClassName,
9
+ mobilePopperSheetPositionClassName,
10
+ mobilePopperSheetStyle,
11
+ mobilePopperSheetSurfaceClassName,
12
+ } from "../lib/mobile-popper-sheet";
7
13
 
8
14
  const MenubarRoot = React.forwardRef<
9
15
  React.ComponentRef<typeof Menubar.Root>,
@@ -50,24 +56,32 @@ const MenubarContent = React.forwardRef<
50
56
  React.ComponentRef<typeof Menubar.Content>,
51
57
  React.ComponentPropsWithoutRef<typeof Menubar.Content>
52
58
  >(({ className, align = "start", alignOffset = -4, sideOffset = 8, ...props }, ref) => (
53
- <Menubar.Portal>
54
- <Menubar.Content
55
- ref={ref}
56
- align={align}
57
- alignOffset={alignOffset}
58
- sideOffset={sideOffset}
59
- className={cn(
60
- "z-50 min-w-[12rem] bg-card rounded-xl shadow-natural p-1",
61
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
62
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
63
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
64
- "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
65
- "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
66
- className,
67
- )}
68
- {...props}
69
- />
70
- </Menubar.Portal>
59
+ <>
60
+ <style>{mobilePopperSheetStyle}</style>
61
+ <Menubar.Portal>
62
+ <Menubar.Content
63
+ ref={ref}
64
+ data-hilum-mobile-sheet="true"
65
+ align={align}
66
+ alignOffset={alignOffset}
67
+ sideOffset={sideOffset}
68
+ className={cn(
69
+ "z-50 min-w-[12rem] bg-card rounded-xl shadow-natural p-1",
70
+ mobilePopperSheetPositionClassName,
71
+ mobilePopperSheetSurfaceClassName,
72
+ "max-sm:overflow-y-auto max-sm:px-2 max-sm:pb-2 max-sm:pt-5",
73
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
74
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
75
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
76
+ mobilePopperSheetMotionClassName,
77
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
78
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
79
+ className,
80
+ )}
81
+ {...props}
82
+ />
83
+ </Menubar.Portal>
84
+ </>
71
85
  ));
72
86
  MenubarContent.displayName = "MenubarContent";
73
87
 
@@ -191,18 +205,26 @@ const MenubarSubContent = React.forwardRef<
191
205
  React.ComponentRef<typeof Menubar.SubContent>,
192
206
  React.ComponentPropsWithoutRef<typeof Menubar.SubContent>
193
207
  >(({ className, ...props }, ref) => (
194
- <Menubar.SubContent
195
- ref={ref}
196
- className={cn(
197
- "z-50 min-w-[8rem] bg-card rounded-xl border border-border shadow-natural p-1",
198
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
199
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
200
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
201
- "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
202
- className,
203
- )}
204
- {...props}
205
- />
208
+ <>
209
+ <style>{mobilePopperSheetStyle}</style>
210
+ <Menubar.SubContent
211
+ ref={ref}
212
+ data-hilum-mobile-sheet="true"
213
+ className={cn(
214
+ "z-50 min-w-[8rem] bg-card rounded-xl border border-border shadow-natural p-1",
215
+ mobilePopperSheetPositionClassName,
216
+ mobilePopperSheetSurfaceClassName,
217
+ "max-sm:overflow-y-auto max-sm:px-2 max-sm:pb-2 max-sm:pt-5",
218
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
219
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
220
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
221
+ mobilePopperSheetMotionClassName,
222
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
223
+ className,
224
+ )}
225
+ {...props}
226
+ />
227
+ </>
206
228
  ));
207
229
  MenubarSubContent.displayName = "MenubarSubContent";
208
230
 
@@ -3,6 +3,12 @@
3
3
  import * as React from "react";
4
4
  import { Popover } from "radix-ui";
5
5
  import { cn } from "../lib/utils";
6
+ import {
7
+ mobilePopperSheetMotionClassName,
8
+ mobilePopperSheetPositionClassName,
9
+ mobilePopperSheetStyle,
10
+ mobilePopperSheetSurfaceClassName,
11
+ } from "../lib/mobile-popper-sheet";
6
12
 
7
13
  const PopoverRoot = Popover.Root;
8
14
  const PopoverTrigger = Popover.Trigger;
@@ -12,23 +18,31 @@ const PopoverContent = React.forwardRef<
12
18
  React.ComponentRef<typeof Popover.Content>,
13
19
  React.ComponentPropsWithoutRef<typeof Popover.Content>
14
20
  >(({ className, align = "center", sideOffset = 6, ...props }, ref) => (
15
- <Popover.Portal>
16
- <Popover.Content
17
- ref={ref}
18
- align={align}
19
- sideOffset={sideOffset}
20
- className={cn(
21
- "z-50 w-72 rounded-xl bg-card p-4 shadow-natural outline-none",
22
- "data-[state=open]:animate-in data-[state=closed]:animate-out",
23
- "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
24
- "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
25
- "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
26
- "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
27
- className,
28
- )}
29
- {...props}
30
- />
31
- </Popover.Portal>
21
+ <>
22
+ <style>{mobilePopperSheetStyle}</style>
23
+ <Popover.Portal>
24
+ <Popover.Content
25
+ ref={ref}
26
+ data-hilum-mobile-sheet="true"
27
+ align={align}
28
+ sideOffset={sideOffset}
29
+ className={cn(
30
+ "relative z-50 w-72 rounded-xl bg-card p-4 shadow-natural outline-none",
31
+ mobilePopperSheetPositionClassName,
32
+ mobilePopperSheetSurfaceClassName,
33
+ "max-sm:overflow-y-auto max-sm:px-4 max-sm:pb-4 max-sm:pt-6",
34
+ "data-[state=open]:animate-in data-[state=closed]:animate-out",
35
+ "data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
36
+ "data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
37
+ mobilePopperSheetMotionClassName,
38
+ "data-[side=bottom]:slide-in-from-top-2 data-[side=top]:slide-in-from-bottom-2",
39
+ "data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2",
40
+ className,
41
+ )}
42
+ {...props}
43
+ />
44
+ </Popover.Portal>
45
+ </>
32
46
  ));
33
47
  PopoverContent.displayName = "PopoverContent";
34
48