sanity-plugin-internationalized-array 1.8.0 → 1.9.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity-plugin-internationalized-array",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Store localized fields in an array to save on attributes",
5
5
  "keywords": [
6
6
  "sanity",
@@ -0,0 +1,39 @@
1
+ import {AddIcon} from '@sanity/icons'
2
+ import {Button, Grid} from '@sanity/ui'
3
+ import React from 'react'
4
+
5
+ import {MAX_COLUMNS} from '../constants'
6
+ import {Language, Value} from '../types'
7
+
8
+ type AddButtonsProps = {
9
+ languages: Language[]
10
+ readOnly: boolean
11
+ value: Value[] | undefined
12
+ onClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
13
+ }
14
+
15
+ export default function AddButtons(props: AddButtonsProps) {
16
+ const {languages, readOnly, value, onClick} = props
17
+
18
+ return languages.length > 0 ? (
19
+ <Grid columns={Math.min(languages.length, MAX_COLUMNS)} gap={2}>
20
+ {languages.map((language) => (
21
+ <Button
22
+ key={language.id}
23
+ tone="primary"
24
+ mode="ghost"
25
+ fontSize={1}
26
+ disabled={
27
+ readOnly ||
28
+ Boolean(value?.find((item) => item._key === language.id))
29
+ }
30
+ text={language.id.toUpperCase()}
31
+ // Only show plus icon if there's one row or less
32
+ icon={languages.length > MAX_COLUMNS ? undefined : AddIcon}
33
+ value={language.id}
34
+ onClick={onClick}
35
+ />
36
+ ))}
37
+ </Grid>
38
+ ) : null
39
+ }
@@ -0,0 +1,112 @@
1
+ import {Box, Stack, Text, useToast} from '@sanity/ui'
2
+ import React, {useCallback, useMemo} from 'react'
3
+ import {
4
+ insert,
5
+ isSanityDocument,
6
+ ObjectSchemaType,
7
+ PatchEvent,
8
+ setIfMissing,
9
+ } from 'sanity'
10
+ import {useDocumentPane} from 'sanity/desk'
11
+
12
+ import {createValueSchemaTypeName} from '../utils/createValueSchemaTypeName'
13
+ import AddButtons from './AddButtons'
14
+ import {useInternationalizedArrayContext} from './InternationalizedArrayContext'
15
+
16
+ type DocumentAddButtonsProps = {
17
+ schemaType: ObjectSchemaType
18
+ value: Record<string, any> | undefined
19
+ }
20
+
21
+ export default function DocumentAddButtons(props: DocumentAddButtonsProps) {
22
+ const {filteredLanguages} = useInternationalizedArrayContext()
23
+ const {fields} = props.schemaType
24
+ const value = isSanityDocument(props.value) ? props.value : undefined
25
+
26
+ const toast = useToast()
27
+ const {onChange} = useDocumentPane()
28
+
29
+ // Find every internationalizedArray field at the document root
30
+ // TODO: This should be a recursive search through nested fields
31
+ const internationalizedArrayFields = useMemo(
32
+ () =>
33
+ fields.filter((field) =>
34
+ field.type.name.startsWith('internationalizedArray')
35
+ ),
36
+ [fields]
37
+ )
38
+
39
+ const handleDocumentButtonClick = useCallback(
40
+ (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
41
+ const languageId = event.currentTarget.value
42
+ if (!languageId) {
43
+ toast.push({
44
+ status: 'error',
45
+ title: 'No language selected',
46
+ })
47
+ return
48
+ }
49
+
50
+ if (internationalizedArrayFields.length === 0) {
51
+ toast.push({
52
+ status: 'error',
53
+ title: 'No internationalizedArray fields found in document root',
54
+ })
55
+ return
56
+ }
57
+
58
+ // Find every internationalizedArray field that is empty for the selected language
59
+ const emptyLanguageFields = internationalizedArrayFields.filter(
60
+ (field) => {
61
+ const fieldValue = value?.[field.name]
62
+ const fieldValueLanguage =
63
+ fieldValue && Array.isArray(fieldValue)
64
+ ? fieldValue.find((v) => v._key === languageId)
65
+ : undefined
66
+
67
+ return !fieldValueLanguage
68
+ }
69
+ )
70
+
71
+ // Write a new patch for each empty field
72
+ const patches = emptyLanguageFields
73
+ .map((field) => {
74
+ const fieldKey = field.name
75
+
76
+ return [
77
+ setIfMissing([], [fieldKey]),
78
+ insert(
79
+ [
80
+ {
81
+ _key: languageId,
82
+ _type: createValueSchemaTypeName(field.type),
83
+ },
84
+ ],
85
+ 'after',
86
+ [fieldKey, -1]
87
+ ),
88
+ ]
89
+ })
90
+ .flat()
91
+
92
+ onChange(PatchEvent.from(patches))
93
+ },
94
+ [internationalizedArrayFields, onChange, toast, value]
95
+ )
96
+
97
+ return (
98
+ <Stack space={3}>
99
+ <Box>
100
+ <Text size={1} weight="semibold">
101
+ Add translation to internationalized fields
102
+ </Text>
103
+ </Box>
104
+ <AddButtons
105
+ languages={filteredLanguages}
106
+ readOnly={false}
107
+ value={undefined}
108
+ onClick={handleDocumentButtonClick}
109
+ />
110
+ </Stack>
111
+ )
112
+ }
@@ -1,6 +1,6 @@
1
1
  import {AddIcon} from '@sanity/icons'
2
2
  import {useLanguageFilterStudioContext} from '@sanity/language-filter'
3
- import {Button, Grid, Stack, useToast} from '@sanity/ui'
3
+ import {Button, Card, Grid, Stack, Text, useToast} from '@sanity/ui'
4
4
  import React, {useCallback, useEffect, useMemo} from 'react'
5
5
  import {
6
6
  ArrayOfObjectsInputProps,
@@ -16,6 +16,7 @@ import type {Value} from '../types'
16
16
  import {checkAllLanguagesArePresent} from '../utils/checkAllLanguagesArePresent'
17
17
  import {createAddAllTitle} from '../utils/createAddAllTitle'
18
18
  import {createAddLanguagePatches} from '../utils/createAddLanguagePatches'
19
+ import AddButtons from './AddButtons'
19
20
  import Feedback from './Feedback'
20
21
  import {useInternationalizedArrayContext} from './InternationalizedArrayContext'
21
22
 
@@ -205,10 +206,11 @@ export default function InternationalizedArray(
205
206
  filteredLanguages?.length > 0 &&
206
207
  // Not every language has a value yet
207
208
  !allLanguagesArePresent
209
+ const fieldHasMembers = members?.length > 0
208
210
 
209
211
  return (
210
212
  <Stack space={2}>
211
- {members?.length > 0 ? (
213
+ {fieldHasMembers ? (
212
214
  <>
213
215
  {filteredMembers.map((member) => {
214
216
  if (member.kind === 'item') {
@@ -229,36 +231,23 @@ export default function InternationalizedArray(
229
231
  </>
230
232
  ) : null}
231
233
 
234
+ {/* Give some feedback in the UI so the field doesn't look "missing" */}
235
+ {!addButtonsAreVisible && !fieldHasMembers ? (
236
+ <Card border tone="transparent" padding={3} radius={2}>
237
+ <Text size={1}>
238
+ This internationalized field currently has no translations.
239
+ </Text>
240
+ </Card>
241
+ ) : null}
242
+
232
243
  {addButtonsAreVisible ? (
233
244
  <Stack space={2}>
234
- {/* Hide language-specific buttons if there's only one */}
235
- {/* No more than 7 columns */}
236
- {filteredLanguages.length > 1 ? (
237
- <Grid
238
- columns={Math.min(filteredLanguages.length, MAX_COLUMNS)}
239
- gap={2}
240
- >
241
- {filteredLanguages.map((language) => (
242
- <Button
243
- key={language.id}
244
- tone="primary"
245
- mode="ghost"
246
- fontSize={1}
247
- disabled={
248
- readOnly ||
249
- Boolean(value?.find((item) => item._key === language.id))
250
- }
251
- text={language.id.toUpperCase()}
252
- // Only show plus icon if there's one row or less
253
- icon={
254
- filteredLanguages.length > MAX_COLUMNS ? undefined : AddIcon
255
- }
256
- value={language.id}
257
- onClick={handleAddLanguage}
258
- />
259
- ))}
260
- </Grid>
261
- ) : null}
245
+ <AddButtons
246
+ languages={filteredLanguages}
247
+ value={value}
248
+ readOnly={readOnly}
249
+ onClick={handleAddLanguage}
250
+ />
262
251
  {buttonAddAll ? (
263
252
  <Button
264
253
  tone="primary"
@@ -1,4 +1,5 @@
1
1
  import {useLanguageFilterStudioContext} from '@sanity/language-filter'
2
+ import {Stack} from '@sanity/ui'
2
3
  import equal from 'fast-deep-equal'
3
4
  import {createContext, useContext, useDeferredValue, useMemo} from 'react'
4
5
  import {ObjectInputProps, useClient, useFormBuilder} from 'sanity'
@@ -7,6 +8,7 @@ import {suspend} from 'suspend-react'
7
8
  import {namespace, version} from '../cache'
8
9
  import {CONFIG_DEFAULT} from '../constants'
9
10
  import {Language, PluginConfig} from '../types'
11
+ import DocumentAddButtons from './DocumentAddButtons'
10
12
  import {getSelectedValue} from './getSelectedValue'
11
13
 
12
14
  // This provider makes the plugin config available to all components in the document form
@@ -77,6 +79,9 @@ export function InternationalizedArrayProvider(
77
79
  : languages
78
80
  }, [deferredDocument, languageFilterOptions, languages, selectedLanguageIds])
79
81
 
82
+ const showDocumentButtons =
83
+ internationalizedArray.buttonLocations.includes('document')
84
+
80
85
  return (
81
86
  <InternationalizedArrayContext.Provider
82
87
  value={{
@@ -85,7 +90,17 @@ export function InternationalizedArrayProvider(
85
90
  filteredLanguages,
86
91
  }}
87
92
  >
88
- {props.renderDefault(props)}
93
+ {showDocumentButtons ? (
94
+ <Stack space={5}>
95
+ <DocumentAddButtons
96
+ schemaType={props.schemaType}
97
+ value={props.value}
98
+ />
99
+ {props.renderDefault(props)}
100
+ </Stack>
101
+ ) : (
102
+ props.renderDefault(props)
103
+ )}
89
104
  </InternationalizedArrayContext.Provider>
90
105
  )
91
106
  }
@@ -28,7 +28,7 @@ const createTranslateFieldActions: (
28
28
  const disabled =
29
29
  value && Array.isArray(value)
30
30
  ? Boolean(value?.find((item) => item._key === language.id))
31
- : true
31
+ : false
32
32
  const hidden = !filteredLanguages.some((f) => f.id === language.id)
33
33
 
34
34
  const {onChange} = useDocumentPane()
@@ -67,7 +67,7 @@ const AddMissingTranslationsFieldAction: (
67
67
  {languages, filteredLanguages}
68
68
  ) => {
69
69
  const value = useFormValue(fieldActionProps.path) as Value[]
70
- const disabled = value.length === filteredLanguages.length
70
+ const disabled = value && value.length === filteredLanguages.length
71
71
  const hidden = checkAllLanguagesArePresent(filteredLanguages, value)
72
72
 
73
73
  const {onChange} = useDocumentPane()
package/src/types.ts CHANGED
@@ -118,7 +118,7 @@ export type PluginConfig = {
118
118
  * Locations where the "+ EN" add language buttons are visible
119
119
  * @defaultValue ['field']
120
120
  * */
121
- buttonLocations: ('field' | 'unstable__fieldAction')[]
121
+ buttonLocations: ('field' | 'unstable__fieldAction' | 'document')[]
122
122
  /**
123
123
  * Show or hide the "Add missing languages" button
124
124
  * @defaultValue true
@@ -1,6 +1,7 @@
1
1
  import {FormInsertPatch, insert, Path, SchemaType} from 'sanity'
2
2
 
3
3
  import {Language, Value} from '../types'
4
+ import {createValueSchemaTypeName} from './createValueSchemaTypeName'
4
5
 
5
6
  type AddConfig = {
6
7
  // New keys to add to the field
@@ -27,7 +28,7 @@ export function createAddLanguagePatches(config: AddConfig): FormInsertPatch[] {
27
28
  path = [],
28
29
  } = config
29
30
 
30
- const itemBase = {_type: `${schemaType.name}Value`}
31
+ const itemBase = {_type: createValueSchemaTypeName(schemaType)}
31
32
 
32
33
  // Create new items
33
34
  const newItems =
@@ -0,0 +1,5 @@
1
+ import {SchemaType} from 'sanity'
2
+
3
+ export function createValueSchemaTypeName(schemaType: SchemaType): string {
4
+ return `${schemaType.name}Value`
5
+ }