@withone/connect 0.1.2 → 0.3.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
@@ -145,6 +145,88 @@ export function ConnectWithOne() {
145
145
  | `open()` | Opens the Connect modal over the current page |
146
146
  | `close()` | Tears down the modal frame and its listeners |
147
147
 
148
+
149
+ ### Optional: the pre-built button
150
+
151
+ Any element wired to `open()` works — the button is **optional**. It
152
+ ships as a custom element, `<one-connect-button>`, so the SAME tag
153
+ works in React, Next, Vue, Svelte, or plain HTML — no refs, no mount
154
+ calls. Importing the package registers it. It wires the whole flow
155
+ itself and manages Connect → Connecting → Connected.
156
+
157
+ ```tsx
158
+ // React / Next (any framework — same tag everywhere)
159
+ import "@withone/connect";
160
+
161
+ export function ConnectWithOne() {
162
+ return (
163
+ <one-connect-button
164
+ authorize-url="/api/one/authorize"
165
+ label="Connect your apps"
166
+ platforms='[{"name":"Stripe","imageUrl":"/icons/stripe.svg"},{"name":"PostHog","imageUrl":"/icons/posthog.svg"}]'
167
+ onSuccess={() => {/* tokens stored server-side — refresh app state */}}
168
+ />
169
+ );
170
+ }
171
+ ```
172
+
173
+ ```html
174
+ <!-- Plain HTML / any framework -->
175
+ <one-connect-button
176
+ authorize-url="/api/one/authorize"
177
+ label="Connect your apps"
178
+ ></one-connect-button>
179
+ <script>
180
+ document.querySelector("one-connect-button")
181
+ .addEventListener("success", () => location.reload());
182
+ </script>
183
+ ```
184
+
185
+ | Attribute | What it does |
186
+ |---|---|
187
+ | `authorize-url` | Your backend authorize route (required) |
188
+ | `label` | Button text (default "Connect your apps") |
189
+ | `variant` | `default` pill · `accent` brand pill · `block` consent card |
190
+ | `theme` | `light` / `dark` — matches YOUR page |
191
+ | `app-theme` | Theme for One's card |
192
+ | `platforms` | JSON array of `{name, imageUrl}` — provider chips, fan on hover |
193
+ | `more-count` | The `+N` chip (e.g. `274`) |
194
+ | `description` | Sub-line on the `block` variant |
195
+ | `accent-color` | Fill for the `accent` variant (lime fallback) |
196
+ | `connected-label` | Label after success |
197
+
198
+ Events: `success`, `error` (detail = message), `close` — or set the
199
+ `onSuccess` / `onError` / `onClose` function props (React 19, Vue and
200
+ Svelte set these naturally).
201
+
202
+ TypeScript + React: add this once so JSX accepts the tag:
203
+
204
+ ```ts
205
+ // one-connect-button.d.ts
206
+ declare module "react" {
207
+ namespace JSX {
208
+ interface IntrinsicElements {
209
+ "one-connect-button": React.DetailedHTMLProps<
210
+ React.HTMLAttributes<HTMLElement>,
211
+ HTMLElement
212
+ > & {
213
+ "authorize-url"?: string; "app-theme"?: string; label?: string;
214
+ variant?: string; theme?: string; platforms?: string;
215
+ "more-count"?: string; description?: string;
216
+ "accent-color"?: string; "connected-label"?: string;
217
+ onSuccess?: () => void; onError?: (e: string) => void;
218
+ onClose?: () => void;
219
+ };
220
+ }
221
+ }
222
+ }
223
+ export {};
224
+ ```
225
+
226
+ Programmatic alternative: `mountConnectButton(container, options)`
227
+ takes the same options as an object (plus `connect: {…useOneConnect
228
+ props}`) and returns `{ setState, destroy }`.
229
+
148
230
  ## 3 · Backend — the authorize route
149
231
 
150
232
  Generates `state` (CSRF proof) and PKCE (proof that whoever redeems the code is this server), stashes both in an httpOnly cookie, and 302s the browser to One.
@@ -0,0 +1,18 @@
1
+ import type { ConnectButtonHandle, ConnectButtonOptions } from "./types";
2
+ export declare const mountConnectButton: (container: HTMLElement, options: ConnectButtonOptions) => ConnectButtonHandle;
3
+ /**
4
+ * <one-connect-button> — the simple path. One tag, any framework
5
+ * (React, Vue, Svelte, plain HTML): the element wires the whole flow
6
+ * itself from its attributes and manages Connect → Connecting →
7
+ * Connected. Registered automatically in the browser on import;
8
+ * defined inside a function so importing this module on a server
9
+ * (Next.js SSR) never touches HTMLElement.
10
+ *
11
+ * Attributes: authorize-url (required), app-theme, label, variant,
12
+ * theme, platforms (JSON array of {name, imageUrl}), more-count,
13
+ * description, accent-color, connected-label.
14
+ * Events: "success" | "error" (detail: message) | "close" — plus
15
+ * matching function properties (onSuccess/onError/onClose) that
16
+ * React 19 / Vue / Svelte set naturally as props.
17
+ */
18
+ export declare function registerConnectButton(): void;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { useOneConnect } from "./useOneConnect";
2
2
  export { completeOneConnect } from "./complete";
3
- export type { OneConnectProps, OneConnectHandle, OneConnectResult, } from "./types";
3
+ export { mountConnectButton, registerConnectButton } from "./button";
4
+ export type { OneConnectProps, OneConnectHandle, OneConnectResult, ConnectButtonOptions, ConnectButtonPlatform, ConnectButtonHandle, } from "./types";
package/dist/index.esm.js CHANGED
@@ -1 +1 @@
1
- const e="@withone/connect:result",t="one-connect-frame";function n(){const e=document.getElementById(t);e&&e.remove()}const o="one-connect-success";function i(){var e;null===(e=document.getElementById(o))||void 0===e||e.remove()}function r(){return document.getElementById(t)}const s=s=>{let a=null,c=!1;const d=()=>{"undefined"!=typeof window&&a&&(window.removeEventListener("message",a),a=null),n()},l=(e,t)=>{if(!c){c=!0,d(),"success"===e&&function(e){var t;i();const n="dark"===e,r=document.createElement("div");r.id=o,Object.assign(r.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 s=document.createElement("div");Object.assign(s.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 a=n?"#a1a1aa":"#6b7280",c=n?"rgba(25, 25, 25, 0.97)":"rgba(255, 255, 255, 0.97)",d=n?"rgba(255,255,255,0.08)":"rgba(228,228,223,0.9)",l=n?"rgba(45, 45, 44, 0.9)":"rgba(232, 232, 228, 0.95)";s.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:${c};border:1px solid ${d};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:${a};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:${a};">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:${l};border:1px solid ${d};border-top:0;"><span style="font-size:11px;color:${a};">Secured by</span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${n?"#fafafa":"#111114"};">one</span></div>`,r.appendChild(s),document.body.appendChild(r),requestAnimationFrame(()=>{r.style.opacity="1"});const u=()=>{window.removeEventListener("keydown",p),r.style.opacity="0",window.setTimeout(()=>r.remove(),180)};function p(e){"Escape"===e.key&&u()}for(const e of s.querySelectorAll("[data-one-close]"))e.addEventListener("click",u);r.addEventListener("click",e=>{e.target===r&&u()}),window.addEventListener("keydown",p),null===(t=s.querySelector("[data-one-close]"))||void 0===t||t.focus()}(s.appTheme);try{var n,r;if("success"===e)null===(n=s.onSuccess)||void 0===n||n.call(s);else null===(r=s.onError)||void 0===r||r.call(s,null!=t?t:"The connection was not completed.")}catch{}}},u=t=>{const n=t.data;if(!n)return;const o=r();if(o&&t.source===o.contentWindow)if("@withone/connect:exit"!==n.type)n.type!==e||t.origin!==window.location.origin||"success"!==n.status&&"error"!==n.status||l(n.status,n.message);else{d();try{var i;null===(i=s.onClose)||void 0===i||i.call(s)}catch{}}},p=()=>{var e;const t=r();if(!t)return;let n,o;try{var i,s;n=null!==(i=null===(s=t.contentWindow)||void 0===s?void 0:s.location.href)&&void 0!==i?i:""}catch{return}try{o=new URL(n).searchParams}catch{return}const a=o.get("one_connect");"success"!==a&&"error"!==a||(t.style.visibility="hidden",l(a,null!==(e=o.get("one_connect_message"))&&void 0!==e?e:void 0))};return{open:()=>{if("undefined"==typeof window)return;c=!1,a=u,window.addEventListener("message",a);(function(e){n();const o=document.createElement("iframe");return o.id=t,o.src=e,o.setAttribute("allowtransparency","true"),Object.assign(o.style,{position:"fixed",inset:"0",width:"100%",height:"100%",border:"0",zIndex:"2147483000",background:"transparent",colorScheme:"normal"}),document.body.appendChild(o),o})((()=>{try{const e=new URL(s.authorize.url);return s.appTheme&&(e.hash=`one_theme=${s.appTheme}`),e.toString()}catch{return s.authorize.url}})()).addEventListener("load",p)},close:()=>{i(),d()}}};function a(t={status:"success"}){if("undefined"==typeof window)return!1;if(window.parent&&window.parent!==window){const n={type:e,status:t.status,message:t.message};try{return window.parent.postMessage(n,window.location.origin),!0}catch{return!1}}return!1}export{a as completeOneConnect,s as useOneConnect};
1
+ const e="@withone/connect:result",t="one-connect-frame";function n(){const e=document.getElementById(t);e&&e.remove()}const o="one-connect-success";function i(){var e;null===(e=document.getElementById(o))||void 0===e||e.remove()}function r(){return document.getElementById(t)}const c=c=>{let s=null,l=!1;const a=()=>{"undefined"!=typeof window&&s&&(window.removeEventListener("message",s),s=null),n()},d=(e,t)=>{if(!l){l=!0,a(),"success"===e&&function(e){var t;i();const n="dark"===e,r=document.createElement("div");r.id=o,Object.assign(r.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 s=n?"#a1a1aa":"#6b7280",l=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:${l};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:${s};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:${s};">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:${s};">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>`,r.appendChild(c),document.body.appendChild(r),requestAnimationFrame(()=>{r.style.opacity="1"});const p=()=>{window.removeEventListener("keydown",u),r.style.opacity="0",window.setTimeout(()=>r.remove(),180)};function u(e){"Escape"===e.key&&p()}for(const e of c.querySelectorAll("[data-one-close]"))e.addEventListener("click",p);r.addEventListener("click",e=>{e.target===r&&p()}),window.addEventListener("keydown",u),null===(t=c.querySelector("[data-one-close]"))||void 0===t||t.focus()}(c.appTheme);try{var n,r;if("success"===e)null===(n=c.onSuccess)||void 0===n||n.call(c);else null===(r=c.onError)||void 0===r||r.call(c,null!=t?t:"The connection was not completed.")}catch{}}},p=t=>{const n=t.data;if(!n)return;const o=r();if(o&&t.source===o.contentWindow)if("@withone/connect:exit"!==n.type)n.type!==e||t.origin!==window.location.origin||"success"!==n.status&&"error"!==n.status||d(n.status,n.message);else{a();try{var i;null===(i=c.onClose)||void 0===i||i.call(c)}catch{}}},u=()=>{var e;const t=r();if(!t)return;let n,o;try{var i,c;n=null!==(i=null===(c=t.contentWindow)||void 0===c?void 0:c.location.href)&&void 0!==i?i:""}catch{return}try{o=new URL(n).searchParams}catch{return}const s=o.get("one_connect");"success"!==s&&"error"!==s||(t.style.visibility="hidden",d(s,null!==(e=o.get("one_connect_message"))&&void 0!==e?e:void 0))};return{open:()=>{if("undefined"==typeof window)return;l=!1,s=p,window.addEventListener("message",s);(function(e){n();const o=document.createElement("iframe");return o.id=t,o.src=e,o.setAttribute("allowtransparency","true"),Object.assign(o.style,{position:"fixed",inset:"0",width:"100%",height:"100%",border:"0",zIndex:"2147483000",background:"transparent",colorScheme:"normal"}),document.body.appendChild(o),o})((()=>{try{const e=new URL(c.authorize.url);return c.appTheme&&(e.hash=`one_theme=${c.appTheme}`),e.toString()}catch{return c.authorize.url}})()).addEventListener("load",u)},close:()=>{i(),a()}}};function s(t={status:"success"}){if("undefined"==typeof window)return!1;if(window.parent&&window.parent!==window){const n={type:e,status:t.status,message:t.message};try{return window.parent.postMessage(n,window.location.origin),!0}catch{return!1}}return!1}function l(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 a="one-connect-button-styles",d="cubic-bezier(.2,.9,.25,1)",p={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)"},u={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",h="#3FE3A5",m="#0A0C0B";const b=(e,t)=>{var n,o;!function(){if("undefined"==typeof document||document.getElementById(a))return;const e=document.createElement("style");e.id=a,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 ${d},box-shadow .2s ${d},opacity .2s ${d}}`,".owcb:hover{transform:translateY(-1px)}",".owcb:active{transform:translateY(0)}",`.owcb:focus-visible{outline:2px solid ${h};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 ${d};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 ${d},opacity .2s ${d}}`,".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?u:p,r=null!==(n=t.variant)&&void 0!==n?n:"default",s=null!==(o=t.label)&&void 0!==o?o:"Connect your apps";let l="idle",b=!1;const g=c({...t.connect,onSuccess:()=>{var e,n;v("connected"),null===(e=(n=t.connect).onSuccess)||void 0===e||e.call(n)},onError:e=>{var n,o;v("idle"),null===(n=(o=t.connect).onError)||void 0===n||n.call(o,e)},onClose:()=>{var e,n;v("idle"),null===(e=(n=t.connect).onClose)||void 0===e||e.call(n)}}),w=document.createElement("button");w.type="button",w.className="block"===r?"owcb owcb-block":"owcb",w.addEventListener("click",()=>{"idle"!==l&&"connected"!==l||(v("connecting"),g.open())});const x=()=>{var e,n,o;const c="accent"===r,a="block"===r,d=a?i.surface:c?null!==(e=t.accentColor)&&void 0!==e?e:f:i.btnBg,p=a?i.fg:c?m:i.btnFg,u=a?i.surface:d,b=a?i.muted:p;w.style.background="connected"!==l||a?d:h,w.style.color="connected"!==l||a?p:m,w.style.boxShadow=a?`inset 0 0 0 1px ${i.line}`:"connected"===l?"none":c?`0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${null!==(n=t.accentColor)&&void 0!==n?n:f}38`:i.shadow,w.disabled="connecting"===l,w.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=m}),n.appendChild(t)}else n.textContent=e.name.charAt(0).toUpperCase(),n.style.font="600 10px ui-monospace,monospace",n.style.color=m;c.appendChild(n)}const s=(null!==(i=e.moreCount)&&void 0!==i?i:0)+Math.max(0,r.length-4);if(s>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=`+${s}`,c.appendChild(e)}return c}(t,u,b);if(a){var x;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!==(x=t.connectedLabel)&&void 0!==x?x:"Connected":s,e.appendChild(n),"connecting"===l){const t=document.createElement("span");t.className="owcb-spinner",e.appendChild(t)}else g&&e.appendChild(g);if(w.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,w.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 w.appendChild(o)}if("connecting"===l){const e=document.createElement("span");e.className="owcb-spinner",w.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",w.appendChild(e)}else g&&w.appendChild(g);const v=document.createElement("span");if(v.className="owcb-label",v.textContent="connecting"===l?"Connecting…":"connected"===l?null!==(o=t.connectedLabel)&&void 0!==o?o:"Connected":s,w.appendChild(v),"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",w.appendChild(e)}},v=e=>{b||(l=e,x())};return x(),e.appendChild(w),{setState:v,destroy:()=>{b=!0,g.close(),w.remove()}}};function g(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class e extends HTMLElement{constructor(...e){super(...e),l(this,"onSuccess",null),l(this,"onError",null),l(this,"onClose",null),l(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,s;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null;const l=this.getAttribute("authorize-url");if(!l)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=b(this,{connect:{authorize:{url:l},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!==(s=this.getAttribute("connected-label"))&&void 0!==s?s:void 0})}}l(e,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",e)}}g();export{s as completeOneConnect,b as mountConnectButton,g as registerConnectButton,c as useOneConnect};
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- "use strict";const e="@withone/connect:result",t="one-connect-frame";function n(){const e=document.getElementById(t);e&&e.remove()}const o="one-connect-success";function i(){var e;null===(e=document.getElementById(o))||void 0===e||e.remove()}function r(){return document.getElementById(t)}exports.completeOneConnect=function(t={status:"success"}){if("undefined"==typeof window)return!1;if(window.parent&&window.parent!==window){const n={type:e,status:t.status,message:t.message};try{return window.parent.postMessage(n,window.location.origin),!0}catch{return!1}}return!1},exports.useOneConnect=s=>{let a=null,c=!1;const d=()=>{"undefined"!=typeof window&&a&&(window.removeEventListener("message",a),a=null),n()},l=(e,t)=>{if(!c){c=!0,d(),"success"===e&&function(e){var t;i();const n="dark"===e,r=document.createElement("div");r.id=o,Object.assign(r.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 s=document.createElement("div");Object.assign(s.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 a=n?"#a1a1aa":"#6b7280",c=n?"rgba(25, 25, 25, 0.97)":"rgba(255, 255, 255, 0.97)",d=n?"rgba(255,255,255,0.08)":"rgba(228,228,223,0.9)",l=n?"rgba(45, 45, 44, 0.9)":"rgba(232, 232, 228, 0.95)";s.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:${c};border:1px solid ${d};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:${a};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:${a};">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:${l};border:1px solid ${d};border-top:0;"><span style="font-size:11px;color:${a};">Secured by</span><span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${n?"#fafafa":"#111114"};">one</span></div>`,r.appendChild(s),document.body.appendChild(r),requestAnimationFrame(()=>{r.style.opacity="1"});const u=()=>{window.removeEventListener("keydown",p),r.style.opacity="0",window.setTimeout(()=>r.remove(),180)};function p(e){"Escape"===e.key&&u()}for(const e of s.querySelectorAll("[data-one-close]"))e.addEventListener("click",u);r.addEventListener("click",e=>{e.target===r&&u()}),window.addEventListener("keydown",p),null===(t=s.querySelector("[data-one-close]"))||void 0===t||t.focus()}(s.appTheme);try{var n,r;if("success"===e)null===(n=s.onSuccess)||void 0===n||n.call(s);else null===(r=s.onError)||void 0===r||r.call(s,null!=t?t:"The connection was not completed.")}catch{}}},u=t=>{const n=t.data;if(!n)return;const o=r();if(o&&t.source===o.contentWindow)if("@withone/connect:exit"!==n.type)n.type!==e||t.origin!==window.location.origin||"success"!==n.status&&"error"!==n.status||l(n.status,n.message);else{d();try{var i;null===(i=s.onClose)||void 0===i||i.call(s)}catch{}}},p=()=>{var e;const t=r();if(!t)return;let n,o;try{var i,s;n=null!==(i=null===(s=t.contentWindow)||void 0===s?void 0:s.location.href)&&void 0!==i?i:""}catch{return}try{o=new URL(n).searchParams}catch{return}const a=o.get("one_connect");"success"!==a&&"error"!==a||(t.style.visibility="hidden",l(a,null!==(e=o.get("one_connect_message"))&&void 0!==e?e:void 0))};return{open:()=>{if("undefined"==typeof window)return;c=!1,a=u,window.addEventListener("message",a);(function(e){n();const o=document.createElement("iframe");return o.id=t,o.src=e,o.setAttribute("allowtransparency","true"),Object.assign(o.style,{position:"fixed",inset:"0",width:"100%",height:"100%",border:"0",zIndex:"2147483000",background:"transparent",colorScheme:"normal"}),document.body.appendChild(o),o})((()=>{try{const e=new URL(s.authorize.url);return s.appTheme&&(e.hash=`one_theme=${s.appTheme}`),e.toString()}catch{return s.authorize.url}})()).addEventListener("load",p)},close:()=>{i(),d()}}};
1
+ "use strict";const e="@withone/connect:result",t="one-connect-frame";function n(){const e=document.getElementById(t);e&&e.remove()}const o="one-connect-success";function i(){var e;null===(e=document.getElementById(o))||void 0===e||e.remove()}function r(){return document.getElementById(t)}const c=c=>{let s=null,l=!1;const a=()=>{"undefined"!=typeof window&&s&&(window.removeEventListener("message",s),s=null),n()},d=(e,t)=>{if(!l){l=!0,a(),"success"===e&&function(e){var t;i();const n="dark"===e,r=document.createElement("div");r.id=o,Object.assign(r.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 s=n?"#a1a1aa":"#6b7280",l=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:${l};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:${s};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:${s};">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:${s};">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>`,r.appendChild(c),document.body.appendChild(r),requestAnimationFrame(()=>{r.style.opacity="1"});const p=()=>{window.removeEventListener("keydown",u),r.style.opacity="0",window.setTimeout(()=>r.remove(),180)};function u(e){"Escape"===e.key&&p()}for(const e of c.querySelectorAll("[data-one-close]"))e.addEventListener("click",p);r.addEventListener("click",e=>{e.target===r&&p()}),window.addEventListener("keydown",u),null===(t=c.querySelector("[data-one-close]"))||void 0===t||t.focus()}(c.appTheme);try{var n,r;if("success"===e)null===(n=c.onSuccess)||void 0===n||n.call(c);else null===(r=c.onError)||void 0===r||r.call(c,null!=t?t:"The connection was not completed.")}catch{}}},p=t=>{const n=t.data;if(!n)return;const o=r();if(o&&t.source===o.contentWindow)if("@withone/connect:exit"!==n.type)n.type!==e||t.origin!==window.location.origin||"success"!==n.status&&"error"!==n.status||d(n.status,n.message);else{a();try{var i;null===(i=c.onClose)||void 0===i||i.call(c)}catch{}}},u=()=>{var e;const t=r();if(!t)return;let n,o;try{var i,c;n=null!==(i=null===(c=t.contentWindow)||void 0===c?void 0:c.location.href)&&void 0!==i?i:""}catch{return}try{o=new URL(n).searchParams}catch{return}const s=o.get("one_connect");"success"!==s&&"error"!==s||(t.style.visibility="hidden",d(s,null!==(e=o.get("one_connect_message"))&&void 0!==e?e:void 0))};return{open:()=>{if("undefined"==typeof window)return;l=!1,s=p,window.addEventListener("message",s);(function(e){n();const o=document.createElement("iframe");return o.id=t,o.src=e,o.setAttribute("allowtransparency","true"),Object.assign(o.style,{position:"fixed",inset:"0",width:"100%",height:"100%",border:"0",zIndex:"2147483000",background:"transparent",colorScheme:"normal"}),document.body.appendChild(o),o})((()=>{try{const e=new URL(c.authorize.url);return c.appTheme&&(e.hash=`one_theme=${c.appTheme}`),e.toString()}catch{return c.authorize.url}})()).addEventListener("load",u)},close:()=>{i(),a()}}};function s(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 l="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(l))return;const e=document.createElement("style");e.id=l,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",s=null!==(o=t.label)&&void 0!==o?o:"Connect your apps";let m="idle",b=!1;const g=c({...t.connect,onSuccess:()=>{var e,n;v("connected"),null===(e=(n=t.connect).onSuccess)||void 0===e||e.call(n)},onError:e=>{var n,o;v("idle"),null===(n=(o=t.connect).onError)||void 0===n||n.call(o,e)},onClose:()=>{var e,n;v("idle"),null===(e=(n=t.connect).onClose)||void 0===e||e.call(n)}}),w=document.createElement("button");w.type="button",w.className="block"===r?"owcb owcb-block":"owcb",w.addEventListener("click",()=>{"idle"!==m&&"connected"!==m||(v("connecting"),g.open())});const x=()=>{var e,n,o;const c="accent"===r,l="block"===r,a=l?i.surface:c?null!==(e=t.accentColor)&&void 0!==e?e:u:i.btnBg,d=l?i.fg:c?h:i.btnFg,p=l?i.surface:a,b=l?i.muted:d;w.style.background="connected"!==m||l?a:f,w.style.color="connected"!==m||l?d:h,w.style.boxShadow=l?`inset 0 0 0 1px ${i.line}`:"connected"===m?"none":c?`0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${null!==(n=t.accentColor)&&void 0!==n?n:u}38`:i.shadow,w.disabled="connecting"===m,w.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 s=(null!==(i=e.moreCount)&&void 0!==i?i:0)+Math.max(0,r.length-4);if(s>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=`+${s}`,c.appendChild(e)}return c}(t,p,b);if(l){var x;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!==(x=t.connectedLabel)&&void 0!==x?x:"Connected":s,e.appendChild(n),"connecting"===m){const t=document.createElement("span");t.className="owcb-spinner",e.appendChild(t)}else g&&e.appendChild(g);if(w.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,w.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 w.appendChild(o)}if("connecting"===m){const e=document.createElement("span");e.className="owcb-spinner",w.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",w.appendChild(e)}else g&&w.appendChild(g);const v=document.createElement("span");if(v.className="owcb-label",v.textContent="connecting"===m?"Connecting…":"connected"===m?null!==(o=t.connectedLabel)&&void 0!==o?o:"Connected":s,w.appendChild(v),"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",w.appendChild(e)}},v=e=>{b||(m=e,x())};return x(),e.appendChild(w),{setState:v,destroy:()=>{b=!0,g.close(),w.remove()}}};function b(){if("undefined"!=typeof window&&"undefined"!=typeof customElements&&!customElements.get("one-connect-button")){class e extends HTMLElement{constructor(...e){super(...e),s(this,"onSuccess",null),s(this,"onError",null),s(this,"onClose",null),s(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,s;null===(e=this.handle)||void 0===e||e.destroy(),this.handle=null;const l=this.getAttribute("authorize-url");if(!l)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:l},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!==(s=this.getAttribute("connected-label"))&&void 0!==s?s:void 0})}}s(e,"observedAttributes",["authorize-url","app-theme","label","variant","theme","platforms","more-count","description","accent-color","connected-label"]),customElements.define("one-connect-button",e)}}b(),exports.completeOneConnect=function(t={status:"success"}){if("undefined"==typeof window)return!1;if(window.parent&&window.parent!==window){const n={type:e,status:t.status,message:t.message};try{return window.parent.postMessage(n,window.location.origin),!0}catch{return!1}}return!1},exports.mountConnectButton=m,exports.registerConnectButton=b,exports.useOneConnect=c;
@@ -50,3 +50,47 @@ export interface OneConnectMessage {
50
50
  status: "success" | "error";
51
51
  message?: string;
52
52
  }
53
+
54
+ /** A provider chip on the pre-built button. The SDK ships no One URLs,
55
+ * so icon sources are the consumer's to provide. */
56
+ export interface ConnectButtonPlatform {
57
+ name: string;
58
+ imageUrl?: string;
59
+ }
60
+
61
+ export interface ConnectButtonOptions {
62
+ /** Everything useOneConnect takes — the button wires open() and the
63
+ * Connecting/Connected states around your callbacks. */
64
+ connect: OneConnectProps;
65
+ /** "Connect your apps" unless overridden. */
66
+ label?: string;
67
+ /** default = neutral pill; accent = brand-colored pill; block =
68
+ * full-width consent card with description + "Secured by One" foot. */
69
+ variant?: "default" | "accent" | "block";
70
+ /** Matches the host page, not One's card (that's connect.appTheme). */
71
+ theme?: "light" | "dark";
72
+ /** Provider chips fanned on hover; first four render, the rest fold
73
+ * into the +N chip together with moreCount. */
74
+ platforms?: ConnectButtonPlatform[];
75
+ /** Extra count for the +N chip (e.g. 274 for "the whole catalog"). */
76
+ moreCount?: number;
77
+ /** Sub-line on the block variant, shown while idle. */
78
+ description?: string;
79
+ /** accent variant fill; lime (#CCFF00) when omitted. */
80
+ accentColor?: string;
81
+ /** Label for the connected state ("4 apps connected"). */
82
+ connectedLabel?: string;
83
+ }
84
+
85
+ export interface ConnectButtonHandle {
86
+ /** Manually override the visual state. */
87
+ setState: (state: "idle" | "connecting" | "connected") => void;
88
+ /** Remove the button and tear down the flow wiring. */
89
+ destroy: () => void;
90
+ }
91
+
92
+ /** Registers the <one-connect-button> custom element (no-op on servers
93
+ * and when already registered). Importing the package does this
94
+ * automatically in browsers; calling it manually is only needed if a
95
+ * bundler stripped module side effects. */
96
+ export declare function registerConnectButton(): void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/connect",
3
- "version": "0.1.2",
3
+ "version": "0.3.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",
package/src/button.ts ADDED
@@ -0,0 +1,414 @@
1
+ import { useOneConnect } from "./useOneConnect";
2
+ import type {
3
+ ConnectButtonHandle,
4
+ ConnectButtonOptions,
5
+ OneConnectHandle,
6
+ } from "./types";
7
+
8
+ /**
9
+ * Optional pre-built trigger for the connect flow. Entirely opt-in —
10
+ * `useOneConnect` alone with any element the consumer likes remains
11
+ * fully supported. Framework-agnostic like the rest of the SDK: it
12
+ * renders real DOM into a container, so React/Vue/vanilla all mount it
13
+ * the same way. Zero dependencies, zero One URLs — provider icons are
14
+ * passed in by the consumer.
15
+ *
16
+ * The button owns the flow wiring: click opens the card, the label
17
+ * turns "Connecting…" while the card is up, and lands on a spring
18
+ * "Connected" state on success (Connect → Connecting → Connected; the
19
+ * verb never changes).
20
+ */
21
+
22
+ const STYLE_ID = "one-connect-button-styles";
23
+ const EASE = "cubic-bezier(.2,.9,.25,1)";
24
+
25
+ interface Palette {
26
+ btnBg: string;
27
+ btnFg: string;
28
+ surface: string;
29
+ fg: string;
30
+ muted: string;
31
+ line: string;
32
+ shadow: string;
33
+ }
34
+
35
+ const LIGHT: Palette = {
36
+ btnBg: "#0A0C0B",
37
+ btnFg: "#ffffff",
38
+ surface: "#ffffff",
39
+ fg: "#0A0C0B",
40
+ muted: "#6B7280",
41
+ line: "#D1D5DB",
42
+ shadow: "0 1px 2px rgba(10,12,11,.08), 0 4px 14px rgba(10,12,11,.06)",
43
+ };
44
+
45
+ const DARK: Palette = {
46
+ btnBg: "#F2F5F3",
47
+ btnFg: "#0A0C0B",
48
+ surface: "#101312",
49
+ fg: "#F2F5F3",
50
+ muted: "#8A938E",
51
+ line: "#2A302D",
52
+ shadow: "0 1px 2px rgba(0,0,0,.4), 0 8px 24px rgba(0,0,0,.35)",
53
+ };
54
+
55
+ const LIME = "#CCFF00";
56
+ const SPRING = "#3FE3A5";
57
+ const CARBON = "#0A0C0B";
58
+
59
+ /** The One ring mark — same glyph the card footer pairs with the
60
+ * wordmark. Inline so the SDK stays free of One URLs. */
61
+ const ONE_MARK_SVG =
62
+ '<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>';
63
+ const ARROW_SVG =
64
+ '<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>';
65
+ const TICK_SVG =
66
+ '<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>';
67
+
68
+ function ensureStyles(): void {
69
+ if (typeof document === "undefined" || document.getElementById(STYLE_ID))
70
+ return;
71
+ const style = document.createElement("style");
72
+ style.id = STYLE_ID;
73
+ style.textContent = [
74
+ `.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 ${EASE},box-shadow .2s ${EASE},opacity .2s ${EASE}}`,
75
+ ".owcb:hover{transform:translateY(-1px)}",
76
+ ".owcb:active{transform:translateY(0)}",
77
+ `.owcb:focus-visible{outline:2px solid ${SPRING};outline-offset:3px}`,
78
+ ".owcb[disabled]{cursor:default;opacity:.75;transform:none}",
79
+ ".owcb-stack{display:inline-flex;align-items:center;flex-shrink:0}",
80
+ `.owcb-chip{width:22px;height:22px;border-radius:6px;display:grid;place-items:center;overflow:hidden;margin-left:-7px;transition:margin-left .28s ${EASE};position:relative}`,
81
+ ".owcb-chip:first-child{margin-left:0}",
82
+ ".owcb-chip img{width:14px;height:14px;display:block;object-fit:contain}",
83
+ ".owcb-chip.owcb-more{font-family:ui-monospace,monospace;font-size:9px;font-weight:500;letter-spacing:-.02em}",
84
+ ".owcb:hover .owcb-chip{margin-left:-2px}",
85
+ ".owcb:hover .owcb-chip:first-child{margin-left:0}",
86
+ ".owcb-label{white-space:nowrap}",
87
+ `.owcb-arrow{opacity:.5;transition:transform .2s ${EASE},opacity .2s ${EASE}}`,
88
+ ".owcb:hover .owcb-arrow{transform:translateX(2px);opacity:.8}",
89
+ `.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}`,
90
+ "@keyframes owcb-spin{to{transform:rotate(360deg)}}",
91
+ ".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}",
92
+ ".owcb-block-top{display:flex;align-items:center;justify-content:space-between;gap:12px}",
93
+ ".owcb-block-title{font-size:15px;font-weight:600;letter-spacing:-.01em}",
94
+ ".owcb-block-sub{font-size:13px;line-height:1.5;margin-top:6px;font-weight:400}",
95
+ ".owcb-block-foot{margin-top:14px;padding-top:12px;display:flex;align-items:center;gap:6px;font-size:11.5px;font-weight:400}",
96
+ "@media (prefers-reduced-motion:reduce){.owcb,.owcb-chip,.owcb-arrow{transition:none}.owcb-spinner{animation-duration:1.4s}}",
97
+ ].join("\n");
98
+ document.head.appendChild(style);
99
+ }
100
+
101
+ function buildStack(
102
+ options: ConnectButtonOptions,
103
+ chipRing: string,
104
+ moreColor: string,
105
+ ): HTMLElement | null {
106
+ const platforms = options.platforms ?? [];
107
+ if (platforms.length === 0 && !options.moreCount) return null;
108
+ const stack = document.createElement("span");
109
+ stack.className = "owcb-stack";
110
+ stack.setAttribute("aria-hidden", "true");
111
+ for (const platform of platforms.slice(0, 4)) {
112
+ const chip = document.createElement("span");
113
+ chip.className = "owcb-chip";
114
+ chip.style.background = "#ffffff";
115
+ chip.style.boxShadow = `0 0 0 1.5px ${chipRing}`;
116
+ if (platform.imageUrl) {
117
+ const img = document.createElement("img");
118
+ img.alt = "";
119
+ img.src = platform.imageUrl;
120
+ img.addEventListener("error", () => {
121
+ img.style.display = "none";
122
+ chip.textContent = platform.name.charAt(0).toUpperCase();
123
+ chip.style.font = "600 10px ui-monospace,monospace";
124
+ chip.style.color = CARBON;
125
+ });
126
+ chip.appendChild(img);
127
+ } else {
128
+ chip.textContent = platform.name.charAt(0).toUpperCase();
129
+ chip.style.font = "600 10px ui-monospace,monospace";
130
+ chip.style.color = CARBON;
131
+ }
132
+ stack.appendChild(chip);
133
+ }
134
+ const extra =
135
+ (options.moreCount ?? 0) + Math.max(0, platforms.length - 4);
136
+ if (extra > 0) {
137
+ const more = document.createElement("span");
138
+ more.className = "owcb-chip owcb-more";
139
+ more.style.background = "#ffffff";
140
+ more.style.boxShadow = `0 0 0 1.5px ${chipRing}`;
141
+ more.style.color = moreColor;
142
+ more.textContent = `+${extra}`;
143
+ stack.appendChild(more);
144
+ }
145
+ return stack;
146
+ }
147
+
148
+ export const mountConnectButton = (
149
+ container: HTMLElement,
150
+ options: ConnectButtonOptions,
151
+ ): ConnectButtonHandle => {
152
+ ensureStyles();
153
+
154
+ const palette = options.theme === "dark" ? DARK : LIGHT;
155
+ const variant = options.variant ?? "default";
156
+ const label = options.label ?? "Connect your apps";
157
+
158
+ let state: "idle" | "connecting" | "connected" = "idle";
159
+ let destroyed = false;
160
+
161
+ const handle: OneConnectHandle = useOneConnect({
162
+ ...options.connect,
163
+ onSuccess: () => {
164
+ setState("connected");
165
+ options.connect.onSuccess?.();
166
+ },
167
+ onError: (error) => {
168
+ setState("idle");
169
+ options.connect.onError?.(error);
170
+ },
171
+ onClose: () => {
172
+ setState("idle");
173
+ options.connect.onClose?.();
174
+ },
175
+ });
176
+
177
+ const button = document.createElement("button");
178
+ button.type = "button";
179
+ button.className = variant === "block" ? "owcb owcb-block" : "owcb";
180
+ button.addEventListener("click", () => {
181
+ if (state !== "idle" && state !== "connected") return;
182
+ setState("connecting");
183
+ handle.open();
184
+ });
185
+
186
+ const paint = () => {
187
+ const accent = variant === "accent";
188
+ const block = variant === "block";
189
+ const bg = block
190
+ ? palette.surface
191
+ : accent
192
+ ? (options.accentColor ?? LIME)
193
+ : palette.btnBg;
194
+ const fg = block ? palette.fg : accent ? CARBON : palette.btnFg;
195
+ const chipRing = block ? palette.surface : bg;
196
+ const moreColor = block ? palette.muted : fg;
197
+
198
+ button.style.background = state === "connected" && !block ? SPRING : bg;
199
+ button.style.color = state === "connected" && !block ? CARBON : fg;
200
+ button.style.boxShadow = block
201
+ ? `inset 0 0 0 1px ${palette.line}`
202
+ : state === "connected"
203
+ ? "none"
204
+ : accent
205
+ ? `0 1px 2px rgba(10,12,11,.1), 0 6px 20px ${(options.accentColor ?? LIME)}38`
206
+ : palette.shadow;
207
+ button.disabled = state === "connecting";
208
+ button.innerHTML = "";
209
+
210
+ const stack = buildStack(options, chipRing, moreColor);
211
+
212
+ if (block) {
213
+ const top = document.createElement("span");
214
+ top.className = "owcb-block-top";
215
+ const title = document.createElement("span");
216
+ title.className = "owcb-block-title";
217
+ title.textContent =
218
+ state === "connecting"
219
+ ? "Connecting…"
220
+ : state === "connected"
221
+ ? (options.connectedLabel ?? "Connected")
222
+ : label;
223
+ top.appendChild(title);
224
+ if (state === "connecting") {
225
+ const spinner = document.createElement("span");
226
+ spinner.className = "owcb-spinner";
227
+ top.appendChild(spinner);
228
+ } else if (stack) {
229
+ top.appendChild(stack);
230
+ }
231
+ button.appendChild(top);
232
+ if (options.description && state === "idle") {
233
+ const sub = document.createElement("span");
234
+ sub.className = "owcb-block-sub";
235
+ sub.style.color = palette.muted;
236
+ sub.textContent = options.description;
237
+ button.appendChild(sub);
238
+ }
239
+ // Mirrors the card/overlay footer strip: "Secured by" + ring mark
240
+ // + "one" wordmark, so the button and the surface it opens read as
241
+ // the same product.
242
+ const foot = document.createElement("span");
243
+ foot.className = "owcb-block-foot";
244
+ foot.style.color = palette.muted;
245
+ foot.style.borderTop = `1px solid ${palette.line}`;
246
+ foot.innerHTML =
247
+ `<span style="font-size:11px;color:${palette.muted}">Secured by</span>` +
248
+ `<span style="display:inline-flex;color:${palette.fg}">${ONE_MARK_SVG}</span>` +
249
+ `<span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${palette.fg}">one</span>`;
250
+ button.appendChild(foot);
251
+ return;
252
+ }
253
+
254
+ if (state === "connecting") {
255
+ const spinner = document.createElement("span");
256
+ spinner.className = "owcb-spinner";
257
+ button.appendChild(spinner);
258
+ } else if (state === "connected") {
259
+ const tick = document.createElement("span");
260
+ tick.innerHTML = TICK_SVG;
261
+ tick.style.display = "inline-flex";
262
+ button.appendChild(tick);
263
+ } else if (stack) {
264
+ button.appendChild(stack);
265
+ }
266
+
267
+ const text = document.createElement("span");
268
+ text.className = "owcb-label";
269
+ text.textContent =
270
+ state === "connecting"
271
+ ? "Connecting…"
272
+ : state === "connected"
273
+ ? (options.connectedLabel ?? "Connected")
274
+ : label;
275
+ button.appendChild(text);
276
+
277
+ if (state === "idle") {
278
+ const arrow = document.createElement("span");
279
+ arrow.innerHTML = ARROW_SVG;
280
+ arrow.style.display = "inline-flex";
281
+ button.appendChild(arrow);
282
+ }
283
+ };
284
+
285
+ const setState = (next: "idle" | "connecting" | "connected") => {
286
+ if (destroyed) return;
287
+ state = next;
288
+ paint();
289
+ };
290
+
291
+ paint();
292
+ container.appendChild(button);
293
+
294
+ return {
295
+ setState,
296
+ destroy: () => {
297
+ destroyed = true;
298
+ handle.close();
299
+ button.remove();
300
+ },
301
+ };
302
+ };
303
+
304
+ /**
305
+ * <one-connect-button> — the simple path. One tag, any framework
306
+ * (React, Vue, Svelte, plain HTML): the element wires the whole flow
307
+ * itself from its attributes and manages Connect → Connecting →
308
+ * Connected. Registered automatically in the browser on import;
309
+ * defined inside a function so importing this module on a server
310
+ * (Next.js SSR) never touches HTMLElement.
311
+ *
312
+ * Attributes: authorize-url (required), app-theme, label, variant,
313
+ * theme, platforms (JSON array of {name, imageUrl}), more-count,
314
+ * description, accent-color, connected-label.
315
+ * Events: "success" | "error" (detail: message) | "close" — plus
316
+ * matching function properties (onSuccess/onError/onClose) that
317
+ * React 19 / Vue / Svelte set naturally as props.
318
+ */
319
+ export function registerConnectButton(): void {
320
+ if (typeof window === "undefined" || typeof customElements === "undefined")
321
+ return;
322
+ if (customElements.get("one-connect-button")) return;
323
+
324
+ class OneConnectButtonElement extends HTMLElement {
325
+ static observedAttributes = [
326
+ "authorize-url",
327
+ "app-theme",
328
+ "label",
329
+ "variant",
330
+ "theme",
331
+ "platforms",
332
+ "more-count",
333
+ "description",
334
+ "accent-color",
335
+ "connected-label",
336
+ ];
337
+
338
+ onSuccess: (() => void) | null = null;
339
+ onError: ((error: string) => void) | null = null;
340
+ onClose: (() => void) | null = null;
341
+
342
+ private handle: ConnectButtonHandle | null = null;
343
+
344
+ connectedCallback(): void {
345
+ this.mount();
346
+ }
347
+
348
+ disconnectedCallback(): void {
349
+ this.handle?.destroy();
350
+ this.handle = null;
351
+ }
352
+
353
+ attributeChangedCallback(): void {
354
+ if (this.isConnected && this.handle) this.mount();
355
+ }
356
+
357
+ private mount(): void {
358
+ this.handle?.destroy();
359
+ this.handle = null;
360
+ const authorizeUrl = this.getAttribute("authorize-url");
361
+ if (!authorizeUrl) return; // nothing to wire yet
362
+
363
+ let platforms: ConnectButtonOptions["platforms"];
364
+ const rawPlatforms = this.getAttribute("platforms");
365
+ if (rawPlatforms) {
366
+ try {
367
+ const parsed = JSON.parse(rawPlatforms) as unknown;
368
+ if (Array.isArray(parsed)) platforms = parsed;
369
+ } catch {
370
+ /* malformed platforms JSON — render without chips */
371
+ }
372
+ }
373
+
374
+ const moreCountRaw = this.getAttribute("more-count");
375
+ const moreCount = moreCountRaw ? parseInt(moreCountRaw, 10) : undefined;
376
+
377
+ this.handle = mountConnectButton(this, {
378
+ connect: {
379
+ authorize: { url: authorizeUrl },
380
+ appTheme:
381
+ (this.getAttribute("app-theme") as "light" | "dark" | null) ??
382
+ undefined,
383
+ onSuccess: () => {
384
+ this.onSuccess?.();
385
+ this.dispatchEvent(new CustomEvent("success"));
386
+ },
387
+ onError: (error) => {
388
+ this.onError?.(error);
389
+ this.dispatchEvent(new CustomEvent("error", { detail: error }));
390
+ },
391
+ onClose: () => {
392
+ this.onClose?.();
393
+ this.dispatchEvent(new CustomEvent("close"));
394
+ },
395
+ },
396
+ label: this.getAttribute("label") ?? undefined,
397
+ variant:
398
+ (this.getAttribute("variant") as ConnectButtonOptions["variant"]) ??
399
+ undefined,
400
+ theme:
401
+ (this.getAttribute("theme") as "light" | "dark" | null) ?? undefined,
402
+ platforms,
403
+ moreCount: Number.isFinite(moreCount) ? moreCount : undefined,
404
+ description: this.getAttribute("description") ?? undefined,
405
+ accentColor: this.getAttribute("accent-color") ?? undefined,
406
+ connectedLabel: this.getAttribute("connected-label") ?? undefined,
407
+ });
408
+ }
409
+ }
410
+
411
+ customElements.define("one-connect-button", OneConnectButtonElement);
412
+ }
413
+
414
+ registerConnectButton();
package/src/index.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  export { useOneConnect } from "./useOneConnect";
2
2
  export { completeOneConnect } from "./complete";
3
+ export { mountConnectButton, registerConnectButton } from "./button";
3
4
  export type {
4
5
  OneConnectProps,
5
6
  OneConnectHandle,
6
7
  OneConnectResult,
8
+ ConnectButtonOptions,
9
+ ConnectButtonPlatform,
10
+ ConnectButtonHandle,
7
11
  } from "./types";
@@ -50,3 +50,47 @@ export interface OneConnectMessage {
50
50
  status: "success" | "error";
51
51
  message?: string;
52
52
  }
53
+
54
+ /** A provider chip on the pre-built button. The SDK ships no One URLs,
55
+ * so icon sources are the consumer's to provide. */
56
+ export interface ConnectButtonPlatform {
57
+ name: string;
58
+ imageUrl?: string;
59
+ }
60
+
61
+ export interface ConnectButtonOptions {
62
+ /** Everything useOneConnect takes — the button wires open() and the
63
+ * Connecting/Connected states around your callbacks. */
64
+ connect: OneConnectProps;
65
+ /** "Connect your apps" unless overridden. */
66
+ label?: string;
67
+ /** default = neutral pill; accent = brand-colored pill; block =
68
+ * full-width consent card with description + "Secured by One" foot. */
69
+ variant?: "default" | "accent" | "block";
70
+ /** Matches the host page, not One's card (that's connect.appTheme). */
71
+ theme?: "light" | "dark";
72
+ /** Provider chips fanned on hover; first four render, the rest fold
73
+ * into the +N chip together with moreCount. */
74
+ platforms?: ConnectButtonPlatform[];
75
+ /** Extra count for the +N chip (e.g. 274 for "the whole catalog"). */
76
+ moreCount?: number;
77
+ /** Sub-line on the block variant, shown while idle. */
78
+ description?: string;
79
+ /** accent variant fill; lime (#CCFF00) when omitted. */
80
+ accentColor?: string;
81
+ /** Label for the connected state ("4 apps connected"). */
82
+ connectedLabel?: string;
83
+ }
84
+
85
+ export interface ConnectButtonHandle {
86
+ /** Manually override the visual state. */
87
+ setState: (state: "idle" | "connecting" | "connected") => void;
88
+ /** Remove the button and tear down the flow wiring. */
89
+ destroy: () => void;
90
+ }
91
+
92
+ /** Registers the <one-connect-button> custom element (no-op on servers
93
+ * and when already registered). Importing the package does this
94
+ * automatically in browsers; calling it manually is only needed if a
95
+ * bundler stripped module side effects. */
96
+ export declare function registerConnectButton(): void;
@@ -98,6 +98,7 @@ export function showSuccessOverlay(theme?: "dark" | "light"): void {
98
98
  "</div>" +
99
99
  `<div style="height:48px;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:6px;border-radius:0 0 28px 28px;background:${footerBg};border:1px solid ${cardBorder};border-top:0;">` +
100
100
  `<span style="font-size:11px;color:${muted};">Secured by</span>` +
101
+ `<span style="display:inline-flex;color:${dark ? "#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>` +
101
102
  `<span style="font-size:12px;font-weight:600;letter-spacing:-0.02em;color:${dark ? "#fafafa" : "#111114"};">one</span>` +
102
103
  "</div>";
103
104