diff --git a/app/(navigation)/presets/components/PresetDetail.tsx b/app/(navigation)/presets/components/PresetDetail.tsx
index 913643c6..e89efef6 100644
--- a/app/(navigation)/presets/components/PresetDetail.tsx
+++ b/app/(navigation)/presets/components/PresetDetail.tsx
@@ -38,6 +38,7 @@ import { AiModel } from "@/api/ai";
import { Extension } from "@/api/store";
import { getExtensionIdsFromString } from "@/utils/getExtensionIdsFromString";
import { AIExtension } from "@/components/ai-extension";
+import { FloatingActionBar } from "@/components/floating-action-bar";
import { renderSafePromptContent } from "@/utils/sanitizePromptContent";
import { isTouchDevice } from "../utils/isTouchDevice";
import { shortenUrl } from "@/utils/common";
@@ -370,23 +371,27 @@ export function PresetDetail({ preset, relatedPresets, models, extensions }: Pre
- {/* Floating Action Bar for Mobile */}
- {isTouch && (
-
-
-
-
-
- )}
+ ,
+ label: "Add to Raycast",
+ onClick: handleAddToRaycast,
+ variant: "primary",
+ },
+ {
+ icon: ,
+ label: "Copy JSON",
+ onClick: handleCopyData,
+ },
+ {
+ icon: ,
+ label: "Share URL",
+ onClick: handleCopyUrl,
+ },
+ ]}
+ />
>
);
}
diff --git a/app/(navigation)/prompts/[[...slug]]/prompts.tsx b/app/(navigation)/prompts/[[...slug]]/prompts.tsx
index dbb36bae..13eca675 100644
--- a/app/(navigation)/prompts/[[...slug]]/prompts.tsx
+++ b/app/(navigation)/prompts/[[...slug]]/prompts.tsx
@@ -40,6 +40,7 @@ import { TooltipTrigger } from "@/components/tooltip";
import { Extension } from "@/api/store";
import { AIExtension } from "@/components/ai-extension";
import { renderSafePromptContent } from "@/utils/sanitizePromptContent";
+import { FloatingActionBar } from "@/components/floating-action-bar";
type Props = {
models: AiModel[];
@@ -453,23 +454,27 @@ export function Prompts({ models, extensions }: Props) {
- {/* Floating Action Bar for Mobile */}
- {isTouch && selectedPrompts.length > 0 && (
-
-
-
-
-
- )}
+ 0}
+ actions={[
+ {
+ icon: ,
+ label: "Add to Raycast",
+ onClick: handleAddToRaycast,
+ variant: "primary",
+ },
+ {
+ icon: ,
+ label: "Copy JSON",
+ onClick: handleCopyData,
+ },
+ {
+ icon: ,
+ label: "Share URL",
+ onClick: handleCopyUrl,
+ },
+ ]}
+ />
);
}
diff --git a/app/(navigation)/prompts/shared/shared.tsx b/app/(navigation)/prompts/shared/shared.tsx
index 989ffc77..fa3378e1 100644
--- a/app/(navigation)/prompts/shared/shared.tsx
+++ b/app/(navigation)/prompts/shared/shared.tsx
@@ -44,6 +44,7 @@ import { Extension } from "@/api/store";
import { AIExtension } from "@/components/ai-extension";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/tooltip";
import { renderSafePromptContent } from "@/utils/sanitizePromptContent";
+import { FloatingActionBar } from "@/components/floating-action-bar";
export function Shared({ prompts, extensions }: { prompts: Prompt[]; extensions: Extension[] }) {
const router = useRouter();
@@ -346,23 +347,27 @@ export function Shared({ prompts, extensions }: { prompts: Prompt[]; extensions:
)}
- {/* Floating Action Bar for Mobile */}
- {isTouch && selectedPrompts.length > 0 && (
-
-
-
-
-
- )}
+ 0}
+ actions={[
+ {
+ icon: ,
+ label: "Add to Raycast",
+ onClick: handleAddToRaycast,
+ variant: "primary",
+ },
+ {
+ icon: ,
+ label: "Copy JSON",
+ onClick: handleCopyData,
+ },
+ {
+ icon: ,
+ label: "Share URL",
+ onClick: handleCopyUrl,
+ },
+ ]}
+ />
);
}
diff --git a/app/(navigation)/quicklinks/[[...slug]]/quicklinks.tsx b/app/(navigation)/quicklinks/[[...slug]]/quicklinks.tsx
index 2d422dea..6f61f740 100644
--- a/app/(navigation)/quicklinks/[[...slug]]/quicklinks.tsx
+++ b/app/(navigation)/quicklinks/[[...slug]]/quicklinks.tsx
@@ -34,6 +34,7 @@ import { shortenUrl } from "@/utils/common";
import { toast } from "@/components/toast";
import { Input, InputSlot } from "@/components/input";
import { getRaycastFlavor } from "@/app/RaycastFlavor";
+import { FloatingActionBar } from "@/components/floating-action-bar";
export function Quicklinks() {
const [enableViewObserver, setEnableViewObserver] = React.useState(false);
@@ -432,23 +433,27 @@ export function Quicklinks() {
- {/* Floating Action Bar for Mobile */}
- {isTouch && selectedQuicklinks.length > 0 && (
-
-
-
-
-
- )}
+ 0}
+ actions={[
+ {
+ icon: ,
+ label: "Add to Raycast",
+ onClick: handleAddToRaycast,
+ variant: "primary",
+ },
+ {
+ icon: ,
+ label: "Copy JSON",
+ onClick: handleCopyData,
+ },
+ {
+ icon: ,
+ label: "Share URL",
+ onClick: handleCopyUrl,
+ },
+ ]}
+ />
);
}
diff --git a/app/(navigation)/quicklinks/shared/shared.tsx b/app/(navigation)/quicklinks/shared/shared.tsx
index f5ce5d10..2b6a1cc7 100644
--- a/app/(navigation)/quicklinks/shared/shared.tsx
+++ b/app/(navigation)/quicklinks/shared/shared.tsx
@@ -22,6 +22,7 @@ import { QuicklinkComponent } from "../components/quicklink";
import { toast } from "@/components/toast";
import { shortenUrl } from "@/utils/common";
import { getRaycastFlavor } from "@/app/RaycastFlavor";
+import { FloatingActionBar } from "@/components/floating-action-bar";
export function Shared({ quicklinks }: { quicklinks: Quicklink[] }) {
const router = useRouter();
@@ -297,23 +298,27 @@ export function Shared({ quicklinks }: { quicklinks: Quicklink[] }) {
)}
- {/* Floating Action Bar for Mobile */}
- {isTouch && selectedQuicklinks.length > 0 && (
-
-
-
-
-
- )}
+ 0}
+ actions={[
+ {
+ icon: ,
+ label: "Add to Raycast",
+ onClick: handleAddToRaycast,
+ variant: "primary",
+ },
+ {
+ icon: ,
+ label: "Copy JSON",
+ onClick: handleCopyData,
+ },
+ {
+ icon: ,
+ label: "Share URL",
+ onClick: handleCopyUrl,
+ },
+ ]}
+ />
);
}
diff --git a/app/(navigation)/snippets/[[...slug]]/snippets.tsx b/app/(navigation)/snippets/[[...slug]]/snippets.tsx
index 30ce455d..d3385a21 100644
--- a/app/(navigation)/snippets/[[...slug]]/snippets.tsx
+++ b/app/(navigation)/snippets/[[...slug]]/snippets.tsx
@@ -41,6 +41,7 @@ import { ButtonGroup } from "@/components/button-group";
import { InfoDialog } from "../components/InfoDialog";
import { Kbd, Kbds } from "@/components/kbd";
import { getRaycastFlavor, getIsWindows } from "@/app/RaycastFlavor";
+import { FloatingActionBar } from "@/components/floating-action-bar";
const modifiers = ["!", ":", "_", "__", "-", "@", "@@", "$", ";", ";;", "/", "//", "none"] as const;
@@ -549,23 +550,27 @@ export default function Snippets() {
- {/* Floating Action Bar for Mobile */}
- {isTouch && selectedSnippets.length > 0 && (
-
-
-
-
-
- )}
+ 0}
+ actions={[
+ {
+ icon: ,
+ label: "Add to Raycast",
+ onClick: handleAddToRaycast,
+ variant: "primary",
+ },
+ {
+ icon: ,
+ label: "Copy JSON",
+ onClick: handleCopyData,
+ },
+ {
+ icon: ,
+ label: "Share URL",
+ onClick: handleCopyUrl,
+ },
+ ]}
+ />
);
}
diff --git a/app/(navigation)/snippets/shared/shared.tsx b/app/(navigation)/snippets/shared/shared.tsx
index ee754ea8..76c9651f 100644
--- a/app/(navigation)/snippets/shared/shared.tsx
+++ b/app/(navigation)/snippets/shared/shared.tsx
@@ -18,6 +18,7 @@ import { Snippet } from "../snippets";
import { ButtonGroup } from "@/components/button-group";
import { InfoDialog } from "../components/InfoDialog";
import { Kbd, Kbds } from "@/components/kbd";
+import { FloatingActionBar } from "@/components/floating-action-bar";
export function Shared({ snippets }: { snippets: Snippet[] }) {
const router = useRouter();
@@ -319,23 +320,27 @@ export function Shared({ snippets }: { snippets: Snippet[] }) {
- {/* Floating Action Bar for Mobile */}
- {isTouch && selectedSnippets.length > 0 && (
-
-
-
-
-
- )}
+ 0}
+ actions={[
+ {
+ icon: ,
+ label: "Add to Raycast",
+ onClick: handleAddToRaycast,
+ variant: "primary",
+ },
+ {
+ icon: ,
+ label: "Copy JSON",
+ onClick: handleCopyData,
+ },
+ {
+ icon: ,
+ label: "Download",
+ onClick: handleDownload,
+ },
+ ]}
+ />
);
}
diff --git a/components/floating-action-bar.module.css b/components/floating-action-bar.module.css
new file mode 100644
index 00000000..d865522a
--- /dev/null
+++ b/components/floating-action-bar.module.css
@@ -0,0 +1,65 @@
+.floatingActionBar {
+ position: fixed;
+ z-index: 50;
+ bottom: 12px;
+ left: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding: 8px;
+ border-radius: 16px;
+ backdrop-filter: blur(20px);
+ background-color: rgba(0, 0, 0, 0.9);
+ box-shadow:
+ 0 8px 32px rgba(0, 0, 0, 0.3),
+ inset 0 0 0 1px rgba(255, 255, 255, 0.1);
+ gap: 8px;
+ transform: translateX(-50%);
+ transition: all 300ms ease;
+
+ @media (min-width: 768px) {
+ display: none;
+ }
+}
+
+.floatingActionButton {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: 8px 12px;
+ border: none;
+ border-radius: 8px;
+ background-color: transparent;
+ color: rgba(255, 255, 255, 0.8);
+ cursor: pointer;
+ font-size: 12px;
+ font-weight: 500;
+ gap: 6px;
+ transition: all 200ms ease;
+ white-space: nowrap;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, 0.1);
+ color: white;
+ }
+
+ &:active {
+ transform: scale(0.95);
+ }
+
+ &[data-variant="primary"] {
+ background-color: rgba(255, 99, 99, 0.15);
+ color: #ff6363;
+
+ &:hover {
+ background-color: rgba(255, 99, 99, 0.25);
+ }
+ }
+
+ svg {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+ }
+}
diff --git a/components/floating-action-bar.tsx b/components/floating-action-bar.tsx
new file mode 100644
index 00000000..591b4166
--- /dev/null
+++ b/components/floating-action-bar.tsx
@@ -0,0 +1,38 @@
+"use client";
+
+import React from "react";
+import styles from "./floating-action-bar.module.css";
+
+export interface FloatingAction {
+ icon: React.ReactNode;
+ label: string;
+ onClick: () => void;
+ variant?: "primary" | "default";
+}
+
+interface FloatingActionBarProps {
+ actions: FloatingAction[];
+ isVisible: boolean;
+}
+
+export function FloatingActionBar({ actions, isVisible }: FloatingActionBarProps) {
+ if (!isVisible) {
+ return null;
+ }
+
+ return (
+
+ {actions.map((action, index) => (
+
+ ))}
+
+ );
+}