diff --git a/.github/copilot/integrate-social-share-button.prompt.md b/.github/copilot/integrate-social-share-button.prompt.md index bea2b48..251b0b1 100644 --- a/.github/copilot/integrate-social-share-button.prompt.md +++ b/.github/copilot/integrate-social-share-button.prompt.md @@ -1,653 +1,209 @@ --- agent: agent description: > - Integrate SocialShareButton into any web project. Covers CDN (recommended), - npm (advanced), and React/Preact/Qwik Wrapper Components (optional). Framework-specific - steps for CRA, Next.js App Router, Next.js Pages Router, Vite/Vue/Angular, - Preact, Qwik, and Vanilla HTML. Use this skill whenever a developer asks how to add the - share button to their project. + Automatically integrate @aossie-org/social-share-button into a client web project + with zero manual steps except specifying where to place the button. --- # SocialShareButton — Integration Skill -You are helping a developer integrate the **SocialShareButton** library -(`social-share-button-aossie`) into their project. Follow these rules strictly. +## Goal ---- - -> 🚫 **IMPORTANT:** Do NOT create new files like `ShareButton.jsx` or `ShareButton.tsx`! -> ✅ Add code directly to the developer's **existing** component (Header, Navbar, etc.) -> 📄 **Exception:** If a separate file is needed (e.g. a dedicated share component), follow **Method 3 — React Wrapper Component** exclusively. - ---- - -## How to respond - -The README defines **3 installation methods**. Ask (or infer) which the developer wants: - -| Method | When to use | -| ------------------------------------------------------- | ------------------------------------------------------------------------- | -| **Method 1 — CDN (Recommended)** | Most projects. No build step needed. Load via ` - - -``` + ``` +- **NPM Method**: Install dependency (`npm i @aossie-org/social-share-button`, `pnpm add`, `yarn add`, or `bun add`). ---- +### 4. Ask User Placement & Style -### CDN — Create React App +- **Method Preference**: Always recommend **CDN** over NPM. +- **Placement Prompting**: Ask the user explicitly: + - Which file they want to import/place the Social Share button in. + - The exact placement location inside that file (e.g., to the left of, right of, above, or below a specific existing component or DOM element, such as next to a logo, navigation items, or primary action buttons). +- **React / Next.js Guidance**: Always recommend placing in the **Navbar / Header** every time when integrating React or Next.js using CDN. +- **Vanilla HTML Guidance**: For HTML projects, ask for their main HTML file (e.g., `index.html`) and exact placement relative to existing HTML elements. +- **Style Options**: Prompt for preferred button style (`default` | `round` | `square`). -**Step 1:** Add CDN to `public/index.html`: +### 5. Inject Integration Code into Existing Files -```html - - - - -
- - -``` - -**Step 2:** Open an **existing** component that renders on every page — typically `src/components/Header.jsx`, `src/layouts/MainLayout.jsx`, or your root `App.jsx`. Add the snippet below to that component so the share button is consistently available across your app. - -```jsx -import { useEffect, useRef } from "react"; -import { useLocation } from "react-router-dom"; // omit if not using React Router - -// ⬇️ Replace 'Header' with the name of the component where you want the -// share button to appear — e.g. Navbar, MainLayout, App, etc. -function Header() { - const shareButtonRef = useRef(null); - const initRef = useRef(false); - const { pathname } = useLocation(); // omit if not using React Router - - useEffect(() => { - if (initRef.current || !window.SocialShareButton) return; - - shareButtonRef.current = new window.SocialShareButton({ - container: "#share-button", - }); - initRef.current = true; - - return () => { - if (shareButtonRef.current?.destroy) { - shareButtonRef.current.destroy(); - } - initRef.current = false; - }; - }, []); - - // Keep the share URL and title in sync with the current route - useEffect(() => { - if (shareButtonRef.current) { - shareButtonRef.current.updateOptions({ - url: window.location.href, - title: document.title, - }); - } - }, [pathname]); // re-runs on every client-side route change - - return ( -
-
-
- ); -} -``` +- 🛑 **No New Files**: Inject directly into target existing file (e.g., `Header`, `Footer`, `page.tsx`). +- **ESM Import**: `import SocialShareButton from "@aossie-org/social-share-button";` +- **CSS Import**: `@aossie-org/social-share-button/css` +- **Next.js**: Add `"use client";` at top of interactive client components. --- -### CDN — Next.js App Router +## Framework Integration Guides -**Step 1:** Add CDN to `app/layout.tsx`: +### ⚛️ React / Next.js (NPM Method) -```tsx -import Script from "next/script"; - -export default function RootLayout({ children }: { children: React.ReactNode }) { - return ( - - - - - - {children} - - - - ); -} -``` - -**Step 2:** Open an existing component that is rendered on every page — typically `components/Header.tsx`, `components/Navbar.tsx`, or `components/Layout.tsx`. Since `_document.tsx` loads the script globally, the button is ready to initialize in any of these components. - -```tsx import { useEffect, useRef } from "react"; -import { useRouter } from "next/router"; - -// ⬇️ Replace 'Header' with the name of the component where you want the -// share button to appear — e.g. Navbar, MainLayout, App, etc. -export default function Header() { - const shareButtonRef = useRef(null); - const containerRef = useRef(null); - const initRef = useRef(false); - const { pathname } = useRouter(); - - useEffect(() => { - const initButton = () => { - if (initRef.current || !window.SocialShareButton || !containerRef.current) return; - - shareButtonRef.current = new window.SocialShareButton({ - container: "#share-button", - }); - initRef.current = true; - }; - - if (window.SocialShareButton) { - initButton(); - } else { - const checkInterval = setInterval(() => { - if (window.SocialShareButton) { - clearInterval(checkInterval); - initButton(); - } - }, 100); - - return () => { - clearInterval(checkInterval); - if (shareButtonRef.current?.destroy) { - shareButtonRef.current.destroy(); - } - initRef.current = false; - }; - } +import SocialShareButton from "@aossie-org/social-share-button"; +import "@aossie-org/social-share-button/css"; - return () => { - if (shareButtonRef.current?.destroy) { - shareButtonRef.current.destroy(); - } - initRef.current = false; - }; - }, []); +export default function Header({ style = "default" }) { + const shareContainerRef = useRef(null); - // Keep the share URL and title in sync with the current route useEffect(() => { - if (shareButtonRef.current) { - shareButtonRef.current.updateOptions({ - url: window.location.href, - title: document.title, - }); - } - }, [pathname]); // re-runs on every client-side navigation + if (!shareContainerRef.current) return; + const shareInstance = new SocialShareButton({ + container: shareContainerRef.current, + buttonStyle: style, // selected style from Step 4 ("default" | "round" | "square") + }); + return () => shareInstance.destroy?.(); + }, [style]); return (
-
+
); } - -declare global { - interface Window { - SocialShareButton: any; - } -} -``` - ---- - -### CDN — Vite / Vue / Angular - -**Step 1:** Add CDN to root `index.html`: - -```html - - - - -
- - ``` -**Step 2:** Open your root or layout component (e.g., `App.vue`, `app.component.html`, or `App.jsx`). Add a container `
` where you want the button to appear, then initialize the button after the DOM is ready: - -```javascript -// Add
to your component's template/HTML first, -// then initialize once the DOM is ready (e.g., in mounted(), ngAfterViewInit(), or useEffect()): -new window.SocialShareButton({ - container: "#share-button", -}); -``` +> **Note for CDN in React/Next.js**: Add CDN `` and ` - -``` - -**Step 2:** Open your root or layout component (typically `src/components/Header.jsx` or your root `App.jsx`). Add a container element and initialize inside the `useEffect` hook: +### 🟣 Preact (NPM Method) ```jsx import { useEffect, useRef } from "preact/hooks"; +import SocialShareButton from "@aossie-org/social-share-button"; +import "@aossie-org/social-share-button/css"; -// ⬇️ Replace 'Header' with the name of the component where you want the -// share button to appear — e.g. Navbar, MainLayout, App, etc. -export default function Header() { - const shareButtonRef = useRef(null); +export default function Footer({ style = "default" }) { const containerRef = useRef(null); - const initRef = useRef(false); useEffect(() => { - if (initRef.current || !window.SocialShareButton || !containerRef.current) return; - - shareButtonRef.current = new window.SocialShareButton({ - container: "#share-button", - }); - initRef.current = true; - - return () => { - if (shareButtonRef.current?.destroy) { - shareButtonRef.current.destroy(); - } - initRef.current = false; - }; - }, []); + if (!containerRef.current) return; + const instance = new SocialShareButton({ container: containerRef.current, buttonStyle: style }); + return () => instance.destroy?.(); + }, [style]); return ( -
-
-
+
+ +
); } ``` --- -### CDN — Qwik +### 🟢 Vue 3 (NPM Method) -**Step 1:** Add CDN to your root or layout page (e.g. `src/root.tsx` or layout index): +```vue + -```html - - - - - - -``` - -**Step 2:** Create a container element and initialize the button in `useVisibleTask$`: - -```tsx -import { component$, useVisibleTask$, useSignal } from "@builder.io/qwik"; - -export default component$(() => { - const containerRef = useSignal(); - - useVisibleTask$(({ cleanup }) => { - if (typeof window !== "undefined" && (window as any).SocialShareButton && containerRef.value) { - const shareButton = new (window as any).SocialShareButton({ - container: containerRef.value, - }); + ``` -> No CDN tags needed — the npm package includes both JS and CSS. - --- -## Method 3 — React / Preact / Qwik Wrapper Components (Optional) +### 🅰️ Angular (NPM Method) -Only use this when the developer **explicitly** wants a reusable component wrapper. +```typescript +import { Component, ElementRef, AfterViewInit, OnDestroy, ViewChild, Input } from "@angular/core"; +// @ts-ignore +import SocialShareButton from "@aossie-org/social-share-button"; -### React Wrapper Component +@Component({ + selector: "app-header", + template: `
`, + styleUrls: ["../../node_modules/@aossie-org/social-share-button/css"], +}) +export class HeaderComponent implements AfterViewInit, OnDestroy { + @ViewChild("container") container!: ElementRef; + @Input() style: string = "default"; + private instance: any; -Tell them to copy `src/social-share-button-react.jsx` from the library into their project: - -```jsx -import SocialShareButton from "./components/SocialShareButton"; - -function App() { - return ( - - ); + ngAfterViewInit(): void { + if (this.container?.nativeElement) { + this.instance = new SocialShareButton({ + container: this.container.nativeElement, + buttonStyle: this.style, + }); + } + } + ngOnDestroy(): void { + this.instance?.destroy?.(); + } } ``` -### Preact Wrapper Component +--- -Tell them to copy `src/social-share-button-preact.jsx` from the library into their project: +### 🌐 Vanilla HTML & JS -```jsx -import SocialShareButton from "./components/SocialShareButton"; +#### NPM Method -function App() { - return ( - - ); -} +```html +
``` -### Qwik Wrapper Component - -Tell them to copy `src/social-share-button-qwik.tsx` from the library into their project: - -```tsx -import { component$ } from "@builder.io/qwik"; -import { SocialShareButton } from "./components/SocialShareButton"; +```javascript +import SocialShareButton from "@aossie-org/social-share-button"; +import "@aossie-org/social-share-button/css"; -export default component$(() => { - return ; -}); +new SocialShareButton({ container: "#share-button", buttonStyle: style }); ``` ---- - -## All constructor options - -| Option | Type | Default | Description | -| ------------------ | -------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- | -| `container` | string/Element | — | **Required.** CSS selector or DOM element | -| `url` | string | `window.location.href` | URL to share | -| `title` | string | `document.title` | Share title/headline | -| `description` | string | `''` | Additional description text | -| `hashtags` | array | `[]` | e.g. `['js', 'webdev']` | -| `via` | string | `''` | Twitter handle (without @) | -| `platforms` | array | `whatsapp, facebook, twitter, linkedin, telegram, reddit, pinterest, discord` | Platforms to show: `whatsapp facebook twitter linkedin telegram reddit email pinterest discord` | -| `buttonText` | string | `'Share'` | Button label text | -| `buttonStyle` | string | `'default'` | `default` `primary` `compact` `icon-only` | -| `buttonColor` | string | `''` | Custom button background color | -| `buttonHoverColor` | string | `''` | Custom button hover color | -| `customClass` | string | `''` | Additional CSS class for button | -| `theme` | string | `'dark'` | `dark` or `light` | -| `modalPosition` | string | `'center'` | Modal position on screen | -| `showButton` | boolean | `true` | Show/hide the share button | -| `onShare` | function | `null` | `(platform, url) => void` | -| `onCopy` | function | `null` | `(url) => void` | -| `analytics` | boolean | `true` | Set `false` to disable all event emission | -| `onAnalytics` | function | `null` | `(payload) => void` — direct analytics hook | -| `analyticsPlugins` | array | `[]` | Adapter instances from `social-share-analytics.js` | -| `componentId` | string | `null` | Label this instance for analytics tracking | -| `debug` | boolean | `false` | Log analytics events to console | - ---- - -## Dynamic URL updates (SPA routing) - -Call `updateOptions()` on route change so the shared URL and title always reflect the current page. +#### CDN Method -> The framework-specific examples above already include this pattern. The snippet below is the standalone reference: - -```jsx -// Next.js App Router: import { usePathname } from "next/navigation"; -// Next.js Pages Router: import { useRouter } from "next/router"; -// React Router: import { useLocation } from "react-router-dom"; - -const shareButton = useRef(null); -// Get the current pathname from your router, e.g.: -// const pathname = usePathname(); // Next.js App Router -// const { pathname } = useRouter(); // Next.js Pages Router -// const { pathname } = useLocation(); // React Router - -useEffect(() => { - shareButton.current = new window.SocialShareButton({ - container: "#share-button", +```html + +
+ + ``` - ---- - -## Troubleshooting - -| Symptom | Cause | Fix | -| --------------------------------------------------- | ------------------------------------------------- | --------------------------------------------------------------------------------------------- | -| Multiple buttons appearing | Component re-renders creating duplicate instances | Use `useRef` + `initRef` guard (shown in all examples above) | -| Button not appearing | Script loads after component renders | Add `if (window.SocialShareButton)` null check | -| Modal not opening | CSS not loaded or ID mismatch | Verify CSS CDN in ``; match `container: '#share-button'` with `
` | -| `TypeError: SocialShareButton is not a constructor` | CDN script not loaded yet | Use interval polling (see Next.js examples above) | -| URL not updating on navigation | Component initialized once, doesn't track routes | Use `updateOptions()` on route change | - ---- - -## Common mistakes to prevent - -| ❌ Wrong | ✅ Correct | -| ----------------------------------------------------- | ------------------------------------------------------------------------- | -| Creating `ShareButton.jsx` / `ShareButton.tsx` | Add directly to existing `Header.jsx`, `Navbar.tsx`, etc. | -| Calling `new SocialShareButton()` inside JSX `return` | Call only inside `useEffect` / lifecycle hook | -| Not calling `destroy()` on unmount | Always clean up — prevents duplicate modals on re-mount | -| Mismatched container ID | `container: '#share-button'` must exactly match `
` | -| Script loads after component renders in Next.js | Use `strategy="beforeInteractive"` **or** poll with `setInterval` | - ---- - -## Output format - -- Ask the developer their **method** (CDN / npm / Wrapper Component) and their **framework** (needed to select the correct CDN integration steps or wrapper component). -- Show only the snippet(s) relevant to their choices. -- Always modify **existing** files — never suggest creating new component files (unless they explicitly ask for a Wrapper Component, in which case instruct them to copy the relevant file from `src/` to their components folder). -- When modifying an existing file, mark additions with `// ADD THIS`. -- Do not add abstractions, wrappers, or extra files beyond what the README shows.