@withone/connect 0.3.1 → 0.4.0

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.
package/README.md CHANGED
@@ -155,21 +155,28 @@ calls. Importing the package registers it. It wires the whole flow
155
155
  itself and manages Connect → Connecting → Connected.
156
156
 
157
157
  ```tsx
158
- // React / Next (any framework — same tag everywhere)
159
- import "@withone/connect";
158
+ // React / Next — a real component:
159
+ import { ConnectButton } from "@withone/connect/react";
160
160
 
161
161
  export function ConnectWithOne() {
162
162
  return (
163
- <one-connect-button
164
- authorize-url="/api/one/authorize"
163
+ <ConnectButton
164
+ authorizeUrl="/api/one/authorize"
165
165
  label="Connect your apps"
166
- platforms='[{"name":"Stripe","imageUrl":"/icons/stripe.svg"},{"name":"PostHog","imageUrl":"/icons/posthog.svg"}]'
166
+ platforms={[
167
+ { name: "Stripe", imageUrl: "/icons/stripe.svg" },
168
+ { name: "PostHog", imageUrl: "/icons/posthog.svg" },
169
+ ]}
167
170
  onSuccess={() => {/* tokens stored server-side — refresh app state */}}
168
171
  />
169
172
  );
170
173
  }
171
174
  ```
172
175
 
176
+ Every other framework (Vue, Svelte, plain HTML) uses the SAME widget as
177
+ a custom element — `import "@withone/connect"` registers
178
+ `<one-connect-button>`:
179
+
173
180
  ```html
174
181
  <!-- Plain HTML / any framework -->
175
182
  <one-connect-button
@@ -0,0 +1 @@
1
+ "use strict";var e=require("react");function t(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var o=n.call(e,t);if("object"!=typeof o)return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:t+""}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const n="one-connect-frame";function o(){const e=document.getElementById(n);e&&e.remove()}const i="one-connect-success";function r(){var e;null===(e=document.getElementById(i))||void 0===e||e.remove()}function c(){return document.getElementById(n)}const l=e=>{let t=null,l=!1;const s=()=>{"undefined"!=typeof window&&t&&(window.removeEventListener("message",t),t=null),o()},a=(t,n)=>{if(!l){l=!0,s(),"success"===t&&function(e){var t;r();const n="dark"===e,o=document.createElement("div");o.id=i,Object.assign(o.style,{position:"fixed",inset:"0",zIndex:"2147483000",display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(8, 8, 8, 0.5)",opacity:"0",transition:"opacity 160ms ease"});const c=document.createElement("div");Object.assign(c.style,{width:"520px",maxWidth:"calc(100vw - 16px)",height:"520px",maxHeight:"calc(100vh - 16px)",display:"flex",flexDirection:"column",textAlign:"center",fontFamily:"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"});const l=n?"#a1a1aa":"#6b7280",s=n?"rgba(25, 25, 25, 0.97)":"rgba(255, 255, 255, 0.97)",a=n?"rgba(255,255,255,0.08)":"rgba(228,228,223,0.9)",d=n?"rgba(45, 45, 44, 0.9)":"rgba(232, 232, 228, 0.95)";c.innerHTML=`<div style="position:relative;flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:40px 48px;border-radius:28px 28px 0 0;background:${s};border:1px solid ${a};border-bottom:0;"><button data-one-close aria-label="Close" style="position:absolute;top:16px;right:16px;width:20px;height:20px;padding:0;border:0;background:none;cursor:pointer;color:${l};line-height:0;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg></button><div style="width:56px;height:56px;border-radius:50%;background:#10b981;display:flex;align-items:center;justify-content:center;"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg></div><div style="font-size:18px;font-weight:600;letter-spacing:-0.01em;color:${n?"#fafafa":"#111114"};">Access granted</div><div style="font-size:13px;line-height:1.5;max-width:280px;color:${l};">Your tools are connected. You can pick up right where you left off.</div><button data-one-close style="width:100%;margin-top:8px;padding:11px 0;border:0;border-radius:12px;cursor:pointer;font-size:14px;font-weight:500;background:${n?"#fafafa":"#111114"};color:${n?"#111114":"#fafafa"};">Close</button></div><div style="height:48px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:0 0 28px 28px;background:${d};border:1px solid ${a};border-top:0;"><span style="font-size:11px;color:${l};">Secured by</span><span style="display:inline-flex;color:${n?"#fafafa":"#111114"};"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4"><circle cx="12" cy="12" r="8.5"/></svg></span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${n?"#fafafa":"#111114"};">one</span></div>`,o.appendChild(c),document.body.appendChild(o),requestAnimationFrame(()=>{o.style.opacity="1"});const p=()=>{window.removeEventListener("keydown",u),o.style.opacity="0",window.setTimeout(()=>o.remove(),180)};function u(e){"Escape"===e.key&&p()}for(const e of c.querySelectorAll("[data-one-close]"))e.addEventListener("click",p);o.addEventListener("click",e=>{e.target===o&&p()}),window.addEventListener("keydown",u),null===(t=c.querySelector("[data-one-close]"))||void 0===t||t.focus()}(e.appTheme);try{var o,c;if("success"===t)null===(o=e.onSuccess)||void 0===o||o.call(e);else null===(c=e.onError)||void 0===c||c.call(e,null!=n?n:"The connection was not completed.")}catch{}}},d=t=>{const n=t.data;if(!n)return;const o=c();if(o&&t.source===o.contentWindow)if("@withone/connect:exit"!==n.type)"@withone/connect:result"!==n.type||t.origin!==window.location.origin||"success"!==n.status&&"error"!==n.status||a(n.status,n.message);else{s();try{var i;null===(i=e.onClose)||void 0===i||i.call(e)}catch{}}},p=()=>{var e;const t=c();if(!t)return;let n,o;try{var i,r;n=null!==(i=null===(r=t.contentWindow)||void 0===r?void 0:r.location.href)&&void 0!==i?i:""}catch{return}try{o=new URL(n).searchParams}catch{return}const l=o.get("one_connect");"success"!==l&&"error"!==l||(t.style.visibility="hidden",a(l,null!==(e=o.get("one_connect_message"))&&void 0!==e?e:void 0))};return{open:()=>{if("undefined"==typeof window)return;l=!1,t=d,window.addEventListener("message",t);(function(e){o();const t=document.createElement("iframe");return t.id=n,t.src=e,t.setAttribute("allowtransparency","true"),Object.assign(t.style,{position:"fixed",inset:"0",width:"100%",height:"100%",border:"0",zIndex:"2147483000",background:"transparent",colorScheme:"normal"}),document.body.appendChild(t),t})((()=>{try{const t=new URL(e.authorize.url,"undefined"==typeof window?void 0:window.location.origin);return e.appTheme&&(t.hash=`one_theme=${e.appTheme}`),t.toString()}catch{return e.authorize.url}})()).addEventListener("load",p)},close:()=>{r(),s()}}},s="one-connect-button-styles",a="cubic-bezier(.2,.9,.25,1)",d={btnBg:"#0A0C0B",btnFg:"#ffffff",surface:"#ffffff",fg:"#0A0C0B",muted:"#6B7280",line:"#D1D5DB",shadow:"0 1px 2px rgba(10,12,11,.08), 0 4px 14px rgba(10,12,11,.06)"},p={btnBg:"#F2F5F3",btnFg:"#0A0C0B",surface:"#101312",fg:"#F2F5F3",muted:"#8A938E",line:"#2A302D",shadow:"0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35)"},u="#CCFF00",f="#3FE3A5",h="#0A0C0B";const m=(e,t)=>{var n,o;!function(){if("undefined"==typeof document||document.getElementById(s))return;const e=document.createElement("style");e.id=s,e.textContent=[`.owcb{font:500 14.5px/1.2 inherit;font-family:inherit;letter-spacing:-.01em;cursor:pointer;border:0;display:inline-flex;align-items:center;gap:12px;padding:12px 16px 12px 14px;border-radius:12px;transition:transform .2s ${a},box-shadow .2s ${a},opacity .2s ${a}}`,".owcb:hover{transform:translateY(-1px)}",".owcb:active{transform:translateY(0)}",`.owcb:focus-visible{outline:2px solid ${f};outline-offset:3px}`,".owcb[disabled]{cursor:default;opacity:.75;transform:none}",".owcb-stack{display:inline-flex;align-items:center;flex-shrink:0}",`.owcb-chip{width:22px;height:22px;border-radius:6px;display:grid;place-items:center;overflow:hidden;margin-left:-7px;transition:margin-left .28s ${a};position:relative}`,".owcb-chip:first-child{margin-left:0}",".owcb-chip img{width:14px;height:14px;display:block;object-fit:contain}",".owcb-chip.owcb-more{font-family:ui-monospace,monospace;font-size:9px;font-weight:500;letter-spacing:-.02em}",".owcb:hover .owcb-chip{margin-left:-2px}",".owcb:hover .owcb-chip:first-child{margin-left:0}",".owcb-label{white-space:nowrap}",`.owcb-arrow{opacity:.5;transition:transform .2s ${a},opacity .2s ${a}}`,".owcb:hover .owcb-arrow{transform:translateX(2px);opacity:.8}",".owcb-spinner{width:16px;height:16px;border-radius:50%;border:2px solid currentColor;border-top-color:transparent;opacity:.7;animation:owcb-spin .7s linear infinite}","@keyframes owcb-spin{to{transform:rotate(360deg)}}",".owcb.owcb-block{display:flex;flex-direction:column;align-items:stretch;width:100%;max-width:420px;text-align:left;padding:16px;border-radius:14px;gap:0}",".owcb-block-top{display:flex;align-items:center;justify-content:space-between;gap:12px}",".owcb-block-title{font-size:15px;font-weight:600;letter-spacing:-.01em}",".owcb-block-sub{font-size:13px;line-height:1.5;margin-top:6px;font-weight:400}",".owcb-block-foot{margin-top:14px;padding-top:12px;display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:400}","@media (prefers-reduced-motion:reduce){.owcb,.owcb-chip,.owcb-arrow{transition:none}.owcb-spinner{animation-duration:1.4s}}"].join("\n"),document.head.appendChild(e)}();const i="dark"===t.theme?p:d,r=null!==(n=t.variant)&&void 0!==n?n:"default",c=null!==(o=t.label)&&void 0!==o?o:"Connect your apps";let m="idle",b=!1;const g=l({...t.connect,onSuccess:()=>{var e,n;x("connected"),null===(e=(n=t.connect).onSuccess)||void 0===e||e.call(n)},onError:e=>{var n,o;x("idle"),null===(n=(o=t.connect).onError)||void 0===n||n.call(o,e)},onClose:()=>{var e,n;x("idle"),null===(e=(n=t.connect).onClose)||void 0===e||e.call(n)}}),v=document.createElement("button");v.type="button",v.className="block"===r?"owcb owcb-block":"owcb",v.addEventListener("click",()=>{"idle"!==m&&"connected"!==m||(x("connecting"),g.open())});const w=()=>{var e,n,o;const l="accent"===r,s="block"===r,a=s?i.surface:l?null!==(e=t.accentColor)&&void 0!==e?e:u:i.btnBg,d=s?i.fg:l?h:i.btnFg,p=s?i.surface:a,b=s?i.muted:d;v.style.background="connected"!==m||s?a:f,v.style.color="connected"!==m||s?d:h,v.style.boxShadow=s?`inset 0 0 0 1px ${i.line}`:"connected"===m?"none":l?`0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${null!==(n=t.accentColor)&&void 0!==n?n:u}38`:i.shadow,v.disabled="connecting"===m,v.innerHTML="";const g=function(e,t,n){var o,i;const r=null!==(o=e.platforms)&&void 0!==o?o:[];if(0===r.length&&!e.moreCount)return null;const c=document.createElement("span");c.className="owcb-stack",c.setAttribute("aria-hidden","true");for(const e of r.slice(0,4)){const n=document.createElement("span");if(n.className="owcb-chip",n.style.background="#ffffff",n.style.boxShadow=`0 0 0 1.5px ${t}`,e.imageUrl){const t=document.createElement("img");t.alt="",t.src=e.imageUrl,t.addEventListener("error",()=>{t.style.display="none",n.textContent=e.name.charAt(0).toUpperCase(),n.style.font="600 10px ui-monospace,monospace",n.style.color=h}),n.appendChild(t)}else n.textContent=e.name.charAt(0).toUpperCase(),n.style.font="600 10px ui-monospace,monospace",n.style.color=h;c.appendChild(n)}const l=(null!==(i=e.moreCount)&&void 0!==i?i:0)+Math.max(0,r.length-4);if(l>0){const e=document.createElement("span");e.className="owcb-chip owcb-more",e.style.background="#ffffff",e.style.boxShadow=`0 0 0 1.5px ${t}`,e.style.color=n,e.textContent=`+${l}`,c.appendChild(e)}return c}(t,p,b);if(s){var w;const e=document.createElement("span");e.className="owcb-block-top";const n=document.createElement("span");if(n.className="owcb-block-title",n.textContent="connecting"===m?"Connecting…":"connected"===m?null!==(w=t.connectedLabel)&&void 0!==w?w:"Connected":c,e.appendChild(n),"connecting"===m){const t=document.createElement("span");t.className="owcb-spinner",e.appendChild(t)}else g&&e.appendChild(g);if(v.appendChild(e),t.description&&"idle"===m){const e=document.createElement("span");e.className="owcb-block-sub",e.style.color=i.muted,e.textContent=t.description,v.appendChild(e)}const o=document.createElement("span");return o.className="owcb-block-foot",o.style.color=i.muted,o.style.borderTop=`1px solid ${i.line}`,o.innerHTML=`<span style="font-size:11px;color:${i.muted}">Secured by</span><span style="display:inline-flex;color:${i.fg}"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4" style="flex-shrink:0"><circle cx="12" cy="12" r="8.5"/></svg></span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${i.fg}">one</span>`,void v.appendChild(o)}if("connecting"===m){const e=document.createElement("span");e.className="owcb-spinner",v.appendChild(e)}else if("connected"===m){const e=document.createElement("span");e.innerHTML='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px"><path d="M3 8.5l3.5 3.5L13 5"/></svg>',e.style.display="inline-flex",v.appendChild(e)}else g&&v.appendChild(g);const x=document.createElement("span");if(x.className="owcb-label",x.textContent="connecting"===m?"Connecting…":"connected"===m?null!==(o=t.connectedLabel)&&void 0!==o?o:"Connected":c,v.appendChild(x),"idle"===m){const e=document.createElement("span");e.innerHTML='<svg class="owcb-arrow" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3l5 5-5 5"/></svg>',e.style.display="inline-flex",v.appendChild(e)}},x=e=>{b||(m=e,w())};return w(),e.appendChild(v),{setState:x,destroy:()=>{b=!0,g.close(),v.remove()}}};!function(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class e extends HTMLElement{constructor(...e){super(...e),t(this,"onSuccess",null),t(this,"onError",null),t(this,"onClose",null),t(this,"handle",null)}connectedCallback(){this.mount()}disconnectedCallback(){var e;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null}attributeChangedCallback(){this.isConnected&&this.handle&&this.mount()}mount(){var e,t,n,o,i,r,c,l;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null;const s=this.getAttribute("authorize-url");if(!s)return;let a;const d=this.getAttribute("platforms");if(d)try{const e=JSON.parse(d);Array.isArray(e)&&(a=e)}catch{}const p=this.getAttribute("more-count"),u=p?parseInt(p,10):void 0;this.handle=m(this,{connect:{authorize:{url:s},appTheme:null!==(t=this.getAttribute("app-theme"))&&void 0!==t?t:void 0,onSuccess:()=>{var e;null===(e=this.onSuccess)||void 0===e||e.call(this),this.dispatchEvent(new CustomEvent("success"))},onError:e=>{var t;null===(t=this.onError)||void 0===t||t.call(this,e),this.dispatchEvent(new CustomEvent("error",{detail:e}))},onClose:()=>{var e;null===(e=this.onClose)||void 0===e||e.call(this),this.dispatchEvent(new CustomEvent("close"))}},label:null!==(n=this.getAttribute("label"))&&void 0!==n?n:void 0,variant:null!==(o=this.getAttribute("variant"))&&void 0!==o?o:void 0,theme:null!==(i=this.getAttribute("theme"))&&void 0!==i?i:void 0,platforms:a,moreCount:Number.isFinite(u)?u:void 0,description:null!==(r=this.getAttribute("description"))&&void 0!==r?r:void 0,accentColor:null!==(c=this.getAttribute("accent-color"))&&void 0!==c?c:void 0,connectedLabel:null!==(l=this.getAttribute("connected-label"))&&void 0!==l?l:void 0})}}t(e,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",e)}}(),exports.ConnectButton=function(t){var n;const o=e.useRef(null),i=e.useRef(null),r=e.useRef({onSuccess:t.onSuccess,onError:t.onError,onClose:t.onClose});r.current={onSuccess:t.onSuccess,onError:t.onError,onClose:t.onClose};const c=JSON.stringify(null!==(n=t.platforms)&&void 0!==n?n:[]);return e.useEffect(()=>{if(!o.current)return;const e={connect:{authorize:{url:t.authorizeUrl},appTheme:t.appTheme,onSuccess:()=>{var e,t;return null===(e=(t=r.current).onSuccess)||void 0===e?void 0:e.call(t)},onError:e=>{var t,n;return null===(t=(n=r.current).onError)||void 0===t?void 0:t.call(n,e)},onClose:()=>{var e,t;return null===(e=(t=r.current).onClose)||void 0===e?void 0:e.call(t)}},label:t.label,variant:t.variant,theme:t.theme,platforms:t.platforms,moreCount:t.moreCount,description:t.description,accentColor:t.accentColor,connectedLabel:t.connectedLabel};return i.current=m(o.current,e),()=>{var e;null===(e=i.current)||void 0===e||e.destroy(),i.current=null}},[t.authorizeUrl,t.appTheme,t.label,t.variant,t.theme,c,t.moreCount,t.description,t.accentColor,t.connectedLabel]),e.createElement("div",{ref:o,className:t.className,style:t.style})};
@@ -0,0 +1,24 @@
1
+ import type { CSSProperties, ReactElement } from "react";
2
+ import type { ConnectButtonPlatform } from "./types";
3
+ export interface ConnectButtonProps {
4
+ /** The app's own backend authorize route ("/api/one/authorize" is
5
+ * fine — relative resolves against the page). */
6
+ authorizeUrl: string;
7
+ /** Theme for One's card (rides the URL fragment). */
8
+ appTheme?: "light" | "dark";
9
+ onSuccess?: () => void;
10
+ onError?: (error: string) => void;
11
+ onClose?: () => void;
12
+ label?: string;
13
+ variant?: "default" | "accent" | "block";
14
+ /** Matches the host page, not One's card. */
15
+ theme?: "light" | "dark";
16
+ platforms?: ConnectButtonPlatform[];
17
+ moreCount?: number;
18
+ description?: string;
19
+ accentColor?: string;
20
+ connectedLabel?: string;
21
+ className?: string;
22
+ style?: CSSProperties;
23
+ }
24
+ export declare function ConnectButton(props: ConnectButtonProps): ReactElement;
@@ -0,0 +1 @@
1
+ import{useRef as e,useEffect as t,createElement as n}from"react";function o(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||!e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var o=n.call(e,t);if("object"!=typeof o)return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:t+""}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}const i="one-connect-frame";function r(){const e=document.getElementById(i);e&&e.remove()}const c="one-connect-success";function l(){var e;null===(e=document.getElementById(c))||void 0===e||e.remove()}function s(){return document.getElementById(i)}const a=e=>{let t=null,n=!1;const o=()=>{"undefined"!=typeof window&&t&&(window.removeEventListener("message",t),t=null),r()},a=(t,i)=>{if(!n){n=!0,o(),"success"===t&&function(e){var t;l();const n="dark"===e,o=document.createElement("div");o.id=c,Object.assign(o.style,{position:"fixed",inset:"0",zIndex:"2147483000",display:"flex",alignItems:"center",justifyContent:"center",background:"rgba(8, 8, 8, 0.5)",opacity:"0",transition:"opacity 160ms ease"});const i=document.createElement("div");Object.assign(i.style,{width:"520px",maxWidth:"calc(100vw - 16px)",height:"520px",maxHeight:"calc(100vh - 16px)",display:"flex",flexDirection:"column",textAlign:"center",fontFamily:"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"});const r=n?"#a1a1aa":"#6b7280",s=n?"rgba(25, 25, 25, 0.97)":"rgba(255, 255, 255, 0.97)",a=n?"rgba(255,255,255,0.08)":"rgba(228,228,223,0.9)",d=n?"rgba(45, 45, 44, 0.9)":"rgba(232, 232, 228, 0.95)";i.innerHTML=`<div style="position:relative;flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:40px 48px;border-radius:28px 28px 0 0;background:${s};border:1px solid ${a};border-bottom:0;"><button data-one-close aria-label="Close" style="position:absolute;top:16px;right:16px;width:20px;height:20px;padding:0;border:0;background:none;cursor:pointer;color:${r};line-height:0;"><svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><path d="M18 6L6 18M6 6l12 12"/></svg></button><div style="width:56px;height:56px;border-radius:50%;background:#10b981;display:flex;align-items:center;justify-content:center;"><svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M5 13l4 4L19 7"/></svg></div><div style="font-size:18px;font-weight:600;letter-spacing:-0.01em;color:${n?"#fafafa":"#111114"};">Access granted</div><div style="font-size:13px;line-height:1.5;max-width:280px;color:${r};">Your tools are connected. You can pick up right where you left off.</div><button data-one-close style="width:100%;margin-top:8px;padding:11px 0;border:0;border-radius:12px;cursor:pointer;font-size:14px;font-weight:500;background:${n?"#fafafa":"#111114"};color:${n?"#111114":"#fafafa"};">Close</button></div><div style="height:48px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:0 0 28px 28px;background:${d};border:1px solid ${a};border-top:0;"><span style="font-size:11px;color:${r};">Secured by</span><span style="display:inline-flex;color:${n?"#fafafa":"#111114"};"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4"><circle cx="12" cy="12" r="8.5"/></svg></span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${n?"#fafafa":"#111114"};">one</span></div>`,o.appendChild(i),document.body.appendChild(o),requestAnimationFrame(()=>{o.style.opacity="1"});const p=()=>{window.removeEventListener("keydown",u),o.style.opacity="0",window.setTimeout(()=>o.remove(),180)};function u(e){"Escape"===e.key&&p()}for(const e of i.querySelectorAll("[data-one-close]"))e.addEventListener("click",p);o.addEventListener("click",e=>{e.target===o&&p()}),window.addEventListener("keydown",u),null===(t=i.querySelector("[data-one-close]"))||void 0===t||t.focus()}(e.appTheme);try{var r,s;if("success"===t)null===(r=e.onSuccess)||void 0===r||r.call(e);else null===(s=e.onError)||void 0===s||s.call(e,null!=i?i:"The connection was not completed.")}catch{}}},d=t=>{const n=t.data;if(!n)return;const i=s();if(i&&t.source===i.contentWindow)if("@withone/connect:exit"!==n.type)"@withone/connect:result"!==n.type||t.origin!==window.location.origin||"success"!==n.status&&"error"!==n.status||a(n.status,n.message);else{o();try{var r;null===(r=e.onClose)||void 0===r||r.call(e)}catch{}}},p=()=>{var e;const t=s();if(!t)return;let n,o;try{var i,r;n=null!==(i=null===(r=t.contentWindow)||void 0===r?void 0:r.location.href)&&void 0!==i?i:""}catch{return}try{o=new URL(n).searchParams}catch{return}const c=o.get("one_connect");"success"!==c&&"error"!==c||(t.style.visibility="hidden",a(c,null!==(e=o.get("one_connect_message"))&&void 0!==e?e:void 0))};return{open:()=>{if("undefined"==typeof window)return;n=!1,t=d,window.addEventListener("message",t);(function(e){r();const t=document.createElement("iframe");return t.id=i,t.src=e,t.setAttribute("allowtransparency","true"),Object.assign(t.style,{position:"fixed",inset:"0",width:"100%",height:"100%",border:"0",zIndex:"2147483000",background:"transparent",colorScheme:"normal"}),document.body.appendChild(t),t})((()=>{try{const t=new URL(e.authorize.url,"undefined"==typeof window?void 0:window.location.origin);return e.appTheme&&(t.hash=`one_theme=${e.appTheme}`),t.toString()}catch{return e.authorize.url}})()).addEventListener("load",p)},close:()=>{l(),o()}}},d="one-connect-button-styles",p="cubic-bezier(.2,.9,.25,1)",u={btnBg:"#0A0C0B",btnFg:"#ffffff",surface:"#ffffff",fg:"#0A0C0B",muted:"#6B7280",line:"#D1D5DB",shadow:"0 1px 2px rgba(10,12,11,.08), 0 4px 14px rgba(10,12,11,.06)"},h={btnBg:"#F2F5F3",btnFg:"#0A0C0B",surface:"#101312",fg:"#F2F5F3",muted:"#8A938E",line:"#2A302D",shadow:"0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35)"},f="#CCFF00",m="#3FE3A5",b="#0A0C0B";const g=(e,t)=>{var n,o;!function(){if("undefined"==typeof document||document.getElementById(d))return;const e=document.createElement("style");e.id=d,e.textContent=[`.owcb{font:500 14.5px/1.2 inherit;font-family:inherit;letter-spacing:-.01em;cursor:pointer;border:0;display:inline-flex;align-items:center;gap:12px;padding:12px 16px 12px 14px;border-radius:12px;transition:transform .2s ${p},box-shadow .2s ${p},opacity .2s ${p}}`,".owcb:hover{transform:translateY(-1px)}",".owcb:active{transform:translateY(0)}",`.owcb:focus-visible{outline:2px solid ${m};outline-offset:3px}`,".owcb[disabled]{cursor:default;opacity:.75;transform:none}",".owcb-stack{display:inline-flex;align-items:center;flex-shrink:0}",`.owcb-chip{width:22px;height:22px;border-radius:6px;display:grid;place-items:center;overflow:hidden;margin-left:-7px;transition:margin-left .28s ${p};position:relative}`,".owcb-chip:first-child{margin-left:0}",".owcb-chip img{width:14px;height:14px;display:block;object-fit:contain}",".owcb-chip.owcb-more{font-family:ui-monospace,monospace;font-size:9px;font-weight:500;letter-spacing:-.02em}",".owcb:hover .owcb-chip{margin-left:-2px}",".owcb:hover .owcb-chip:first-child{margin-left:0}",".owcb-label{white-space:nowrap}",`.owcb-arrow{opacity:.5;transition:transform .2s ${p},opacity .2s ${p}}`,".owcb:hover .owcb-arrow{transform:translateX(2px);opacity:.8}",".owcb-spinner{width:16px;height:16px;border-radius:50%;border:2px solid currentColor;border-top-color:transparent;opacity:.7;animation:owcb-spin .7s linear infinite}","@keyframes owcb-spin{to{transform:rotate(360deg)}}",".owcb.owcb-block{display:flex;flex-direction:column;align-items:stretch;width:100%;max-width:420px;text-align:left;padding:16px;border-radius:14px;gap:0}",".owcb-block-top{display:flex;align-items:center;justify-content:space-between;gap:12px}",".owcb-block-title{font-size:15px;font-weight:600;letter-spacing:-.01em}",".owcb-block-sub{font-size:13px;line-height:1.5;margin-top:6px;font-weight:400}",".owcb-block-foot{margin-top:14px;padding-top:12px;display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:400}","@media (prefers-reduced-motion:reduce){.owcb,.owcb-chip,.owcb-arrow{transition:none}.owcb-spinner{animation-duration:1.4s}}"].join("\n"),document.head.appendChild(e)}();const i="dark"===t.theme?h:u,r=null!==(n=t.variant)&&void 0!==n?n:"default",c=null!==(o=t.label)&&void 0!==o?o:"Connect your apps";let l="idle",s=!1;const g=a({...t.connect,onSuccess:()=>{var e,n;x("connected"),null===(e=(n=t.connect).onSuccess)||void 0===e||e.call(n)},onError:e=>{var n,o;x("idle"),null===(n=(o=t.connect).onError)||void 0===n||n.call(o,e)},onClose:()=>{var e,n;x("idle"),null===(e=(n=t.connect).onClose)||void 0===e||e.call(n)}}),v=document.createElement("button");v.type="button",v.className="block"===r?"owcb owcb-block":"owcb",v.addEventListener("click",()=>{"idle"!==l&&"connected"!==l||(x("connecting"),g.open())});const w=()=>{var e,n,o;const s="accent"===r,a="block"===r,d=a?i.surface:s?null!==(e=t.accentColor)&&void 0!==e?e:f:i.btnBg,p=a?i.fg:s?b:i.btnFg,u=a?i.surface:d,h=a?i.muted:p;v.style.background="connected"!==l||a?d:m,v.style.color="connected"!==l||a?p:b,v.style.boxShadow=a?`inset 0 0 0 1px ${i.line}`:"connected"===l?"none":s?`0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${null!==(n=t.accentColor)&&void 0!==n?n:f}38`:i.shadow,v.disabled="connecting"===l,v.innerHTML="";const g=function(e,t,n){var o,i;const r=null!==(o=e.platforms)&&void 0!==o?o:[];if(0===r.length&&!e.moreCount)return null;const c=document.createElement("span");c.className="owcb-stack",c.setAttribute("aria-hidden","true");for(const e of r.slice(0,4)){const n=document.createElement("span");if(n.className="owcb-chip",n.style.background="#ffffff",n.style.boxShadow=`0 0 0 1.5px ${t}`,e.imageUrl){const t=document.createElement("img");t.alt="",t.src=e.imageUrl,t.addEventListener("error",()=>{t.style.display="none",n.textContent=e.name.charAt(0).toUpperCase(),n.style.font="600 10px ui-monospace,monospace",n.style.color=b}),n.appendChild(t)}else n.textContent=e.name.charAt(0).toUpperCase(),n.style.font="600 10px ui-monospace,monospace",n.style.color=b;c.appendChild(n)}const l=(null!==(i=e.moreCount)&&void 0!==i?i:0)+Math.max(0,r.length-4);if(l>0){const e=document.createElement("span");e.className="owcb-chip owcb-more",e.style.background="#ffffff",e.style.boxShadow=`0 0 0 1.5px ${t}`,e.style.color=n,e.textContent=`+${l}`,c.appendChild(e)}return c}(t,u,h);if(a){var w;const e=document.createElement("span");e.className="owcb-block-top";const n=document.createElement("span");if(n.className="owcb-block-title",n.textContent="connecting"===l?"Connecting…":"connected"===l?null!==(w=t.connectedLabel)&&void 0!==w?w:"Connected":c,e.appendChild(n),"connecting"===l){const t=document.createElement("span");t.className="owcb-spinner",e.appendChild(t)}else g&&e.appendChild(g);if(v.appendChild(e),t.description&&"idle"===l){const e=document.createElement("span");e.className="owcb-block-sub",e.style.color=i.muted,e.textContent=t.description,v.appendChild(e)}const o=document.createElement("span");return o.className="owcb-block-foot",o.style.color=i.muted,o.style.borderTop=`1px solid ${i.line}`,o.innerHTML=`<span style="font-size:11px;color:${i.muted}">Secured by</span><span style="display:inline-flex;color:${i.fg}"><svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2.4" style="flex-shrink:0"><circle cx="12" cy="12" r="8.5"/></svg></span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${i.fg}">one</span>`,void v.appendChild(o)}if("connecting"===l){const e=document.createElement("span");e.className="owcb-spinner",v.appendChild(e)}else if("connected"===l){const e=document.createElement("span");e.innerHTML='<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px"><path d="M3 8.5l3.5 3.5L13 5"/></svg>',e.style.display="inline-flex",v.appendChild(e)}else g&&v.appendChild(g);const x=document.createElement("span");if(x.className="owcb-label",x.textContent="connecting"===l?"Connecting…":"connected"===l?null!==(o=t.connectedLabel)&&void 0!==o?o:"Connected":c,v.appendChild(x),"idle"===l){const e=document.createElement("span");e.innerHTML='<svg class="owcb-arrow" width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="M6 3l5 5-5 5"/></svg>',e.style.display="inline-flex",v.appendChild(e)}},x=e=>{s||(l=e,w())};return w(),e.appendChild(v),{setState:x,destroy:()=>{s=!0,g.close(),v.remove()}}};function v(o){var i;const r=e(null),c=e(null),l=e({onSuccess:o.onSuccess,onError:o.onError,onClose:o.onClose});l.current={onSuccess:o.onSuccess,onError:o.onError,onClose:o.onClose};const s=JSON.stringify(null!==(i=o.platforms)&&void 0!==i?i:[]);return t(()=>{if(!r.current)return;const e={connect:{authorize:{url:o.authorizeUrl},appTheme:o.appTheme,onSuccess:()=>{var e,t;return null===(e=(t=l.current).onSuccess)||void 0===e?void 0:e.call(t)},onError:e=>{var t,n;return null===(t=(n=l.current).onError)||void 0===t?void 0:t.call(n,e)},onClose:()=>{var e,t;return null===(e=(t=l.current).onClose)||void 0===e?void 0:e.call(t)}},label:o.label,variant:o.variant,theme:o.theme,platforms:o.platforms,moreCount:o.moreCount,description:o.description,accentColor:o.accentColor,connectedLabel:o.connectedLabel};return c.current=g(r.current,e),()=>{var e;null===(e=c.current)||void 0===e||e.destroy(),c.current=null}},[o.authorizeUrl,o.appTheme,o.label,o.variant,o.theme,s,o.moreCount,o.description,o.accentColor,o.connectedLabel]),n("div",{ref:r,className:o.className,style:o.style})}!function(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class e extends HTMLElement{constructor(...e){super(...e),o(this,"onSuccess",null),o(this,"onError",null),o(this,"onClose",null),o(this,"handle",null)}connectedCallback(){this.mount()}disconnectedCallback(){var e;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null}attributeChangedCallback(){this.isConnected&&this.handle&&this.mount()}mount(){var e,t,n,o,i,r,c,l;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null;const s=this.getAttribute("authorize-url");if(!s)return;let a;const d=this.getAttribute("platforms");if(d)try{const e=JSON.parse(d);Array.isArray(e)&&(a=e)}catch{}const p=this.getAttribute("more-count"),u=p?parseInt(p,10):void 0;this.handle=g(this,{connect:{authorize:{url:s},appTheme:null!==(t=this.getAttribute("app-theme"))&&void 0!==t?t:void 0,onSuccess:()=>{var e;null===(e=this.onSuccess)||void 0===e||e.call(this),this.dispatchEvent(new CustomEvent("success"))},onError:e=>{var t;null===(t=this.onError)||void 0===t||t.call(this,e),this.dispatchEvent(new CustomEvent("error",{detail:e}))},onClose:()=>{var e;null===(e=this.onClose)||void 0===e||e.call(this),this.dispatchEvent(new CustomEvent("close"))}},label:null!==(n=this.getAttribute("label"))&&void 0!==n?n:void 0,variant:null!==(o=this.getAttribute("variant"))&&void 0!==o?o:void 0,theme:null!==(i=this.getAttribute("theme"))&&void 0!==i?i:void 0,platforms:a,moreCount:Number.isFinite(u)?u:void 0,description:null!==(r=this.getAttribute("description"))&&void 0!==r?r:void 0,accentColor:null!==(c=this.getAttribute("accent-color"))&&void 0!==c?c:void 0,connectedLabel:null!==(l=this.getAttribute("connected-label"))&&void 0!==l?l:void 0})}}o(e,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",e)}}();export{v as ConnectButton};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/connect",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Frontend bindings for One Connect, a drop-in OAuth flow that lets your users grant your application scoped, revocable access to their own connected tools. The user owns the connections; you hold a token scoped to exactly what they granted.",
5
5
  "files": [
6
6
  "dist",
@@ -24,10 +24,12 @@
24
24
  "@rollup/plugin-node-resolve": "^15.2.3",
25
25
  "@rollup/plugin-terser": "^0.4.4",
26
26
  "@tsconfig/recommended": "^1.0.3",
27
+ "@types/react": "^19.2.18",
27
28
  "@typescript-eslint/eslint-plugin": "^7.0.0",
28
29
  "@typescript-eslint/parser": "^7.0.0",
29
30
  "eslint": "^8.57.0",
30
31
  "prettier": "^3.2.5",
32
+ "react": "^19.2.8",
31
33
  "rollup": "^4.12.0",
32
34
  "rollup-plugin-copy": "^3.5.0",
33
35
  "rollup-plugin-typescript2": "^0.36.0",
@@ -59,5 +61,25 @@
59
61
  "agents"
60
62
  ],
61
63
  "license": "GPL-3.0",
62
- "homepage": "https://withone.ai"
64
+ "homepage": "https://withone.ai",
65
+ "exports": {
66
+ ".": {
67
+ "types": "./dist/index.d.ts",
68
+ "import": "./dist/index.esm.js",
69
+ "require": "./dist/index.umd.js"
70
+ },
71
+ "./react": {
72
+ "types": "./dist/react.d.ts",
73
+ "import": "./dist/react.esm.js",
74
+ "require": "./dist/react.cjs.js"
75
+ }
76
+ },
77
+ "peerDependencies": {
78
+ "react": ">=17"
79
+ },
80
+ "peerDependenciesMeta": {
81
+ "react": {
82
+ "optional": true
83
+ }
84
+ }
63
85
  }
@@ -0,0 +1,23 @@
1
+ /** Hand-maintained declarations for the "@withone/connect/react" subpath. */
2
+ import type { CSSProperties, ReactElement } from "react";
3
+ import type { ConnectButtonPlatform } from "./types";
4
+
5
+ export interface ConnectButtonProps {
6
+ authorizeUrl: string;
7
+ appTheme?: "light" | "dark";
8
+ onSuccess?: () => void;
9
+ onError?: (error: string) => void;
10
+ onClose?: () => void;
11
+ label?: string;
12
+ variant?: "default" | "accent" | "block";
13
+ theme?: "light" | "dark";
14
+ platforms?: ConnectButtonPlatform[];
15
+ moreCount?: number;
16
+ description?: string;
17
+ accentColor?: string;
18
+ connectedLabel?: string;
19
+ className?: string;
20
+ style?: CSSProperties;
21
+ }
22
+
23
+ export declare function ConnectButton(props: ConnectButtonProps): ReactElement;
package/src/react.ts ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * React wrapper — `import { ConnectButton } from "@withone/connect/react"`.
3
+ *
4
+ * A real component over the framework-agnostic core, so React apps get
5
+ * idiomatic props (arrays, functions) instead of the custom element's
6
+ * string attributes. React is a peer dependency of THIS subpath only;
7
+ * the core package stays dependency-free for every other framework.
8
+ */
9
+ import { createElement, useEffect, useRef } from "react";
10
+ import type { CSSProperties, ReactElement } from "react";
11
+
12
+ import { mountConnectButton } from "./button";
13
+ import type {
14
+ ConnectButtonHandle,
15
+ ConnectButtonPlatform,
16
+ ConnectButtonOptions,
17
+ } from "./types";
18
+
19
+ export interface ConnectButtonProps {
20
+ /** The app's own backend authorize route ("/api/one/authorize" is
21
+ * fine — relative resolves against the page). */
22
+ authorizeUrl: string;
23
+ /** Theme for One's card (rides the URL fragment). */
24
+ appTheme?: "light" | "dark";
25
+ onSuccess?: () => void;
26
+ onError?: (error: string) => void;
27
+ onClose?: () => void;
28
+ label?: string;
29
+ variant?: "default" | "accent" | "block";
30
+ /** Matches the host page, not One's card. */
31
+ theme?: "light" | "dark";
32
+ platforms?: ConnectButtonPlatform[];
33
+ moreCount?: number;
34
+ description?: string;
35
+ accentColor?: string;
36
+ connectedLabel?: string;
37
+ className?: string;
38
+ style?: CSSProperties;
39
+ }
40
+
41
+ export function ConnectButton(props: ConnectButtonProps): ReactElement {
42
+ const containerRef = useRef<HTMLDivElement | null>(null);
43
+ const handleRef = useRef<ConnectButtonHandle | null>(null);
44
+
45
+ // Callbacks stay fresh without remounting the button.
46
+ const callbacksRef = useRef({
47
+ onSuccess: props.onSuccess,
48
+ onError: props.onError,
49
+ onClose: props.onClose,
50
+ });
51
+ callbacksRef.current = {
52
+ onSuccess: props.onSuccess,
53
+ onError: props.onError,
54
+ onClose: props.onClose,
55
+ };
56
+
57
+ const platformsKey = JSON.stringify(props.platforms ?? []);
58
+
59
+ useEffect(() => {
60
+ if (!containerRef.current) return;
61
+ const options: ConnectButtonOptions = {
62
+ connect: {
63
+ authorize: { url: props.authorizeUrl },
64
+ appTheme: props.appTheme,
65
+ onSuccess: () => callbacksRef.current.onSuccess?.(),
66
+ onError: (error) => callbacksRef.current.onError?.(error),
67
+ onClose: () => callbacksRef.current.onClose?.(),
68
+ },
69
+ label: props.label,
70
+ variant: props.variant,
71
+ theme: props.theme,
72
+ platforms: props.platforms,
73
+ moreCount: props.moreCount,
74
+ description: props.description,
75
+ accentColor: props.accentColor,
76
+ connectedLabel: props.connectedLabel,
77
+ };
78
+ handleRef.current = mountConnectButton(containerRef.current, options);
79
+ return () => {
80
+ handleRef.current?.destroy();
81
+ handleRef.current = null;
82
+ };
83
+ // Remount only when the visual/config inputs actually change.
84
+ // eslint-disable-next-line react-hooks/exhaustive-deps
85
+ }, [
86
+ props.authorizeUrl,
87
+ props.appTheme,
88
+ props.label,
89
+ props.variant,
90
+ props.theme,
91
+ platformsKey,
92
+ props.moreCount,
93
+ props.description,
94
+ props.accentColor,
95
+ props.connectedLabel,
96
+ ]);
97
+
98
+ return createElement("div", {
99
+ ref: containerRef,
100
+ className: props.className,
101
+ style: props.style,
102
+ });
103
+ }