sanity-plugin-media 4.3.5 → 5.0.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.
Files changed (166) hide show
  1. package/dist/index.d.ts +190 -413
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +2532 -3564
  4. package/dist/index.js.map +1 -1
  5. package/package.json +29 -35
  6. package/dist/index.cjs +0 -5753
  7. package/dist/index.cjs.map +0 -1
  8. package/dist/index.d.cts +0 -462
  9. package/sanity.json +0 -8
  10. package/src/__tests__/fixtures/createEpicTestStore.ts +0 -28
  11. package/src/__tests__/fixtures/listenMock.ts +0 -9
  12. package/src/__tests__/fixtures/mockSanityClient.ts +0 -84
  13. package/src/__tests__/fixtures/renderWithProviders.tsx +0 -55
  14. package/src/__tests__/fixtures/rootState.ts +0 -27
  15. package/src/__tests__/fixtures/withinDialog.ts +0 -28
  16. package/src/components/AssetGridVirtualized/index.tsx +0 -94
  17. package/src/components/AssetMetadata/index.tsx +0 -122
  18. package/src/components/AssetTableVirtualized/index.tsx +0 -73
  19. package/src/components/AutoTagInputWrapper/index.tsx +0 -87
  20. package/src/components/Browser/Browser.test.tsx +0 -45
  21. package/src/components/Browser/index.tsx +0 -90
  22. package/src/components/Browser/useBrowserInit.ts +0 -126
  23. package/src/components/ButtonAssetCopy/index.tsx +0 -65
  24. package/src/components/ButtonViewGroup/index.tsx +0 -39
  25. package/src/components/CardAsset/CardAsset.test.tsx +0 -323
  26. package/src/components/CardAsset/index.tsx +0 -290
  27. package/src/components/CardUpload/index.tsx +0 -161
  28. package/src/components/Controls/index.tsx +0 -136
  29. package/src/components/DebugControls/index.tsx +0 -80
  30. package/src/components/Dialog/index.tsx +0 -11
  31. package/src/components/DialogAssetEdit/Details.tsx +0 -181
  32. package/src/components/DialogAssetEdit/DialogAssetEdit.test.tsx +0 -216
  33. package/src/components/DialogAssetEdit/index.tsx +0 -492
  34. package/src/components/DialogConfirm/index.tsx +0 -88
  35. package/src/components/DialogSearchFacets/index.tsx +0 -42
  36. package/src/components/DialogTagCreate/DialogTagCreate.test.tsx +0 -121
  37. package/src/components/DialogTagCreate/index.tsx +0 -103
  38. package/src/components/DialogTagEdit/DialogTagEdit.test.tsx +0 -165
  39. package/src/components/DialogTagEdit/index.tsx +0 -190
  40. package/src/components/DialogTags/index.tsx +0 -45
  41. package/src/components/Dialogs/index.tsx +0 -76
  42. package/src/components/DocumentList/index.tsx +0 -62
  43. package/src/components/FileAssetPreview/index.tsx +0 -37
  44. package/src/components/FileIcon/index.tsx +0 -43
  45. package/src/components/FormBuilderTool/FormBuilderTool.test.tsx +0 -63
  46. package/src/components/FormBuilderTool/index.tsx +0 -69
  47. package/src/components/FormFieldInputLabel/index.tsx +0 -66
  48. package/src/components/FormFieldInputTags/index.tsx +0 -98
  49. package/src/components/FormFieldInputText/index.tsx +0 -41
  50. package/src/components/FormFieldInputTextarea/index.tsx +0 -43
  51. package/src/components/FormSubmitButton/index.tsx +0 -59
  52. package/src/components/Header/index.tsx +0 -80
  53. package/src/components/Image/index.tsx +0 -41
  54. package/src/components/Items/index.tsx +0 -68
  55. package/src/components/Notifications/index.tsx +0 -24
  56. package/src/components/OrderSelect/index.tsx +0 -66
  57. package/src/components/PickedBar/index.tsx +0 -77
  58. package/src/components/Progress/index.tsx +0 -38
  59. package/src/components/ReduxProvider/index.tsx +0 -96
  60. package/src/components/SearchFacet/index.tsx +0 -66
  61. package/src/components/SearchFacetNumber/index.tsx +0 -133
  62. package/src/components/SearchFacetSelect/index.tsx +0 -110
  63. package/src/components/SearchFacetString/index.tsx +0 -88
  64. package/src/components/SearchFacetTags/index.tsx +0 -121
  65. package/src/components/SearchFacets/index.tsx +0 -72
  66. package/src/components/SearchFacetsControl/index.tsx +0 -140
  67. package/src/components/TableHeader/index.tsx +0 -110
  68. package/src/components/TableHeaderItem/index.tsx +0 -61
  69. package/src/components/TableRowAsset/index.tsx +0 -420
  70. package/src/components/TableRowUpload/index.tsx +0 -164
  71. package/src/components/Tag/index.tsx +0 -200
  72. package/src/components/TagIcon/index.tsx +0 -22
  73. package/src/components/TagView/index.tsx +0 -39
  74. package/src/components/TagViewHeader/index.tsx +0 -70
  75. package/src/components/TagsPanel/index.tsx +0 -40
  76. package/src/components/TagsVirtualized/index.tsx +0 -160
  77. package/src/components/TextInputNumber/index.tsx +0 -32
  78. package/src/components/TextInputSearch/index.tsx +0 -60
  79. package/src/components/Tool/index.tsx +0 -13
  80. package/src/components/UploadDropzone/UploadDropzone.test.tsx +0 -40
  81. package/src/components/UploadDropzone/index.tsx +0 -173
  82. package/src/config/orders.ts +0 -28
  83. package/src/config/searchFacets.ts +0 -312
  84. package/src/constants.ts +0 -87
  85. package/src/contexts/AssetSourceDispatchContext.tsx +0 -37
  86. package/src/contexts/DropzoneDispatchContext.tsx +0 -34
  87. package/src/contexts/ToolOptionsContext.tsx +0 -65
  88. package/src/formSchema/index.test.ts +0 -56
  89. package/src/formSchema/index.ts +0 -39
  90. package/src/hooks/useBreakpointIndex.ts +0 -49
  91. package/src/hooks/useKeyPress.ts +0 -39
  92. package/src/hooks/useOnScreen.ts +0 -34
  93. package/src/hooks/usePortalPopoverProps.ts +0 -13
  94. package/src/hooks/useTypedSelector.ts +0 -7
  95. package/src/hooks/useVersionedClient.ts +0 -6
  96. package/src/index.ts +0 -5
  97. package/src/modules/assets/actions.ts +0 -42
  98. package/src/modules/assets/deleteAndUpdateEpics.test.ts +0 -87
  99. package/src/modules/assets/fetchEpic.test.ts +0 -73
  100. package/src/modules/assets/index.ts +0 -825
  101. package/src/modules/assets/reducer.test.ts +0 -91
  102. package/src/modules/assets/tagsAndListenerEpics.test.ts +0 -206
  103. package/src/modules/debug/index.ts +0 -28
  104. package/src/modules/dialog/actions.ts +0 -10
  105. package/src/modules/dialog/epics.test.ts +0 -168
  106. package/src/modules/dialog/index.ts +0 -238
  107. package/src/modules/dialog/reducer.test.ts +0 -185
  108. package/src/modules/index.ts +0 -124
  109. package/src/modules/notifications/epics.test.ts +0 -374
  110. package/src/modules/notifications/index.ts +0 -199
  111. package/src/modules/notifications/reducer.test.ts +0 -54
  112. package/src/modules/search/index.test.ts +0 -36
  113. package/src/modules/search/index.ts +0 -167
  114. package/src/modules/selected/index.ts +0 -22
  115. package/src/modules/selectors.test.ts +0 -21
  116. package/src/modules/selectors.ts +0 -17
  117. package/src/modules/tags/epics.test.ts +0 -96
  118. package/src/modules/tags/index.test.ts +0 -42
  119. package/src/modules/tags/index.ts +0 -540
  120. package/src/modules/types.ts +0 -3
  121. package/src/modules/uploads/actions.ts +0 -13
  122. package/src/modules/uploads/epics.test.ts +0 -109
  123. package/src/modules/uploads/index.test.ts +0 -59
  124. package/src/modules/uploads/index.ts +0 -282
  125. package/src/operators/checkTagName.test.ts +0 -29
  126. package/src/operators/checkTagName.ts +0 -33
  127. package/src/operators/debugThrottle.ts +0 -25
  128. package/src/plugin.tsx +0 -54
  129. package/src/schemas/tag.ts +0 -28
  130. package/src/styled/GlobalStyles/index.tsx +0 -40
  131. package/src/styled/react-select/creatable.tsx +0 -184
  132. package/src/styled/react-select/single.tsx +0 -184
  133. package/src/types/index.ts +0 -379
  134. package/src/types/sanity-ui.d.ts +0 -6
  135. package/src/utils/applyMediaTags.ts +0 -87
  136. package/src/utils/blocksToText.test.ts +0 -43
  137. package/src/utils/blocksToText.ts +0 -27
  138. package/src/utils/constructFilter.test.ts +0 -120
  139. package/src/utils/constructFilter.ts +0 -98
  140. package/src/utils/generatePreviewBlobUrl.test.ts +0 -70
  141. package/src/utils/generatePreviewBlobUrl.ts +0 -53
  142. package/src/utils/getAssetResolution.test.ts +0 -13
  143. package/src/utils/getAssetResolution.ts +0 -7
  144. package/src/utils/getDocumentAssetIds.test.ts +0 -50
  145. package/src/utils/getDocumentAssetIds.ts +0 -35
  146. package/src/utils/getSchemeColor.test.ts +0 -12
  147. package/src/utils/getSchemeColor.ts +0 -43
  148. package/src/utils/getTagSelectOptions.test.ts +0 -44
  149. package/src/utils/getTagSelectOptions.ts +0 -16
  150. package/src/utils/getUniqueDocuments.test.ts +0 -26
  151. package/src/utils/getUniqueDocuments.ts +0 -15
  152. package/src/utils/imageDprUrl.test.ts +0 -46
  153. package/src/utils/imageDprUrl.ts +0 -27
  154. package/src/utils/isSupportedAssetType.test.ts +0 -16
  155. package/src/utils/isSupportedAssetType.ts +0 -15
  156. package/src/utils/mediaField.ts +0 -73
  157. package/src/utils/sanitizeFormData.test.ts +0 -59
  158. package/src/utils/sanitizeFormData.ts +0 -26
  159. package/src/utils/typeGuards.test.ts +0 -18
  160. package/src/utils/typeGuards.ts +0 -9
  161. package/src/utils/uploadSanityAsset.test.ts +0 -29
  162. package/src/utils/uploadSanityAsset.ts +0 -97
  163. package/src/utils/withMaxConcurrency.test.ts +0 -43
  164. package/src/utils/withMaxConcurrency.ts +0 -55
  165. package/src/utils/zodFormResolver.ts +0 -17
  166. package/v2-incompatible.js +0 -11
@@ -1,121 +0,0 @@
1
- import {screen, waitFor} from '@testing-library/react'
2
- import userEvent from '@testing-library/user-event'
3
- import {describe, expect, it, vi} from 'vitest'
4
-
5
- import {renderWithProviders} from '../../__tests__/fixtures/renderWithProviders'
6
- import {createTestRootState} from '../../__tests__/fixtures/rootState'
7
- import {getDialogRoot, inputByName, withinDialog} from '../../__tests__/fixtures/withinDialog'
8
- import {tagsActions} from '../../modules/tags'
9
- import DialogTagCreate from './index'
10
-
11
- describe('DialogTagCreate', () => {
12
- it('dispatches tag create flow when form is valid', async () => {
13
- const user = userEvent.setup()
14
- const {store} = renderWithProviders(
15
- <DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
16
- <span />
17
- </DialogTagCreate>,
18
- )
19
-
20
- const dlg = withinDialog(/create tag/i, screen)
21
- await user.type(inputByName(/create tag/i, screen, 'name'), 'my-tag')
22
- await user.click(dlg.getByRole('button', {name: /save and close/i}))
23
-
24
- expect(store.getState().tags.creating).toBe(true)
25
- })
26
-
27
- it('dispatches createRequest with a trimmed tag name', async () => {
28
- const user = userEvent.setup()
29
- const {store} = renderWithProviders(
30
- <DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
31
- <span />
32
- </DialogTagCreate>,
33
- )
34
- const dispatchSpy = vi.spyOn(store, 'dispatch')
35
- const dlg = withinDialog(/create tag/i, screen)
36
-
37
- await user.type(inputByName(/create tag/i, screen, 'name'), ' spaced ')
38
- await user.click(dlg.getByRole('button', {name: /save and close/i}))
39
-
40
- await waitFor(() => {
41
- let createAction
42
- for (const call of dispatchSpy.mock.calls) {
43
- const action = call[0]
44
- if (tagsActions.createRequest.match(action)) {
45
- createAction = action
46
- break
47
- }
48
- }
49
- expect(createAction).toBeDefined()
50
- expect(createAction?.payload).toEqual({name: 'spaced'})
51
- })
52
- })
53
-
54
- it('keeps Save disabled until the name is non-empty and valid', async () => {
55
- const user = userEvent.setup()
56
- renderWithProviders(
57
- <DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
58
- <span />
59
- </DialogTagCreate>,
60
- )
61
-
62
- expect(
63
- withinDialog(/create tag/i, screen).getByRole('button', {name: /save and close/i}),
64
- ).toBeDisabled()
65
-
66
- const nameInput = inputByName(/create tag/i, screen, 'name')
67
- await user.type(nameInput, 'a')
68
- await user.tab()
69
- await waitFor(() => {
70
- expect(
71
- withinDialog(/create tag/i, screen).getByRole('button', {name: /save and close/i}),
72
- ).not.toBeDisabled()
73
- })
74
- })
75
-
76
- it('clears the entire dialog stack when the dialog close control is used', async () => {
77
- const user = userEvent.setup()
78
- const base = createTestRootState({
79
- dialog: {
80
- items: [
81
- {id: 'dlg-1', type: 'tagCreate'},
82
- {id: 'tags', type: 'tags'},
83
- ],
84
- },
85
- })
86
-
87
- const {store} = renderWithProviders(
88
- <DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
89
- <span />
90
- </DialogTagCreate>,
91
- {preloaded: base},
92
- )
93
-
94
- const dlg = withinDialog(/create tag/i, screen)
95
- await user.click(dlg.getByRole('button', {name: /close dialog/i}))
96
-
97
- expect(store.getState().dialog.items).toEqual([])
98
- })
99
-
100
- it('shows an error indicator beside the name when tag creation failed on the server', async () => {
101
- const base = createTestRootState({
102
- tags: {
103
- ...createTestRootState().tags,
104
- creatingError: {message: 'Tag already exists', statusCode: 409},
105
- },
106
- })
107
-
108
- renderWithProviders(
109
- <DialogTagCreate dialog={{id: 'dlg-1', type: 'tagCreate'}}>
110
- <span />
111
- </DialogTagCreate>,
112
- {preloaded: base},
113
- )
114
-
115
- await waitFor(() => {
116
- expect(
117
- getDialogRoot(/create tag/i, screen).querySelector('[data-sanity-icon="error-outline"]'),
118
- ).toBeTruthy()
119
- })
120
- })
121
- })
@@ -1,103 +0,0 @@
1
- import {Box, Flex} from '@sanity/ui'
2
- import {type ReactNode, useEffect} from 'react'
3
- import {type SubmitHandler, useForm} from 'react-hook-form'
4
- import {useDispatch} from 'react-redux'
5
-
6
- import {tagFormSchema} from '../../formSchema'
7
- import useTypedSelector from '../../hooks/useTypedSelector'
8
- import {dialogActions} from '../../modules/dialog'
9
- import {tagsActions} from '../../modules/tags'
10
- import type {DialogTagCreateProps, TagFormData} from '../../types'
11
- import sanitizeFormData from '../../utils/sanitizeFormData'
12
- import zodFormResolver from '../../utils/zodFormResolver'
13
- import Dialog from '../Dialog'
14
- import FormFieldInputText from '../FormFieldInputText'
15
- import FormSubmitButton from '../FormSubmitButton'
16
-
17
- type Props = {
18
- children: ReactNode
19
- dialog: DialogTagCreateProps
20
- }
21
-
22
- const DialogTagCreate = (props: Props) => {
23
- const {
24
- children,
25
- dialog: {id},
26
- } = props
27
-
28
- const dispatch = useDispatch()
29
-
30
- const creating = useTypedSelector((state) => state.tags.creating)
31
- const creatingError = useTypedSelector((state) => state.tags.creatingError)
32
-
33
- const {
34
- // Read the formState before render to subscribe the form state through Proxy
35
- formState: {errors, isDirty, isValid},
36
- handleSubmit,
37
- register,
38
- setError,
39
- } = useForm<TagFormData>({
40
- defaultValues: {
41
- name: '',
42
- },
43
- mode: 'onChange',
44
- resolver: zodFormResolver<TagFormData>(tagFormSchema),
45
- })
46
-
47
- const formUpdating = creating
48
-
49
- const handleClose = () => {
50
- dispatch(dialogActions.clear())
51
- }
52
-
53
- // - submit react-hook-form
54
- const onSubmit: SubmitHandler<TagFormData> = (formData) => {
55
- const sanitizedFormData = sanitizeFormData(formData)
56
-
57
- dispatch(tagsActions.createRequest({name: sanitizedFormData.name}))
58
- }
59
-
60
- useEffect(() => {
61
- if (creatingError) {
62
- setError('name', {
63
- message: creatingError?.message,
64
- })
65
- }
66
- }, [creatingError, setError])
67
-
68
- const Footer = () => (
69
- <Box padding={3}>
70
- <Flex justify="flex-end">
71
- {/* Submit button */}
72
- <FormSubmitButton
73
- disabled={formUpdating || !isDirty || !isValid}
74
- isValid={isValid}
75
- onClick={handleSubmit(onSubmit)}
76
- />
77
- </Flex>
78
- </Box>
79
- )
80
-
81
- return (
82
- <Dialog animate footer={<Footer />} header="Create Tag" id={id} onClose={handleClose} width={1}>
83
- {/* Form fields */}
84
- <Box as="form" padding={4} onSubmit={handleSubmit(onSubmit)}>
85
- {/* Hidden button to enable enter key submissions */}
86
- <button style={{display: 'none'}} tabIndex={-1} type="submit" />
87
-
88
- {/* Title */}
89
- <FormFieldInputText
90
- {...register('name')}
91
- disabled={formUpdating}
92
- error={errors?.name?.message}
93
- label="Name"
94
- name="name"
95
- />
96
- </Box>
97
-
98
- {children}
99
- </Dialog>
100
- )
101
- }
102
-
103
- export default DialogTagCreate
@@ -1,165 +0,0 @@
1
- import {fireEvent, screen, waitFor} from '@testing-library/react'
2
- import userEvent from '@testing-library/user-event'
3
- import {Subject} from 'rxjs'
4
- import {describe, expect, it, vi} from 'vitest'
5
-
6
- import {createMockSanityClient} from '../../__tests__/fixtures/mockSanityClient'
7
- import {renderWithProviders} from '../../__tests__/fixtures/renderWithProviders'
8
- import {createTestRootState} from '../../__tests__/fixtures/rootState'
9
- import {inputByName, withinDialog} from '../../__tests__/fixtures/withinDialog'
10
- import {tagsActions} from '../../modules/tags'
11
- import type {Tag} from '../../types'
12
- import DialogTagEdit from './index'
13
-
14
- const tag: Tag = {
15
- _id: 't1',
16
- _type: 'media.tag',
17
- _createdAt: '',
18
- _updatedAt: '',
19
- _rev: 'r1',
20
- name: {_type: 'slug', current: 'alpha'},
21
- }
22
-
23
- const tagsPreloaded = {
24
- allIds: ['t1'],
25
- byIds: {
26
- t1: {_type: 'tag' as const, tag, picked: false, updating: false},
27
- },
28
- creating: false,
29
- fetchCount: -1,
30
- fetching: false,
31
- panelVisible: true,
32
- }
33
-
34
- vi.mock('../../hooks/useVersionedClient', () => ({
35
- default: () =>
36
- createMockSanityClient({
37
- listen: vi.fn(() => new Subject()),
38
- }),
39
- }))
40
-
41
- describe('DialogTagEdit', () => {
42
- it('dispatches updateRequest when name changes and form submits', async () => {
43
- const user = userEvent.setup()
44
- const {store} = renderWithProviders(
45
- <DialogTagEdit dialog={{id: 'dlg-1', type: 'tagEdit', tagId: 't1'}}>
46
- <span />
47
- </DialogTagEdit>,
48
- {
49
- preloaded: {
50
- tags: tagsPreloaded,
51
- },
52
- },
53
- )
54
-
55
- const dlg = withinDialog(/edit tag/i, screen)
56
- const input = inputByName(/edit tag/i, screen, 'name')
57
- await user.clear(input)
58
- await user.type(input, 'beta')
59
- await user.click(dlg.getByRole('button', {name: /save and close/i}))
60
-
61
- expect(store.getState().tags.byIds.t1.updating).toBe(true)
62
- })
63
-
64
- it('dispatches updateRequest with slug-shaped form data', async () => {
65
- const user = userEvent.setup()
66
- const {store} = renderWithProviders(
67
- <DialogTagEdit dialog={{id: 'dlg-1', type: 'tagEdit', tagId: 't1'}}>
68
- <span />
69
- </DialogTagEdit>,
70
- {
71
- preloaded: {
72
- tags: tagsPreloaded,
73
- },
74
- },
75
- )
76
- const dispatchSpy = vi.spyOn(store, 'dispatch')
77
- const dlg = withinDialog(/edit tag/i, screen)
78
-
79
- const input = inputByName(/edit tag/i, screen, 'name')
80
- await user.clear(input)
81
- await user.type(input, 'gamma')
82
- await user.click(dlg.getByRole('button', {name: /save and close/i}))
83
-
84
- await waitFor(() => {
85
- let updateAction
86
- for (const call of dispatchSpy.mock.calls) {
87
- const action = call[0]
88
- if (tagsActions.updateRequest.match(action)) {
89
- updateAction = action
90
- break
91
- }
92
- }
93
- expect(updateAction).toBeDefined()
94
- expect(updateAction?.payload).toMatchObject({
95
- closeDialogId: 't1',
96
- formData: {
97
- name: {_type: 'slug', current: 'gamma'},
98
- },
99
- tag,
100
- })
101
- })
102
- })
103
-
104
- it('does not enable Save until the name is edited', () => {
105
- renderWithProviders(
106
- <DialogTagEdit dialog={{id: 'dlg-1', type: 'tagEdit', tagId: 't1'}}>
107
- <span />
108
- </DialogTagEdit>,
109
- {
110
- preloaded: {
111
- tags: tagsPreloaded,
112
- },
113
- },
114
- )
115
-
116
- const dlg = withinDialog(/edit tag/i, screen)
117
- expect(dlg.getByRole('button', {name: /save and close/i})).toBeDisabled()
118
- })
119
-
120
- it('removes only this dialog when closed', async () => {
121
- const user = userEvent.setup()
122
- const base = createTestRootState({
123
- dialog: {
124
- items: [
125
- {id: 'dlg-1', type: 'tagEdit', tagId: 't1'},
126
- {id: 'tags', type: 'tags'},
127
- ],
128
- },
129
- tags: tagsPreloaded,
130
- })
131
-
132
- const {store} = renderWithProviders(
133
- <DialogTagEdit dialog={{id: 'dlg-1', type: 'tagEdit', tagId: 't1'}}>
134
- <span />
135
- </DialogTagEdit>,
136
- {preloaded: base},
137
- )
138
-
139
- const dlg = withinDialog(/edit tag/i, screen)
140
- await user.click(dlg.getByRole('button', {name: /close dialog/i}))
141
-
142
- expect(store.getState().dialog.items).toEqual([{id: 'tags', type: 'tags'}])
143
- })
144
-
145
- it('opens the delete confirmation dialog when Delete is clicked', async () => {
146
- const {store} = renderWithProviders(
147
- <DialogTagEdit dialog={{id: 'dlg-1', type: 'tagEdit', tagId: 't1'}}>
148
- <span />
149
- </DialogTagEdit>,
150
- {
151
- preloaded: {
152
- tags: tagsPreloaded,
153
- },
154
- },
155
- )
156
-
157
- const dlg = withinDialog(/edit tag/i, screen)
158
- fireEvent.click(dlg.getByRole('button', {name: /^delete$/i}))
159
-
160
- const confirm = store.getState().dialog.items.find((d) => d.type === 'confirm')
161
- expect(confirm).toBeDefined()
162
- expect(confirm?.title).toMatch(/permanently delete/i)
163
- expect(confirm?.headerTitle).toBe('Confirm deletion')
164
- })
165
- })
@@ -1,190 +0,0 @@
1
- import type {MutationEvent} from '@sanity/client'
2
- import {Box, Button, Card, Flex, Text} from '@sanity/ui'
3
- import groq from 'groq'
4
- import {type ReactNode, useCallback, useEffect, useState} from 'react'
5
- import {type SubmitHandler, useForm} from 'react-hook-form'
6
- import {useDispatch} from 'react-redux'
7
-
8
- import {tagFormSchema} from '../../formSchema'
9
- import useTypedSelector from '../../hooks/useTypedSelector'
10
- import useVersionedClient from '../../hooks/useVersionedClient'
11
- import {dialogActions} from '../../modules/dialog'
12
- import {selectTagById, tagsActions} from '../../modules/tags'
13
- import type {DialogTagEditProps, Tag, TagFormData} from '../../types'
14
- import sanitizeFormData from '../../utils/sanitizeFormData'
15
- import zodFormResolver from '../../utils/zodFormResolver'
16
- import Dialog from '../Dialog'
17
- import FormFieldInputText from '../FormFieldInputText'
18
- import FormSubmitButton from '../FormSubmitButton'
19
-
20
- type Props = {
21
- children: ReactNode
22
- dialog: DialogTagEditProps
23
- }
24
-
25
- const DialogTagEdit = (props: Props) => {
26
- const {
27
- children,
28
- dialog: {id, tagId},
29
- } = props
30
-
31
- const client = useVersionedClient()
32
-
33
- const dispatch = useDispatch()
34
- const tagItem = useTypedSelector((state) => selectTagById(state, String(tagId))) // TODO: double check string cast
35
-
36
- // - Generate a snapshot of the current tag
37
- const [tagSnapshot, setTagSnapshot] = useState(tagItem?.tag)
38
-
39
- const currentTag = tagItem ? tagItem?.tag : tagSnapshot
40
- const generateDefaultValues = (tag?: Tag) => ({
41
- name: tag?.name?.current || '',
42
- })
43
-
44
- const {
45
- // Read the formState before render to subscribe the form state through Proxy
46
- formState: {errors, isDirty, isValid},
47
- handleSubmit,
48
- register,
49
- reset,
50
- setError,
51
- } = useForm<TagFormData>({
52
- defaultValues: generateDefaultValues(tagItem?.tag),
53
- mode: 'onChange',
54
- resolver: zodFormResolver<TagFormData>(tagFormSchema),
55
- })
56
-
57
- const formUpdating = !tagItem || tagItem?.updating
58
-
59
- const handleClose = () => {
60
- dispatch(dialogActions.remove({id}))
61
- }
62
-
63
- // Submit react-hook-form
64
- const onSubmit: SubmitHandler<TagFormData> = (formData) => {
65
- if (!tagItem?.tag) {
66
- return
67
- }
68
- const sanitizedFormData = sanitizeFormData(formData)
69
- dispatch(
70
- tagsActions.updateRequest({
71
- closeDialogId: tagItem?.tag?._id,
72
- formData: {
73
- name: {
74
- _type: 'slug',
75
- current: sanitizedFormData.name,
76
- },
77
- },
78
- tag: tagItem?.tag,
79
- }),
80
- )
81
- }
82
-
83
- const handleDelete = () => {
84
- if (!tagItem?.tag) {
85
- return
86
- }
87
-
88
- dispatch(
89
- dialogActions.showConfirmDeleteTag({
90
- closeDialogId: tagItem?.tag?._id,
91
- tag: tagItem?.tag,
92
- }),
93
- )
94
- }
95
-
96
- const handleTagUpdate = useCallback(
97
- (update: MutationEvent) => {
98
- const {result, transition} = update
99
- if (result && transition === 'update') {
100
- // Regenerate snapshot
101
- setTagSnapshot(result as Tag)
102
- // Reset react-hook-form
103
- reset(generateDefaultValues(result as Tag))
104
- }
105
- },
106
- [reset],
107
- )
108
-
109
- useEffect(() => {
110
- if (tagItem?.error) {
111
- setError('name', {
112
- message: tagItem.error?.message,
113
- })
114
- }
115
- }, [setError, tagItem.error])
116
-
117
- // - Listen for asset mutations and update snapshot
118
- useEffect(() => {
119
- if (!tagItem?.tag) {
120
- return undefined
121
- }
122
-
123
- // Remember that Sanity listeners ignore joins, order clauses and projections
124
- const subscriptionAsset = client
125
- .listen(groq`*[_id == $id]`, {id: tagItem?.tag._id})
126
- .subscribe(handleTagUpdate)
127
-
128
- return () => {
129
- subscriptionAsset?.unsubscribe()
130
- }
131
- }, [client, handleTagUpdate, tagItem?.tag])
132
-
133
- const Footer = () => (
134
- <Box padding={3}>
135
- <Flex justify="space-between">
136
- {/* Delete button */}
137
- <Button
138
- disabled={formUpdating}
139
- fontSize={1}
140
- mode="bleed"
141
- onClick={handleDelete}
142
- text="Delete"
143
- tone="critical"
144
- />
145
-
146
- {/* Submit button */}
147
- <FormSubmitButton
148
- disabled={formUpdating || !isDirty || !isValid}
149
- isValid={isValid}
150
- lastUpdated={tagItem?.tag?._updatedAt}
151
- onClick={handleSubmit(onSubmit)}
152
- />
153
- </Flex>
154
- </Box>
155
- )
156
-
157
- if (!currentTag) {
158
- return null
159
- }
160
-
161
- return (
162
- <Dialog animate footer={<Footer />} header="Edit Tag" id={id} onClose={handleClose} width={1}>
163
- {/* Form fields */}
164
- <Box as="form" padding={4} onSubmit={handleSubmit(onSubmit)}>
165
- {/* Deleted notification */}
166
- {!tagItem && (
167
- <Card marginBottom={3} padding={3} radius={2} shadow={1} tone="critical">
168
- <Text size={1}>This tag cannot be found – it may have been deleted.</Text>
169
- </Card>
170
- )}
171
-
172
- {/* Hidden button to enable enter key submissions */}
173
- <button style={{display: 'none'}} tabIndex={-1} type="submit" />
174
-
175
- {/* Title */}
176
- <FormFieldInputText
177
- {...register('name')}
178
- disabled={formUpdating}
179
- error={errors?.name?.message}
180
- label="Name"
181
- name="name"
182
- />
183
- </Box>
184
-
185
- {children}
186
- </Dialog>
187
- )
188
- }
189
-
190
- export default DialogTagEdit
@@ -1,45 +0,0 @@
1
- import {Box} from '@sanity/ui'
2
- import {type ReactNode, useCallback} from 'react'
3
- import {useDispatch} from 'react-redux'
4
-
5
- import {dialogActions} from '../../modules/dialog'
6
- import type {DialogTagsProps} from '../../types'
7
- import Dialog from '../Dialog'
8
- import TagView from '../TagView'
9
-
10
- type Props = {
11
- children: ReactNode
12
- dialog: DialogTagsProps
13
- }
14
-
15
- const DialogTags = (props: Props) => {
16
- const {
17
- children,
18
- dialog: {id},
19
- } = props
20
-
21
- // Redux
22
- const dispatch = useDispatch()
23
-
24
- // Callbacks
25
- const handleClose = useCallback(() => {
26
- dispatch(dialogActions.clear())
27
- }, [])
28
-
29
- return (
30
- <Dialog animate header="All Tags" id={id} onClose={handleClose} width={1}>
31
- <Box
32
- style={{
33
- height: '100%',
34
- minHeight: '420px', // explicit height required as <TagView> is virtualized
35
- }}
36
- >
37
- <TagView />
38
- </Box>
39
-
40
- {children}
41
- </Dialog>
42
- )
43
- }
44
-
45
- export default DialogTags
@@ -1,76 +0,0 @@
1
- import useTypedSelector from '../../hooks/useTypedSelector'
2
- import type {Dialog} from '../../types'
3
- import DialogAssetEdit from '../DialogAssetEdit'
4
- import DialogConfirm from '../DialogConfirm'
5
- import DialogSearchFacets from '../DialogSearchFacets'
6
- import DialogTagCreate from '../DialogTagCreate'
7
- import DialogTagEdit from '../DialogTagEdit'
8
- import DialogTags from '../DialogTags'
9
-
10
- const Dialogs = () => {
11
- // Redux
12
- const currentDialogs = useTypedSelector((state) => state.dialog.items)
13
-
14
- const renderDialogs = (dialogs: Dialog[], index: number) => {
15
- if (dialogs.length === 0 || index >= dialogs.length) {
16
- return null
17
- }
18
-
19
- const dialog = dialogs[index]
20
- const childDialogs = renderDialogs(dialogs, index + 1)
21
-
22
- if (dialog.type === 'assetEdit') {
23
- return (
24
- <DialogAssetEdit dialog={dialog} key={index}>
25
- {childDialogs}
26
- </DialogAssetEdit>
27
- )
28
- }
29
-
30
- if (dialog.type === 'confirm') {
31
- return (
32
- <DialogConfirm dialog={dialog} key={index}>
33
- {childDialogs}
34
- </DialogConfirm>
35
- )
36
- }
37
-
38
- if (dialog.type === 'searchFacets') {
39
- return (
40
- <DialogSearchFacets dialog={dialog} key={index}>
41
- {childDialogs}
42
- </DialogSearchFacets>
43
- )
44
- }
45
-
46
- if (dialog.type === 'tagCreate') {
47
- return (
48
- <DialogTagCreate dialog={dialog} key={index}>
49
- {childDialogs}
50
- </DialogTagCreate>
51
- )
52
- }
53
-
54
- if (dialog.type === 'tagEdit') {
55
- return (
56
- <DialogTagEdit dialog={dialog} key={index}>
57
- {childDialogs}
58
- </DialogTagEdit>
59
- )
60
- }
61
-
62
- if (dialog.type === 'tags') {
63
- return (
64
- <DialogTags dialog={dialog} key={index}>
65
- {childDialogs}
66
- </DialogTags>
67
- )
68
- }
69
-
70
- return null
71
- }
72
-
73
- return renderDialogs(currentDialogs, 0)
74
- }
75
-
76
- export default Dialogs