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) => ( + + ))} +
+ ); +}