@planningcenter/organization-avatars 1.4.0 → 1.6.2

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"),l=require("react"),B=require("react-dom"),M=require("@planningcenter/url"),C=require("@planningcenter/tapestry"),j=require("@planningcenter/icons/paths/general"),O=require("@planningcenter/icons/paths/services"),P=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 c=a.length;for(o=0;o<c;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 T(){for(var a,o,t=0,r="",c=arguments.length;t<c;t++)(a=arguments[t])&&(o=z(a))&&(r&&(r+=" "),r+=o);return r}function $(){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 L(a){const o=a.split("");return[o.shift()?.toUpperCase(),o.join("")].join("")}const I=a=>a==="dark_mode_avatar"?"dark":"light";function S({avatarUrl:a,organization:o,mode:t,pcoEnv:r,readOnly:c,onAvatarUpdate:v}){const[s,p]=l.useState(null),[d,x]=l.useState(!1),[u,f]=l.useState(!1),[_,h]=l.useState(""),i=l.useId(),k=l.useRef(null),b=I(t),F=g=>{const[n]=g;n&&p(Object.assign(n,{preview:URL.createObjectURL(n)}))};l.useEffect(()=>()=>{s?.preview&&(URL.revokeObjectURL(s.preview),h(""))},[s]);const N=async g=>{const n=await fetch(M.pcoApiUrl("accounts",{env:r}),{method:"PATCH",headers:{"Content-Type":"application/json","X-CSRF-Token":$(),Accept:"application/json"},body:JSON.stringify({data:{attributes:g}})}),m=await n.json();if(!n.ok)throw m;return m},R=async g=>{if(g.preventDefault(),!(!s||d||u)){x(!0);try{const n=new FileReader,m=await new Promise((y,w)=>{n.onloadend=()=>y(n.result),n.onerror=w,n.readAsDataURL(s)}),A=await N({[t]:m,[`${t}_cache`]:""});p(null),v(t,A.data.attributes[t].url),k.current?.close()}catch{h("We were unable to save your avatar.")}finally{x(!1)}}},U=async()=>{if(!(d||u)){f(!0);try{await N({[`remove_${t}`]:"1"}),v(t,""),k.current?.close()}catch{h("We were unable to delete your avatar.")}finally{f(!1)}}},q=()=>{p(null),h("")};return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"pco-org-avatar",children:[e.jsx("button",{type:"button",commandfor:i,command:"show-modal",disabled:c,className:`pco-org-avatar__button pco-org-avatar__button--${b}`,children:a?e.jsxs(e.Fragment,{children:[e.jsx("img",{src:a,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:j.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:O.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:j.pencil})})})]})}),e.jsxs("div",{className:"pco-org-avatar__label",children:[L(b)," mode"]})]}),B.createPortal(e.jsx("dialog",{id:i,ref:k,className:"pco-org-avatar__dialog",onClose:q,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 — ",b," mode"]}),e.jsx("button",{type:"button",commandfor:i,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:j.smallX})})})]}),e.jsxs("div",{className:"pco-org-avatar__dialog-body",children:[_&&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:j.exclamationTriangle})}),e.jsx("p",{children:_})]}),e.jsx(P,{accept:{"image/*":[".jpg",".jpeg",".png"]},multiple:!1,onDropAccepted:F,children:({getRootProps:g,getInputProps:n,isFocused:m,isDragAccept:A,isDragReject:y,isDragActive:w})=>{const D=T("pco-org-avatar__dialog-dropzone",`pco-org-avatar__dialog-dropzone--${b}`,{"pco-org-avatar__dialog-dropzone--image":s||a,"pco-org-avatar__dialog-dropzone--focused":m,"pco-org-avatar__dialog-dropzone--accepted":A,"pco-org-avatar__dialog-dropzone--rejected":w&&y});return e.jsxs("div",{...g({className:D}),children:[e.jsx("input",{...n()}),s?e.jsx("img",{src:s.preview,alt:`Church logo for ${o}`}):a?e.jsx("img",{src:a,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:j.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:[a&&e.jsx(C.Button,{label:"Delete logo",kind:"delete",onClick:U,loading:u,disabled:u||d,className:"pco-org-avatar__dialog-delete"}),e.jsx(C.Button,{type:"button",label:"Cancel",commandfor:i,command:"close",disabled:d||u}),e.jsx(C.Button,{type:"submit",label:"Update logo",kind:"primary",loading:d,disabled:!s||d||u})]})]})}),document.body)]})}function E({avatarUrl:a,darkModeAvatarUrl:o,orgName:t,onAvatarUpdate:r,pcoEnv:c,readOnly:v,showDarkModeAvatar:s=!1}){const[p,d]=l.useState(a),[x,u]=l.useState(o),f=(h,i)=>{h==="avatar"?(d(i),r?.("avatar",i)):h==="dark_mode_avatar"&&(u(i),r?.("dark_mode_avatar",i))},_=x||p;return e.jsxs("div",{className:"pco-org-avatars",children:[e.jsx(S,{avatarUrl:p,organization:t,mode:"avatar",pcoEnv:c,readOnly:v,onAvatarUpdate:f}),s&&e.jsx(S,{avatarUrl:_,organization:t,mode:"dark_mode_avatar",pcoEnv:c,readOnly:v,onAvatarUpdate:f})]})}exports.OrganizationAvatars=E;
package/dist/index.js CHANGED
@@ -1,118 +1,118 @@
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";
1
+ import { jsxs as n, Fragment as f, jsx as e } from "react/jsx-runtime";
2
+ import { useState as v, useId as O, useRef as P, useEffect as T } from "react";
3
3
  import { createPortal as L } from "react-dom";
4
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) {
5
+ import { Button as z } from "@planningcenter/tapestry";
6
+ import { pencil as x, smallX as E, exclamationTriangle as W, toCloudArrow as q } from "@planningcenter/icons/paths/general";
7
+ import { image as G } from "@planningcenter/icons/paths/services";
8
+ import J from "react-dropzone";
9
+ function R(a) {
10
10
  var o, r, t = "";
11
- if (typeof e == "string" || typeof e == "number") t += e;
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);
15
- } else for (r in e) e[r] && (t && (t += " "), t += r);
11
+ if (typeof a == "string" || typeof a == "number") t += a;
12
+ else if (typeof a == "object") if (Array.isArray(a)) {
13
+ var d = a.length;
14
+ for (o = 0; o < d; o++) a[o] && (r = R(a[o])) && (t && (t += " "), t += r);
15
+ } else for (r in a) a[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 X() {
19
+ for (var a, o, r = 0, t = "", d = arguments.length; r < d; r++) (a = arguments[r]) && (o = R(a)) && (t && (t += " "), t += o);
20
20
  return t;
21
21
  }
22
- function K() {
23
- const e = document.querySelector('meta[name="csrf-token"]');
24
- if (!e || !e.content)
22
+ function H() {
23
+ const a = document.querySelector('meta[name="csrf-token"]');
24
+ if (!a || !a.content)
25
25
  throw new Error(
26
26
  'CSRF token not found. Ensure your Rails application includes <meta name="csrf-token" content="..."> in the document head.'
27
27
  );
28
- return e.content;
28
+ return a.content;
29
29
  }
30
- function Q(e) {
31
- const o = e.split("");
30
+ function K(a) {
31
+ const o = a.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({
36
- avatarUrl: e,
34
+ const Q = (a) => a === "dark_mode_avatar" ? "dark" : "light";
35
+ function D({
36
+ avatarUrl: a,
37
37
  organization: o,
38
38
  mode: r,
39
39
  pcoEnv: t,
40
- onAvatarUpdate: s
40
+ readOnly: d,
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 [i] = p;
44
- i && m(
43
+ const [l, u] = v(null), [s, y] = v(!1), [h, b] = v(!1), [A, p] = v(""), c = O(), N = P(null), w = Q(r), M = (g) => {
44
+ const [i] = g;
45
+ i && u(
45
46
  Object.assign(i, {
46
47
  preview: URL.createObjectURL(i)
47
48
  })
48
49
  );
49
50
  };
50
- D(() => {
51
- f(e);
52
- }, [e]), D(() => () => {
53
- l?.preview && (URL.revokeObjectURL(l.preview), b(""));
51
+ T(() => () => {
52
+ l?.preview && (URL.revokeObjectURL(l.preview), p(""));
54
53
  }, [l]);
55
- const x = async (p) => {
54
+ const U = async (g) => {
56
55
  const i = await fetch(I("accounts", { env: t }), {
57
56
  method: "PATCH",
58
57
  headers: {
59
58
  "Content-Type": "application/json",
60
- "X-CSRF-Token": K(),
59
+ "X-CSRF-Token": H(),
61
60
  Accept: "application/json"
62
61
  },
63
62
  body: JSON.stringify({
64
63
  data: {
65
- attributes: p
64
+ attributes: g
66
65
  }
67
66
  })
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);
67
+ }), m = await i.json();
68
+ if (!i.ok) throw m;
69
+ return m;
70
+ }, S = async (g) => {
71
+ if (g.preventDefault(), !(!l || s || h)) {
72
+ y(!0);
74
73
  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,
74
+ const i = new FileReader(), m = await new Promise((k, C) => {
75
+ i.onloadend = () => k(i.result), i.onerror = C, i.readAsDataURL(l);
76
+ }), j = await U({
77
+ [r]: m,
79
78
  [`${r}_cache`]: ""
80
79
  });
81
- f(k.data.attributes[r].url), m(null), s(r, k.data.attributes[r].url), N.current?.close();
80
+ u(null), _(r, j.data.attributes[r].url), N.current?.close();
82
81
  } catch {
83
- b("We were unable to save your avatar.");
82
+ p("We were unable to save your avatar.");
84
83
  } finally {
85
- U(!1);
84
+ y(!1);
86
85
  }
87
86
  }
88
- }, O = async () => {
89
- if (!(c || d)) {
90
- v(!0);
87
+ }, F = async () => {
88
+ if (!(s || h)) {
89
+ b(!0);
91
90
  try {
92
- await x({
91
+ await U({
93
92
  [`remove_${r}`]: "1"
94
- }), f(""), s(r, ""), N.current?.close();
93
+ }), _(r, ""), N.current?.close();
95
94
  } catch {
96
- b("We were unable to delete your avatar.");
95
+ p("We were unable to delete your avatar.");
97
96
  } finally {
98
- v(!1);
97
+ b(!1);
99
98
  }
100
99
  }
101
- }, $ = () => {
102
- m(null), b("");
100
+ }, B = () => {
101
+ u(null), p("");
103
102
  };
104
- return /* @__PURE__ */ n(_, { children: [
103
+ return /* @__PURE__ */ n(f, { children: [
105
104
  /* @__PURE__ */ n("div", { className: "pco-org-avatar", children: [
106
- /* @__PURE__ */ a(
105
+ /* @__PURE__ */ e(
107
106
  "button",
108
107
  {
109
108
  type: "button",
110
- commandfor: y,
109
+ commandfor: c,
111
110
  command: "show-modal",
111
+ disabled: d,
112
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}` }),
115
- /* @__PURE__ */ a("div", { className: "tds-btn tds-btn--interaction tds-btn--icononly", children: /* @__PURE__ */ a(
113
+ children: a ? /* @__PURE__ */ n(f, { children: [
114
+ /* @__PURE__ */ e("img", { src: a, alt: `Church logo for ${o}` }),
115
+ /* @__PURE__ */ e("div", { className: "tds-btn tds-btn--interaction tds-btn--icononly", children: /* @__PURE__ */ e(
116
116
  "svg",
117
117
  {
118
118
  width: "14",
@@ -120,51 +120,64 @@ function R({
120
120
  viewBox: "0 0 16 16",
121
121
  fill: "currentColor",
122
122
  "aria-hidden": "true",
123
- children: /* @__PURE__ */ a("path", { d: E })
123
+ children: /* @__PURE__ */ e("path", { d: x })
124
124
  }
125
125
  ) })
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
- )
126
+ ] }) : /* @__PURE__ */ n(f, { children: [
127
+ /* @__PURE__ */ e(
128
+ "svg",
129
+ {
130
+ width: "48",
131
+ height: "48",
132
+ viewBox: "0 0 16 16",
133
+ fill: "currentColor",
134
+ "aria-hidden": "true",
135
+ children: /* @__PURE__ */ e("path", { d: G })
136
+ }
137
+ ),
138
+ /* @__PURE__ */ e("div", { className: "tds-btn tds-btn--interaction tds-btn--icononly", children: /* @__PURE__ */ e(
139
+ "svg",
140
+ {
141
+ width: "14",
142
+ height: "14",
143
+ viewBox: "0 0 16 16",
144
+ fill: "currentColor",
145
+ "aria-hidden": "true",
146
+ children: /* @__PURE__ */ e("path", { d: x })
147
+ }
148
+ ) })
149
+ ] })
137
150
  }
138
151
  ),
139
152
  /* @__PURE__ */ n("div", { className: "pco-org-avatar__label", children: [
140
- Q(w),
153
+ K(w),
141
154
  " mode"
142
155
  ] })
143
156
  ] }),
144
157
  L(
145
- /* @__PURE__ */ a(
158
+ /* @__PURE__ */ e(
146
159
  "dialog",
147
160
  {
148
- id: y,
161
+ id: c,
149
162
  ref: N,
150
163
  className: "pco-org-avatar__dialog",
151
- onClose: $,
152
- children: /* @__PURE__ */ n("form", { onSubmit: F, children: [
164
+ onClose: B,
165
+ children: /* @__PURE__ */ n("form", { onSubmit: S, children: [
153
166
  /* @__PURE__ */ n("div", { className: "pco-org-avatar__dialog-header", children: [
154
167
  /* @__PURE__ */ n("h1", { className: "pco-org-avatar__dialog-title", children: [
155
168
  "Church logo — ",
156
169
  w,
157
170
  " mode"
158
171
  ] }),
159
- /* @__PURE__ */ a(
172
+ /* @__PURE__ */ e(
160
173
  "button",
161
174
  {
162
175
  type: "button",
163
- commandfor: y,
176
+ commandfor: c,
164
177
  command: "close",
165
178
  className: "pco-org-avatar__dialog-close",
166
179
  "aria-label": "Close modal",
167
- children: /* @__PURE__ */ a(
180
+ children: /* @__PURE__ */ e(
168
181
  "svg",
169
182
  {
170
183
  width: "16",
@@ -172,15 +185,15 @@ function R({
172
185
  viewBox: "0 0 16 16",
173
186
  fill: "currentColor",
174
187
  "aria-hidden": "true",
175
- children: /* @__PURE__ */ a("path", { d: W })
188
+ children: /* @__PURE__ */ e("path", { d: E })
176
189
  }
177
190
  )
178
191
  }
179
192
  )
180
193
  ] }),
181
194
  /* @__PURE__ */ n("div", { className: "pco-org-avatar__dialog-body", children: [
182
- z && /* @__PURE__ */ n("div", { role: "alert", className: "pco-org-avatar__dialog-alert", children: [
183
- /* @__PURE__ */ a(
195
+ A && /* @__PURE__ */ n("div", { role: "alert", className: "pco-org-avatar__dialog-alert", children: [
196
+ /* @__PURE__ */ e(
184
197
  "svg",
185
198
  {
186
199
  width: "16",
@@ -188,66 +201,66 @@ function R({
188
201
  viewBox: "0 0 16 16",
189
202
  className: "symbol",
190
203
  "aria-hidden": "true",
191
- children: /* @__PURE__ */ a("path", { d: q })
204
+ children: /* @__PURE__ */ e("path", { d: W })
192
205
  }
193
206
  ),
194
- /* @__PURE__ */ a("p", { children: z })
207
+ /* @__PURE__ */ e("p", { children: A })
195
208
  ] }),
196
- /* @__PURE__ */ a(
197
- X,
209
+ /* @__PURE__ */ e(
210
+ J,
198
211
  {
199
212
  accept: { "image/*": [".jpg", ".jpeg", ".png"] },
200
213
  multiple: !1,
201
- onDropAccepted: S,
214
+ onDropAccepted: M,
202
215
  children: ({
203
- getRootProps: p,
216
+ getRootProps: g,
204
217
  getInputProps: i,
205
- isFocused: g,
206
- isDragAccept: k,
207
- isDragReject: A,
218
+ isFocused: m,
219
+ isDragAccept: j,
220
+ isDragReject: k,
208
221
  isDragActive: C
209
222
  }) => {
210
- const B = H(
223
+ const $ = X(
211
224
  "pco-org-avatar__dialog-dropzone",
212
225
  `pco-org-avatar__dialog-dropzone--${w}`,
213
226
  {
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
227
+ "pco-org-avatar__dialog-dropzone--image": l || a,
228
+ "pco-org-avatar__dialog-dropzone--focused": m,
229
+ "pco-org-avatar__dialog-dropzone--accepted": j,
230
+ "pco-org-avatar__dialog-dropzone--rejected": C && k
218
231
  }
219
232
  );
220
- return /* @__PURE__ */ n("div", { ...p({ className: B }), children: [
221
- /* @__PURE__ */ a("input", { ...i() }),
222
- l ? /* @__PURE__ */ a(
233
+ return /* @__PURE__ */ n("div", { ...g({ className: $ }), children: [
234
+ /* @__PURE__ */ e("input", { ...i() }),
235
+ l ? /* @__PURE__ */ e(
223
236
  "img",
224
237
  {
225
238
  src: l.preview,
226
239
  alt: `Church logo for ${o}`
227
240
  }
228
- ) : h ? /* @__PURE__ */ a(
241
+ ) : a ? /* @__PURE__ */ e(
229
242
  "img",
230
243
  {
231
- src: h,
244
+ src: a,
232
245
  alt: `Church logo for ${o}`
233
246
  }
234
- ) : /* @__PURE__ */ n(_, { children: [
235
- /* @__PURE__ */ a(
247
+ ) : /* @__PURE__ */ n(f, { children: [
248
+ /* @__PURE__ */ e(
236
249
  "svg",
237
250
  {
238
251
  width: "64",
239
252
  height: "64",
240
253
  viewBox: "0 0 16 16",
241
254
  "aria-hidden": "true",
242
- children: /* @__PURE__ */ a("path", { d: G })
255
+ children: /* @__PURE__ */ e("path", { d: q })
243
256
  }
244
257
  ),
245
- C && A ? /* @__PURE__ */ n(_, { children: [
246
- /* @__PURE__ */ a("h2", { children: "Wrong file type" }),
247
- /* @__PURE__ */ a("p", { children: "Please upload a PNG or JPG file." })
248
- ] }) : /* @__PURE__ */ n(_, { children: [
249
- /* @__PURE__ */ a("h2", { children: "Add church logo" }),
250
- /* @__PURE__ */ a("p", { children: "Use a high-quality image (up to 20mb) with a transparent background." })
258
+ C && k ? /* @__PURE__ */ n(f, { children: [
259
+ /* @__PURE__ */ e("h2", { children: "Wrong file type" }),
260
+ /* @__PURE__ */ e("p", { children: "Please upload a PNG or JPG file." })
261
+ ] }) : /* @__PURE__ */ n(f, { children: [
262
+ /* @__PURE__ */ e("h2", { children: "Add church logo" }),
263
+ /* @__PURE__ */ e("p", { children: "Use a high-quality image (up to 20mb) with a transparent background." })
251
264
  ] })
252
265
  ] })
253
266
  ] });
@@ -256,35 +269,35 @@ function R({
256
269
  )
257
270
  ] }),
258
271
  /* @__PURE__ */ n("div", { className: "pco-org-avatar__dialog-actions", children: [
259
- h && /* @__PURE__ */ a(
260
- j,
272
+ a && /* @__PURE__ */ e(
273
+ z,
261
274
  {
262
275
  label: "Delete logo",
263
276
  kind: "delete",
264
- onClick: O,
265
- loading: d,
266
- disabled: d || c,
277
+ onClick: F,
278
+ loading: h,
279
+ disabled: h || s,
267
280
  className: "pco-org-avatar__dialog-delete"
268
281
  }
269
282
  ),
270
- /* @__PURE__ */ a(
271
- j,
283
+ /* @__PURE__ */ e(
284
+ z,
272
285
  {
273
286
  type: "button",
274
287
  label: "Cancel",
275
- commandfor: y,
288
+ commandfor: c,
276
289
  command: "close",
277
- disabled: c || d
290
+ disabled: s || h
278
291
  }
279
292
  ),
280
- /* @__PURE__ */ a(
281
- j,
293
+ /* @__PURE__ */ e(
294
+ z,
282
295
  {
283
296
  type: "submit",
284
297
  label: "Update logo",
285
298
  kind: "primary",
286
- loading: c,
287
- disabled: !l || c || d
299
+ loading: s,
300
+ disabled: !l || s || h
288
301
  }
289
302
  )
290
303
  ] })
@@ -295,41 +308,45 @@ function R({
295
308
  )
296
309
  ] });
297
310
  }
298
- function ia({
299
- avatarUrl: e,
311
+ function ne({
312
+ avatarUrl: a,
300
313
  darkModeAvatarUrl: o,
301
314
  orgName: r,
302
- pcoEnv: t,
303
- showDarkModeAvatar: s = !1
315
+ onAvatarUpdate: t,
316
+ pcoEnv: d,
317
+ readOnly: _,
318
+ showDarkModeAvatar: l = !1
304
319
  }) {
305
- const [l, m] = u(e), [h, f] = u(
320
+ const [u, s] = v(a), [y, h] = v(
306
321
  o
307
- ), c = (d, v) => {
308
- d === "avatar" ? m(v) : d === "dark_mode_avatar" && f(v);
322
+ ), b = (p, c) => {
323
+ p === "avatar" ? (s(c), t?.("avatar", c)) : p === "dark_mode_avatar" && (h(c), t?.("dark_mode_avatar", c));
309
324
  };
310
325
  return /* @__PURE__ */ n("div", { className: "pco-org-avatars", children: [
311
- /* @__PURE__ */ a(
312
- R,
326
+ /* @__PURE__ */ e(
327
+ D,
313
328
  {
314
- avatarUrl: l,
329
+ avatarUrl: u,
315
330
  organization: r,
316
331
  mode: "avatar",
317
- pcoEnv: t,
318
- onAvatarUpdate: c
332
+ pcoEnv: d,
333
+ readOnly: _,
334
+ onAvatarUpdate: b
319
335
  }
320
336
  ),
321
- s && /* @__PURE__ */ a(
322
- R,
337
+ l && /* @__PURE__ */ e(
338
+ D,
323
339
  {
324
- avatarUrl: h || l,
340
+ avatarUrl: y || u,
325
341
  organization: r,
326
342
  mode: "dark_mode_avatar",
327
- pcoEnv: t,
328
- onAvatarUpdate: c
343
+ pcoEnv: d,
344
+ readOnly: _,
345
+ onAvatarUpdate: b
329
346
  }
330
347
  )
331
348
  ] });
332
349
  }
333
350
  export {
334
- ia as OrganizationAvatars
351
+ ne as OrganizationAvatars
335
352
  };
@@ -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.2",
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",