@sanity/language-filter 3.0.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2022 Sanity.io
3
+ Copyright (c) 2023 Sanity.io
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -68,8 +68,8 @@ Add it as a plugin in sanity.config.ts (or .js), and configure it:
68
68
  defaultLanguages: ['nb'],
69
69
  // Only show language filter for document type `page` (schemaType.name)
70
70
  documentTypes: ['page'],
71
- filterField: (enclosingType, field, selectedLanguageIds) =>
72
- !enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(field.name),
71
+ filterField: (enclosingType, member, selectedLanguageIds) =>
72
+ !enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(member.name),
73
73
  })
74
74
  ]
75
75
  })
package/lib/index.esm.js CHANGED
@@ -1,2 +1 @@
1
- const e=["subscribeSelectedIds"],n=["members","options","schemaType","renderDefault","subscribeSelectedIds"];function t(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);n&&(r=r.filter((function(n){return Object.getOwnPropertyDescriptor(e,n).enumerable}))),t.push.apply(t,r)}return t}function r(e){for(var n=1;n<arguments.length;n++){var r=null!=arguments[n]?arguments[n]:{};n%2?t(Object(r),!0).forEach((function(n){l(e,n,r[n])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):t(Object(r)).forEach((function(n){Object.defineProperty(e,n,Object.getOwnPropertyDescriptor(r,n))}))}return e}function l(e,n,t){return n in e?Object.defineProperty(e,n,{value:t,enumerable:!0,configurable:!0,writable:!0}):e[n]=t,e}function i(e,n){if(null==e)return{};var t,r,l=function(e,n){if(null==e)return{};var t,r,l={},i=Object.keys(e);for(r=0;r<i.length;r++)t=i[r],n.indexOf(t)>=0||(l[t]=e[t]);return l}(e,n);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r<i.length;r++)t=i[r],n.indexOf(t)>=0||Object.prototype.propertyIsEnumerable.call(e,t)&&(l[t]=e[t])}return l}import{jsx as c,jsxs as o,Fragment as a}from"react/jsx-runtime";import{definePlugin as s}from"sanity";import{createContext as d,useMemo as u,useContext as g,useState as f,useEffect as p,useCallback as h}from"react";import{useClickOutside as m,Box as b,Card as y,Stack as S,Text as O,Flex as v,Checkbox as j,Popover as w,Button as I}from"@sanity/ui";const L=(e,n,t)=>!e.name.startsWith("locale")||t.includes(n.name);function T(e,n){var t,r;const l=function(e){return"object"===(null==e?void 0:e.jsonType)&&"document"===P(e).name}(e)&&(null==(t=null==e?void 0:e.options)?void 0:t.languageFilter),i=!n.documentTypes;return!!(i&&!1!==l||!i&&l||e&&(null==(r=n.documentTypes)?void 0:r.includes(e.name)))}function P(e){return e.type?P(e.type):e}const D=d(void 0);function k(e){let{options:n,enabled:t,children:r}=e;const l=u((()=>({options:n,enabled:t})),[n,t]);return c(D.Provider,{value:l,children:r})}const x="@sanity/plugin/language-filter/selected-languages";function C(e){let{supportedLanguages:n,defaultLanguages:t}=e;return n.filter((e=>!(null==t?void 0:t.includes(e.id))))}function E(e){return f((()=>function(e){const n=C(e).map((e=>e.id));let t=n;try{const e=window.localStorage.getItem(x);e&&(t=JSON.parse(e))}catch(e){}var r;return r=n,t=t.filter((e=>r.includes(e))),t}(e)))}function F(n){const t=g(D),{options:l,enabled:o}=t||{},{subscribeSelectedIds:a}=n,s=i(n,e);return o&&l?c(N,r(r({},s),{},{options:l,subscribeSelectedIds:a})):n.renderDefault(s)}function N(e){var t;const{members:l,options:c,schemaType:o,renderDefault:a,subscribeSelectedIds:s}=e,d=i(e,n),[g,f]=E(c);p((()=>{const e=s(f);return()=>e()}),[s,f]);const h=u((()=>{var e;return[...null!=(e=c.defaultLanguages)?e:[],...g]}),[c.defaultLanguages,g]),m=null!=(t=c.filterField)?t:L,b=u((()=>l.filter((e=>"field"===e.kind&&m(o,e,h)||"fieldSet"===e.kind)).map((e=>"fieldSet"===e.kind?r(r({},e),{},{fieldSet:r(r({},e.fieldSet),{},{members:e.fieldSet.members.filter((e=>"field"===e.kind&&m(o,e,h)))})}):e))),[o,l,m,h]);return a(r(r({},d),{},{members:b,schemaType:o,renderDefault:a}))}function A(e){const{options:n,onSelectedIdsChange:t}=e,{defaultLanguages:r}=n,[l,i]=E(n),c=u((()=>C(n)),[n]),o=h((e=>{var n;i(e),n=e,window.localStorage.setItem(x,JSON.stringify(n)),t(e)}),[t,i]),a=h((()=>o(c.map((e=>e.id)))),[o,c]),s=h((()=>{o([])}),[o]),d=h((e=>{let n=l;n=n.includes(e)?n.filter((n=>n!==e)):[...n,e],o(n)}),[o,l]);return{activeLanguages:u((()=>[...null!=r?r:[],...l]),[r,l]),allSelected:l.length===c.length,selectAll:a,selectNone:s,toggleLanguage:d}}function z(e){const{options:n,onSelectedIdsChange:t}=e,r=n.supportedLanguages.filter((e=>{var t;return null==(t=n.defaultLanguages)?void 0:t.includes(e.id)})),l=n.supportedLanguages.filter((e=>{var t;return!(null==(t=n.defaultLanguages)?void 0:t.includes(e.id))})),[i,s]=f(!1),{activeLanguages:d,allSelected:u,selectAll:g,selectNone:p,toggleLanguage:L}=A({options:n,onSelectedIdsChange:t}),[T,P]=f(null),[D,k]=f(null),x=h((e=>{e.currentTarget.checked?g():p()}),[g,p]),C=h((()=>s((e=>!e))),[]),E=h((()=>s(!1)),[]);m(E,[T,D]);const F=o(b,{overflow:"auto",padding:1,children:[r.length>0&&c(y,{radius:2,children:o(S,{padding:2,space:3,children:[c(b,{paddingBottom:2,children:o(O,{size:1,weight:"semibold",children:["Default language",r.length>1&&c(a,{children:"s"})]})}),r.map((e=>c(O,{children:e.title},e.id)))]})}),o(S,{marginTop:3,padding:2,space:2,children:[c(b,{paddingBottom:2,children:c(O,{size:1,weight:"semibold",children:"Show translations"})}),c(y,{as:"label",children:o(v,{align:"center",gap:2,children:[c(j,{checked:u,name:"_allSelected",onChange:x}),c(b,{flex:1,children:c(O,{muted:!u,weight:"semibold",children:"All translations"})})]})}),l.map((e=>c(B,{id:e.id,onToggle:L,selected:d.includes(e.id),title:e.title},e.id)))]})]}),N=n.supportedLanguages.length;return c(w,{content:F,open:i,portal:!0,ref:k,children:c(I,{text:o(v,{gap:1,children:[c(b,{children:"Filter languages:"}),o(v,{gap:1,justify:"space-around",children:[c(v,{style:{width:"".concat(Math.floor(Math.log10(N)+1),"ch")},justify:"flex-end",children:d.length}),c(b,{children:"/"}),c(b,{children:N})]})]}),mode:"bleed",onClick:C,ref:P,selected:i})})}function B(e){const{id:n,onToggle:t,selected:r,title:l}=e,i=h((()=>{t(n)}),[n,t]);return c(y,{as:"label",children:o(v,{align:"center",gap:2,children:[c(j,{checked:r,name:"language-".concat(n),onChange:i}),c(b,{flex:1,children:c(O,{muted:!r,children:l})})]})})}const J=s((e=>{const{onSelectedIdsChange:n,subscribeSelectedIds:t}=function(){const e=[];return{onSelectedIdsChange:n=>{e.forEach((e=>e(n)))},subscribeSelectedIds:n=>(e.push(n),()=>{e.splice(e.indexOf(n),1)})}}(),l=()=>c(z,{options:e,onSelectedIdsChange:n});return{name:"@sanity/language-filter",document:{unstable_languageFilter:(n,t)=>{let{schemaType:r,schema:i}=t;return T(i.get(r),e)?[...n,l]:n}},form:{components:{input:function(n){const l=T(n.schemaType,e);return l?c(k,{enabled:l,options:e,children:n.renderDefault(n)}):"object"===n.schemaType.jsonType?c(F,r(r({},n),{},{subscribeSelectedIds:t})):n.renderDefault(n)}}}}}));export{L as defaultFilterField,T as isLanguageFilterEnabled,J as languageFilter};
2
- //# sourceMappingURL=index.esm.js.map
1
+ var e;const t=["members","schemaType","renderDefault"];function n(e,t){if(null==e)return{};var n,r,l=function(e,t){if(null==e)return{};var n,r,l={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(l[n]=e[n]);return l}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(l[n]=e[n])}return l}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function i(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var r=n.call(e,t||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}import{jsx as o,jsxs as a,Fragment as c}from"react/jsx-runtime";import{useFormValue as u,TextWithTone as s,definePlugin as d,isObjectSchemaType as g}from"sanity";import{useState as f,createContext as p,useMemo as m,useContext as h,useCallback as y}from"react";import{Box as b,useClickOutside as v,Card as O,Stack as L,Text as j,Button as S,Flex as w,TextInput as T,Popover as P,Badge as x}from"@sanity/ui";import D from"styled-components";import{EyeClosedIcon as I,EyeOpenIcon as k,TranslateIcon as C,CheckmarkCircleIcon as N,CircleIcon as z}from"@sanity/icons";const A=(e,t,n)=>!e.name.startsWith("locale")||n.includes(t.name);function F(e,t){var n,r;const l=function(e){return"object"===(null==e?void 0:e.jsonType)&&"document"===E(e).name}(e)&&(null==(n=null==e?void 0:e.options)?void 0:n.languageFilter),i=!t.documentTypes;return!!(i&&!1!==l||!i&&l||e&&(null==(r=t.documentTypes)?void 0:r.includes(e.name)))}function E(e){return e.type?E(e.type):e}const J="@sanity/plugin/language-filter/selected-languages";function _(e){let{supportedLanguages:t,defaultLanguages:n}=e;return t.filter((e=>!(null==n?void 0:n.includes(e.id))))}function B(e){return f((()=>function(e){const t=_(e).map((e=>e.id));let n=t;try{const e=window.localStorage.getItem(J);e&&(n=JSON.parse(e))}catch(e){}var r;return r=t,n=n.filter((e=>r.includes(e))),n}(e)))}const H={options:{supportedLanguages:[],defaultLanguages:[],documentTypes:[],filterField:A},selectedLanguageIds:[],setSelectedLanguageIds:()=>console.error("LanguageFilterStudioContext not initialized")},W=p(H);function q(){return h(W)}function G(e){const{members:r,schemaType:i,renderDefault:o}=e,a=n(e,t),{selectedLanguageIds:c,options:u}=q(),{filterField:s}=u,d=m((()=>r.filter((e=>"field"===e.kind&&s(i,e,c)||"fieldSet"===e.kind)).map((e=>"fieldSet"===e.kind?l(l({},e),{},{fieldSet:l(l({},e.fieldSet),{},{members:e.fieldSet.members.filter((e=>"field"===e.kind&&s(i,e,c)))})}):e))),[i,r,s,c]);return o(l(l({},a),{},{members:d,schemaType:i,renderDefault:o}))}function K(){const{selectedLanguageIds:e,setSelectedLanguageIds:t,options:n}=q(),{defaultLanguages:r}=n,l=m((()=>_(n)),[n]),i=y((e=>{var n;t(e),n=e,window.localStorage.setItem(J,JSON.stringify(n))}),[t]),o=y((()=>i(l.map((e=>e.id)))),[i,l]),a=y((()=>{i([])}),[i]),c=y((t=>{let n=e;n=n.includes(t)?n.filter((e=>e!==t)):[...n,t],i(n)}),[i,e]);return{activeLanguages:m((()=>[...null!=r?r:[],...e]),[r,e]),allSelected:e.length===l.length,selectAll:o,selectNone:a,toggleLanguage:c}}const M=D(b)(e||(Q=["\n max-height: calc(100vh - 200px);\n"],R||(R=Q.slice(0)),e=Object.freeze(Object.defineProperties(Q,{raw:{value:Object.freeze(R)}}))));var Q,R;function U(e){const{options:t}=e,n=t.supportedLanguages.filter((e=>{var n;return null==(n=t.defaultLanguages)?void 0:n.includes(e.id)})),r=t.supportedLanguages.filter((e=>{var n;return!(null==(n=t.defaultLanguages)?void 0:n.includes(e.id))})),[l,i]=f(!1),{activeLanguages:u,allSelected:d,selectAll:g,selectNone:p,toggleLanguage:m}=K(),[h,x]=f(null),[D,N]=f(null),z=y((e=>{"ALL"===e.currentTarget.value?g():p()}),[g,p]),A=y((()=>i((e=>!e))),[]),F=y((()=>i(!1)),[]);v(F,[h,D]);const E=t.supportedLanguages.length,[J,_]=f(""),B=y((e=>{e.currentTarget.value?_(e.currentTarget.value):_("")}),[]),H=a(M,{overflow:"auto",padding:1,children:[n.length>0&&o(O,{radius:2,children:a(L,{padding:2,space:3,children:[o(b,{paddingBottom:2,children:a(j,{size:1,weight:"semibold",children:["Default language",n.length>1&&o(c,{children:"s"})]})}),n.map((e=>o(j,{children:e.title},e.id)))]})}),a(L,{padding:1,space:1,children:[o(S,{mode:"bleed",onClick:z,justify:"flex-start",value:d?"NONE":"ALL",disabled:!!J,children:a(w,{gap:3,align:"center",children:[o(j,{size:2,children:d?o(s,{tone:"primary",children:o(I,{})}):o(k,{})}),o(b,{flex:1,children:o(j,{children:d?"Hide All":"Show All"})})]})}),o(O,{borderTop:!0}),E>4?o(T,{onChange:B,value:J,placeholder:"Filter languages"}):null,r.filter((e=>!J||e.title.toLowerCase().includes(J.toLowerCase()))).map((e=>o(V,{id:e.id,onToggle:m,selected:u.includes(e.id),title:e.title},e.id)))]})]}),W=u.length===E?"Showing all":"Showing ".concat(u.length," / ").concat(E);return o(P,{content:H,open:l,portal:!0,ref:N,children:o(S,{text:W,icon:C,mode:"bleed",onClick:A,ref:x,selected:l})})}function V(e){const{id:t,onToggle:n,selected:r,title:l}=e,i=y((()=>{n(t)}),[t,n]);return o(S,{mode:"bleed",onClick:i,justify:"flex-start",children:a(w,{gap:3,align:"center",children:[o(j,{size:2,children:r?o(s,{tone:"positive",children:o(N,{})}):o(z,{})}),o(b,{flex:1,children:o(j,{children:l})}),o(x,{children:t})]})})}const X=d((e=>{const t=()=>o(U,{options:e}),n=l(l({},H.options),e);return{name:"@sanity/language-filter",studio:{components:{layout:e=>function(e){const t=m((()=>l(l({},H.options),e.options)),[e.options]),[n,r]=B(t);return o(W.Provider,{value:{options:t,selectedLanguageIds:n,setSelectedLanguageIds:r},children:e.renderDefault(e)})}(l(l({},e),{},{options:n}))}},document:{unstable_languageFilter:(n,r)=>{let{schemaType:l,schema:i}=r;return F(i.get(l),e)?[...n,t]:n}},form:{components:{input:e=>"root"!==e.id&&g(e.schemaType)?function(e){const{options:t}=q(),n=u(["_type"]),{documentTypes:r}=t;return"string"==typeof n&&r.includes(n)?o(G,l({},e)):e.renderDefault(e)}(e):e.renderDefault(e)}}}}));export{A as defaultFilterField,F as isLanguageFilterEnabled,X as languageFilter,q as useLanguageFilterStudioContext};//# sourceMappingURL=index.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/filterField.ts","../src/LanguageFilterContext.tsx","../src/useSelectedLanguageIds.ts","../src/LanguageFilterObjectInput.tsx","../src/usePaneLanguages.ts","../src/LanguageFilterMenuButton.tsx","../src/plugin.tsx","../src/languageSubscription.ts"],"sourcesContent":["import type {SchemaType} from 'sanity'\nimport {FilterFieldFunction, LanguageFilterConfig, LanguageFilterSchema} from './types'\n\nexport const defaultFilterField: FilterFieldFunction = (\n enclosingType,\n field,\n selectedLanguageIds\n) => !enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(field.name)\n\nexport function isLanguageFilterEnabled(\n schemaType: SchemaType | undefined,\n options: LanguageFilterConfig\n): boolean {\n const schemaFilter =\n isDocument(schemaType) && (schemaType as LanguageFilterSchema)?.options?.languageFilter\n const defaultEnabled = !options.documentTypes\n\n return !!(\n (defaultEnabled && schemaFilter !== false) ||\n (!defaultEnabled && schemaFilter) ||\n (schemaType && options.documentTypes?.includes(schemaType.name))\n )\n}\n\nfunction isDocument(schemaType?: SchemaType) {\n return schemaType?.jsonType === 'object' && getRootType(schemaType).name === 'document'\n}\n\nfunction getRootType(schema: SchemaType): SchemaType {\n if (schema.type) {\n return getRootType(schema.type)\n }\n return schema\n}\n","import React, {createContext, PropsWithChildren, useContext, useMemo} from 'react'\nimport {LanguageFilterConfig} from './types'\n\nexport interface LanguageFilterContextValue {\n // eslint-disable-next-line react/require-default-props\n options: LanguageFilterConfig\n // eslint-disable-next-line react/require-default-props\n enabled: boolean\n}\n\nconst LanguageFilterContext = createContext<LanguageFilterContextValue | undefined>(undefined)\n\nexport function LanguageFilterProvider({\n options,\n enabled,\n children,\n}: PropsWithChildren<\n Omit<LanguageFilterContextValue, 'selectedLanguageIds' | 'setSelectedLanguageIds'>\n>) {\n const value = useMemo(() => ({options, enabled}), [options, enabled])\n return <LanguageFilterContext.Provider value={value}>{children}</LanguageFilterContext.Provider>\n}\n\nexport function useLanguageFilterContext() {\n return useContext(LanguageFilterContext)\n}\n","import {Language, LanguageFilterConfig} from './types'\nimport {useState} from 'react'\nconst storageKey = '@sanity/plugin/language-filter/selected-languages'\n\nexport function getPersistedLanguageIds(options: LanguageFilterConfig): string[] {\n const selectableLangs = getSelectableLanguages(options).map((l) => l.id)\n\n let selected: string[] = selectableLangs\n try {\n const persistedValue = window.localStorage.getItem(storageKey)\n if (persistedValue) {\n selected = JSON.parse(persistedValue)\n }\n } catch (err) {} // eslint-disable-line no-empty\n\n // constrain persisted/selected languages to the ones currently supported\n selected = intersection(selected, selectableLangs)\n return selected\n}\n\nexport function persistLanguageIds(languageIds: string[]): void {\n window.localStorage.setItem(storageKey, JSON.stringify(languageIds))\n}\n\nfunction intersection(array1: string[], array2: string[]) {\n return array1.filter((value) => array2.includes(value))\n}\n\nexport function getSelectableLanguages({\n supportedLanguages,\n defaultLanguages,\n}: LanguageFilterConfig): Language[] {\n return supportedLanguages.filter((lang) => !defaultLanguages?.includes(lang.id))\n}\n\nexport function useSelectedLanguageIds(\n options: LanguageFilterConfig\n): [string[], (ids: string[]) => void] {\n return useState(() => getPersistedLanguageIds(options))\n}\n","import React, {useEffect, useMemo} from 'react'\nimport {ObjectInputProps, ObjectMember} from 'sanity'\nimport {LanguageFilterConfig} from './types'\nimport {defaultFilterField} from './filterField'\nimport {useLanguageFilterContext} from './LanguageFilterContext'\nimport {useSelectedLanguageIds} from './useSelectedLanguageIds'\n\nexport type LanguageFilterObjectInputProps = {\n options: LanguageFilterConfig\n /**\n * We need a way to communicate state changes between the pane menu and input components.\n * LanguageFilter button lives outside the input-render tree, so Context is out.\n * This is a workaround for that.\n */\n subscribeSelectedIds: (callback: (ids: string[]) => void) => () => void\n} & ObjectInputProps\n\nexport function LanguageFilterObjectInput(\n props: ObjectInputProps & {\n subscribeSelectedIds: (callback: (ids: string[]) => void) => () => void\n }\n) {\n const context = useLanguageFilterContext()\n const {options, enabled} = context || {}\n const {subscribeSelectedIds, ...restProps} = props\n if (!enabled || !options) {\n return props.renderDefault(restProps)\n }\n return (\n <FilteredObjectInput\n {...restProps}\n options={options}\n subscribeSelectedIds={subscribeSelectedIds}\n />\n )\n}\n\nfunction FilteredObjectInput(props: LanguageFilterObjectInputProps) {\n const {\n members: membersProp,\n options,\n schemaType,\n renderDefault,\n subscribeSelectedIds,\n ...restProps\n } = props\n const [selectedIds, setSelectedIds] = useSelectedLanguageIds(options)\n\n useEffect(() => {\n const unsubscribe = subscribeSelectedIds(setSelectedIds)\n return () => unsubscribe()\n }, [subscribeSelectedIds, setSelectedIds])\n\n const activeLanguages = useMemo(\n () => [...(options.defaultLanguages ?? []), ...selectedIds],\n [options.defaultLanguages, selectedIds]\n )\n\n const filterField = options.filterField ?? defaultFilterField\n\n const members: ObjectMember[] = useMemo(() => {\n return membersProp\n .filter((member) => {\n return (\n (member.kind === 'field' && filterField(schemaType, member, activeLanguages)) ||\n member.kind === 'fieldSet'\n )\n })\n .map((member) => {\n if (member.kind === 'fieldSet') {\n return {\n ...member,\n fieldSet: {\n ...member.fieldSet,\n members: member.fieldSet.members.filter((fieldsetMember) => {\n return (\n fieldsetMember.kind === 'field' &&\n filterField(schemaType, fieldsetMember, activeLanguages)\n )\n }),\n },\n }\n }\n return member\n })\n }, [schemaType, membersProp, filterField, activeLanguages])\n\n return renderDefault({...restProps, members, schemaType, renderDefault})\n}\n","import {useCallback, useMemo} from 'react'\nimport {LanguageFilterConfig} from './types'\nimport {\n getSelectableLanguages,\n persistLanguageIds,\n useSelectedLanguageIds,\n} from './useSelectedLanguageIds'\n\nexport interface UsePaneLanguagesParams {\n options: LanguageFilterConfig\n /**\n * We need a way to communicate state changes between the pane menu and input components.\n * LanguageFilter button lives outside the input-render tree, so Context is out.\n * This is a workaround for that.\n */\n onSelectedIdsChange: (ids: string[]) => void\n}\n\nexport function usePaneLanguages(props: UsePaneLanguagesParams): {\n activeLanguages: string[]\n allSelected: boolean\n selectAll: () => void\n selectNone: () => void\n toggleLanguage: (languageId: string) => void\n} {\n const {options, onSelectedIdsChange} = props\n const {defaultLanguages} = options\n\n const [selectedIds, setSelectedIds] = useSelectedLanguageIds(options)\n\n const selectableLanguages = useMemo(() => getSelectableLanguages(options), [options])\n\n const updateSelectedIds = useCallback(\n (ids: string[]) => {\n setSelectedIds(ids)\n persistLanguageIds(ids)\n onSelectedIdsChange(ids)\n },\n [onSelectedIdsChange, setSelectedIds]\n )\n\n const selectAll = useCallback(\n () => updateSelectedIds(selectableLanguages.map((l) => l.id)),\n [updateSelectedIds, selectableLanguages]\n )\n\n const selectNone = useCallback(() => {\n updateSelectedIds([])\n }, [updateSelectedIds])\n\n const toggleLanguage = useCallback(\n (languageId: string) => {\n let lang = selectedIds\n\n if (lang.includes(languageId)) {\n lang = lang.filter((l) => l !== languageId)\n } else {\n lang = [...lang, languageId]\n }\n\n updateSelectedIds(lang)\n },\n [updateSelectedIds, selectedIds]\n )\n\n const activeLanguages = useMemo(\n () => [...(defaultLanguages ?? []), ...selectedIds],\n [defaultLanguages, selectedIds]\n )\n\n return {\n activeLanguages,\n allSelected: selectedIds.length === selectableLanguages.length,\n selectAll,\n selectNone,\n toggleLanguage,\n }\n}\n","import {Box, Button, Card, Checkbox, Flex, Popover, Stack, Text, useClickOutside} from '@sanity/ui'\nimport React, {FormEvent, useCallback, useState} from 'react'\nimport {usePaneLanguages} from './usePaneLanguages'\nimport {LanguageFilterConfig} from './types'\n\nexport interface LanguageFilterMenuButtonProps {\n options: LanguageFilterConfig\n onSelectedIdsChange: (ids: string[]) => void\n}\n\nexport function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {\n const {options, onSelectedIdsChange} = props\n\n const defaultLanguages = options.supportedLanguages.filter((l) =>\n options.defaultLanguages?.includes(l.id)\n )\n\n const languageOptions = options.supportedLanguages.filter(\n (l) => !options.defaultLanguages?.includes(l.id)\n )\n const [open, setOpen] = useState(false)\n const {activeLanguages, allSelected, selectAll, selectNone, toggleLanguage} = usePaneLanguages({\n options,\n onSelectedIdsChange,\n })\n const [button, setButton] = useState<HTMLElement | null>(null)\n const [popover, setPopover] = useState<HTMLElement | null>(null)\n\n const handleToggleAll = useCallback(\n (event: FormEvent<HTMLInputElement>) => {\n const checked = event.currentTarget.checked\n\n if (checked) {\n selectAll()\n } else {\n selectNone()\n }\n },\n [selectAll, selectNone]\n )\n\n const handleClick = useCallback(() => setOpen((o) => !o), [])\n\n const handleClickOutside = useCallback(() => setOpen(false), [])\n\n useClickOutside(handleClickOutside, [button, popover])\n\n const content = (\n <Box overflow=\"auto\" padding={1}>\n {defaultLanguages.length > 0 && (\n <Card radius={2}>\n <Stack padding={2} space={3}>\n <Box paddingBottom={2}>\n <Text size={1} weight=\"semibold\">\n Default language{defaultLanguages.length > 1 && <>s</>}\n </Text>\n </Box>\n\n {defaultLanguages.map((l) => (\n <Text key={l.id}>{l.title}</Text>\n ))}\n </Stack>\n </Card>\n )}\n\n <Stack marginTop={3} padding={2} space={2}>\n <Box paddingBottom={2}>\n <Text size={1} weight=\"semibold\">\n Show translations\n </Text>\n </Box>\n\n <Card as=\"label\">\n <Flex align=\"center\" gap={2}>\n <Checkbox checked={allSelected} name=\"_allSelected\" onChange={handleToggleAll} />\n <Box flex={1}>\n <Text muted={!allSelected} weight=\"semibold\">\n All translations\n </Text>\n </Box>\n </Flex>\n </Card>\n\n {languageOptions.map((lang) => (\n <LanguageFilterOption\n id={lang.id}\n key={lang.id}\n onToggle={toggleLanguage}\n selected={activeLanguages.includes(lang.id)}\n title={lang.title}\n />\n ))}\n </Stack>\n </Box>\n )\n\n const langCount = options.supportedLanguages.length\n return (\n <Popover content={content} open={open} portal ref={setPopover}>\n <Button\n text={\n <Flex gap={1}>\n <Box>Filter languages:</Box>\n <Flex gap={1} justify=\"space-around\">\n <Flex\n style={{width: `${Math.floor(Math.log10(langCount) + 1)}ch`}}\n justify=\"flex-end\"\n >\n {activeLanguages.length}\n </Flex>\n <Box>/</Box>\n <Box>{langCount}</Box>\n </Flex>\n </Flex>\n }\n mode=\"bleed\"\n onClick={handleClick}\n ref={setButton}\n selected={open}\n />\n </Popover>\n )\n}\n\nfunction LanguageFilterOption(props: {\n id: string\n onToggle: (id: string) => void\n selected: boolean\n title: string\n}) {\n const {id, onToggle, selected, title} = props\n\n const handleChange = useCallback(() => {\n onToggle(id)\n }, [id, onToggle])\n\n return (\n <Card as=\"label\">\n <Flex align=\"center\" gap={2}>\n <Checkbox checked={selected} name={`language-${id}`} onChange={handleChange} />\n <Box flex={1}>\n <Text muted={!selected}>{title}</Text>\n </Box>\n </Flex>\n </Card>\n )\n}\n","import React from 'react'\nimport {definePlugin, DocumentLanguageFilterComponent, ObjectInputProps} from 'sanity'\nimport {LanguageFilterObjectInput} from './LanguageFilterObjectInput'\nimport {LanguageFilterMenuButton} from './LanguageFilterMenuButton'\nimport {LanguageFilterConfig} from './types'\nimport {isLanguageFilterEnabled} from './filterField'\nimport {LanguageFilterProvider} from './LanguageFilterContext'\nimport {createSelectedLanguageIdsBus} from './languageSubscription'\n\n/**\n * ## Usage in sanity.config.ts (or .js)\n *\n * ```\n * import {defineConfig} from 'sanity'\n * import {languageFilter} from '@sanity/language-filter'\n *\n * export const defineConfig({\n * /...\n * plugins: [\n * languageFilter({\n * supportedLanguages: [\n * {id: 'nb', title: 'Norwegian (Bokmål)'},\n * {id: 'nn', title: 'Norwegian (Nynorsk)'},\n * {id: 'en', title: 'English'},\n * {id: 'es', title: 'Spanish'},\n * {id: 'arb', title: 'Arabic'},\n * {id: 'pt', title: 'Portuguese'},\n * //...\n * ],\n * // Select Norwegian (Bokmål) by default\n * defaultLanguages: ['nb'],\n * // Only show language filter for document type `page` (schemaType.name)\n * // Can also enable via document-options: options.languageFilter: true\n * documentTypes: ['page'],\n * // default filter function shown\n * filterField: (enclosingType, field, selectedLanguageIds) =>\n * !enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(field.name),\n * })\n * ]\n * })\n * ```\n */\nexport const languageFilter = definePlugin<LanguageFilterConfig>((options) => {\n const {onSelectedIdsChange, subscribeSelectedIds} = createSelectedLanguageIdsBus()\n\n const RenderLanguageFilter: DocumentLanguageFilterComponent = () => {\n return <LanguageFilterMenuButton options={options} onSelectedIdsChange={onSelectedIdsChange} />\n }\n\n return {\n name: '@sanity/language-filter',\n document: {\n unstable_languageFilter: (prev, {schemaType, schema}) => {\n if (isLanguageFilterEnabled(schema.get(schemaType), options)) {\n return [...prev, RenderLanguageFilter]\n }\n return prev\n },\n },\n\n form: {\n components: {\n // eslint-disable-next-line func-name-matching\n input: function LanguageFilterWrapper(props) {\n const enabled = isLanguageFilterEnabled(props.schemaType, options)\n // will only be considered enabled for document, so this is only done once\n if (enabled) {\n return (\n <LanguageFilterProvider enabled={enabled} options={options}>\n {props.renderDefault(props)}\n </LanguageFilterProvider>\n )\n }\n if (props.schemaType.jsonType === 'object') {\n return (\n <LanguageFilterObjectInput\n {...(props as ObjectInputProps)}\n subscribeSelectedIds={subscribeSelectedIds}\n />\n )\n }\n\n return props.renderDefault(props)\n },\n },\n },\n }\n})\n","export type LanguageSubscription = (ids: string[]) => void\nexport type Unsubscribe = () => void\nexport type LanguageSubscribe = (subscription: LanguageSubscription) => Unsubscribe\n\nexport interface SelectedLanguageIdsBus {\n onSelectedIdsChange: (ids: string[]) => void\n subscribeSelectedIds: LanguageSubscribe\n}\n\n/**\n * We need a way to communicate state changes between the pane menu and input components.\n * LanguageFilter button lives outside the input-render tree, so Context is out.\n * This is a workaround for that.\n */\nexport function createSelectedLanguageIdsBus(): SelectedLanguageIdsBus {\n const subs: LanguageSubscription[] = []\n\n const onSelectedIdsChange = (ids: string[]) => {\n subs.forEach((s) => s(ids))\n }\n const subscribeSelectedIds = (subscription: LanguageSubscription) => {\n subs.push(subscription)\n return () => {\n subs.splice(subs.indexOf(subscription), 1)\n }\n }\n\n return {\n onSelectedIdsChange,\n subscribeSelectedIds,\n }\n}\n"],"names":["defaultFilterField","enclosingType","field","selectedLanguageIds","name","startsWith","includes","isLanguageFilterEnabled","schemaType","options","_a","_b","schemaFilter","jsonType","getRootType","isDocument","languageFilter","defaultEnabled","documentTypes","schema","type","LanguageFilterContext","createContext","LanguageFilterProvider","_ref","enabled","children","value","useMemo","jsx","Provider","storageKey","getSelectableLanguages","_ref2","supportedLanguages","defaultLanguages","filter","lang","id","useSelectedLanguageIds","useState","selectableLangs","map","l","selected","persistedValue","window","localStorage","getItem","JSON","parse","err","array2","getPersistedLanguageIds","LanguageFilterObjectInput","props","context","useContext","subscribeSelectedIds","restProps","_excluded","FilteredObjectInput","renderDefault","members","membersProp","_excluded2","selectedIds","setSelectedIds","useEffect","unsubscribe","activeLanguages","filterField","member","kind","_objectSpread","fieldSet","fieldsetMember","usePaneLanguages","onSelectedIdsChange","selectableLanguages","updateSelectedIds","useCallback","ids","languageIds","setItem","stringify","selectAll","selectNone","toggleLanguage","languageId","allSelected","length","LanguageFilterMenuButton","languageOptions","open","setOpen","button","setButton","popover","setPopover","handleToggleAll","event","currentTarget","checked","handleClick","o","handleClickOutside","useClickOutside","content","jsxs","Box","overflow","padding","Card","radius","Stack","space","paddingBottom","Text","size","weight","Fragment","title","marginTop","as","Flex","align","gap","Checkbox","onChange","flex","muted","LanguageFilterOption","onToggle","langCount","Popover","portal","ref","Button","text","justify","style","width","Math","floor","log10","mode","onClick","handleChange","definePlugin","subs","forEach","s","subscription","push","splice","indexOf","createSelectedLanguageIdsBus","RenderLanguageFilter","document","unstable_languageFilter","prev","_ref3","get","form","components","input"],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/lib/index.js CHANGED
@@ -1,2 +1 @@
1
- "use strict";const e=["subscribeSelectedIds"],t=["members","options","schemaType","renderDefault","subscribeSelectedIds"];function n(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e);t&&(l=l.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,l)}return n}function l(e){for(var t=1;t<arguments.length;t++){var l=null!=arguments[t]?arguments[t]:{};t%2?n(Object(l),!0).forEach((function(t){r(e,t,l[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(l)):n(Object(l)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(l,t))}))}return e}function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function s(e,t){if(null==e)return{};var n,l,r=function(e,t){if(null==e)return{};var n,l,r={},s=Object.keys(e);for(l=0;l<s.length;l++)n=s[l],t.indexOf(n)>=0||(r[n]=e[n]);return r}(e,t);if(Object.getOwnPropertySymbols){var s=Object.getOwnPropertySymbols(e);for(l=0;l<s.length;l++)n=s[l],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}Object.defineProperty(exports,"__esModule",{value:!0});var a=require("react/jsx-runtime"),i=require("sanity"),c=require("react"),o=require("@sanity/ui");const u=(e,t,n)=>!e.name.startsWith("locale")||n.includes(t.name);function d(e,t){var n,l;const r=function(e){return"object"===(null==e?void 0:e.jsonType)&&"document"===g(e).name}(e)&&(null==(n=null==e?void 0:e.options)?void 0:n.languageFilter),s=!t.documentTypes;return!!(s&&!1!==r||!s&&r||e&&(null==(l=t.documentTypes)?void 0:l.includes(e.name)))}function g(e){return e.type?g(e.type):e}const f=c.createContext(void 0);function p(e){let{options:t,enabled:n,children:l}=e;const r=c.useMemo((()=>({options:t,enabled:n})),[t,n]);return a.jsx(f.Provider,{value:r,children:l})}const h="@sanity/plugin/language-filter/selected-languages";function b(e){let{supportedLanguages:t,defaultLanguages:n}=e;return t.filter((e=>!(null==n?void 0:n.includes(e.id))))}function x(e){return c.useState((()=>function(e){const t=b(e).map((e=>e.id));let n=t;try{const e=window.localStorage.getItem(h);e&&(n=JSON.parse(e))}catch(e){}var l;return l=t,n=n.filter((e=>l.includes(e))),n}(e)))}function m(t){const n=c.useContext(f),{options:r,enabled:i}=n||{},{subscribeSelectedIds:o}=t,u=s(t,e);return i&&r?a.jsx(j,l(l({},u),{},{options:r,subscribeSelectedIds:o})):t.renderDefault(u)}function j(e){var n;const{members:r,options:a,schemaType:i,renderDefault:o,subscribeSelectedIds:d}=e,g=s(e,t),[f,p]=x(a);c.useEffect((()=>{const e=d(p);return()=>e()}),[d,p]);const h=c.useMemo((()=>{var e;return[...null!=(e=a.defaultLanguages)?e:[],...f]}),[a.defaultLanguages,f]),b=null!=(n=a.filterField)?n:u,m=c.useMemo((()=>r.filter((e=>"field"===e.kind&&b(i,e,h)||"fieldSet"===e.kind)).map((e=>"fieldSet"===e.kind?l(l({},e),{},{fieldSet:l(l({},e.fieldSet),{},{members:e.fieldSet.members.filter((e=>"field"===e.kind&&b(i,e,h)))})}):e))),[i,r,b,h]);return o(l(l({},g),{},{members:m,schemaType:i,renderDefault:o}))}function y(e){const{options:t,onSelectedIdsChange:n}=e,{defaultLanguages:l}=t,[r,s]=x(t),a=c.useMemo((()=>b(t)),[t]),i=c.useCallback((e=>{var t;s(e),t=e,window.localStorage.setItem(h,JSON.stringify(t)),n(e)}),[n,s]),o=c.useCallback((()=>i(a.map((e=>e.id)))),[i,a]),u=c.useCallback((()=>{i([])}),[i]),d=c.useCallback((e=>{let t=r;t=t.includes(e)?t.filter((t=>t!==e)):[...t,e],i(t)}),[i,r]);return{activeLanguages:c.useMemo((()=>[...null!=l?l:[],...r]),[l,r]),allSelected:r.length===a.length,selectAll:o,selectNone:u,toggleLanguage:d}}function S(e){const{options:t,onSelectedIdsChange:n}=e,l=t.supportedLanguages.filter((e=>{var n;return null==(n=t.defaultLanguages)?void 0:n.includes(e.id)})),r=t.supportedLanguages.filter((e=>{var n;return!(null==(n=t.defaultLanguages)?void 0:n.includes(e.id))})),[s,i]=c.useState(!1),{activeLanguages:u,allSelected:d,selectAll:g,selectNone:f,toggleLanguage:p}=y({options:t,onSelectedIdsChange:n}),[h,b]=c.useState(null),[x,m]=c.useState(null),j=c.useCallback((e=>{e.currentTarget.checked?g():f()}),[g,f]),S=c.useCallback((()=>i((e=>!e))),[]),O=c.useCallback((()=>i(!1)),[]);o.useClickOutside(O,[h,x]);const C=a.jsxs(o.Box,{overflow:"auto",padding:1,children:[l.length>0&&a.jsx(o.Card,{radius:2,children:a.jsxs(o.Stack,{padding:2,space:3,children:[a.jsx(o.Box,{paddingBottom:2,children:a.jsxs(o.Text,{size:1,weight:"semibold",children:["Default language",l.length>1&&a.jsx(a.Fragment,{children:"s"})]})}),l.map((e=>a.jsx(o.Text,{children:e.title},e.id)))]})}),a.jsxs(o.Stack,{marginTop:3,padding:2,space:2,children:[a.jsx(o.Box,{paddingBottom:2,children:a.jsx(o.Text,{size:1,weight:"semibold",children:"Show translations"})}),a.jsx(o.Card,{as:"label",children:a.jsxs(o.Flex,{align:"center",gap:2,children:[a.jsx(o.Checkbox,{checked:d,name:"_allSelected",onChange:j}),a.jsx(o.Box,{flex:1,children:a.jsx(o.Text,{muted:!d,weight:"semibold",children:"All translations"})})]})}),r.map((e=>a.jsx(v,{id:e.id,onToggle:p,selected:u.includes(e.id),title:e.title},e.id)))]})]}),k=t.supportedLanguages.length;return a.jsx(o.Popover,{content:C,open:s,portal:!0,ref:m,children:a.jsx(o.Button,{text:a.jsxs(o.Flex,{gap:1,children:[a.jsx(o.Box,{children:"Filter languages:"}),a.jsxs(o.Flex,{gap:1,justify:"space-around",children:[a.jsx(o.Flex,{style:{width:"".concat(Math.floor(Math.log10(k)+1),"ch")},justify:"flex-end",children:u.length}),a.jsx(o.Box,{children:"/"}),a.jsx(o.Box,{children:k})]})]}),mode:"bleed",onClick:S,ref:b,selected:s})})}function v(e){const{id:t,onToggle:n,selected:l,title:r}=e,s=c.useCallback((()=>{n(t)}),[t,n]);return a.jsx(o.Card,{as:"label",children:a.jsxs(o.Flex,{align:"center",gap:2,children:[a.jsx(o.Checkbox,{checked:l,name:"language-".concat(t),onChange:s}),a.jsx(o.Box,{flex:1,children:a.jsx(o.Text,{muted:!l,children:r})})]})})}const O=i.definePlugin((e=>{const{onSelectedIdsChange:t,subscribeSelectedIds:n}=function(){const e=[];return{onSelectedIdsChange:t=>{e.forEach((e=>e(t)))},subscribeSelectedIds:t=>(e.push(t),()=>{e.splice(e.indexOf(t),1)})}}(),r=()=>a.jsx(S,{options:e,onSelectedIdsChange:t});return{name:"@sanity/language-filter",document:{unstable_languageFilter:(t,n)=>{let{schemaType:l,schema:s}=n;return d(s.get(l),e)?[...t,r]:t}},form:{components:{input:function(t){const r=d(t.schemaType,e);return r?a.jsx(p,{enabled:r,options:e,children:t.renderDefault(t)}):"object"===t.schemaType.jsonType?a.jsx(m,l(l({},t),{},{subscribeSelectedIds:n})):t.renderDefault(t)}}}}}));exports.defaultFilterField=u,exports.isLanguageFilterEnabled=d,exports.languageFilter=O;
2
- //# sourceMappingURL=index.js.map
1
+ "use strict";var e;const t=["members","schemaType","renderDefault"];function n(e,t){if(null==e)return{};var n,r,l=function(e,t){if(null==e)return{};var n,r,l={},i=Object.keys(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||(l[n]=e[n]);return l}(e,t);if(Object.getOwnPropertySymbols){var i=Object.getOwnPropertySymbols(e);for(r=0;r<i.length;r++)n=i[r],t.indexOf(n)>=0||Object.prototype.propertyIsEnumerable.call(e,n)&&(l[n]=e[n])}return l}function r(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function l(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[t]?arguments[t]:{};t%2?r(Object(n),!0).forEach((function(t){i(e,t,n[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):r(Object(n)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))}))}return e}function i(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var n=e[Symbol.toPrimitive];if(void 0!==n){var r=n.call(e,t||"default");if("object"!=typeof r)return r;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(exports,"__esModule",{value:!0});var o=require("react/jsx-runtime"),s=require("sanity"),a=require("react"),u=require("@sanity/ui"),c=require("styled-components"),d=require("@sanity/icons");function g(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var p=g(c);const f=(e,t,n)=>!e.name.startsWith("locale")||n.includes(t.name);function m(e,t){var n,r;const l=function(e){return"object"===(null==e?void 0:e.jsonType)&&"document"===x(e).name}(e)&&(null==(n=null==e?void 0:e.options)?void 0:n.languageFilter),i=!t.documentTypes;return!!(i&&!1!==l||!i&&l||e&&(null==(r=t.documentTypes)?void 0:r.includes(e.name)))}function x(e){return e.type?x(e.type):e}const j="@sanity/plugin/language-filter/selected-languages";function b(e){let{supportedLanguages:t,defaultLanguages:n}=e;return t.filter((e=>!(null==n?void 0:n.includes(e.id))))}function h(e){return a.useState((()=>function(e){const t=b(e).map((e=>e.id));let n=t;try{const e=window.localStorage.getItem(j);e&&(n=JSON.parse(e))}catch(e){}var r;return r=t,n=n.filter((e=>r.includes(e))),n}(e)))}const y={options:{supportedLanguages:[],defaultLanguages:[],documentTypes:[],filterField:f},selectedLanguageIds:[],setSelectedLanguageIds:()=>console.error("LanguageFilterStudioContext not initialized")},v=a.createContext(y);function O(){return a.useContext(v)}function S(e){const{members:r,schemaType:i,renderDefault:o}=e,s=n(e,t),{selectedLanguageIds:u,options:c}=O(),{filterField:d}=c,g=a.useMemo((()=>r.filter((e=>"field"===e.kind&&d(i,e,u)||"fieldSet"===e.kind)).map((e=>"fieldSet"===e.kind?l(l({},e),{},{fieldSet:l(l({},e.fieldSet),{},{members:e.fieldSet.members.filter((e=>"field"===e.kind&&d(i,e,u)))})}):e))),[i,r,d,u]);return o(l(l({},s),{},{members:g,schemaType:i,renderDefault:o}))}function L(){const{selectedLanguageIds:e,setSelectedLanguageIds:t,options:n}=O(),{defaultLanguages:r}=n,l=a.useMemo((()=>b(n)),[n]),i=a.useCallback((e=>{var n;t(e),n=e,window.localStorage.setItem(j,JSON.stringify(n))}),[t]),o=a.useCallback((()=>i(l.map((e=>e.id)))),[i,l]),s=a.useCallback((()=>{i([])}),[i]),u=a.useCallback((t=>{let n=e;n=n.includes(t)?n.filter((e=>e!==t)):[...n,t],i(n)}),[i,e]);return{activeLanguages:a.useMemo((()=>[...null!=r?r:[],...e]),[r,e]),allSelected:e.length===l.length,selectAll:o,selectNone:s,toggleLanguage:u}}const T=p.default(u.Box)(e||(C=["\n max-height: calc(100vh - 200px);\n"],k||(k=C.slice(0)),e=Object.freeze(Object.defineProperties(C,{raw:{value:Object.freeze(k)}}))));var C,k;function w(e){const{options:t}=e,n=t.supportedLanguages.filter((e=>{var n;return null==(n=t.defaultLanguages)?void 0:n.includes(e.id)})),r=t.supportedLanguages.filter((e=>{var n;return!(null==(n=t.defaultLanguages)?void 0:n.includes(e.id))})),[l,i]=a.useState(!1),{activeLanguages:c,allSelected:g,selectAll:p,selectNone:f,toggleLanguage:m}=L(),[x,j]=a.useState(null),[b,h]=a.useState(null),y=a.useCallback((e=>{"ALL"===e.currentTarget.value?p():f()}),[p,f]),v=a.useCallback((()=>i((e=>!e))),[]),O=a.useCallback((()=>i(!1)),[]);u.useClickOutside(O,[x,b]);const S=t.supportedLanguages.length,[C,k]=a.useState(""),w=a.useCallback((e=>{e.currentTarget.value?k(e.currentTarget.value):k("")}),[]),I=o.jsxs(T,{overflow:"auto",padding:1,children:[n.length>0&&o.jsx(u.Card,{radius:2,children:o.jsxs(u.Stack,{padding:2,space:3,children:[o.jsx(u.Box,{paddingBottom:2,children:o.jsxs(u.Text,{size:1,weight:"semibold",children:["Default language",n.length>1&&o.jsx(o.Fragment,{children:"s"})]})}),n.map((e=>o.jsx(u.Text,{children:e.title},e.id)))]})}),o.jsxs(u.Stack,{padding:1,space:1,children:[o.jsx(u.Button,{mode:"bleed",onClick:y,justify:"flex-start",value:g?"NONE":"ALL",disabled:!!C,children:o.jsxs(u.Flex,{gap:3,align:"center",children:[o.jsx(u.Text,{size:2,children:g?o.jsx(s.TextWithTone,{tone:"primary",children:o.jsx(d.EyeClosedIcon,{})}):o.jsx(d.EyeOpenIcon,{})}),o.jsx(u.Box,{flex:1,children:o.jsx(u.Text,{children:g?"Hide All":"Show All"})})]})}),o.jsx(u.Card,{borderTop:!0}),S>4?o.jsx(u.TextInput,{onChange:w,value:C,placeholder:"Filter languages"}):null,r.filter((e=>!C||e.title.toLowerCase().includes(C.toLowerCase()))).map((e=>o.jsx(P,{id:e.id,onToggle:m,selected:c.includes(e.id),title:e.title},e.id)))]})]}),F=c.length===S?"Showing all":"Showing ".concat(c.length," / ").concat(S);return o.jsx(u.Popover,{content:I,open:l,portal:!0,ref:h,children:o.jsx(u.Button,{text:F,icon:d.TranslateIcon,mode:"bleed",onClick:v,ref:j,selected:l})})}function P(e){const{id:t,onToggle:n,selected:r,title:l}=e,i=a.useCallback((()=>{n(t)}),[t,n]);return o.jsx(u.Button,{mode:"bleed",onClick:i,justify:"flex-start",children:o.jsxs(u.Flex,{gap:3,align:"center",children:[o.jsx(u.Text,{size:2,children:r?o.jsx(s.TextWithTone,{tone:"positive",children:o.jsx(d.CheckmarkCircleIcon,{})}):o.jsx(d.CircleIcon,{})}),o.jsx(u.Box,{flex:1,children:o.jsx(u.Text,{children:l})}),o.jsx(u.Badge,{children:t})]})})}const I=s.definePlugin((e=>{const t=()=>o.jsx(w,{options:e}),n=l(l({},y.options),e);return{name:"@sanity/language-filter",studio:{components:{layout:e=>function(e){const t=a.useMemo((()=>l(l({},y.options),e.options)),[e.options]),[n,r]=h(t);return o.jsx(v.Provider,{value:{options:t,selectedLanguageIds:n,setSelectedLanguageIds:r},children:e.renderDefault(e)})}(l(l({},e),{},{options:n}))}},document:{unstable_languageFilter:(n,r)=>{let{schemaType:l,schema:i}=r;return m(i.get(l),e)?[...n,t]:n}},form:{components:{input:e=>"root"!==e.id&&s.isObjectSchemaType(e.schemaType)?function(e){const{options:t}=O(),n=s.useFormValue(["_type"]),{documentTypes:r}=t;return"string"==typeof n&&r.includes(n)?o.jsx(S,l({},e)):e.renderDefault(e)}(e):e.renderDefault(e)}}}}));exports.defaultFilterField=f,exports.isLanguageFilterEnabled=m,exports.languageFilter=I,exports.useLanguageFilterStudioContext=O;//# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/filterField.ts","../src/LanguageFilterContext.tsx","../src/useSelectedLanguageIds.ts","../src/LanguageFilterObjectInput.tsx","../src/usePaneLanguages.ts","../src/LanguageFilterMenuButton.tsx","../src/plugin.tsx","../src/languageSubscription.ts"],"sourcesContent":["import type {SchemaType} from 'sanity'\nimport {FilterFieldFunction, LanguageFilterConfig, LanguageFilterSchema} from './types'\n\nexport const defaultFilterField: FilterFieldFunction = (\n enclosingType,\n field,\n selectedLanguageIds\n) => !enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(field.name)\n\nexport function isLanguageFilterEnabled(\n schemaType: SchemaType | undefined,\n options: LanguageFilterConfig\n): boolean {\n const schemaFilter =\n isDocument(schemaType) && (schemaType as LanguageFilterSchema)?.options?.languageFilter\n const defaultEnabled = !options.documentTypes\n\n return !!(\n (defaultEnabled && schemaFilter !== false) ||\n (!defaultEnabled && schemaFilter) ||\n (schemaType && options.documentTypes?.includes(schemaType.name))\n )\n}\n\nfunction isDocument(schemaType?: SchemaType) {\n return schemaType?.jsonType === 'object' && getRootType(schemaType).name === 'document'\n}\n\nfunction getRootType(schema: SchemaType): SchemaType {\n if (schema.type) {\n return getRootType(schema.type)\n }\n return schema\n}\n","import React, {createContext, PropsWithChildren, useContext, useMemo} from 'react'\nimport {LanguageFilterConfig} from './types'\n\nexport interface LanguageFilterContextValue {\n // eslint-disable-next-line react/require-default-props\n options: LanguageFilterConfig\n // eslint-disable-next-line react/require-default-props\n enabled: boolean\n}\n\nconst LanguageFilterContext = createContext<LanguageFilterContextValue | undefined>(undefined)\n\nexport function LanguageFilterProvider({\n options,\n enabled,\n children,\n}: PropsWithChildren<\n Omit<LanguageFilterContextValue, 'selectedLanguageIds' | 'setSelectedLanguageIds'>\n>) {\n const value = useMemo(() => ({options, enabled}), [options, enabled])\n return <LanguageFilterContext.Provider value={value}>{children}</LanguageFilterContext.Provider>\n}\n\nexport function useLanguageFilterContext() {\n return useContext(LanguageFilterContext)\n}\n","import {Language, LanguageFilterConfig} from './types'\nimport {useState} from 'react'\nconst storageKey = '@sanity/plugin/language-filter/selected-languages'\n\nexport function getPersistedLanguageIds(options: LanguageFilterConfig): string[] {\n const selectableLangs = getSelectableLanguages(options).map((l) => l.id)\n\n let selected: string[] = selectableLangs\n try {\n const persistedValue = window.localStorage.getItem(storageKey)\n if (persistedValue) {\n selected = JSON.parse(persistedValue)\n }\n } catch (err) {} // eslint-disable-line no-empty\n\n // constrain persisted/selected languages to the ones currently supported\n selected = intersection(selected, selectableLangs)\n return selected\n}\n\nexport function persistLanguageIds(languageIds: string[]): void {\n window.localStorage.setItem(storageKey, JSON.stringify(languageIds))\n}\n\nfunction intersection(array1: string[], array2: string[]) {\n return array1.filter((value) => array2.includes(value))\n}\n\nexport function getSelectableLanguages({\n supportedLanguages,\n defaultLanguages,\n}: LanguageFilterConfig): Language[] {\n return supportedLanguages.filter((lang) => !defaultLanguages?.includes(lang.id))\n}\n\nexport function useSelectedLanguageIds(\n options: LanguageFilterConfig\n): [string[], (ids: string[]) => void] {\n return useState(() => getPersistedLanguageIds(options))\n}\n","import React, {useEffect, useMemo} from 'react'\nimport {ObjectInputProps, ObjectMember} from 'sanity'\nimport {LanguageFilterConfig} from './types'\nimport {defaultFilterField} from './filterField'\nimport {useLanguageFilterContext} from './LanguageFilterContext'\nimport {useSelectedLanguageIds} from './useSelectedLanguageIds'\n\nexport type LanguageFilterObjectInputProps = {\n options: LanguageFilterConfig\n /**\n * We need a way to communicate state changes between the pane menu and input components.\n * LanguageFilter button lives outside the input-render tree, so Context is out.\n * This is a workaround for that.\n */\n subscribeSelectedIds: (callback: (ids: string[]) => void) => () => void\n} & ObjectInputProps\n\nexport function LanguageFilterObjectInput(\n props: ObjectInputProps & {\n subscribeSelectedIds: (callback: (ids: string[]) => void) => () => void\n }\n) {\n const context = useLanguageFilterContext()\n const {options, enabled} = context || {}\n const {subscribeSelectedIds, ...restProps} = props\n if (!enabled || !options) {\n return props.renderDefault(restProps)\n }\n return (\n <FilteredObjectInput\n {...restProps}\n options={options}\n subscribeSelectedIds={subscribeSelectedIds}\n />\n )\n}\n\nfunction FilteredObjectInput(props: LanguageFilterObjectInputProps) {\n const {\n members: membersProp,\n options,\n schemaType,\n renderDefault,\n subscribeSelectedIds,\n ...restProps\n } = props\n const [selectedIds, setSelectedIds] = useSelectedLanguageIds(options)\n\n useEffect(() => {\n const unsubscribe = subscribeSelectedIds(setSelectedIds)\n return () => unsubscribe()\n }, [subscribeSelectedIds, setSelectedIds])\n\n const activeLanguages = useMemo(\n () => [...(options.defaultLanguages ?? []), ...selectedIds],\n [options.defaultLanguages, selectedIds]\n )\n\n const filterField = options.filterField ?? defaultFilterField\n\n const members: ObjectMember[] = useMemo(() => {\n return membersProp\n .filter((member) => {\n return (\n (member.kind === 'field' && filterField(schemaType, member, activeLanguages)) ||\n member.kind === 'fieldSet'\n )\n })\n .map((member) => {\n if (member.kind === 'fieldSet') {\n return {\n ...member,\n fieldSet: {\n ...member.fieldSet,\n members: member.fieldSet.members.filter((fieldsetMember) => {\n return (\n fieldsetMember.kind === 'field' &&\n filterField(schemaType, fieldsetMember, activeLanguages)\n )\n }),\n },\n }\n }\n return member\n })\n }, [schemaType, membersProp, filterField, activeLanguages])\n\n return renderDefault({...restProps, members, schemaType, renderDefault})\n}\n","import {useCallback, useMemo} from 'react'\nimport {LanguageFilterConfig} from './types'\nimport {\n getSelectableLanguages,\n persistLanguageIds,\n useSelectedLanguageIds,\n} from './useSelectedLanguageIds'\n\nexport interface UsePaneLanguagesParams {\n options: LanguageFilterConfig\n /**\n * We need a way to communicate state changes between the pane menu and input components.\n * LanguageFilter button lives outside the input-render tree, so Context is out.\n * This is a workaround for that.\n */\n onSelectedIdsChange: (ids: string[]) => void\n}\n\nexport function usePaneLanguages(props: UsePaneLanguagesParams): {\n activeLanguages: string[]\n allSelected: boolean\n selectAll: () => void\n selectNone: () => void\n toggleLanguage: (languageId: string) => void\n} {\n const {options, onSelectedIdsChange} = props\n const {defaultLanguages} = options\n\n const [selectedIds, setSelectedIds] = useSelectedLanguageIds(options)\n\n const selectableLanguages = useMemo(() => getSelectableLanguages(options), [options])\n\n const updateSelectedIds = useCallback(\n (ids: string[]) => {\n setSelectedIds(ids)\n persistLanguageIds(ids)\n onSelectedIdsChange(ids)\n },\n [onSelectedIdsChange, setSelectedIds]\n )\n\n const selectAll = useCallback(\n () => updateSelectedIds(selectableLanguages.map((l) => l.id)),\n [updateSelectedIds, selectableLanguages]\n )\n\n const selectNone = useCallback(() => {\n updateSelectedIds([])\n }, [updateSelectedIds])\n\n const toggleLanguage = useCallback(\n (languageId: string) => {\n let lang = selectedIds\n\n if (lang.includes(languageId)) {\n lang = lang.filter((l) => l !== languageId)\n } else {\n lang = [...lang, languageId]\n }\n\n updateSelectedIds(lang)\n },\n [updateSelectedIds, selectedIds]\n )\n\n const activeLanguages = useMemo(\n () => [...(defaultLanguages ?? []), ...selectedIds],\n [defaultLanguages, selectedIds]\n )\n\n return {\n activeLanguages,\n allSelected: selectedIds.length === selectableLanguages.length,\n selectAll,\n selectNone,\n toggleLanguage,\n }\n}\n","import {Box, Button, Card, Checkbox, Flex, Popover, Stack, Text, useClickOutside} from '@sanity/ui'\nimport React, {FormEvent, useCallback, useState} from 'react'\nimport {usePaneLanguages} from './usePaneLanguages'\nimport {LanguageFilterConfig} from './types'\n\nexport interface LanguageFilterMenuButtonProps {\n options: LanguageFilterConfig\n onSelectedIdsChange: (ids: string[]) => void\n}\n\nexport function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {\n const {options, onSelectedIdsChange} = props\n\n const defaultLanguages = options.supportedLanguages.filter((l) =>\n options.defaultLanguages?.includes(l.id)\n )\n\n const languageOptions = options.supportedLanguages.filter(\n (l) => !options.defaultLanguages?.includes(l.id)\n )\n const [open, setOpen] = useState(false)\n const {activeLanguages, allSelected, selectAll, selectNone, toggleLanguage} = usePaneLanguages({\n options,\n onSelectedIdsChange,\n })\n const [button, setButton] = useState<HTMLElement | null>(null)\n const [popover, setPopover] = useState<HTMLElement | null>(null)\n\n const handleToggleAll = useCallback(\n (event: FormEvent<HTMLInputElement>) => {\n const checked = event.currentTarget.checked\n\n if (checked) {\n selectAll()\n } else {\n selectNone()\n }\n },\n [selectAll, selectNone]\n )\n\n const handleClick = useCallback(() => setOpen((o) => !o), [])\n\n const handleClickOutside = useCallback(() => setOpen(false), [])\n\n useClickOutside(handleClickOutside, [button, popover])\n\n const content = (\n <Box overflow=\"auto\" padding={1}>\n {defaultLanguages.length > 0 && (\n <Card radius={2}>\n <Stack padding={2} space={3}>\n <Box paddingBottom={2}>\n <Text size={1} weight=\"semibold\">\n Default language{defaultLanguages.length > 1 && <>s</>}\n </Text>\n </Box>\n\n {defaultLanguages.map((l) => (\n <Text key={l.id}>{l.title}</Text>\n ))}\n </Stack>\n </Card>\n )}\n\n <Stack marginTop={3} padding={2} space={2}>\n <Box paddingBottom={2}>\n <Text size={1} weight=\"semibold\">\n Show translations\n </Text>\n </Box>\n\n <Card as=\"label\">\n <Flex align=\"center\" gap={2}>\n <Checkbox checked={allSelected} name=\"_allSelected\" onChange={handleToggleAll} />\n <Box flex={1}>\n <Text muted={!allSelected} weight=\"semibold\">\n All translations\n </Text>\n </Box>\n </Flex>\n </Card>\n\n {languageOptions.map((lang) => (\n <LanguageFilterOption\n id={lang.id}\n key={lang.id}\n onToggle={toggleLanguage}\n selected={activeLanguages.includes(lang.id)}\n title={lang.title}\n />\n ))}\n </Stack>\n </Box>\n )\n\n const langCount = options.supportedLanguages.length\n return (\n <Popover content={content} open={open} portal ref={setPopover}>\n <Button\n text={\n <Flex gap={1}>\n <Box>Filter languages:</Box>\n <Flex gap={1} justify=\"space-around\">\n <Flex\n style={{width: `${Math.floor(Math.log10(langCount) + 1)}ch`}}\n justify=\"flex-end\"\n >\n {activeLanguages.length}\n </Flex>\n <Box>/</Box>\n <Box>{langCount}</Box>\n </Flex>\n </Flex>\n }\n mode=\"bleed\"\n onClick={handleClick}\n ref={setButton}\n selected={open}\n />\n </Popover>\n )\n}\n\nfunction LanguageFilterOption(props: {\n id: string\n onToggle: (id: string) => void\n selected: boolean\n title: string\n}) {\n const {id, onToggle, selected, title} = props\n\n const handleChange = useCallback(() => {\n onToggle(id)\n }, [id, onToggle])\n\n return (\n <Card as=\"label\">\n <Flex align=\"center\" gap={2}>\n <Checkbox checked={selected} name={`language-${id}`} onChange={handleChange} />\n <Box flex={1}>\n <Text muted={!selected}>{title}</Text>\n </Box>\n </Flex>\n </Card>\n )\n}\n","import React from 'react'\nimport {definePlugin, DocumentLanguageFilterComponent, ObjectInputProps} from 'sanity'\nimport {LanguageFilterObjectInput} from './LanguageFilterObjectInput'\nimport {LanguageFilterMenuButton} from './LanguageFilterMenuButton'\nimport {LanguageFilterConfig} from './types'\nimport {isLanguageFilterEnabled} from './filterField'\nimport {LanguageFilterProvider} from './LanguageFilterContext'\nimport {createSelectedLanguageIdsBus} from './languageSubscription'\n\n/**\n * ## Usage in sanity.config.ts (or .js)\n *\n * ```\n * import {defineConfig} from 'sanity'\n * import {languageFilter} from '@sanity/language-filter'\n *\n * export const defineConfig({\n * /...\n * plugins: [\n * languageFilter({\n * supportedLanguages: [\n * {id: 'nb', title: 'Norwegian (Bokmål)'},\n * {id: 'nn', title: 'Norwegian (Nynorsk)'},\n * {id: 'en', title: 'English'},\n * {id: 'es', title: 'Spanish'},\n * {id: 'arb', title: 'Arabic'},\n * {id: 'pt', title: 'Portuguese'},\n * //...\n * ],\n * // Select Norwegian (Bokmål) by default\n * defaultLanguages: ['nb'],\n * // Only show language filter for document type `page` (schemaType.name)\n * // Can also enable via document-options: options.languageFilter: true\n * documentTypes: ['page'],\n * // default filter function shown\n * filterField: (enclosingType, field, selectedLanguageIds) =>\n * !enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(field.name),\n * })\n * ]\n * })\n * ```\n */\nexport const languageFilter = definePlugin<LanguageFilterConfig>((options) => {\n const {onSelectedIdsChange, subscribeSelectedIds} = createSelectedLanguageIdsBus()\n\n const RenderLanguageFilter: DocumentLanguageFilterComponent = () => {\n return <LanguageFilterMenuButton options={options} onSelectedIdsChange={onSelectedIdsChange} />\n }\n\n return {\n name: '@sanity/language-filter',\n document: {\n unstable_languageFilter: (prev, {schemaType, schema}) => {\n if (isLanguageFilterEnabled(schema.get(schemaType), options)) {\n return [...prev, RenderLanguageFilter]\n }\n return prev\n },\n },\n\n form: {\n components: {\n // eslint-disable-next-line func-name-matching\n input: function LanguageFilterWrapper(props) {\n const enabled = isLanguageFilterEnabled(props.schemaType, options)\n // will only be considered enabled for document, so this is only done once\n if (enabled) {\n return (\n <LanguageFilterProvider enabled={enabled} options={options}>\n {props.renderDefault(props)}\n </LanguageFilterProvider>\n )\n }\n if (props.schemaType.jsonType === 'object') {\n return (\n <LanguageFilterObjectInput\n {...(props as ObjectInputProps)}\n subscribeSelectedIds={subscribeSelectedIds}\n />\n )\n }\n\n return props.renderDefault(props)\n },\n },\n },\n }\n})\n","export type LanguageSubscription = (ids: string[]) => void\nexport type Unsubscribe = () => void\nexport type LanguageSubscribe = (subscription: LanguageSubscription) => Unsubscribe\n\nexport interface SelectedLanguageIdsBus {\n onSelectedIdsChange: (ids: string[]) => void\n subscribeSelectedIds: LanguageSubscribe\n}\n\n/**\n * We need a way to communicate state changes between the pane menu and input components.\n * LanguageFilter button lives outside the input-render tree, so Context is out.\n * This is a workaround for that.\n */\nexport function createSelectedLanguageIdsBus(): SelectedLanguageIdsBus {\n const subs: LanguageSubscription[] = []\n\n const onSelectedIdsChange = (ids: string[]) => {\n subs.forEach((s) => s(ids))\n }\n const subscribeSelectedIds = (subscription: LanguageSubscription) => {\n subs.push(subscription)\n return () => {\n subs.splice(subs.indexOf(subscription), 1)\n }\n }\n\n return {\n onSelectedIdsChange,\n subscribeSelectedIds,\n }\n}\n"],"names":["defaultFilterField","enclosingType","field","selectedLanguageIds","name","startsWith","includes","isLanguageFilterEnabled","schemaType","options","_a","_b","schemaFilter","jsonType","getRootType","isDocument","languageFilter","defaultEnabled","documentTypes","schema","type","LanguageFilterContext","createContext","LanguageFilterProvider","_ref","enabled","children","value","useMemo","jsx","Provider","storageKey","getSelectableLanguages","_ref2","supportedLanguages","defaultLanguages","filter","lang","id","useSelectedLanguageIds","useState","selectableLangs","map","l","selected","persistedValue","window","localStorage","getItem","JSON","parse","err","array2","getPersistedLanguageIds","LanguageFilterObjectInput","props","context","useContext","subscribeSelectedIds","restProps","_excluded","FilteredObjectInput","renderDefault","members","membersProp","_excluded2","selectedIds","setSelectedIds","useEffect","unsubscribe","activeLanguages","filterField","member","kind","_objectSpread","fieldSet","fieldsetMember","usePaneLanguages","onSelectedIdsChange","selectableLanguages","updateSelectedIds","useCallback","ids","languageIds","setItem","stringify","selectAll","selectNone","toggleLanguage","languageId","allSelected","length","LanguageFilterMenuButton","languageOptions","open","setOpen","button","setButton","popover","setPopover","handleToggleAll","event","currentTarget","checked","handleClick","o","handleClickOutside","useClickOutside","content","jsxs","Box","overflow","padding","Card","radius","Stack","space","paddingBottom","Text","size","weight","Fragment","title","marginTop","as","Flex","align","gap","Checkbox","onChange","flex","muted","LanguageFilterOption","onToggle","langCount","Popover","portal","ref","Button","text","justify","style","width","Math","floor","log10","mode","onClick","handleChange","definePlugin","subs","forEach","s","subscription","push","splice","indexOf","createSelectedLanguageIdsBus","RenderLanguageFilter","document","unstable_languageFilter","prev","_ref3","get","form","components","input"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":""}
@@ -1,3 +1,5 @@
1
+ /// <reference types="react" />
2
+
1
3
  import {FieldMember} from 'sanity'
2
4
  import {FieldsetState} from 'sanity'
3
5
  import {ObjectSchemaType} from 'sanity'
@@ -72,4 +74,19 @@ export declare interface LanguageFilterSchema extends ObjectSchemaType {
72
74
  options?: LanguageFilterOptions
73
75
  }
74
76
 
77
+ declare interface LanguageFilterStudioContextProps {
78
+ options: Required<LanguageFilterConfig>
79
+ }
80
+
81
+ declare interface LanguageFilterStudioContextValue extends LanguageFilterStudioContextProps {
82
+ selectedLanguageIds: string[]
83
+ setSelectedLanguageIds: (ids: string[]) => void
84
+ }
85
+
86
+ /**
87
+ * Retrieves plugin options and the currently selected
88
+ * language IDs from anywhere in the Studio
89
+ */
90
+ export declare function useLanguageFilterStudioContext(): LanguageFilterStudioContextValue
91
+
75
92
  export {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sanity/language-filter",
3
- "version": "3.0.0",
3
+ "version": "3.1.0",
4
4
  "description": "A Sanity plugin that supports filtering localized fields by language",
5
5
  "homepage": "https://github.com/sanity-io/language-filter#readme",
6
6
  "bugs": {
@@ -58,6 +58,7 @@
58
58
  "@sanity/plugin-kit": "^2.1.5",
59
59
  "@sanity/semantic-release-preset": "^2.0.2",
60
60
  "@types/jest": "^29.2.1",
61
+ "@types/styled-components": "^5.1.26",
61
62
  "@typescript-eslint/eslint-plugin": "^5.42.0",
62
63
  "@typescript-eslint/parser": "^5.42.0",
63
64
  "eslint": "^8.26.0",
@@ -74,12 +75,14 @@
74
75
  "react": "^18",
75
76
  "rimraf": "^3.0.2",
76
77
  "sanity": "^3.0.0",
78
+ "styled-components": "^5.3.8",
77
79
  "ts-jest": "^29.0.3",
78
80
  "typescript": "^4.8.4"
79
81
  },
80
82
  "peerDependencies": {
81
83
  "react": "^18",
82
- "sanity": "^3.0.0"
84
+ "sanity": "^3",
85
+ "styled-components": "^5.2"
83
86
  },
84
87
  "engines": {
85
88
  "node": ">=14"
@@ -1,15 +1,38 @@
1
- import {Box, Button, Card, Checkbox, Flex, Popover, Stack, Text, useClickOutside} from '@sanity/ui'
2
- import React, {FormEvent, useCallback, useState} from 'react'
3
- import {usePaneLanguages} from './usePaneLanguages'
1
+ import {
2
+ TextInput,
3
+ Badge,
4
+ Box,
5
+ Button,
6
+ Card,
7
+ Flex,
8
+ Popover,
9
+ Stack,
10
+ Text,
11
+ useClickOutside,
12
+ } from '@sanity/ui'
13
+ import React, {FormEvent, MouseEventHandler, useCallback, useState} from 'react'
14
+ import styled from 'styled-components'
4
15
  import {LanguageFilterConfig} from './types'
16
+ import {usePaneLanguages} from './usePaneLanguages'
17
+ import {
18
+ CheckmarkCircleIcon,
19
+ CircleIcon,
20
+ EyeClosedIcon,
21
+ EyeOpenIcon,
22
+ TranslateIcon,
23
+ } from '@sanity/icons'
24
+ import {TextWithTone} from 'sanity'
25
+
26
+ const StyledBox = styled(Box)`
27
+ max-height: calc(100vh - 200px);
28
+ `
5
29
 
6
30
  export interface LanguageFilterMenuButtonProps {
7
31
  options: LanguageFilterConfig
8
- onSelectedIdsChange: (ids: string[]) => void
9
32
  }
10
33
 
11
34
  export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
12
- const {options, onSelectedIdsChange} = props
35
+ const {options} = props
13
36
 
14
37
  const defaultLanguages = options.supportedLanguages.filter((l) =>
15
38
  options.defaultLanguages?.includes(l.id)
@@ -19,16 +42,13 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
19
42
  (l) => !options.defaultLanguages?.includes(l.id)
20
43
  )
21
44
  const [open, setOpen] = useState(false)
22
- const {activeLanguages, allSelected, selectAll, selectNone, toggleLanguage} = usePaneLanguages({
23
- options,
24
- onSelectedIdsChange,
25
- })
45
+ const {activeLanguages, allSelected, selectAll, selectNone, toggleLanguage} = usePaneLanguages()
26
46
  const [button, setButton] = useState<HTMLElement | null>(null)
27
47
  const [popover, setPopover] = useState<HTMLElement | null>(null)
28
48
 
29
- const handleToggleAll = useCallback(
30
- (event: FormEvent<HTMLInputElement>) => {
31
- const checked = event.currentTarget.checked
49
+ const handleToggleAll: MouseEventHandler<HTMLButtonElement> = useCallback(
50
+ (event) => {
51
+ const checked = event.currentTarget.value === 'ALL'
32
52
 
33
53
  if (checked) {
34
54
  selectAll()
@@ -45,8 +65,20 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
45
65
 
46
66
  useClickOutside(handleClickOutside, [button, popover])
47
67
 
68
+ const langCount = options.supportedLanguages.length
69
+
70
+ // Search filter query
71
+ const [query, setQuery] = useState(``)
72
+ const handleQuery = useCallback((event: FormEvent<HTMLInputElement>) => {
73
+ if (event.currentTarget.value) {
74
+ setQuery(event.currentTarget.value)
75
+ } else {
76
+ setQuery(``)
77
+ }
78
+ }, [])
79
+
48
80
  const content = (
49
- <Box overflow="auto" padding={1}>
81
+ <StyledBox overflow="auto" padding={1}>
50
82
  {defaultLanguages.length > 0 && (
51
83
  <Card radius={2}>
52
84
  <Stack padding={2} space={3}>
@@ -63,56 +95,65 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
63
95
  </Card>
64
96
  )}
65
97
 
66
- <Stack marginTop={3} padding={2} space={2}>
67
- <Box paddingBottom={2}>
68
- <Text size={1} weight="semibold">
69
- Show translations
70
- </Text>
71
- </Box>
72
-
73
- <Card as="label">
74
- <Flex align="center" gap={2}>
75
- <Checkbox checked={allSelected} name="_allSelected" onChange={handleToggleAll} />
98
+ <Stack padding={1} space={1}>
99
+ <Button
100
+ mode="bleed"
101
+ onClick={handleToggleAll}
102
+ justify="flex-start"
103
+ value={allSelected ? 'NONE' : 'ALL'}
104
+ disabled={!!query}
105
+ >
106
+ <Flex gap={3} align="center">
107
+ <Text size={2}>
108
+ {allSelected ? (
109
+ <TextWithTone tone="primary">
110
+ <EyeClosedIcon />
111
+ </TextWithTone>
112
+ ) : (
113
+ <EyeOpenIcon />
114
+ )}
115
+ </Text>
76
116
  <Box flex={1}>
77
- <Text muted={!allSelected} weight="semibold">
78
- All translations
79
- </Text>
117
+ <Text>{allSelected ? `Hide All` : `Show All`}</Text>
80
118
  </Box>
81
119
  </Flex>
82
- </Card>
83
-
84
- {languageOptions.map((lang) => (
85
- <LanguageFilterOption
86
- id={lang.id}
87
- key={lang.id}
88
- onToggle={toggleLanguage}
89
- selected={activeLanguages.includes(lang.id)}
90
- title={lang.title}
91
- />
92
- ))}
120
+ </Button>
121
+
122
+ <Card borderTop />
123
+
124
+ {langCount > 4 ? (
125
+ <TextInput onChange={handleQuery} value={query} placeholder="Filter languages" />
126
+ ) : null}
127
+
128
+ {languageOptions
129
+ .filter((language) => {
130
+ if (query) {
131
+ return language.title.toLowerCase().includes(query.toLowerCase())
132
+ }
133
+ return true
134
+ })
135
+ .map((lang) => (
136
+ <LanguageFilterOption
137
+ id={lang.id}
138
+ key={lang.id}
139
+ onToggle={toggleLanguage}
140
+ selected={activeLanguages.includes(lang.id)}
141
+ title={lang.title}
142
+ />
143
+ ))}
93
144
  </Stack>
94
- </Box>
145
+ </StyledBox>
95
146
  )
96
147
 
97
- const langCount = options.supportedLanguages.length
148
+ const buttonText =
149
+ activeLanguages.length === langCount
150
+ ? `Showing all`
151
+ : `Showing ${activeLanguages.length} / ${langCount}`
98
152
  return (
99
153
  <Popover content={content} open={open} portal ref={setPopover}>
100
154
  <Button
101
- text={
102
- <Flex gap={1}>
103
- <Box>Filter languages:</Box>
104
- <Flex gap={1} justify="space-around">
105
- <Flex
106
- style={{width: `${Math.floor(Math.log10(langCount) + 1)}ch`}}
107
- justify="flex-end"
108
- >
109
- {activeLanguages.length}
110
- </Flex>
111
- <Box>/</Box>
112
- <Box>{langCount}</Box>
113
- </Flex>
114
- </Flex>
115
- }
155
+ text={buttonText}
156
+ icon={TranslateIcon}
116
157
  mode="bleed"
117
158
  onClick={handleClick}
118
159
  ref={setButton}
@@ -135,13 +176,22 @@ function LanguageFilterOption(props: {
135
176
  }, [id, onToggle])
136
177
 
137
178
  return (
138
- <Card as="label">
139
- <Flex align="center" gap={2}>
140
- <Checkbox checked={selected} name={`language-${id}`} onChange={handleChange} />
179
+ <Button mode="bleed" onClick={handleChange} justify="flex-start">
180
+ <Flex gap={3} align="center">
181
+ <Text size={2}>
182
+ {selected ? (
183
+ <TextWithTone tone="positive">
184
+ <CheckmarkCircleIcon />
185
+ </TextWithTone>
186
+ ) : (
187
+ <CircleIcon />
188
+ )}
189
+ </Text>
141
190
  <Box flex={1}>
142
- <Text muted={!selected}>{title}</Text>
191
+ <Text>{title}</Text>
143
192
  </Box>
193
+ <Badge>{id}</Badge>
144
194
  </Flex>
145
- </Card>
195
+ </Button>
146
196
  )
147
197
  }
@@ -1,68 +1,30 @@
1
- import React, {useEffect, useMemo} from 'react'
2
- import {ObjectInputProps, ObjectMember} from 'sanity'
3
- import {LanguageFilterConfig} from './types'
4
- import {defaultFilterField} from './filterField'
5
- import {useLanguageFilterContext} from './LanguageFilterContext'
6
- import {useSelectedLanguageIds} from './useSelectedLanguageIds'
1
+ import React, {useMemo} from 'react'
2
+ import {ObjectInputProps, ObjectMember, useFormValue} from 'sanity'
3
+ import {useLanguageFilterStudioContext} from './LanguageFilterStudioContext'
7
4
 
8
- export type LanguageFilterObjectInputProps = {
9
- options: LanguageFilterConfig
10
- /**
11
- * We need a way to communicate state changes between the pane menu and input components.
12
- * LanguageFilter button lives outside the input-render tree, so Context is out.
13
- * This is a workaround for that.
14
- */
15
- subscribeSelectedIds: (callback: (ids: string[]) => void) => () => void
16
- } & ObjectInputProps
5
+ // First check that this Object is in a schema type for which language-filter is enabled
6
+ export function FilteredObjectWrapper(props: ObjectInputProps) {
7
+ const {options} = useLanguageFilterStudioContext()
17
8
 
18
- export function LanguageFilterObjectInput(
19
- props: ObjectInputProps & {
20
- subscribeSelectedIds: (callback: (ids: string[]) => void) => () => void
21
- }
22
- ) {
23
- const context = useLanguageFilterContext()
24
- const {options, enabled} = context || {}
25
- const {subscribeSelectedIds, ...restProps} = props
26
- if (!enabled || !options) {
27
- return props.renderDefault(restProps)
28
- }
29
- return (
30
- <FilteredObjectInput
31
- {...restProps}
32
- options={options}
33
- subscribeSelectedIds={subscribeSelectedIds}
34
- />
35
- )
36
- }
37
-
38
- function FilteredObjectInput(props: LanguageFilterObjectInputProps) {
39
- const {
40
- members: membersProp,
41
- options,
42
- schemaType,
43
- renderDefault,
44
- subscribeSelectedIds,
45
- ...restProps
46
- } = props
47
- const [selectedIds, setSelectedIds] = useSelectedLanguageIds(options)
9
+ const documentType = useFormValue(['_type'])
10
+ const {documentTypes} = options
11
+ const languageFilterEnabled =
12
+ typeof documentType === 'string' && documentTypes.includes(documentType)
48
13
 
49
- useEffect(() => {
50
- const unsubscribe = subscribeSelectedIds(setSelectedIds)
51
- return () => unsubscribe()
52
- }, [subscribeSelectedIds, setSelectedIds])
53
-
54
- const activeLanguages = useMemo(
55
- () => [...(options.defaultLanguages ?? []), ...selectedIds],
56
- [options.defaultLanguages, selectedIds]
57
- )
14
+ return languageFilterEnabled ? <FilteredObjectInput {...props} /> : props.renderDefault(props)
15
+ }
58
16
 
59
- const filterField = options.filterField ?? defaultFilterField
17
+ // Modify the object members based on selected languages in the filter
18
+ export function FilteredObjectInput(props: ObjectInputProps) {
19
+ const {members: membersProp, schemaType, renderDefault, ...restProps} = props
20
+ const {selectedLanguageIds, options} = useLanguageFilterStudioContext()
21
+ const {filterField} = options
60
22
 
61
23
  const members: ObjectMember[] = useMemo(() => {
62
24
  return membersProp
63
25
  .filter((member) => {
64
26
  return (
65
- (member.kind === 'field' && filterField(schemaType, member, activeLanguages)) ||
27
+ (member.kind === 'field' && filterField(schemaType, member, selectedLanguageIds)) ||
66
28
  member.kind === 'fieldSet'
67
29
  )
68
30
  })
@@ -75,7 +37,7 @@ function FilteredObjectInput(props: LanguageFilterObjectInputProps) {
75
37
  members: member.fieldSet.members.filter((fieldsetMember) => {
76
38
  return (
77
39
  fieldsetMember.kind === 'field' &&
78
- filterField(schemaType, fieldsetMember, activeLanguages)
40
+ filterField(schemaType, fieldsetMember, selectedLanguageIds)
79
41
  )
80
42
  }),
81
43
  },
@@ -83,7 +45,7 @@ function FilteredObjectInput(props: LanguageFilterObjectInputProps) {
83
45
  }
84
46
  return member
85
47
  })
86
- }, [schemaType, membersProp, filterField, activeLanguages])
48
+ }, [schemaType, membersProp, filterField, selectedLanguageIds])
87
49
 
88
50
  return renderDefault({...restProps, members, schemaType, renderDefault})
89
51
  }
@@ -0,0 +1,63 @@
1
+ import React, {createContext, useContext, useMemo} from 'react'
2
+ import {LanguageFilterConfig} from './types'
3
+ import {LayoutProps} from 'sanity'
4
+ import {defaultFilterField} from './filterField'
5
+ import {useSelectedLanguageIds} from './useSelectedLanguageIds'
6
+
7
+ export interface LanguageFilterStudioContextProps {
8
+ // eslint-disable-next-line react/require-default-props
9
+ options: Required<LanguageFilterConfig>
10
+ }
11
+
12
+ export interface LanguageFilterStudioContextValue extends LanguageFilterStudioContextProps {
13
+ selectedLanguageIds: string[]
14
+ setSelectedLanguageIds: (ids: string[]) => void
15
+ }
16
+
17
+ export const defaultContextValue: LanguageFilterStudioContextValue = {
18
+ options: {
19
+ supportedLanguages: [],
20
+ defaultLanguages: [],
21
+ documentTypes: [],
22
+ filterField: defaultFilterField,
23
+ },
24
+ selectedLanguageIds: [],
25
+ setSelectedLanguageIds: () => console.error('LanguageFilterStudioContext not initialized'),
26
+ }
27
+
28
+ const LanguageFilterStudioContext =
29
+ createContext<LanguageFilterStudioContextValue>(defaultContextValue)
30
+
31
+ /**
32
+ * This is a separate Provider from the Context that wraps the document pane
33
+ * but it used to listen to changes to the selected language IDs inside it
34
+ * and provide them to a Studio-wide context
35
+ */
36
+ export function LanguageFilterStudioProvider(
37
+ props: LayoutProps & LanguageFilterStudioContextProps
38
+ ) {
39
+ const options = useMemo(
40
+ () => ({
41
+ ...defaultContextValue.options,
42
+ ...props.options,
43
+ }),
44
+ [props.options]
45
+ )
46
+ const [selectedLanguageIds, setSelectedLanguageIds] = useSelectedLanguageIds(options)
47
+
48
+ return (
49
+ <LanguageFilterStudioContext.Provider
50
+ value={{options, selectedLanguageIds, setSelectedLanguageIds}}
51
+ >
52
+ {props.renderDefault(props)}
53
+ </LanguageFilterStudioContext.Provider>
54
+ )
55
+ }
56
+
57
+ /**
58
+ * Retrieves plugin options and the currently selected
59
+ * language IDs from anywhere in the Studio
60
+ */
61
+ export function useLanguageFilterStudioContext() {
62
+ return useContext(LanguageFilterStudioContext)
63
+ }
package/src/index.ts CHANGED
@@ -12,3 +12,5 @@ export type {
12
12
  FilterFieldFunction,
13
13
  Language,
14
14
  } from './types'
15
+
16
+ export {useLanguageFilterStudioContext} from './LanguageFilterStudioContext'
package/src/plugin.tsx CHANGED
@@ -1,11 +1,15 @@
1
1
  import React from 'react'
2
- import {definePlugin, DocumentLanguageFilterComponent, ObjectInputProps} from 'sanity'
3
- import {LanguageFilterObjectInput} from './LanguageFilterObjectInput'
2
+ import {
3
+ definePlugin,
4
+ DocumentLanguageFilterComponent,
5
+ isObjectSchemaType,
6
+ ObjectInputProps,
7
+ } from 'sanity'
8
+ import {FilteredObjectWrapper} from './LanguageFilterObjectInput'
4
9
  import {LanguageFilterMenuButton} from './LanguageFilterMenuButton'
5
10
  import {LanguageFilterConfig} from './types'
6
11
  import {isLanguageFilterEnabled} from './filterField'
7
- import {LanguageFilterProvider} from './LanguageFilterContext'
8
- import {createSelectedLanguageIdsBus} from './languageSubscription'
12
+ import {defaultContextValue, LanguageFilterStudioProvider} from './LanguageFilterStudioContext'
9
13
 
10
14
  /**
11
15
  * ## Usage in sanity.config.ts (or .js)
@@ -41,14 +45,23 @@ import {createSelectedLanguageIdsBus} from './languageSubscription'
41
45
  * ```
42
46
  */
43
47
  export const languageFilter = definePlugin<LanguageFilterConfig>((options) => {
44
- const {onSelectedIdsChange, subscribeSelectedIds} = createSelectedLanguageIdsBus()
45
-
46
48
  const RenderLanguageFilter: DocumentLanguageFilterComponent = () => {
47
- return <LanguageFilterMenuButton options={options} onSelectedIdsChange={onSelectedIdsChange} />
49
+ return <LanguageFilterMenuButton options={options} />
50
+ }
51
+
52
+ const pluginOptions = {
53
+ ...defaultContextValue.options,
54
+ ...options,
48
55
  }
49
56
 
50
57
  return {
51
58
  name: '@sanity/language-filter',
59
+ studio: {
60
+ components: {
61
+ layout: (props) => LanguageFilterStudioProvider({...props, options: pluginOptions}),
62
+ },
63
+ },
64
+
52
65
  document: {
53
66
  unstable_languageFilter: (prev, {schemaType, schema}) => {
54
67
  if (isLanguageFilterEnabled(schema.get(schemaType), options)) {
@@ -60,24 +73,9 @@ export const languageFilter = definePlugin<LanguageFilterConfig>((options) => {
60
73
 
61
74
  form: {
62
75
  components: {
63
- // eslint-disable-next-line func-name-matching
64
- input: function LanguageFilterWrapper(props) {
65
- const enabled = isLanguageFilterEnabled(props.schemaType, options)
66
- // will only be considered enabled for document, so this is only done once
67
- if (enabled) {
68
- return (
69
- <LanguageFilterProvider enabled={enabled} options={options}>
70
- {props.renderDefault(props)}
71
- </LanguageFilterProvider>
72
- )
73
- }
74
- if (props.schemaType.jsonType === 'object') {
75
- return (
76
- <LanguageFilterObjectInput
77
- {...(props as ObjectInputProps)}
78
- subscribeSelectedIds={subscribeSelectedIds}
79
- />
80
- )
76
+ input: (props) => {
77
+ if (props.id !== 'root' && isObjectSchemaType(props.schemaType)) {
78
+ return FilteredObjectWrapper(props as ObjectInputProps)
81
79
  }
82
80
 
83
81
  return props.renderDefault(props)
@@ -1,42 +1,26 @@
1
1
  import {useCallback, useMemo} from 'react'
2
- import {LanguageFilterConfig} from './types'
3
- import {
4
- getSelectableLanguages,
5
- persistLanguageIds,
6
- useSelectedLanguageIds,
7
- } from './useSelectedLanguageIds'
8
2
 
9
- export interface UsePaneLanguagesParams {
10
- options: LanguageFilterConfig
11
- /**
12
- * We need a way to communicate state changes between the pane menu and input components.
13
- * LanguageFilter button lives outside the input-render tree, so Context is out.
14
- * This is a workaround for that.
15
- */
16
- onSelectedIdsChange: (ids: string[]) => void
17
- }
3
+ import {getSelectableLanguages, persistLanguageIds} from './useSelectedLanguageIds'
4
+ import {useLanguageFilterStudioContext} from './LanguageFilterStudioContext'
18
5
 
19
- export function usePaneLanguages(props: UsePaneLanguagesParams): {
6
+ export function usePaneLanguages(): {
20
7
  activeLanguages: string[]
21
8
  allSelected: boolean
22
9
  selectAll: () => void
23
10
  selectNone: () => void
24
11
  toggleLanguage: (languageId: string) => void
25
12
  } {
26
- const {options, onSelectedIdsChange} = props
13
+ const {selectedLanguageIds, setSelectedLanguageIds, options} = useLanguageFilterStudioContext()
27
14
  const {defaultLanguages} = options
28
15
 
29
- const [selectedIds, setSelectedIds] = useSelectedLanguageIds(options)
30
-
31
16
  const selectableLanguages = useMemo(() => getSelectableLanguages(options), [options])
32
17
 
33
18
  const updateSelectedIds = useCallback(
34
19
  (ids: string[]) => {
35
- setSelectedIds(ids)
20
+ setSelectedLanguageIds(ids)
36
21
  persistLanguageIds(ids)
37
- onSelectedIdsChange(ids)
38
22
  },
39
- [onSelectedIdsChange, setSelectedIds]
23
+ [setSelectedLanguageIds]
40
24
  )
41
25
 
42
26
  const selectAll = useCallback(
@@ -50,7 +34,7 @@ export function usePaneLanguages(props: UsePaneLanguagesParams): {
50
34
 
51
35
  const toggleLanguage = useCallback(
52
36
  (languageId: string) => {
53
- let lang = selectedIds
37
+ let lang = selectedLanguageIds
54
38
 
55
39
  if (lang.includes(languageId)) {
56
40
  lang = lang.filter((l) => l !== languageId)
@@ -60,17 +44,17 @@ export function usePaneLanguages(props: UsePaneLanguagesParams): {
60
44
 
61
45
  updateSelectedIds(lang)
62
46
  },
63
- [updateSelectedIds, selectedIds]
47
+ [updateSelectedIds, selectedLanguageIds]
64
48
  )
65
49
 
66
50
  const activeLanguages = useMemo(
67
- () => [...(defaultLanguages ?? []), ...selectedIds],
68
- [defaultLanguages, selectedIds]
51
+ () => [...(defaultLanguages ?? []), ...selectedLanguageIds],
52
+ [defaultLanguages, selectedLanguageIds]
69
53
  )
70
54
 
71
55
  return {
72
56
  activeLanguages,
73
- allSelected: selectedIds.length === selectableLanguages.length,
57
+ allSelected: selectedLanguageIds.length === selectableLanguages.length,
74
58
  selectAll,
75
59
  selectNone,
76
60
  toggleLanguage,
@@ -1,26 +0,0 @@
1
- import React, {createContext, PropsWithChildren, useContext, useMemo} from 'react'
2
- import {LanguageFilterConfig} from './types'
3
-
4
- export interface LanguageFilterContextValue {
5
- // eslint-disable-next-line react/require-default-props
6
- options: LanguageFilterConfig
7
- // eslint-disable-next-line react/require-default-props
8
- enabled: boolean
9
- }
10
-
11
- const LanguageFilterContext = createContext<LanguageFilterContextValue | undefined>(undefined)
12
-
13
- export function LanguageFilterProvider({
14
- options,
15
- enabled,
16
- children,
17
- }: PropsWithChildren<
18
- Omit<LanguageFilterContextValue, 'selectedLanguageIds' | 'setSelectedLanguageIds'>
19
- >) {
20
- const value = useMemo(() => ({options, enabled}), [options, enabled])
21
- return <LanguageFilterContext.Provider value={value}>{children}</LanguageFilterContext.Provider>
22
- }
23
-
24
- export function useLanguageFilterContext() {
25
- return useContext(LanguageFilterContext)
26
- }