@planningcenter/organization-avatars 1.4.0 → 1.6.1

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
@@ -2,10 +2,16 @@
2
2
 
3
3
  This package provides an `OrganizationAvatars` React component which encapsulates displaying and updating an organization's avatar.
4
4
 
5
- See [the component's type signature](https://github.com/planningcenter/organization-avatars/blob/b6f6377e7c4c4f3eae551a888b7ed0546dd5348e/src/types.ts#L1) for up-to-date props.
5
+ See [the component's type signature](https://github.com/planningcenter/organization-avatars/blob/main/src/types.ts) for up-to-date props.
6
+
7
+ ## Usage
6
8
 
7
9
  If no `darkModeAvatarUrl` is provided, the `avatarUrl` will be rendered with dark styles in the dark mode avatar's UI slot.
8
10
 
9
11
  The `showDarkModeAvatar` prop gates the rendering of the dark mode avatar UI. It is intended to be controlled by a feature flag, and will eventually be removed when dark mode avatars have been rolled out.
10
12
 
11
13
  To see an example of how this package can be used, see the [`/organization` page in Accounts](https://accounts.planningcenteronline.com/organization). The `OrganizationAvatars` component is mounted in [the `_church_information` partial](https://github.com/planningcenter/accounts/blob/main/app/views/organization/show/_church_information.html.erb).
14
+
15
+ ## Development
16
+
17
+ `yarn dev` — Start a local demo app at http://localhost:5173 for interactive testing (API is mocked)
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),i=require("react"),O=require("react-dom"),B=require("@planningcenter/url"),k=require("@planningcenter/tapestry"),A=require("@planningcenter/icons/paths/general"),P=require("@planningcenter/icons/paths/services"),T=require("react-dropzone");function z(a){var o,t,r="";if(typeof a=="string"||typeof a=="number")r+=a;else if(typeof a=="object")if(Array.isArray(a)){var d=a.length;for(o=0;o<d;o++)a[o]&&(t=z(a[o]))&&(r&&(r+=" "),r+=t)}else for(t in a)a[t]&&(r&&(r+=" "),r+=t);return r}function $(){for(var a,o,t=0,r="",d=arguments.length;t<d;t++)(a=arguments[t])&&(o=z(a))&&(r&&(r+=" "),r+=o);return r}function L(){const a=document.querySelector('meta[name="csrf-token"]');if(!a||!a.content)throw new Error('CSRF token not found. Ensure your Rails application includes <meta name="csrf-token" content="..."> in the document head.');return a.content}function E(a){const o=a.split("");return[o.shift()?.toUpperCase(),o.join("")].join("")}const I=a=>a==="dark_mode_avatar"?"dark":"light";function N({avatarUrl:a,organization:o,mode:t,pcoEnv:r,onAvatarUpdate:d}){const[s,p]=i.useState(null),[u,m]=i.useState(a),[l,f]=i.useState(!1),[c,v]=i.useState(!1),[U,j]=i.useState(""),x=i.useId(),C=i.useRef(null),_=I(t),F=g=>{const[n]=g;n&&p(Object.assign(n,{preview:URL.createObjectURL(n)}))};i.useEffect(()=>{m(a)},[a]),i.useEffect(()=>()=>{s?.preview&&(URL.revokeObjectURL(s.preview),j(""))},[s]);const S=async g=>{const n=await fetch(B.pcoApiUrl("accounts",{env:r}),{method:"PATCH",headers:{"Content-Type":"application/json","X-CSRF-Token":L(),Accept:"application/json"},body:JSON.stringify({data:{attributes:g}})}),h=await n.json();if(!n.ok)throw h;return h},R=async g=>{if(g.preventDefault(),!(!s||l||c)){f(!0);try{const n=new FileReader,h=await new Promise((y,w)=>{n.onloadend=()=>y(n.result),n.onerror=w,n.readAsDataURL(s)}),b=await S({[t]:h,[`${t}_cache`]:""});m(b.data.attributes[t].url),p(null),d(t,b.data.attributes[t].url),C.current?.close()}catch{j("We were unable to save your avatar.")}finally{f(!1)}}},q=async()=>{if(!(l||c)){v(!0);try{await S({[`remove_${t}`]:"1"}),m(""),d(t,""),C.current?.close()}catch{j("We were unable to delete your avatar.")}finally{v(!1)}}},D=()=>{p(null),j("")};return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"pco-org-avatar",children:[e.jsx("button",{type:"button",commandfor:x,command:"show-modal",className:`pco-org-avatar__button pco-org-avatar__button--${_}`,children:u?e.jsxs(e.Fragment,{children:[e.jsx("img",{src:u,alt:`Church logo for ${o}`}),e.jsx("div",{className:"tds-btn tds-btn--interaction tds-btn--icononly",children:e.jsx("svg",{width:"14",height:"14",viewBox:"0 0 16 16",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:A.pencil})})})]}):e.jsx("svg",{width:"48",height:"48",viewBox:"0 0 16 16",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:P.image})})}),e.jsxs("div",{className:"pco-org-avatar__label",children:[E(_)," mode"]})]}),O.createPortal(e.jsx("dialog",{id:x,ref:C,className:"pco-org-avatar__dialog",onClose:D,children:e.jsxs("form",{onSubmit:R,children:[e.jsxs("div",{className:"pco-org-avatar__dialog-header",children:[e.jsxs("h1",{className:"pco-org-avatar__dialog-title",children:["Church logo — ",_," mode"]}),e.jsx("button",{type:"button",commandfor:x,command:"close",className:"pco-org-avatar__dialog-close","aria-label":"Close modal",children:e.jsx("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:A.smallX})})})]}),e.jsxs("div",{className:"pco-org-avatar__dialog-body",children:[U&&e.jsxs("div",{role:"alert",className:"pco-org-avatar__dialog-alert",children:[e.jsx("svg",{width:"16",height:"16",viewBox:"0 0 16 16",className:"symbol","aria-hidden":"true",children:e.jsx("path",{d:A.exclamationTriangle})}),e.jsx("p",{children:U})]}),e.jsx(T,{accept:{"image/*":[".jpg",".jpeg",".png"]},multiple:!1,onDropAccepted:F,children:({getRootProps:g,getInputProps:n,isFocused:h,isDragAccept:b,isDragReject:y,isDragActive:w})=>{const M=$("pco-org-avatar__dialog-dropzone",`pco-org-avatar__dialog-dropzone--${_}`,{"pco-org-avatar__dialog-dropzone--image":s||u,"pco-org-avatar__dialog-dropzone--focused":h,"pco-org-avatar__dialog-dropzone--accepted":b,"pco-org-avatar__dialog-dropzone--rejected":w&&y});return e.jsxs("div",{...g({className:M}),children:[e.jsx("input",{...n()}),s?e.jsx("img",{src:s.preview,alt:`Church logo for ${o}`}):u?e.jsx("img",{src:u,alt:`Church logo for ${o}`}):e.jsxs(e.Fragment,{children:[e.jsx("svg",{width:"64",height:"64",viewBox:"0 0 16 16","aria-hidden":"true",children:e.jsx("path",{d:A.toCloudArrow})}),w&&y?e.jsxs(e.Fragment,{children:[e.jsx("h2",{children:"Wrong file type"}),e.jsx("p",{children:"Please upload a PNG or JPG file."})]}):e.jsxs(e.Fragment,{children:[e.jsx("h2",{children:"Add church logo"}),e.jsx("p",{children:"Use a high-quality image (up to 20mb) with a transparent background."})]})]})]})}})]}),e.jsxs("div",{className:"pco-org-avatar__dialog-actions",children:[u&&e.jsx(k.Button,{label:"Delete logo",kind:"delete",onClick:q,loading:c,disabled:c||l,className:"pco-org-avatar__dialog-delete"}),e.jsx(k.Button,{type:"button",label:"Cancel",commandfor:x,command:"close",disabled:l||c}),e.jsx(k.Button,{type:"submit",label:"Update logo",kind:"primary",loading:l,disabled:!s||l||c})]})]})}),document.body)]})}function W({avatarUrl:a,darkModeAvatarUrl:o,orgName:t,pcoEnv:r,showDarkModeAvatar:d=!1}){const[s,p]=i.useState(a),[u,m]=i.useState(o),l=(c,v)=>{c==="avatar"?p(v):c==="dark_mode_avatar"&&m(v)},f=u||s;return e.jsxs("div",{className:"pco-org-avatars",children:[e.jsx(N,{avatarUrl:s,organization:t,mode:"avatar",pcoEnv:r,onAvatarUpdate:l}),d&&e.jsx(N,{avatarUrl:f,organization:t,mode:"dark_mode_avatar",pcoEnv:r,onAvatarUpdate:l})]})}exports.OrganizationAvatars=W;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),i=require("react"),O=require("react-dom"),P=require("@planningcenter/url"),N=require("@planningcenter/tapestry"),_=require("@planningcenter/icons/paths/general"),T=require("@planningcenter/icons/paths/services"),$=require("react-dropzone");function F(a){var o,t,r="";if(typeof a=="string"||typeof a=="number")r+=a;else if(typeof a=="object")if(Array.isArray(a)){var l=a.length;for(o=0;o<l;o++)a[o]&&(t=F(a[o]))&&(r&&(r+=" "),r+=t)}else for(t in a)a[t]&&(r&&(r+=" "),r+=t);return r}function L(){for(var a,o,t=0,r="",l=arguments.length;t<l;t++)(a=arguments[t])&&(o=F(a))&&(r&&(r+=" "),r+=o);return r}function E(){const a=document.querySelector('meta[name="csrf-token"]');if(!a||!a.content)throw new Error('CSRF token not found. Ensure your Rails application includes <meta name="csrf-token" content="..."> in the document head.');return a.content}function I(a){const o=a.split("");return[o.shift()?.toUpperCase(),o.join("")].join("")}const W=a=>a==="dark_mode_avatar"?"dark":"light";function z({avatarUrl:a,organization:o,mode:t,pcoEnv:r,readOnly:l,onAvatarUpdate:v}){const[s,p]=i.useState(null),[c,f]=i.useState(a),[d,j]=i.useState(!1),[u,x]=i.useState(!1),[h,b]=i.useState(""),y=i.useId(),S=i.useRef(null),w=W(t),R=g=>{const[n]=g;n&&p(Object.assign(n,{preview:URL.createObjectURL(n)}))};i.useEffect(()=>{f(a)},[a]),i.useEffect(()=>()=>{s?.preview&&(URL.revokeObjectURL(s.preview),b(""))},[s]);const U=async g=>{const n=await fetch(P.pcoApiUrl("accounts",{env:r}),{method:"PATCH",headers:{"Content-Type":"application/json","X-CSRF-Token":E(),Accept:"application/json"},body:JSON.stringify({data:{attributes:g}})}),m=await n.json();if(!n.ok)throw m;return m},q=async g=>{if(g.preventDefault(),!(!s||d||u)){j(!0);try{const n=new FileReader,m=await new Promise((k,A)=>{n.onloadend=()=>k(n.result),n.onerror=A,n.readAsDataURL(s)}),C=await U({[t]:m,[`${t}_cache`]:""});f(C.data.attributes[t].url),p(null),v(t,C.data.attributes[t].url),S.current?.close()}catch{b("We were unable to save your avatar.")}finally{j(!1)}}},D=async()=>{if(!(d||u)){x(!0);try{await U({[`remove_${t}`]:"1"}),f(""),v(t,""),S.current?.close()}catch{b("We were unable to delete your avatar.")}finally{x(!1)}}},B=()=>{p(null),b("")};return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"pco-org-avatar",children:[e.jsx("button",{type:"button",commandfor:y,command:"show-modal",disabled:l,className:`pco-org-avatar__button pco-org-avatar__button--${w}`,children:c?e.jsxs(e.Fragment,{children:[e.jsx("img",{src:c,alt:`Church logo for ${o}`}),e.jsx("div",{className:"tds-btn tds-btn--interaction tds-btn--icononly",children:e.jsx("svg",{width:"14",height:"14",viewBox:"0 0 16 16",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:_.pencil})})})]}):e.jsxs(e.Fragment,{children:[e.jsx("svg",{width:"48",height:"48",viewBox:"0 0 16 16",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:T.image})}),e.jsx("div",{className:"tds-btn tds-btn--interaction tds-btn--icononly",children:e.jsx("svg",{width:"14",height:"14",viewBox:"0 0 16 16",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:_.pencil})})})]})}),e.jsxs("div",{className:"pco-org-avatar__label",children:[I(w)," mode"]})]}),O.createPortal(e.jsx("dialog",{id:y,ref:S,className:"pco-org-avatar__dialog",onClose:B,children:e.jsxs("form",{onSubmit:q,children:[e.jsxs("div",{className:"pco-org-avatar__dialog-header",children:[e.jsxs("h1",{className:"pco-org-avatar__dialog-title",children:["Church logo — ",w," mode"]}),e.jsx("button",{type:"button",commandfor:y,command:"close",className:"pco-org-avatar__dialog-close","aria-label":"Close modal",children:e.jsx("svg",{width:"16",height:"16",viewBox:"0 0 16 16",fill:"currentColor","aria-hidden":"true",children:e.jsx("path",{d:_.smallX})})})]}),e.jsxs("div",{className:"pco-org-avatar__dialog-body",children:[h&&e.jsxs("div",{role:"alert",className:"pco-org-avatar__dialog-alert",children:[e.jsx("svg",{width:"16",height:"16",viewBox:"0 0 16 16",className:"symbol","aria-hidden":"true",children:e.jsx("path",{d:_.exclamationTriangle})}),e.jsx("p",{children:h})]}),e.jsx($,{accept:{"image/*":[".jpg",".jpeg",".png"]},multiple:!1,onDropAccepted:R,children:({getRootProps:g,getInputProps:n,isFocused:m,isDragAccept:C,isDragReject:k,isDragActive:A})=>{const M=L("pco-org-avatar__dialog-dropzone",`pco-org-avatar__dialog-dropzone--${w}`,{"pco-org-avatar__dialog-dropzone--image":s||c,"pco-org-avatar__dialog-dropzone--focused":m,"pco-org-avatar__dialog-dropzone--accepted":C,"pco-org-avatar__dialog-dropzone--rejected":A&&k});return e.jsxs("div",{...g({className:M}),children:[e.jsx("input",{...n()}),s?e.jsx("img",{src:s.preview,alt:`Church logo for ${o}`}):c?e.jsx("img",{src:c,alt:`Church logo for ${o}`}):e.jsxs(e.Fragment,{children:[e.jsx("svg",{width:"64",height:"64",viewBox:"0 0 16 16","aria-hidden":"true",children:e.jsx("path",{d:_.toCloudArrow})}),A&&k?e.jsxs(e.Fragment,{children:[e.jsx("h2",{children:"Wrong file type"}),e.jsx("p",{children:"Please upload a PNG or JPG file."})]}):e.jsxs(e.Fragment,{children:[e.jsx("h2",{children:"Add church logo"}),e.jsx("p",{children:"Use a high-quality image (up to 20mb) with a transparent background."})]})]})]})}})]}),e.jsxs("div",{className:"pco-org-avatar__dialog-actions",children:[c&&e.jsx(N.Button,{label:"Delete logo",kind:"delete",onClick:D,loading:u,disabled:u||d,className:"pco-org-avatar__dialog-delete"}),e.jsx(N.Button,{type:"button",label:"Cancel",commandfor:y,command:"close",disabled:d||u}),e.jsx(N.Button,{type:"submit",label:"Update logo",kind:"primary",loading:d,disabled:!s||d||u})]})]})}),document.body)]})}function G({avatarUrl:a,darkModeAvatarUrl:o,orgName:t,onAvatarUpdate:r,pcoEnv:l,readOnly:v,showDarkModeAvatar:s=!1}){const[p,c]=i.useState(a),[f,d]=i.useState(o),j=(x,h)=>{x==="avatar"?(c(h),r?.("avatar",h)):x==="dark_mode_avatar"&&(d(h),r?.("dark_mode_avatar",h))},u=f||p;return e.jsxs("div",{className:"pco-org-avatars",children:[e.jsx(z,{avatarUrl:p,organization:t,mode:"avatar",pcoEnv:l,readOnly:v,onAvatarUpdate:j}),s&&e.jsx(z,{avatarUrl:u,organization:t,mode:"dark_mode_avatar",pcoEnv:l,readOnly:v,onAvatarUpdate:j})]})}exports.OrganizationAvatars=G;
package/dist/index.js CHANGED
@@ -1,25 +1,25 @@
1
- import { jsxs as n, Fragment as _, jsx as a } from "react/jsx-runtime";
2
- import { useState as u, useId as P, useRef as T, useEffect as D } from "react";
3
- import { createPortal as L } from "react-dom";
4
- import { pcoApiUrl as I } from "@planningcenter/url";
5
- import { Button as j } from "@planningcenter/tapestry";
6
- import { pencil as E, smallX as W, exclamationTriangle as q, toCloudArrow as G } from "@planningcenter/icons/paths/general";
7
- import { image as J } from "@planningcenter/icons/paths/services";
8
- import X from "react-dropzone";
9
- function M(e) {
1
+ import { jsxs as n, Fragment as f, jsx as a } from "react/jsx-runtime";
2
+ import { useState as g, useId as L, useRef as I, useEffect as R } from "react";
3
+ import { createPortal as E } from "react-dom";
4
+ import { pcoApiUrl as W } from "@planningcenter/url";
5
+ import { Button as x } from "@planningcenter/tapestry";
6
+ import { pencil as M, smallX as q, exclamationTriangle as G, toCloudArrow as J } from "@planningcenter/icons/paths/general";
7
+ import { image as X } from "@planningcenter/icons/paths/services";
8
+ import H from "react-dropzone";
9
+ function F(e) {
10
10
  var o, r, t = "";
11
11
  if (typeof e == "string" || typeof e == "number") t += e;
12
12
  else if (typeof e == "object") if (Array.isArray(e)) {
13
- var s = e.length;
14
- for (o = 0; o < s; o++) e[o] && (r = M(e[o])) && (t && (t += " "), t += r);
13
+ var c = e.length;
14
+ for (o = 0; o < c; o++) e[o] && (r = F(e[o])) && (t && (t += " "), t += r);
15
15
  } else for (r in e) e[r] && (t && (t += " "), t += r);
16
16
  return t;
17
17
  }
18
- function H() {
19
- for (var e, o, r = 0, t = "", s = arguments.length; r < s; r++) (e = arguments[r]) && (o = M(e)) && (t && (t += " "), t += o);
18
+ function K() {
19
+ for (var e, o, r = 0, t = "", c = arguments.length; r < c; r++) (e = arguments[r]) && (o = F(e)) && (t && (t += " "), t += o);
20
20
  return t;
21
21
  }
22
- function K() {
22
+ function Q() {
23
23
  const e = document.querySelector('meta[name="csrf-token"]');
24
24
  if (!e || !e.content)
25
25
  throw new Error(
@@ -27,19 +27,20 @@ function K() {
27
27
  );
28
28
  return e.content;
29
29
  }
30
- function Q(e) {
30
+ function V(e) {
31
31
  const o = e.split("");
32
32
  return [o.shift()?.toUpperCase(), o.join("")].join("");
33
33
  }
34
- const V = (e) => e === "dark_mode_avatar" ? "dark" : "light";
35
- function R({
34
+ const Y = (e) => e === "dark_mode_avatar" ? "dark" : "light";
35
+ function S({
36
36
  avatarUrl: e,
37
37
  organization: o,
38
38
  mode: r,
39
39
  pcoEnv: t,
40
- onAvatarUpdate: s
40
+ readOnly: c,
41
+ onAvatarUpdate: _
41
42
  }) {
42
- const [l, m] = u(null), [h, f] = u(e), [c, U] = u(!1), [d, v] = u(!1), [z, b] = u(""), y = P(), N = T(null), w = V(r), S = (p) => {
43
+ const [l, m] = g(null), [d, b] = g(e), [s, y] = g(!1), [h, w] = g(!1), [u, k] = g(""), C = L(), z = I(null), A = Y(r), B = (p) => {
43
44
  const [i] = p;
44
45
  i && m(
45
46
  Object.assign(i, {
@@ -47,17 +48,17 @@ function R({
47
48
  })
48
49
  );
49
50
  };
50
- D(() => {
51
- f(e);
52
- }, [e]), D(() => () => {
53
- l?.preview && (URL.revokeObjectURL(l.preview), b(""));
51
+ R(() => {
52
+ b(e);
53
+ }, [e]), R(() => () => {
54
+ l?.preview && (URL.revokeObjectURL(l.preview), k(""));
54
55
  }, [l]);
55
- const x = async (p) => {
56
- const i = await fetch(I("accounts", { env: t }), {
56
+ const D = async (p) => {
57
+ const i = await fetch(W("accounts", { env: t }), {
57
58
  method: "PATCH",
58
59
  headers: {
59
60
  "Content-Type": "application/json",
60
- "X-CSRF-Token": K(),
61
+ "X-CSRF-Token": Q(),
61
62
  Accept: "application/json"
62
63
  },
63
64
  body: JSON.stringify({
@@ -65,53 +66,77 @@ function R({
65
66
  attributes: p
66
67
  }
67
68
  })
68
- }), g = await i.json();
69
- if (!i.ok) throw g;
70
- return g;
71
- }, F = async (p) => {
72
- if (p.preventDefault(), !(!l || c || d)) {
73
- U(!0);
69
+ }), v = await i.json();
70
+ if (!i.ok) throw v;
71
+ return v;
72
+ }, $ = async (p) => {
73
+ if (p.preventDefault(), !(!l || s || h)) {
74
+ y(!0);
74
75
  try {
75
- const i = new FileReader(), g = await new Promise((A, C) => {
76
- i.onloadend = () => A(i.result), i.onerror = C, i.readAsDataURL(l);
77
- }), k = await x({
78
- [r]: g,
76
+ const i = new FileReader(), v = await new Promise((N, j) => {
77
+ i.onloadend = () => N(i.result), i.onerror = j, i.readAsDataURL(l);
78
+ }), U = await D({
79
+ [r]: v,
79
80
  [`${r}_cache`]: ""
80
81
  });
81
- f(k.data.attributes[r].url), m(null), s(r, k.data.attributes[r].url), N.current?.close();
82
+ b(U.data.attributes[r].url), m(null), _(r, U.data.attributes[r].url), z.current?.close();
82
83
  } catch {
83
- b("We were unable to save your avatar.");
84
+ k("We were unable to save your avatar.");
84
85
  } finally {
85
- U(!1);
86
+ y(!1);
86
87
  }
87
88
  }
88
89
  }, O = async () => {
89
- if (!(c || d)) {
90
- v(!0);
90
+ if (!(s || h)) {
91
+ w(!0);
91
92
  try {
92
- await x({
93
+ await D({
93
94
  [`remove_${r}`]: "1"
94
- }), f(""), s(r, ""), N.current?.close();
95
+ }), b(""), _(r, ""), z.current?.close();
95
96
  } catch {
96
- b("We were unable to delete your avatar.");
97
+ k("We were unable to delete your avatar.");
97
98
  } finally {
98
- v(!1);
99
+ w(!1);
99
100
  }
100
101
  }
101
- }, $ = () => {
102
- m(null), b("");
102
+ }, P = () => {
103
+ m(null), k("");
103
104
  };
104
- return /* @__PURE__ */ n(_, { children: [
105
+ return /* @__PURE__ */ n(f, { children: [
105
106
  /* @__PURE__ */ n("div", { className: "pco-org-avatar", children: [
106
107
  /* @__PURE__ */ a(
107
108
  "button",
108
109
  {
109
110
  type: "button",
110
- commandfor: y,
111
+ commandfor: C,
111
112
  command: "show-modal",
112
- className: `pco-org-avatar__button pco-org-avatar__button--${w}`,
113
- children: h ? /* @__PURE__ */ n(_, { children: [
114
- /* @__PURE__ */ a("img", { src: h, alt: `Church logo for ${o}` }),
113
+ disabled: c,
114
+ className: `pco-org-avatar__button pco-org-avatar__button--${A}`,
115
+ children: d ? /* @__PURE__ */ n(f, { children: [
116
+ /* @__PURE__ */ a("img", { src: d, alt: `Church logo for ${o}` }),
117
+ /* @__PURE__ */ a("div", { className: "tds-btn tds-btn--interaction tds-btn--icononly", children: /* @__PURE__ */ a(
118
+ "svg",
119
+ {
120
+ width: "14",
121
+ height: "14",
122
+ viewBox: "0 0 16 16",
123
+ fill: "currentColor",
124
+ "aria-hidden": "true",
125
+ children: /* @__PURE__ */ a("path", { d: M })
126
+ }
127
+ ) })
128
+ ] }) : /* @__PURE__ */ n(f, { children: [
129
+ /* @__PURE__ */ a(
130
+ "svg",
131
+ {
132
+ width: "48",
133
+ height: "48",
134
+ viewBox: "0 0 16 16",
135
+ fill: "currentColor",
136
+ "aria-hidden": "true",
137
+ children: /* @__PURE__ */ a("path", { d: X })
138
+ }
139
+ ),
115
140
  /* @__PURE__ */ a("div", { className: "tds-btn tds-btn--interaction tds-btn--icononly", children: /* @__PURE__ */ a(
116
141
  "svg",
117
142
  {
@@ -120,47 +145,37 @@ function R({
120
145
  viewBox: "0 0 16 16",
121
146
  fill: "currentColor",
122
147
  "aria-hidden": "true",
123
- children: /* @__PURE__ */ a("path", { d: E })
148
+ children: /* @__PURE__ */ a("path", { d: M })
124
149
  }
125
150
  ) })
126
- ] }) : /* @__PURE__ */ a(
127
- "svg",
128
- {
129
- width: "48",
130
- height: "48",
131
- viewBox: "0 0 16 16",
132
- fill: "currentColor",
133
- "aria-hidden": "true",
134
- children: /* @__PURE__ */ a("path", { d: J })
135
- }
136
- )
151
+ ] })
137
152
  }
138
153
  ),
139
154
  /* @__PURE__ */ n("div", { className: "pco-org-avatar__label", children: [
140
- Q(w),
155
+ V(A),
141
156
  " mode"
142
157
  ] })
143
158
  ] }),
144
- L(
159
+ E(
145
160
  /* @__PURE__ */ a(
146
161
  "dialog",
147
162
  {
148
- id: y,
149
- ref: N,
163
+ id: C,
164
+ ref: z,
150
165
  className: "pco-org-avatar__dialog",
151
- onClose: $,
152
- children: /* @__PURE__ */ n("form", { onSubmit: F, children: [
166
+ onClose: P,
167
+ children: /* @__PURE__ */ n("form", { onSubmit: $, children: [
153
168
  /* @__PURE__ */ n("div", { className: "pco-org-avatar__dialog-header", children: [
154
169
  /* @__PURE__ */ n("h1", { className: "pco-org-avatar__dialog-title", children: [
155
170
  "Church logo — ",
156
- w,
171
+ A,
157
172
  " mode"
158
173
  ] }),
159
174
  /* @__PURE__ */ a(
160
175
  "button",
161
176
  {
162
177
  type: "button",
163
- commandfor: y,
178
+ commandfor: C,
164
179
  command: "close",
165
180
  className: "pco-org-avatar__dialog-close",
166
181
  "aria-label": "Close modal",
@@ -172,14 +187,14 @@ function R({
172
187
  viewBox: "0 0 16 16",
173
188
  fill: "currentColor",
174
189
  "aria-hidden": "true",
175
- children: /* @__PURE__ */ a("path", { d: W })
190
+ children: /* @__PURE__ */ a("path", { d: q })
176
191
  }
177
192
  )
178
193
  }
179
194
  )
180
195
  ] }),
181
196
  /* @__PURE__ */ n("div", { className: "pco-org-avatar__dialog-body", children: [
182
- z && /* @__PURE__ */ n("div", { role: "alert", className: "pco-org-avatar__dialog-alert", children: [
197
+ u && /* @__PURE__ */ n("div", { role: "alert", className: "pco-org-avatar__dialog-alert", children: [
183
198
  /* @__PURE__ */ a(
184
199
  "svg",
185
200
  {
@@ -188,36 +203,36 @@ function R({
188
203
  viewBox: "0 0 16 16",
189
204
  className: "symbol",
190
205
  "aria-hidden": "true",
191
- children: /* @__PURE__ */ a("path", { d: q })
206
+ children: /* @__PURE__ */ a("path", { d: G })
192
207
  }
193
208
  ),
194
- /* @__PURE__ */ a("p", { children: z })
209
+ /* @__PURE__ */ a("p", { children: u })
195
210
  ] }),
196
211
  /* @__PURE__ */ a(
197
- X,
212
+ H,
198
213
  {
199
214
  accept: { "image/*": [".jpg", ".jpeg", ".png"] },
200
215
  multiple: !1,
201
- onDropAccepted: S,
216
+ onDropAccepted: B,
202
217
  children: ({
203
218
  getRootProps: p,
204
219
  getInputProps: i,
205
- isFocused: g,
206
- isDragAccept: k,
207
- isDragReject: A,
208
- isDragActive: C
220
+ isFocused: v,
221
+ isDragAccept: U,
222
+ isDragReject: N,
223
+ isDragActive: j
209
224
  }) => {
210
- const B = H(
225
+ const T = K(
211
226
  "pco-org-avatar__dialog-dropzone",
212
- `pco-org-avatar__dialog-dropzone--${w}`,
227
+ `pco-org-avatar__dialog-dropzone--${A}`,
213
228
  {
214
- "pco-org-avatar__dialog-dropzone--image": l || h,
215
- "pco-org-avatar__dialog-dropzone--focused": g,
216
- "pco-org-avatar__dialog-dropzone--accepted": k,
217
- "pco-org-avatar__dialog-dropzone--rejected": C && A
229
+ "pco-org-avatar__dialog-dropzone--image": l || d,
230
+ "pco-org-avatar__dialog-dropzone--focused": v,
231
+ "pco-org-avatar__dialog-dropzone--accepted": U,
232
+ "pco-org-avatar__dialog-dropzone--rejected": j && N
218
233
  }
219
234
  );
220
- return /* @__PURE__ */ n("div", { ...p({ className: B }), children: [
235
+ return /* @__PURE__ */ n("div", { ...p({ className: T }), children: [
221
236
  /* @__PURE__ */ a("input", { ...i() }),
222
237
  l ? /* @__PURE__ */ a(
223
238
  "img",
@@ -225,13 +240,13 @@ function R({
225
240
  src: l.preview,
226
241
  alt: `Church logo for ${o}`
227
242
  }
228
- ) : h ? /* @__PURE__ */ a(
243
+ ) : d ? /* @__PURE__ */ a(
229
244
  "img",
230
245
  {
231
- src: h,
246
+ src: d,
232
247
  alt: `Church logo for ${o}`
233
248
  }
234
- ) : /* @__PURE__ */ n(_, { children: [
249
+ ) : /* @__PURE__ */ n(f, { children: [
235
250
  /* @__PURE__ */ a(
236
251
  "svg",
237
252
  {
@@ -239,13 +254,13 @@ function R({
239
254
  height: "64",
240
255
  viewBox: "0 0 16 16",
241
256
  "aria-hidden": "true",
242
- children: /* @__PURE__ */ a("path", { d: G })
257
+ children: /* @__PURE__ */ a("path", { d: J })
243
258
  }
244
259
  ),
245
- C && A ? /* @__PURE__ */ n(_, { children: [
260
+ j && N ? /* @__PURE__ */ n(f, { children: [
246
261
  /* @__PURE__ */ a("h2", { children: "Wrong file type" }),
247
262
  /* @__PURE__ */ a("p", { children: "Please upload a PNG or JPG file." })
248
- ] }) : /* @__PURE__ */ n(_, { children: [
263
+ ] }) : /* @__PURE__ */ n(f, { children: [
249
264
  /* @__PURE__ */ a("h2", { children: "Add church logo" }),
250
265
  /* @__PURE__ */ a("p", { children: "Use a high-quality image (up to 20mb) with a transparent background." })
251
266
  ] })
@@ -256,35 +271,35 @@ function R({
256
271
  )
257
272
  ] }),
258
273
  /* @__PURE__ */ n("div", { className: "pco-org-avatar__dialog-actions", children: [
259
- h && /* @__PURE__ */ a(
260
- j,
274
+ d && /* @__PURE__ */ a(
275
+ x,
261
276
  {
262
277
  label: "Delete logo",
263
278
  kind: "delete",
264
279
  onClick: O,
265
- loading: d,
266
- disabled: d || c,
280
+ loading: h,
281
+ disabled: h || s,
267
282
  className: "pco-org-avatar__dialog-delete"
268
283
  }
269
284
  ),
270
285
  /* @__PURE__ */ a(
271
- j,
286
+ x,
272
287
  {
273
288
  type: "button",
274
289
  label: "Cancel",
275
- commandfor: y,
290
+ commandfor: C,
276
291
  command: "close",
277
- disabled: c || d
292
+ disabled: s || h
278
293
  }
279
294
  ),
280
295
  /* @__PURE__ */ a(
281
- j,
296
+ x,
282
297
  {
283
298
  type: "submit",
284
299
  label: "Update logo",
285
300
  kind: "primary",
286
- loading: c,
287
- disabled: !l || c || d
301
+ loading: s,
302
+ disabled: !l || s || h
288
303
  }
289
304
  )
290
305
  ] })
@@ -295,41 +310,45 @@ function R({
295
310
  )
296
311
  ] });
297
312
  }
298
- function ia({
313
+ function la({
299
314
  avatarUrl: e,
300
315
  darkModeAvatarUrl: o,
301
316
  orgName: r,
302
- pcoEnv: t,
303
- showDarkModeAvatar: s = !1
317
+ onAvatarUpdate: t,
318
+ pcoEnv: c,
319
+ readOnly: _,
320
+ showDarkModeAvatar: l = !1
304
321
  }) {
305
- const [l, m] = u(e), [h, f] = u(
322
+ const [m, d] = g(e), [b, s] = g(
306
323
  o
307
- ), c = (d, v) => {
308
- d === "avatar" ? m(v) : d === "dark_mode_avatar" && f(v);
324
+ ), y = (w, u) => {
325
+ w === "avatar" ? (d(u), t?.("avatar", u)) : w === "dark_mode_avatar" && (s(u), t?.("dark_mode_avatar", u));
309
326
  };
310
327
  return /* @__PURE__ */ n("div", { className: "pco-org-avatars", children: [
311
328
  /* @__PURE__ */ a(
312
- R,
329
+ S,
313
330
  {
314
- avatarUrl: l,
331
+ avatarUrl: m,
315
332
  organization: r,
316
333
  mode: "avatar",
317
- pcoEnv: t,
318
- onAvatarUpdate: c
334
+ pcoEnv: c,
335
+ readOnly: _,
336
+ onAvatarUpdate: y
319
337
  }
320
338
  ),
321
- s && /* @__PURE__ */ a(
322
- R,
339
+ l && /* @__PURE__ */ a(
340
+ S,
323
341
  {
324
- avatarUrl: h || l,
342
+ avatarUrl: b || m,
325
343
  organization: r,
326
344
  mode: "dark_mode_avatar",
327
- pcoEnv: t,
328
- onAvatarUpdate: c
345
+ pcoEnv: c,
346
+ readOnly: _,
347
+ onAvatarUpdate: y
329
348
  }
330
349
  )
331
350
  ] });
332
351
  }
333
352
  export {
334
- ia as OrganizationAvatars
353
+ la as OrganizationAvatars
335
354
  };
@@ -1,2 +1,2 @@
1
1
  import type { OrganizationAvatarProps } from "./types";
2
- export declare function OrganizationAvatar({ avatarUrl, organization, mode, pcoEnv, onAvatarUpdate, }: OrganizationAvatarProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function OrganizationAvatar({ avatarUrl, organization, mode, pcoEnv, readOnly, onAvatarUpdate, }: OrganizationAvatarProps): import("react/jsx-runtime").JSX.Element;
@@ -1,2 +1,2 @@
1
1
  import type { OrganizationAvatarsProps } from "./types";
2
- export declare function OrganizationAvatars({ avatarUrl: initialAvatarUrl, darkModeAvatarUrl: initialDarkModeAvatarUrl, orgName, pcoEnv, showDarkModeAvatar, }: OrganizationAvatarsProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function OrganizationAvatars({ avatarUrl: initialAvatarUrl, darkModeAvatarUrl: initialDarkModeAvatarUrl, orgName, onAvatarUpdate, pcoEnv, readOnly, showDarkModeAvatar, }: OrganizationAvatarsProps): import("react/jsx-runtime").JSX.Element;
package/dist/style.css CHANGED
@@ -38,12 +38,22 @@
38
38
  visibility: hidden;
39
39
  }
40
40
 
41
- .pco-org-avatar__button:hover div,
42
- .pco-org-avatar__button:focus-visible div {
41
+ .pco-org-avatar__button:not(:disabled):hover div,
42
+ .pco-org-avatar__button:not(:disabled):focus-visible div {
43
43
  opacity: 1;
44
44
  visibility: visible;
45
45
  }
46
46
 
47
+ .pco-org-avatar__button:not(:has(img)):not(:disabled):hover,
48
+ .pco-org-avatar__button:not(:has(img)):not(:disabled):focus-visible {
49
+ border-color: var(--t-fill-color-interaction);
50
+ border-style: solid;
51
+ }
52
+
53
+ .pco-org-avatar__button:disabled {
54
+ cursor: not-allowed;
55
+ }
56
+
47
57
  .pco-org-avatar__label {
48
58
  color: var(--t-text-color-secondary);
49
59
  font-size: var(--t-font-size-sm);
@@ -212,16 +222,24 @@
212
222
  fill: var(--t-fill-color-status-error);
213
223
  }
214
224
 
215
- /* Light mode background - static regardless of UI theme */
225
+ /* Light/dark backgrounds are hardcoded to match the avatar preview context,
226
+ regardless of the UI theme or whether an avatar is uploaded */
227
+ .pco-org-avatar__button--light,
228
+ .pco-org-avatar__dialog-dropzone--light {
229
+ background: hsl(0, 0%, 100%);
230
+ }
231
+
216
232
  .pco-org-avatar__button--light:has(img),
217
233
  .pco-org-avatar__dialog-dropzone--light:has(img) {
218
- background: hsl(0, 0%, 100%);
219
234
  border-color: transparent;
220
235
  }
221
236
 
222
- /* Dark mode background - static regardless of UI theme */
237
+ .pco-org-avatar__button--dark,
238
+ .pco-org-avatar__dialog-dropzone--dark {
239
+ background: hsl(0, 0%, 12%);
240
+ }
241
+
223
242
  .pco-org-avatar__button--dark:has(img),
224
243
  .pco-org-avatar__dialog-dropzone--dark:has(img) {
225
- background: hsl(0, 0%, 12%);
226
244
  border-color: transparent;
227
245
  }
package/dist/types.d.ts CHANGED
@@ -1,17 +1,21 @@
1
1
  import type { Environment } from "@planningcenter/url";
2
+ export type AvatarMode = "avatar" | "dark_mode_avatar";
2
3
  export type OrganizationAvatarsProps = {
3
4
  avatarUrl?: string;
4
5
  darkModeAvatarUrl?: string;
5
6
  orgName: string;
7
+ onAvatarUpdate?: (mode: AvatarMode, newUrl: string) => void;
6
8
  pcoEnv?: Environment;
9
+ readOnly?: boolean;
7
10
  showDarkModeAvatar?: boolean;
8
11
  };
9
12
  export type OrganizationAvatarProps = {
10
13
  avatarUrl?: string;
11
14
  organization: string;
12
- mode: "avatar" | "dark_mode_avatar";
15
+ mode: AvatarMode;
13
16
  pcoEnv?: Environment;
14
- onAvatarUpdate: (attrName: string, newUrl: string) => void;
17
+ readOnly?: boolean;
18
+ onAvatarUpdate: (attrName: AvatarMode, newUrl: string) => void;
15
19
  };
16
20
  export type FileWithPreview = File & {
17
21
  preview: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/organization-avatars",
3
- "version": "1.4.0",
3
+ "version": "1.6.1",
4
4
  "description": "Organization avatar upload components for Planning Center apps",
5
5
  "type": "module",
6
6
  "packageManager": "yarn@1.22.22",
@@ -20,6 +20,7 @@
20
20
  "README.md"
21
21
  ],
22
22
  "scripts": {
23
+ "dev": "vite --config vite.demo.config.ts",
23
24
  "build": "rm -rf dist/ && vite build && tsc && cp src/organization_avatar.css dist/style.css",
24
25
  "lint": "eslint src",
25
26
  "lint:fix": "eslint src --fix",
@@ -29,17 +30,17 @@
29
30
  "clsx": "^2.1.1"
30
31
  },
31
32
  "peerDependencies": {
32
- "@planningcenter/icons": "^15.28.0",
33
- "@planningcenter/tapestry": "^2.7.0",
34
- "@planningcenter/url": "^3.1.0",
33
+ "@planningcenter/icons": "^15.29.1",
34
+ "@planningcenter/tapestry": "^2.10.1",
35
+ "@planningcenter/url": "^3.2.0",
35
36
  "react": "^18.3.0",
36
37
  "react-dom": "^18.3.0",
37
38
  "react-dropzone": "^14.0.0"
38
39
  },
39
40
  "devDependencies": {
40
- "@planningcenter/icons": "^15.28.0",
41
- "@planningcenter/tapestry": "^2.7.0",
42
- "@planningcenter/url": "^3.1.0",
41
+ "@planningcenter/icons": "^15.29.1",
42
+ "@planningcenter/tapestry": "^2.10.1",
43
+ "@planningcenter/url": "^3.2.0",
43
44
  "@types/react": "^18.3.0",
44
45
  "@types/react-dom": "^18.3.0",
45
46
  "@typescript-eslint/eslint-plugin": "^8.54.0",