Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add share-sheet #323 #363

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions animata/fabs/share-sheet.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from "react";

import ShareSheet from "@/animata/fabs/share-sheet";
import { Meta, StoryObj } from "@storybook/react";

const meta = {
title: "Fabs/Share Sheet",
component: ShareSheet,
parameters: {
layout: "centered",
},
tags: ["autodocs"],
argTypes: {
platforms: {
description:
"Array of platforms available to share through. Each platform should have an image, label, action function, and color.",
table: {
type: { summary: "Array of objects" },
},
},
},
} satisfies Meta<typeof ShareSheet>;

export default meta;
type Story = StoryObj<typeof meta>;

const platforms = [
{
image: (
<img
src="https://cdn-icons-png.flaticon.com/512/733/733547.png"
alt="Smiley"
className="h-8 w-8"
/>
),
label: "Smiley Emoji",
key: "smiley",
action: () => console.log("Smiley Share clicked"),
},
{
image: (
<img
src="https://cdn-icons-png.flaticon.com/512/2164/2164594.png"
alt="Rocket"
className="h-8 w-8"
/>
),
label: "Rocket Emoji",
key: "rocket",
action: () => console.log("Rocket Share clicked"),
},
{
image: (
<img
src="https://cdn-icons-png.flaticon.com/512/616/616490.png"
alt="Pizza"
className="h-8 w-8"
/>
),
label: "Pizza Emoji",
key: "pizza",
action: () => console.log("Pizza Share clicked"),
},
{
image: (
<img
src="https://cdn-icons-png.flaticon.com/512/616/616554.png"
alt="Heart"
className="h-8 w-8"
/>
),
label: "Heart Emoji",
key: "heart",
action: () => console.log("Heart Share clicked"),
},
{
image: (
<img
src="https://cdn-icons-png.flaticon.com/512/616/616576.png"
alt="Star"
className="h-8 w-8"
/>
),
label: "Star Emoji",
key: "star",
action: () => console.log("Star Share clicked"),
},
];

export const Primary: Story = {
args: {
platforms: platforms,
},
};
129 changes: 129 additions & 0 deletions animata/fabs/share-sheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import React, { useEffect, useState } from "react";
import { Share } from "lucide-react";

interface ShareSheetProps {
platforms: Array<{
image: React.ReactNode;
label: string;
key: string;
action: () => void;
}>;
}

interface TooltipProps {
text: string;
children: React.ReactNode;
}

const Tooltip: React.FC<TooltipProps> = ({ text, children }) => {
const [visible, setVisible] = useState(false);

return (
<div
onMouseEnter={() => setVisible(true)}
onMouseLeave={() => setVisible(false)}
className="relative inline-block"
>
{children}
{visible && (
<div className="absolute bottom-full left-1/2 z-10 mb-2 -translate-x-1/2 transform whitespace-nowrap rounded bg-gray-800 px-2 py-1 text-sm text-white">
{text}
</div>
)}
</div>
);
};

export default function ShareSheet({ platforms }: ShareSheetProps) {
const [isOpen, setIsOpen] = useState(false);
const [selectedPlatform, setSelectedPlatform] = useState<null | (typeof platforms)[0]>(null);
const [animationProgress, setAnimationProgress] = useState(0);
const [isExitAnimationActive, setIsExitAnimationActive] = useState(false);

const toggleShareSheet = () => setIsOpen((prev) => !prev);

useEffect(() => {
let animationFrame: number;
if (selectedPlatform && !isExitAnimationActive) {
const animate = () => {
setAnimationProgress((prev) => {
if (prev < 100) {
animationFrame = requestAnimationFrame(animate);
return prev + 0.5; // Slower animation
}
return 100;
});
};
animationFrame = requestAnimationFrame(animate);
}
return () => cancelAnimationFrame(animationFrame);
}, [selectedPlatform, isExitAnimationActive]);

useEffect(() => {
if (animationProgress === 100) {
setIsExitAnimationActive(true);
const timer = setTimeout(() => {
setSelectedPlatform(null);
setAnimationProgress(0);
setIsExitAnimationActive(false);
}, 800); // Adjust timing as needed
return () => clearTimeout(timer);
}
}, [animationProgress]);

return (
<div className="relative">
<div className="relative h-12 w-12">
<button
onClick={toggleShareSheet}
className={`absolute inset-0 flex transform items-center justify-center rounded-full border border-white bg-gray-800 text-white shadow-lg backdrop-blur-xl transition-all duration-500 ease-in-out ${
isOpen || selectedPlatform ? "-translate-y-8 opacity-0" : "translate-y-0 opacity-100"
} ${isExitAnimationActive ? "animate-rise" : ""}`}
>
<Share size={20} />
</button>
{selectedPlatform && (
<div
className={`absolute inset-0 flex items-center justify-center overflow-hidden rounded-full border border-white transition-all duration-300 ${
isExitAnimationActive ? "animate-fall" : ""
}`}
style={{
background: `conic-gradient(blue ${animationProgress}%, transparent ${animationProgress}%)`,
}}
>
{React.cloneElement(selectedPlatform.image as React.ReactElement, {
className: "w-10 h-10 rounded-full",
})}
</div>
)}
</div>

<div
className={`absolute left-0 top-0 transform rounded-xl bg-gray-300 p-4 shadow-lg transition-all duration-300 ease-in-out ${
isOpen ? "scale-100 opacity-100" : "scale-0 opacity-0"
}`}
style={{ minWidth: "max-content" }}
>
<div className="flex flex-col gap-4">
{platforms.map((platform, index) => (
<Tooltip text={platform.label} key={index}>
<button
onClick={() => {
platform.action();
setIsOpen(false);
setSelectedPlatform(platform);
}}
className="flex w-full items-center gap-3 rounded-lg border p-2 text-gray-800 transition-all duration-300 ease-in-out hover:scale-110 hover:bg-slate-100 hover:shadow-xl"
>
{React.cloneElement(platform.image as React.ReactElement, {
className: "w-12 h-12 rounded-full",
})}
<span className="text- font-bold">{platform.label}</span>
</button>
</Tooltip>
))}
</div>
</div>
</div>
);
}
38 changes: 38 additions & 0 deletions content/docs/fabs/share-sheet.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
title: Share Sheet
description: Share Sheet
author: Asmit Kumar Rai
---

<ComponentPreview name="fabs-share-sheet--docs" />

## Installation

<Steps>
<Step>Install dependencies</Step>

```bash
npm install lucide-react
```

<Step>Run the following command</Step>

It will create a new file `share-sheet.tsx` inside the `components/animata/fabs` directory.

```bash
mkdir -p components/animata/fabs && touch components/animata/fabs/share-sheet.tsx
```

<Step>Paste the code</Step>{" "}

Open the newly created file and paste the following code:

```jsx file=<rootDir>/animata/fabs/share-sheet.tsx

```

</Steps>

## Credits

Built by [Asmit Kumar Rai](https://github.com/asmit27rai)
Loading