@sanity/language-filter 3.0.1 → 3.1.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 +11 -8
- package/lib/index.esm.js +1 -2
- package/lib/index.esm.js.map +1 -1
- package/lib/index.js +1 -2
- package/lib/index.js.map +1 -1
- package/lib/src/index.d.ts +17 -0
- package/package.json +1 -1
- package/src/LanguageFilterMenuButton.tsx +117 -74
- package/src/LanguageFilterObjectInput.tsx +20 -58
- package/src/LanguageFilterStudioContext.tsx +63 -0
- package/src/index.ts +2 -0
- package/src/plugin.tsx +23 -25
- package/src/usePaneLanguages.ts +19 -32
- package/src/useSelectedLanguageIds.ts +1 -1
- package/src/LanguageFilterContext.tsx +0 -26
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
A Sanity plugin that supports filtering localized fields by language
|
|
9
9
|
|
|
10
|
-

|
|
11
11
|
|
|
12
12
|
## What this plugin solves
|
|
13
13
|
|
|
@@ -31,7 +31,6 @@ This plugin adds features to the Studio to improve handling **field-level transl
|
|
|
31
31
|
|
|
32
32
|
For **document-level translations** you should use the [@sanity/document-internationalization plugin](https://www.npmjs.com/package/@sanity/document-internationalization).
|
|
33
33
|
|
|
34
|
-
|
|
35
34
|
## Installation
|
|
36
35
|
|
|
37
36
|
```
|
|
@@ -45,6 +44,7 @@ yarn add @sanity/language-filter
|
|
|
45
44
|
```
|
|
46
45
|
|
|
47
46
|
## Usage
|
|
47
|
+
|
|
48
48
|
Add it as a plugin in sanity.config.ts (or .js), and configure it:
|
|
49
49
|
|
|
50
50
|
```
|
|
@@ -68,24 +68,26 @@ 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,
|
|
72
|
-
!enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(
|
|
71
|
+
filterField: (enclosingType, member, selectedLanguageIds) =>
|
|
72
|
+
!enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(member.name),
|
|
73
73
|
})
|
|
74
74
|
]
|
|
75
75
|
})
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
Config properties:
|
|
79
|
+
|
|
79
80
|
- `supportedLanguages` is an array of languages with `id` and `title`. If your localized fields are defined using our recommended way described here (https://www.sanity.io/docs/localization), you probably want to share this list of supported languages between this config and your schema.
|
|
80
81
|
- `defaultLanguages` (optional) is an array of strings where each entry must match an `id` from the `supportedLanguages` array. These languages will be listed by default and will not be possible to unselect. If no `defaultLanguages` is configured, all localized fields will be selected by default.
|
|
81
82
|
- `documentTypes` (optional) is an array of strings where each entry must match a `name` from your document schemas. If defined, this property will be used to conditionally show the language filter on specific document schema types. If undefined, the language filter will show on all document schema types.
|
|
82
83
|
- `filterField` (optional) is a function that must return true if the field should be displayed. It is passed the enclosing type (e.g the object type containing the localized fields, the field, and an array of the currently selected language ids.
|
|
83
|
-
This function is called for all fields and in objects for documents that have language filter enabled.
|
|
84
|
-
_Default:_ `!enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(field.name)`
|
|
84
|
+
This function is called for all fields and in objects for documents that have language filter enabled.
|
|
85
|
+
_Default:_ `!enclosingType.name.startsWith('locale') || selectedLanguageIds.includes(field.name)`
|
|
85
86
|
|
|
86
87
|
## Changes in V3
|
|
87
88
|
|
|
88
89
|
### documentTypes
|
|
90
|
+
|
|
89
91
|
Language filter can now be enabled/disabled directly from a schema, using `options.languageFilter: boolean`.
|
|
90
92
|
When `documentTypes` is omitted from plugin config, use `options.languageFilter: false` in a document-definition to hide the filter button.
|
|
91
93
|
When `documentTypes` is provided in plugin config, use `options.languageFilter: true` in a document-definition to show the filter button.
|
|
@@ -99,12 +101,13 @@ export const myDocumentSchema = {
|
|
|
99
101
|
/** ... */
|
|
100
102
|
options: {
|
|
101
103
|
// show language filter for this document type, regardless of how documentTypes for the plugin is configured
|
|
102
|
-
languageFilter: true
|
|
103
|
-
}
|
|
104
|
+
languageFilter: true,
|
|
105
|
+
},
|
|
104
106
|
}
|
|
105
107
|
```
|
|
106
108
|
|
|
107
109
|
### State management
|
|
110
|
+
|
|
108
111
|
Selected languages are now stored as `langs` url-param state; this allows users to copy paste
|
|
109
112
|
a url in the studio with the currently selected languages preselected.
|
|
110
113
|
|
package/lib/index.esm.js
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
var e;const
|
|
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 u}from"react/jsx-runtime";import{useFormValue as c,TextWithTone as s,definePlugin as d,isObjectSchemaType as g}from"sanity";import{useState as f,createContext as p,useMemo as m,useContext as y,useCallback as h}from"react";import{Box as b,useClickOutside as v,Stack as O,Card as L,Button as j,Flex as S,Text 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 F}from"@sanity/icons";const z=(e,t,n)=>!e.name.startsWith("locale")||n.includes(t.name);function A(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){const t=H(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}function H(e){let{supportedLanguages:t,defaultLanguages:n}=e;return t.filter((e=>!(null==n?void 0:n.includes(e.id))))}const W={options:{supportedLanguages:[],defaultLanguages:[],documentTypes:[],filterField:z},selectedLanguageIds:[],setSelectedLanguageIds:()=>console.error("LanguageFilterStudioContext not initialized")},q=p(W);function B(e){const t=m((()=>l(l({},W.options),e.options)),[e.options]),[n,r]=function(e){return f((()=>{var t;return[...null!=(t=e.defaultLanguages)?t:[],..._(e)]}))}(t);return o(q.Provider,{value:{options:t,selectedLanguageIds:n,setSelectedLanguageIds:r},children:e.renderDefault(e)})}function G(){return y(q)}function K(e){const{members:r,schemaType:i,renderDefault:o}=e,a=n(e,t),{selectedLanguageIds:u,options:c}=G(),{filterField:s}=c,d=m((()=>r.filter((e=>"field"===e.kind&&s(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&&s(i,e,u)))})}):e))),[i,r,s,u]);return o(l(l({},a),{},{members:d,schemaType:i,renderDefault:o}))}const M=e=>Array.from(new Set(e));function Q(){const{selectedLanguageIds:e,setSelectedLanguageIds:t,options:n}=G(),{defaultLanguages:r=[]}=n,l=m((()=>H(n)),[n]),i=h((e=>{var n;t(M([...r,...e])),n=M([...r,...e]),window.localStorage.setItem(J,JSON.stringify(n))}),[r,t]),o=h((()=>i(l.map((e=>e.id)))),[i,l]),a=h((()=>{i(r)}),[r,i]),u=h((t=>{let n=e;n=n.includes(t)?n.filter((e=>e!==t)):M([...n,t]),i(n)}),[i,e]);return{activeLanguages:m((()=>M([...null!=r?r:[],...e])),[r,e]),allSelected:e.length===l.length+r.length,selectAll:o,selectNone:a,toggleLanguage:u}}const R=D(b)(e||(U=["\n max-height: calc(100vh - 200px);\n"],V||(V=U.slice(0)),e=Object.freeze(Object.defineProperties(U,{raw:{value:Object.freeze(V)}}))));var U,V;function X(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(!0),{activeLanguages:c,allSelected:d,selectAll:g,selectNone:p,toggleLanguage:m}=Q(),[y,x]=f(null),[D,N]=f(null),F=h((e=>{"ALL"===e.currentTarget.value?g():p()}),[g,p]),z=h((()=>i((e=>!e))),[]),A=h((()=>i(!1)),[]);v(A,[y,D]);const E=t.supportedLanguages.length,[J,_]=f(""),H=h((e=>{e.currentTarget.value?_(e.currentTarget.value):_("")}),[]),W=o(R,{overflow:"auto",children:a(O,{padding:1,space:1,children:[n.length>0&&a(u,{children:[n.map((e=>o(Y,{id:e.id,title:e.title,selected:!0},e.id))),o(L,{borderTop:!0})]}),o(j,{mode:"bleed",onClick:F,justify:"flex-start",value:d?"NONE":"ALL",disabled:!!J,children:a(S,{gap:3,align:"center",children:[o(w,{size:2,children:d?o(s,{tone:"primary",children:o(I,{})}):o(k,{})}),o(b,{flex:1,children:o(w,{children:d?"Hide all":"Show all"})})]})}),o(L,{borderTop:!0}),E>4?o(T,{onChange:H,value:J,placeholder:"Filter languages"}):null,r.filter((e=>!J||e.title.toLowerCase().includes(J.toLowerCase()))).map((e=>o(Y,{id:e.id,onToggle:m,selected:c.includes(e.id),title:e.title},e.id)))]})}),q=c.length===E?"Showing all":"Showing ".concat(c.length," / ").concat(E);return o(P,{content:W,open:l,portal:!0,ref:N,children:o(j,{text:q,icon:C,mode:"bleed",onClick:z,ref:x,selected:l})})}function Y(e){const{id:t,onToggle:n,selected:r,title:l}=e,i=h((()=>{n&&n(t)}),[t,n]),u=!n;return o(j,{mode:"bleed",onClick:i,justify:"flex-start",disabled:u,children:a(S,{gap:3,align:"center",children:[o(w,{size:2,children:r?o(s,{tone:u?"default":"positive",children:o(N,{})}):o(F,{})}),o(b,{flex:1,children:o(w,{children:l})}),o(x,{children:t})]})})}const Z=d((e=>{const t=()=>o(X,{options:e}),n=l(l({},W.options),e);return{name:"@sanity/language-filter",studio:{components:{layout:e=>B(l(l({},e),{},{options:n}))}},document:{unstable_languageFilter:(n,r)=>{let{schemaType:l,schema:i}=r;return A(i.get(l),e)?[...n,t]:n}},form:{components:{input:e=>"root"!==e.id&&g(e.schemaType)?function(e){const{options:t}=G(),n=c(["_type"]),{documentTypes:r}=t;return"string"==typeof n&&r.includes(n)?o(K,l({},e)):e.renderDefault(e)}(e):e.renderDefault(e)}}}}));export{z as defaultFilterField,A as isLanguageFilterEnabled,Z as languageFilter,G as useLanguageFilterStudioContext};//# sourceMappingURL=index.esm.js.map
|
package/lib/index.esm.js.map
CHANGED
|
@@ -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 styled from 'styled-components'\nimport {LanguageFilterConfig} from './types'\nimport {usePaneLanguages} from './usePaneLanguages'\n\nconst StyledBox = styled(Box)`\n max-height: calc(100vh - 200px);\n`\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 <StyledBox 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 </StyledBox>\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","StyledBox","styled","Box","_templateObject","LanguageFilterMenuButton","languageOptions","open","setOpen","button","setButton","popover","setPopover","handleToggleAll","event","currentTarget","checked","handleClick","o","handleClickOutside","useClickOutside","content","jsxs","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";var e;const t=["
|
|
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={},o=Object.keys(e);for(r=0;r<o.length;r++)n=o[r],t.indexOf(n)>=0||(l[n]=e[n]);return l}(e,t);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);for(r=0;r<o.length;r++)n=o[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){o(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 o(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 i=require("react/jsx-runtime"),a=require("sanity"),s=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 f=g(c);const p=(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),o=!t.documentTypes;return!!(o&&!1!==l||!o&&l||e&&(null==(r=t.documentTypes)?void 0:r.includes(e.name)))}function x(e){return e.type?x(e.type):e}const b="@sanity/plugin/language-filter/selected-languages";function j(e){const t=y(e).map((e=>e.id));let n=t;try{const e=window.localStorage.getItem(b);e&&(n=JSON.parse(e))}catch(e){}var r;return r=t,n=n.filter((e=>r.includes(e))),n}function y(e){let{supportedLanguages:t,defaultLanguages:n}=e;return t.filter((e=>!(null==n?void 0:n.includes(e.id))))}const h={options:{supportedLanguages:[],defaultLanguages:[],documentTypes:[],filterField:p},selectedLanguageIds:[],setSelectedLanguageIds:()=>console.error("LanguageFilterStudioContext not initialized")},v=s.createContext(h);function O(e){const t=s.useMemo((()=>l(l({},h.options),e.options)),[e.options]),[n,r]=function(e){return s.useState((()=>{var t;return[...null!=(t=e.defaultLanguages)?t:[],...j(e)]}))}(t);return i.jsx(v.Provider,{value:{options:t,selectedLanguageIds:n,setSelectedLanguageIds:r},children:e.renderDefault(e)})}function S(){return s.useContext(v)}function L(e){const{members:r,schemaType:o,renderDefault:i}=e,a=n(e,t),{selectedLanguageIds:u,options:c}=S(),{filterField:d}=c,g=s.useMemo((()=>r.filter((e=>"field"===e.kind&&d(o,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(o,e,u)))})}):e))),[o,r,d,u]);return i(l(l({},a),{},{members:g,schemaType:o,renderDefault:i}))}const T=e=>Array.from(new Set(e));function C(){const{selectedLanguageIds:e,setSelectedLanguageIds:t,options:n}=S(),{defaultLanguages:r=[]}=n,l=s.useMemo((()=>y(n)),[n]),o=s.useCallback((e=>{var n;t(T([...r,...e])),n=T([...r,...e]),window.localStorage.setItem(b,JSON.stringify(n))}),[r,t]),i=s.useCallback((()=>o(l.map((e=>e.id)))),[o,l]),a=s.useCallback((()=>{o(r)}),[r,o]),u=s.useCallback((t=>{let n=e;n=n.includes(t)?n.filter((e=>e!==t)):T([...n,t]),o(n)}),[o,e]);return{activeLanguages:s.useMemo((()=>T([...null!=r?r:[],...e])),[r,e]),allSelected:e.length===l.length+r.length,selectAll:i,selectNone:a,toggleLanguage:u}}const w=f.default(u.Box)(e||(k=["\n max-height: calc(100vh - 200px);\n"],P||(P=k.slice(0)),e=Object.freeze(Object.defineProperties(k,{raw:{value:Object.freeze(P)}}))));var k,P;function I(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,o]=s.useState(!0),{activeLanguages:c,allSelected:g,selectAll:f,selectNone:p,toggleLanguage:m}=C(),[x,b]=s.useState(null),[j,y]=s.useState(null),h=s.useCallback((e=>{"ALL"===e.currentTarget.value?f():p()}),[f,p]),v=s.useCallback((()=>o((e=>!e))),[]),O=s.useCallback((()=>o(!1)),[]);u.useClickOutside(O,[x,j]);const S=t.supportedLanguages.length,[L,T]=s.useState(""),k=s.useCallback((e=>{e.currentTarget.value?T(e.currentTarget.value):T("")}),[]),P=i.jsx(w,{overflow:"auto",children:i.jsxs(u.Stack,{padding:1,space:1,children:[n.length>0&&i.jsxs(i.Fragment,{children:[n.map((e=>i.jsx(F,{id:e.id,title:e.title,selected:!0},e.id))),i.jsx(u.Card,{borderTop:!0})]}),i.jsx(u.Button,{mode:"bleed",onClick:h,justify:"flex-start",value:g?"NONE":"ALL",disabled:!!L,children:i.jsxs(u.Flex,{gap:3,align:"center",children:[i.jsx(u.Text,{size:2,children:g?i.jsx(a.TextWithTone,{tone:"primary",children:i.jsx(d.EyeClosedIcon,{})}):i.jsx(d.EyeOpenIcon,{})}),i.jsx(u.Box,{flex:1,children:i.jsx(u.Text,{children:g?"Hide all":"Show all"})})]})}),i.jsx(u.Card,{borderTop:!0}),S>4?i.jsx(u.TextInput,{onChange:k,value:L,placeholder:"Filter languages"}):null,r.filter((e=>!L||e.title.toLowerCase().includes(L.toLowerCase()))).map((e=>i.jsx(F,{id:e.id,onToggle:m,selected:c.includes(e.id),title:e.title},e.id)))]})}),I=c.length===S?"Showing all":"Showing ".concat(c.length," / ").concat(S);return i.jsx(u.Popover,{content:P,open:l,portal:!0,ref:y,children:i.jsx(u.Button,{text:I,icon:d.TranslateIcon,mode:"bleed",onClick:v,ref:b,selected:l})})}function F(e){const{id:t,onToggle:n,selected:r,title:l}=e,o=s.useCallback((()=>{n&&n(t)}),[t,n]),c=!n;return i.jsx(u.Button,{mode:"bleed",onClick:o,justify:"flex-start",disabled:c,children:i.jsxs(u.Flex,{gap:3,align:"center",children:[i.jsx(u.Text,{size:2,children:r?i.jsx(a.TextWithTone,{tone:c?"default":"positive",children:i.jsx(d.CheckmarkCircleIcon,{})}):i.jsx(d.CircleIcon,{})}),i.jsx(u.Box,{flex:1,children:i.jsx(u.Text,{children:l})}),i.jsx(u.Badge,{children:t})]})})}const D=a.definePlugin((e=>{const t=()=>i.jsx(I,{options:e}),n=l(l({},h.options),e);return{name:"@sanity/language-filter",studio:{components:{layout:e=>O(l(l({},e),{},{options:n}))}},document:{unstable_languageFilter:(n,r)=>{let{schemaType:l,schema:o}=r;return m(o.get(l),e)?[...n,t]:n}},form:{components:{input:e=>"root"!==e.id&&a.isObjectSchemaType(e.schemaType)?function(e){const{options:t}=S(),n=a.useFormValue(["_type"]),{documentTypes:r}=t;return"string"==typeof n&&r.includes(n)?i.jsx(L,l({},e)):e.renderDefault(e)}(e):e.renderDefault(e)}}}}));exports.defaultFilterField=p,exports.isLanguageFilterEnabled=m,exports.languageFilter=D,exports.useLanguageFilterStudioContext=S;//# 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 styled from 'styled-components'\nimport {LanguageFilterConfig} from './types'\nimport {usePaneLanguages} from './usePaneLanguages'\n\nconst StyledBox = styled(Box)`\n max-height: calc(100vh - 200px);\n`\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 <StyledBox 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 </StyledBox>\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","StyledBox","styled","Box","_templateObject","LanguageFilterMenuButton","languageOptions","open","setOpen","button","setButton","popover","setPopover","handleToggleAll","event","currentTarget","checked","handleClick","o","handleClickOutside","useClickOutside","content","jsxs","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":""}
|
package/lib/src/index.d.ts
CHANGED
|
@@ -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,8 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
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'
|
|
3
14
|
import styled from 'styled-components'
|
|
4
15
|
import {LanguageFilterConfig} from './types'
|
|
5
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'
|
|
6
25
|
|
|
7
26
|
const StyledBox = styled(Box)`
|
|
8
27
|
max-height: calc(100vh - 200px);
|
|
@@ -10,11 +29,10 @@ const StyledBox = styled(Box)`
|
|
|
10
29
|
|
|
11
30
|
export interface LanguageFilterMenuButtonProps {
|
|
12
31
|
options: LanguageFilterConfig
|
|
13
|
-
onSelectedIdsChange: (ids: string[]) => void
|
|
14
32
|
}
|
|
15
33
|
|
|
16
34
|
export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
17
|
-
const {options
|
|
35
|
+
const {options} = props
|
|
18
36
|
|
|
19
37
|
const defaultLanguages = options.supportedLanguages.filter((l) =>
|
|
20
38
|
options.defaultLanguages?.includes(l.id)
|
|
@@ -23,17 +41,14 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
|
23
41
|
const languageOptions = options.supportedLanguages.filter(
|
|
24
42
|
(l) => !options.defaultLanguages?.includes(l.id)
|
|
25
43
|
)
|
|
26
|
-
const [open, setOpen] = useState(false)
|
|
27
|
-
const {activeLanguages, allSelected, selectAll, selectNone, toggleLanguage} = usePaneLanguages(
|
|
28
|
-
options,
|
|
29
|
-
onSelectedIdsChange,
|
|
30
|
-
})
|
|
44
|
+
const [open, setOpen] = useState(!false)
|
|
45
|
+
const {activeLanguages, allSelected, selectAll, selectNone, toggleLanguage} = usePaneLanguages()
|
|
31
46
|
const [button, setButton] = useState<HTMLElement | null>(null)
|
|
32
47
|
const [popover, setPopover] = useState<HTMLElement | null>(null)
|
|
33
48
|
|
|
34
|
-
const handleToggleAll = useCallback(
|
|
35
|
-
(event
|
|
36
|
-
const checked = event.currentTarget.
|
|
49
|
+
const handleToggleAll: MouseEventHandler<HTMLButtonElement> = useCallback(
|
|
50
|
+
(event) => {
|
|
51
|
+
const checked = event.currentTarget.value === 'ALL'
|
|
37
52
|
|
|
38
53
|
if (checked) {
|
|
39
54
|
selectAll()
|
|
@@ -50,74 +65,88 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
|
50
65
|
|
|
51
66
|
useClickOutside(handleClickOutside, [button, popover])
|
|
52
67
|
|
|
53
|
-
const
|
|
54
|
-
<StyledBox overflow="auto" padding={1}>
|
|
55
|
-
{defaultLanguages.length > 0 && (
|
|
56
|
-
<Card radius={2}>
|
|
57
|
-
<Stack padding={2} space={3}>
|
|
58
|
-
<Box paddingBottom={2}>
|
|
59
|
-
<Text size={1} weight="semibold">
|
|
60
|
-
Default language{defaultLanguages.length > 1 && <>s</>}
|
|
61
|
-
</Text>
|
|
62
|
-
</Box>
|
|
68
|
+
const langCount = options.supportedLanguages.length
|
|
63
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
|
+
|
|
80
|
+
const content = (
|
|
81
|
+
<StyledBox overflow="auto">
|
|
82
|
+
<Stack padding={1} space={1}>
|
|
83
|
+
{defaultLanguages.length > 0 && (
|
|
84
|
+
<>
|
|
64
85
|
{defaultLanguages.map((l) => (
|
|
65
|
-
<
|
|
86
|
+
<LanguageFilterOption key={l.id} id={l.id} title={l.title} selected />
|
|
66
87
|
))}
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
<Card borderTop />
|
|
89
|
+
</>
|
|
90
|
+
)}
|
|
91
|
+
|
|
92
|
+
<Button
|
|
93
|
+
mode="bleed"
|
|
94
|
+
onClick={handleToggleAll}
|
|
95
|
+
justify="flex-start"
|
|
96
|
+
value={allSelected ? 'NONE' : 'ALL'}
|
|
97
|
+
disabled={!!query}
|
|
98
|
+
>
|
|
99
|
+
<Flex gap={3} align="center">
|
|
100
|
+
<Text size={2}>
|
|
101
|
+
{allSelected ? (
|
|
102
|
+
<TextWithTone tone="primary">
|
|
103
|
+
<EyeClosedIcon />
|
|
104
|
+
</TextWithTone>
|
|
105
|
+
) : (
|
|
106
|
+
<EyeOpenIcon />
|
|
107
|
+
)}
|
|
108
|
+
</Text>
|
|
81
109
|
<Box flex={1}>
|
|
82
|
-
<Text
|
|
83
|
-
All translations
|
|
84
|
-
</Text>
|
|
110
|
+
<Text>{allSelected ? `Hide all` : `Show all`}</Text>
|
|
85
111
|
</Box>
|
|
86
112
|
</Flex>
|
|
87
|
-
</
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
113
|
+
</Button>
|
|
114
|
+
|
|
115
|
+
<Card borderTop />
|
|
116
|
+
|
|
117
|
+
{langCount > 4 ? (
|
|
118
|
+
<TextInput onChange={handleQuery} value={query} placeholder="Filter languages" />
|
|
119
|
+
) : null}
|
|
120
|
+
|
|
121
|
+
{languageOptions
|
|
122
|
+
.filter((language) => {
|
|
123
|
+
if (query) {
|
|
124
|
+
return language.title.toLowerCase().includes(query.toLowerCase())
|
|
125
|
+
}
|
|
126
|
+
return true
|
|
127
|
+
})
|
|
128
|
+
.map((lang) => (
|
|
129
|
+
<LanguageFilterOption
|
|
130
|
+
id={lang.id}
|
|
131
|
+
key={lang.id}
|
|
132
|
+
onToggle={toggleLanguage}
|
|
133
|
+
selected={activeLanguages.includes(lang.id)}
|
|
134
|
+
title={lang.title}
|
|
135
|
+
/>
|
|
136
|
+
))}
|
|
98
137
|
</Stack>
|
|
99
138
|
</StyledBox>
|
|
100
139
|
)
|
|
101
140
|
|
|
102
|
-
const
|
|
141
|
+
const buttonText =
|
|
142
|
+
activeLanguages.length === langCount
|
|
143
|
+
? `Showing all`
|
|
144
|
+
: `Showing ${activeLanguages.length} / ${langCount}`
|
|
103
145
|
return (
|
|
104
146
|
<Popover content={content} open={open} portal ref={setPopover}>
|
|
105
147
|
<Button
|
|
106
|
-
text={
|
|
107
|
-
|
|
108
|
-
<Box>Filter languages:</Box>
|
|
109
|
-
<Flex gap={1} justify="space-around">
|
|
110
|
-
<Flex
|
|
111
|
-
style={{width: `${Math.floor(Math.log10(langCount) + 1)}ch`}}
|
|
112
|
-
justify="flex-end"
|
|
113
|
-
>
|
|
114
|
-
{activeLanguages.length}
|
|
115
|
-
</Flex>
|
|
116
|
-
<Box>/</Box>
|
|
117
|
-
<Box>{langCount}</Box>
|
|
118
|
-
</Flex>
|
|
119
|
-
</Flex>
|
|
120
|
-
}
|
|
148
|
+
text={buttonText}
|
|
149
|
+
icon={TranslateIcon}
|
|
121
150
|
mode="bleed"
|
|
122
151
|
onClick={handleClick}
|
|
123
152
|
ref={setButton}
|
|
@@ -129,24 +158,38 @@ export function LanguageFilterMenuButton(props: LanguageFilterMenuButtonProps) {
|
|
|
129
158
|
|
|
130
159
|
function LanguageFilterOption(props: {
|
|
131
160
|
id: string
|
|
132
|
-
onToggle: (id: string) => void
|
|
133
161
|
selected: boolean
|
|
134
162
|
title: string
|
|
163
|
+
// eslint-disable-next-line react/require-default-props
|
|
164
|
+
onToggle?: (id: string) => void
|
|
135
165
|
}) {
|
|
136
166
|
const {id, onToggle, selected, title} = props
|
|
137
167
|
|
|
138
168
|
const handleChange = useCallback(() => {
|
|
139
|
-
onToggle
|
|
169
|
+
if (onToggle) {
|
|
170
|
+
onToggle(id)
|
|
171
|
+
}
|
|
140
172
|
}, [id, onToggle])
|
|
141
173
|
|
|
174
|
+
const disabled = !onToggle
|
|
175
|
+
|
|
142
176
|
return (
|
|
143
|
-
<
|
|
144
|
-
<Flex align="center"
|
|
145
|
-
<
|
|
177
|
+
<Button mode="bleed" onClick={handleChange} justify="flex-start" disabled={disabled}>
|
|
178
|
+
<Flex gap={3} align="center">
|
|
179
|
+
<Text size={2}>
|
|
180
|
+
{selected ? (
|
|
181
|
+
<TextWithTone tone={disabled ? 'default' : 'positive'}>
|
|
182
|
+
<CheckmarkCircleIcon />
|
|
183
|
+
</TextWithTone>
|
|
184
|
+
) : (
|
|
185
|
+
<CircleIcon />
|
|
186
|
+
)}
|
|
187
|
+
</Text>
|
|
146
188
|
<Box flex={1}>
|
|
147
|
-
<Text
|
|
189
|
+
<Text>{title}</Text>
|
|
148
190
|
</Box>
|
|
191
|
+
<Badge>{id}</Badge>
|
|
149
192
|
</Flex>
|
|
150
|
-
</
|
|
193
|
+
</Button>
|
|
151
194
|
)
|
|
152
195
|
}
|
|
@@ -1,68 +1,30 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
import {ObjectInputProps, ObjectMember} from 'sanity'
|
|
3
|
-
import {
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
package/src/plugin.tsx
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {
|
|
3
|
-
|
|
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 {
|
|
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}
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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)
|
package/src/usePaneLanguages.ts
CHANGED
|
@@ -1,42 +1,28 @@
|
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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'
|
|
5
|
+
|
|
6
|
+
const unique = (arr: string[]) => Array.from(new Set(arr))
|
|
18
7
|
|
|
19
|
-
export function usePaneLanguages(
|
|
8
|
+
export function usePaneLanguages(): {
|
|
20
9
|
activeLanguages: string[]
|
|
21
10
|
allSelected: boolean
|
|
22
11
|
selectAll: () => void
|
|
23
12
|
selectNone: () => void
|
|
24
13
|
toggleLanguage: (languageId: string) => void
|
|
25
14
|
} {
|
|
26
|
-
const {
|
|
27
|
-
const {defaultLanguages} = options
|
|
28
|
-
|
|
29
|
-
const [selectedIds, setSelectedIds] = useSelectedLanguageIds(options)
|
|
15
|
+
const {selectedLanguageIds, setSelectedLanguageIds, options} = useLanguageFilterStudioContext()
|
|
16
|
+
const {defaultLanguages = []} = options
|
|
30
17
|
|
|
31
18
|
const selectableLanguages = useMemo(() => getSelectableLanguages(options), [options])
|
|
32
19
|
|
|
33
20
|
const updateSelectedIds = useCallback(
|
|
34
21
|
(ids: string[]) => {
|
|
35
|
-
|
|
36
|
-
persistLanguageIds(ids)
|
|
37
|
-
onSelectedIdsChange(ids)
|
|
22
|
+
setSelectedLanguageIds(unique([...defaultLanguages, ...ids]))
|
|
23
|
+
persistLanguageIds(unique([...defaultLanguages, ...ids]))
|
|
38
24
|
},
|
|
39
|
-
[
|
|
25
|
+
[defaultLanguages, setSelectedLanguageIds]
|
|
40
26
|
)
|
|
41
27
|
|
|
42
28
|
const selectAll = useCallback(
|
|
@@ -45,32 +31,33 @@ export function usePaneLanguages(props: UsePaneLanguagesParams): {
|
|
|
45
31
|
)
|
|
46
32
|
|
|
47
33
|
const selectNone = useCallback(() => {
|
|
48
|
-
updateSelectedIds(
|
|
49
|
-
}, [updateSelectedIds])
|
|
34
|
+
updateSelectedIds(defaultLanguages)
|
|
35
|
+
}, [defaultLanguages, updateSelectedIds])
|
|
50
36
|
|
|
51
37
|
const toggleLanguage = useCallback(
|
|
52
38
|
(languageId: string) => {
|
|
53
|
-
let lang =
|
|
39
|
+
let lang = selectedLanguageIds
|
|
54
40
|
|
|
55
41
|
if (lang.includes(languageId)) {
|
|
56
42
|
lang = lang.filter((l) => l !== languageId)
|
|
57
43
|
} else {
|
|
58
|
-
lang = [...lang, languageId]
|
|
44
|
+
lang = unique([...lang, languageId])
|
|
59
45
|
}
|
|
60
46
|
|
|
61
47
|
updateSelectedIds(lang)
|
|
62
48
|
},
|
|
63
|
-
[updateSelectedIds,
|
|
49
|
+
[updateSelectedIds, selectedLanguageIds]
|
|
64
50
|
)
|
|
65
51
|
|
|
66
52
|
const activeLanguages = useMemo(
|
|
67
|
-
() => [...(defaultLanguages ?? []), ...
|
|
68
|
-
[defaultLanguages,
|
|
53
|
+
() => unique([...(defaultLanguages ?? []), ...selectedLanguageIds]),
|
|
54
|
+
[defaultLanguages, selectedLanguageIds]
|
|
69
55
|
)
|
|
70
56
|
|
|
71
57
|
return {
|
|
72
58
|
activeLanguages,
|
|
73
|
-
allSelected:
|
|
59
|
+
allSelected:
|
|
60
|
+
selectedLanguageIds.length === selectableLanguages.length + defaultLanguages.length,
|
|
74
61
|
selectAll,
|
|
75
62
|
selectNone,
|
|
76
63
|
toggleLanguage,
|
|
@@ -36,5 +36,5 @@ export function getSelectableLanguages({
|
|
|
36
36
|
export function useSelectedLanguageIds(
|
|
37
37
|
options: LanguageFilterConfig
|
|
38
38
|
): [string[], (ids: string[]) => void] {
|
|
39
|
-
return useState(() => getPersistedLanguageIds(options))
|
|
39
|
+
return useState(() => [...(options.defaultLanguages ?? []), ...getPersistedLanguageIds(options)])
|
|
40
40
|
}
|
|
@@ -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
|
-
}
|