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