@overmap-ai/forms 1.0.17-master.2 → 1.0.17-master.4

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 (134) hide show
  1. package/dist/forms/typings.d.ts +1 -2
  2. package/dist/forms.js +32 -8
  3. package/dist/forms.umd.cjs +1 -1
  4. package/package.json +5 -2
  5. package/.husky/pre-commit +0 -6
  6. package/.prettierrc.json +0 -10
  7. package/.storybook/StoryDecorator.tsx +0 -22
  8. package/.storybook/main.ts +0 -20
  9. package/.storybook/palettes/green.css +0 -66
  10. package/.storybook/palettes/red.css +0 -66
  11. package/.storybook/preview.css +0 -39
  12. package/.storybook/preview.tsx +0 -31
  13. package/.storybook/tailwind-theme/accentPalette.css +0 -181
  14. package/.storybook/tailwind-theme/backgrounds.css +0 -11
  15. package/.storybook/tailwind-theme/basePalette.css +0 -178
  16. package/dev/publish-alpha.sh +0 -13
  17. package/dev/publish-patch.sh +0 -3
  18. package/eslint.config.js +0 -56
  19. package/src/ColorPicker/ColorPicker.tsx +0 -47
  20. package/src/ColorPicker/index.ts +0 -1
  21. package/src/FileBadge/FileBadge.tsx +0 -27
  22. package/src/FileBadge/index.ts +0 -1
  23. package/src/FileCard/FileCard.stories.tsx +0 -69
  24. package/src/FileCard/FileCard.tsx +0 -53
  25. package/src/FileCard/index.ts +0 -1
  26. package/src/FileIcon/FileIcon.tsx +0 -31
  27. package/src/FileIcon/index.ts +0 -1
  28. package/src/FileViewer/FileViewerProvider.stories.tsx +0 -50
  29. package/src/FileViewer/FileViewerProvider.tsx +0 -72
  30. package/src/FileViewer/context.ts +0 -11
  31. package/src/FileViewer/index.ts +0 -3
  32. package/src/FileViewer/typings.ts +0 -5
  33. package/src/ImageCard/ImageCard.stories.tsx +0 -94
  34. package/src/ImageCard/ImageCard.tsx +0 -82
  35. package/src/ImageCard/index.ts +0 -1
  36. package/src/ImageMarkup/ImageMarkup.stories.tsx +0 -65
  37. package/src/ImageMarkup/ImageMarkup.tsx +0 -268
  38. package/src/ImageMarkup/index.ts +0 -1
  39. package/src/ImageViewer/ImageViewer.stories.tsx +0 -57
  40. package/src/ImageViewer/ImageViewer.tsx +0 -124
  41. package/src/ImageViewer/constants.ts +0 -1
  42. package/src/ImageViewer/index.ts +0 -2
  43. package/src/PDFViewer/PDFViewer.stories.tsx +0 -55
  44. package/src/PDFViewer/PDFViewer.tsx +0 -170
  45. package/src/PDFViewer/constants.ts +0 -1
  46. package/src/PDFViewer/index.ts +0 -2
  47. package/src/SpreadsheetViewer/SpreadsheetViewer.stories.tsx +0 -55
  48. package/src/SpreadsheetViewer/SpreadsheetViewer.tsx +0 -162
  49. package/src/SpreadsheetViewer/constants.ts +0 -8
  50. package/src/SpreadsheetViewer/index.ts +0 -2
  51. package/src/forms/builder/DropDispatch.ts +0 -84
  52. package/src/forms/builder/FieldActions.tsx +0 -155
  53. package/src/forms/builder/FieldBuilder.tsx +0 -386
  54. package/src/forms/builder/FieldSectionWithActions.tsx +0 -260
  55. package/src/forms/builder/FieldWithActions.tsx +0 -129
  56. package/src/forms/builder/FieldsEditor.tsx +0 -180
  57. package/src/forms/builder/FormBuilder.stories.tsx +0 -105
  58. package/src/forms/builder/FormBuilder.tsx +0 -237
  59. package/src/forms/builder/constants.ts +0 -18
  60. package/src/forms/builder/hooks.tsx +0 -24
  61. package/src/forms/builder/index.ts +0 -2
  62. package/src/forms/builder/typings.ts +0 -18
  63. package/src/forms/builder/utils.ts +0 -229
  64. package/src/forms/constants.ts +0 -9
  65. package/src/forms/constantsJsx.tsx +0 -67
  66. package/src/forms/fields/BaseField/BaseField.ts +0 -152
  67. package/src/forms/fields/BaseField/hooks.tsx +0 -60
  68. package/src/forms/fields/BaseField/index.ts +0 -4
  69. package/src/forms/fields/BaseField/layouts.tsx +0 -100
  70. package/src/forms/fields/BaseField/typings.ts +0 -9
  71. package/src/forms/fields/BooleanField/BooleanField.tsx +0 -48
  72. package/src/forms/fields/BooleanField/BooleanInput.tsx +0 -54
  73. package/src/forms/fields/BooleanField/index.ts +0 -2
  74. package/src/forms/fields/CustomField/CustomField.tsx +0 -45
  75. package/src/forms/fields/CustomField/FieldInputClonerField/FieldInputCloner.tsx +0 -25
  76. package/src/forms/fields/CustomField/FieldInputClonerField/FieldInputClonerField.tsx +0 -26
  77. package/src/forms/fields/CustomField/FieldInputClonerField/index.ts +0 -3
  78. package/src/forms/fields/CustomField/FieldInputClonerField/typings.ts +0 -8
  79. package/src/forms/fields/CustomField/index.ts +0 -1
  80. package/src/forms/fields/DateField/DateField.tsx +0 -42
  81. package/src/forms/fields/DateField/DateInput.tsx +0 -39
  82. package/src/forms/fields/DateField/index.ts +0 -2
  83. package/src/forms/fields/FieldSection/FieldSection.tsx +0 -173
  84. package/src/forms/fields/FieldSection/FieldSectionLayout.tsx +0 -56
  85. package/src/forms/fields/FieldSection/index.ts +0 -1
  86. package/src/forms/fields/MultiStringField/MultiStringField.tsx +0 -90
  87. package/src/forms/fields/MultiStringField/MultiStringInput.tsx +0 -207
  88. package/src/forms/fields/MultiStringField/index.ts +0 -2
  89. package/src/forms/fields/NumberField/NumberField.tsx +0 -173
  90. package/src/forms/fields/NumberField/NumberInput.tsx +0 -44
  91. package/src/forms/fields/NumberField/index.ts +0 -2
  92. package/src/forms/fields/QrField/QrField.tsx +0 -38
  93. package/src/forms/fields/QrField/QrInput.module.sass +0 -5
  94. package/src/forms/fields/QrField/QrInput.tsx +0 -144
  95. package/src/forms/fields/QrField/index.ts +0 -2
  96. package/src/forms/fields/SelectField/BaseSelectField.ts +0 -73
  97. package/src/forms/fields/SelectField/MultiSelectField.tsx +0 -53
  98. package/src/forms/fields/SelectField/MultiSelectInput.tsx +0 -80
  99. package/src/forms/fields/SelectField/SelectField.tsx +0 -49
  100. package/src/forms/fields/SelectField/SelectInput.tsx +0 -69
  101. package/src/forms/fields/SelectField/index.ts +0 -4
  102. package/src/forms/fields/StringOrTextFields/StringField/StringField.tsx +0 -61
  103. package/src/forms/fields/StringOrTextFields/StringField/StringInput.tsx +0 -41
  104. package/src/forms/fields/StringOrTextFields/StringField/index.ts +0 -2
  105. package/src/forms/fields/StringOrTextFields/StringOrTextField.ts +0 -143
  106. package/src/forms/fields/StringOrTextFields/TextField/TextField.tsx +0 -52
  107. package/src/forms/fields/StringOrTextFields/TextField/TextInput.tsx +0 -42
  108. package/src/forms/fields/StringOrTextFields/TextField/index.ts +0 -2
  109. package/src/forms/fields/StringOrTextFields/index.ts +0 -2
  110. package/src/forms/fields/UploadField/UploadField.tsx +0 -156
  111. package/src/forms/fields/UploadField/UploadInput.tsx +0 -220
  112. package/src/forms/fields/UploadField/index.ts +0 -2
  113. package/src/forms/fields/UploadField/utils.ts +0 -17
  114. package/src/forms/fields/constants.ts +0 -43
  115. package/src/forms/fields/hooks.tsx +0 -26
  116. package/src/forms/fields/index.ts +0 -12
  117. package/src/forms/fields/typings.ts +0 -45
  118. package/src/forms/fields/utils.ts +0 -125
  119. package/src/forms/index.ts +0 -5
  120. package/src/forms/renderer/FormRenderer/FormRenderer.stories.tsx +0 -142
  121. package/src/forms/renderer/FormRenderer/FormRenderer.tsx +0 -135
  122. package/src/forms/renderer/PatchForm/Field.tsx +0 -41
  123. package/src/forms/renderer/PatchForm/PatchForm.stories.tsx +0 -91
  124. package/src/forms/renderer/PatchForm/Provider.tsx +0 -119
  125. package/src/forms/renderer/PatchForm/index.ts +0 -2
  126. package/src/forms/renderer/index.ts +0 -2
  127. package/src/forms/typings.ts +0 -162
  128. package/src/forms/utils.ts +0 -69
  129. package/src/index.ts +0 -11
  130. package/src/vite-env.d.ts +0 -1
  131. package/tailwind.config.ts +0 -8
  132. package/tsconfig.json +0 -26
  133. package/tsconfig.node.json +0 -10
  134. package/vite.config.ts +0 -23
@@ -1,55 +0,0 @@
1
- import { Button } from "@overmap-ai/blocks"
2
- import type { Meta } from "@storybook/react"
3
- import { ChangeEvent, useCallback, useRef, useState } from "react"
4
-
5
- import { SpreadsheetViewer } from "./SpreadsheetViewer"
6
-
7
- const meta = {
8
- title: "Components/SpreadsheetViewer",
9
- component: SpreadsheetViewer,
10
- tags: ["autodocs"],
11
- parameters: {
12
- disablePanel: true,
13
- },
14
- } satisfies Meta<typeof SpreadsheetViewer>
15
-
16
- export default meta
17
-
18
- export const Basic = () => {
19
- const inputRef = useRef<HTMLInputElement>(null)
20
- const [file, setFile] = useState<File | null>(null)
21
- const [open, setOpen] = useState(false)
22
-
23
- const handleChange = useCallback((e: ChangeEvent<HTMLInputElement>) => {
24
- if (e.target.files) {
25
- const files = Array.from(e.target.files)
26
- if (files.length > 0) {
27
- setFile(files[0] || null)
28
- }
29
- }
30
- }, [])
31
-
32
- return (
33
- <>
34
- <input ref={inputRef} type="file" onChange={handleChange} />
35
- <Button
36
- onClick={() => {
37
- setOpen(!open)
38
- }}
39
- >
40
- open
41
- </Button>
42
- {file && open && (
43
- <SpreadsheetViewer
44
- file={file}
45
- onClose={() => {
46
- setOpen(false)
47
- }}
48
- onDelete={() => {
49
- setFile(null)
50
- }}
51
- />
52
- )}
53
- </>
54
- )
55
- }
@@ -1,162 +0,0 @@
1
- import { Badge, ButtonGroup, IconButton, RiIcon, Spinner, useViewportSize } from "@overmap-ai/blocks"
2
- import { downloadFile } from "@overmap-ai/core"
3
- import * as RadixDialog from "@radix-ui/react-dialog"
4
- import { lazy, memo, Suspense, useCallback, useEffect, useMemo, useState } from "react"
5
- import { read, utils, WorkSheet } from "xlsx"
6
-
7
- import { FileBadge } from "../FileBadge"
8
-
9
- interface SpreadsheetViewerProps {
10
- file: File
11
- onClose: () => void
12
- onDelete?: (file: File) => void
13
- }
14
- export const SpreadsheetViewer = memo((props: SpreadsheetViewerProps) => {
15
- const { file, onClose, onDelete } = props
16
- const { md } = useViewportSize()
17
- const [sheetNames, setSheetNames] = useState<string[]>([])
18
- const [sheets, setSheets] = useState<Record<string, WorkSheet>>({})
19
- const [activeSheet, setActiveSheet] = useState<string | undefined>()
20
- const [data, setData] = useState<{
21
- rowLabels: string[]
22
- columnLabels: string[]
23
- data: { value: unknown; readOnly: boolean }[][]
24
- }>({
25
- rowLabels: [],
26
- columnLabels: [],
27
- data: [[]],
28
- })
29
-
30
- const Spreadsheet = useMemo(() => {
31
- return lazy(() => import("react-spreadsheet"))
32
- }, [file, activeSheet])
33
-
34
- useEffect(() => {
35
- void file.arrayBuffer().then((result) => {
36
- const { SheetNames, Sheets } = read(result)
37
- setSheetNames(SheetNames)
38
- setSheets(Sheets)
39
- setActiveSheet(SheetNames[0])
40
- })
41
- }, [file])
42
-
43
- useEffect(() => {
44
- if (!activeSheet) return
45
- const worksheet = sheets[activeSheet]!
46
- const sheetAsJson = utils.sheet_to_json<NonNullable<unknown>>(worksheet)
47
- const columnLabels = Object.keys(sheetAsJson[0] ?? {}).splice(1)
48
- const rowLabels = sheetAsJson.map((row) => Object.values(row)[0] as string)
49
-
50
- // Extract data based on column labels for each row, format as expected by "react-spreadsheet"
51
- const data = sheetAsJson.map((row: Record<string, unknown>) => {
52
- return columnLabels.map((columnLabel) => ({ value: row[columnLabel], readOnly: true }))
53
- })
54
- setData({ rowLabels, columnLabels, data })
55
- }, [activeSheet, sheets])
56
-
57
- const handleDelete = useCallback(() => {
58
- if (onDelete) onDelete(file)
59
- }, [file, onDelete])
60
-
61
- const handleDownload = useCallback(() => {
62
- downloadFile(file)
63
- }, [file])
64
-
65
- const handleNextSheet = useCallback(() => {
66
- if (!activeSheet) throw new Error(`Expected an activeSheet, got ${activeSheet}`)
67
- const activeSheetIndex = sheetNames.indexOf(activeSheet)
68
- if (activeSheetIndex < 0)
69
- throw new Error(
70
- `Expected activeSheet to be present in sheets, ${activeSheet} not found in ${sheetNames.join(", ")}`,
71
- )
72
-
73
- if (activeSheetIndex === sheetNames.length - 1) return
74
-
75
- setActiveSheet(sheetNames[activeSheetIndex + 1])
76
- }, [activeSheet, sheetNames])
77
-
78
- const handlePreviousSheet = useCallback(() => {
79
- if (!activeSheet) throw new Error(`Expected an activeSheet, got ${activeSheet}`)
80
- const activeSheetIndex = sheetNames.indexOf(activeSheet)
81
- if (activeSheetIndex < 0)
82
- throw new Error(
83
- `Expected activeSheet to be present in sheets, ${activeSheet} not found in ${sheetNames.join(", ")}`,
84
- )
85
-
86
- if (activeSheetIndex === 0) return
87
-
88
- setActiveSheet(sheetNames[activeSheetIndex - 1])
89
- }, [activeSheet, sheetNames])
90
-
91
- return (
92
- <RadixDialog.Root open onOpenChange={onClose}>
93
- <RadixDialog.Portal>
94
- <RadixDialog.Overlay className="light:bg-(--black-a6) fixed inset-0 dark:bg-(--black-a8)" />
95
- <RadixDialog.Content className="fixed inset-0">
96
- <div className="flex h-full w-full flex-col overflow-hidden">
97
- <div className="h-max w-full shrink-0 bg-(--color-background) p-2">
98
- <ButtonGroup size="md" variant="soft" accentColor="base">
99
- <div className="grid w-full grid-cols-3">
100
- <div className="flex gap-2 items-center">
101
- <IconButton onClick={onClose} aria-label="close">
102
- <RiIcon icon="RiCloseLine" />
103
- </IconButton>
104
- <IconButton onClick={handleDownload} aria-label="download">
105
- <RiIcon icon="RiDownload2Line" />
106
- </IconButton>
107
- </div>
108
- <div className="flex justify-center">
109
- <FileBadge
110
- file={file}
111
- accentColor="base"
112
- truncateLength={!md ? 25 : undefined}
113
- />
114
- </div>
115
- <div className="flex justify-end gap-2">
116
- {!!onDelete && (
117
- <IconButton aria-label="delete" onClick={handleDelete}>
118
- <RiIcon icon="RiDeleteBin2Line" />
119
- </IconButton>
120
- )}
121
- </div>
122
- </div>
123
- </ButtonGroup>
124
- </div>
125
- <div className="flex h-full w-full flex-col items-center gap-3 overflow-hidden p-2">
126
- <Suspense fallback={<Spinner />}>
127
- <div className="flex h-max max-h-[calc(100%-50px)] w-max max-w-full overflow-auto [scrollbar-color:var(--base-6)_transparent] [scrollbar-width:thin]">
128
- <Spreadsheet
129
- className="z-[1] h-full"
130
- data={data.data}
131
- rowLabels={data.rowLabels}
132
- columnLabels={data.columnLabels}
133
- // TODO: set darkMode
134
- />
135
- </div>
136
- <div className="flex h-max shrink-0 bg-(--base-2)">
137
- <ButtonGroup
138
- className="flex items-center"
139
- variant="solid"
140
- accentColor="base"
141
- size="sm"
142
- >
143
- <IconButton aria-label="previous" onClick={handlePreviousSheet}>
144
- <RiIcon icon="RiArrowLeftLine" />
145
- </IconButton>
146
- <Badge variant="solid" size="sm" style={{ borderRadius: 0 }} accentColor="base">
147
- {activeSheet}
148
- </Badge>
149
- <IconButton aria-label="next" onClick={handleNextSheet}>
150
- <RiIcon icon="RiArrowRightLine" />
151
- </IconButton>
152
- </ButtonGroup>
153
- </div>
154
- </Suspense>
155
- </div>
156
- </div>
157
- </RadixDialog.Content>
158
- </RadixDialog.Portal>
159
- </RadixDialog.Root>
160
- )
161
- })
162
- SpreadsheetViewer.displayName = "SpreadsheetViewer"
@@ -1,8 +0,0 @@
1
- export const SUPPORTED_SPREADSHEET_FILE_EXTENSIONS = [
2
- // .csv
3
- "text/csv",
4
- // .xlsx
5
- "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
6
- // .xls
7
- "application/vnd.ms-excel",
8
- ]
@@ -1,2 +0,0 @@
1
- export * from "./constants"
2
- export * from "./SpreadsheetViewer"
@@ -1,84 +0,0 @@
1
- import { Reducer } from "react"
2
-
3
- import { SerializedFieldSection } from "../typings"
4
-
5
- export interface SmallFieldSection {
6
- /** is dropping disabled in this section? */
7
- disabled: boolean
8
- /** id's of field that may not be dropped on this section */
9
- conditionFields: Set<string>
10
- /** the index of the section the condition is in */
11
- conditionIndex?: number
12
- /** the index of the section */
13
- index: number
14
- /** label of the section */
15
- label: string | null
16
- }
17
-
18
- // section id -> section state
19
- export type DropState = Record<string, SmallFieldSection>
20
-
21
- export type DropAction = { type: "release" } | { type: "hold"; fieldId: string } | { type: "update"; state: DropState }
22
-
23
- export const reducer: Reducer<DropState, DropAction> = (state, action) => {
24
- const next = { ...state }
25
-
26
- switch (action.type) {
27
- case "release":
28
- for (const sectionId in next) {
29
- next[sectionId]!.disabled = false
30
- }
31
- return next
32
- case "hold":
33
- for (const sectionId in next) {
34
- if (next[sectionId]?.conditionFields.has(action.fieldId)) {
35
- next[sectionId]!.disabled = true
36
- }
37
- }
38
- return next
39
- case "update":
40
- return action.state
41
- }
42
- }
43
-
44
- /** @returns the index of the section containing the field */
45
- const getConditionIndex = (fields: SerializedFieldSection[], identifier?: string): number | undefined => {
46
- if (!identifier) return undefined
47
-
48
- for (let i = 0; i < fields.length; i++) {
49
- const section = fields[i]
50
-
51
- if (!section) continue
52
-
53
- for (const field of section.fields) {
54
- if (field.identifier === identifier) return i
55
- }
56
- }
57
- }
58
-
59
- /** creates a `DropState` from a list of `SerializedFieldSection` objects */
60
- export const initializer = (fields: SerializedFieldSection[]): DropState => {
61
- const acc: DropState = {}
62
-
63
- for (let index = 0; index < fields.length; index++) {
64
- const field = fields[index]
65
- if (!field) throw new Error("Field is undefined.")
66
-
67
- // include any fields that are disabled by the condition of the previous field
68
- const previousConditionFields = index > 0 ? acc[fields[index - 1]!.identifier]?.conditionFields : undefined
69
- const disabledFields = new Set<string>(previousConditionFields)
70
- if (field.condition?.identifier) {
71
- disabledFields.add(field.condition.identifier)
72
- }
73
-
74
- acc[field.identifier] = {
75
- disabled: false,
76
- conditionFields: disabledFields,
77
- conditionIndex: getConditionIndex(fields, field.condition?.identifier),
78
- index,
79
- label: field.label,
80
- }
81
- }
82
-
83
- return acc
84
- }
@@ -1,155 +0,0 @@
1
- import { IconButton, RiIcon } from "@overmap-ai/blocks"
2
- import { Menu } from "@overmap-ai/blocks"
3
- import { useFormikContext } from "formik"
4
- import { ChangeEvent, ComponentProps, memo, ReactNode, useMemo, useRef } from "react"
5
- import type { IconType } from "react-icons"
6
- import { RiArrowDownLine, RiArrowUpLine, RiDeleteBin2Line, RiFileCopyLine, RiImageLine } from "react-icons/ri"
7
-
8
- import { FieldTypeIdentifier } from "../typings"
9
- import { FormikUserFormRevision } from "./typings"
10
-
11
- interface FieldActionsProps {
12
- index: number
13
- type: FieldTypeIdentifier
14
- sectionIndex?: number
15
- remove: () => void
16
- duplicate: () => void
17
- move: (direction: "up" | "down") => void
18
- upload?: (event: ChangeEvent<HTMLInputElement>) => void
19
- }
20
-
21
- interface ActionsType {
22
- Icon: IconType
23
- buttonProps?: ComponentProps<"div">
24
- key: string
25
- text: string
26
- }
27
-
28
- export const FieldActions = memo((props: FieldActionsProps) => {
29
- const { index, type, sectionIndex, remove, duplicate, move, upload } = props
30
- // Upload function prop must be defined for non-section fields
31
- if (type !== "section" && !upload) {
32
- throw new Error("Upload function prop must be defined for non-section fields.")
33
- }
34
-
35
- const { values } = useFormikContext<FormikUserFormRevision>()
36
- const fileInputRef = useRef<HTMLInputElement>(null)
37
-
38
- const actions: ActionsType[] = useMemo(() => {
39
- const actions = [
40
- {
41
- Icon: RiFileCopyLine,
42
- key: "duplicate",
43
- text: "Duplicate",
44
- buttonProps: { onClick: duplicate },
45
- },
46
- ]
47
- // Show delete button only if not first section
48
- if (index !== 0) {
49
- actions.push({
50
- Icon: RiDeleteBin2Line,
51
- key: "delete",
52
- text: "Delete",
53
- buttonProps: { onClick: remove },
54
- })
55
- }
56
-
57
- // Show upload button for non-section fields
58
- if (type !== "section") {
59
- actions.unshift({
60
- Icon: RiImageLine,
61
- key: "upload",
62
- text: "Upload image",
63
- buttonProps: {
64
- onClick: () => {
65
- fileInputRef.current?.click()
66
- },
67
- },
68
- })
69
- }
70
- // Only show down arrow if it's not the last section or last field in the last section
71
- if (
72
- (sectionIndex === undefined && index !== values.fields.length - 1) ||
73
- (sectionIndex !== undefined &&
74
- (sectionIndex < values.fields.length - 1 || index !== values.fields[sectionIndex]!.fields.length - 1))
75
- ) {
76
- actions.unshift({
77
- Icon: RiArrowDownLine,
78
- key: "moveDown",
79
- text: "Move down",
80
- buttonProps: {
81
- onClick: () => {
82
- move("down")
83
- },
84
- },
85
- })
86
- }
87
- // Only show up arrow if it's not the first section or first field in the first section
88
- if (
89
- (sectionIndex === undefined && index !== 0) ||
90
- (sectionIndex !== undefined && (sectionIndex !== 0 || index !== 0))
91
- ) {
92
- actions.unshift({
93
- Icon: RiArrowUpLine,
94
- key: "moveUp",
95
- text: "Move up",
96
- buttonProps: {
97
- onClick: () => {
98
- move("up")
99
- },
100
- },
101
- })
102
- }
103
-
104
- return actions
105
- }, [duplicate, index, move, remove, sectionIndex, type, values.fields])
106
-
107
- return (
108
- <>
109
- {/* For tablet and up */}
110
- <div className="mx-2 flex hidden flex-col gap-5 sm:block">
111
- {actions.map((Action) => {
112
- const Icon = Action.Icon as () => ReactNode
113
- return (
114
- <IconButton
115
- key={Action.key}
116
- type="button"
117
- variant="ghost"
118
- accentColor={Action.key.startsWith("move") ? "base" : undefined}
119
- aria-label={Action.text}
120
- {...(Action.buttonProps as Omit<ComponentProps<typeof IconButton>, "aria-label">)}
121
- >
122
- <Icon />
123
- </IconButton>
124
- )
125
- })}
126
- </div>
127
- {/* For mobile devices */}
128
- <div className="sm:hidden">
129
- <Menu.Root>
130
- <Menu.ClickTrigger>
131
- <IconButton variant="ghost" aria-label="Actions menu">
132
- <RiIcon icon="RiMore2Line" />
133
- </IconButton>
134
- </Menu.ClickTrigger>
135
- <Menu.Content>
136
- {actions.map((Action) => {
137
- const Icon = Action.Icon as () => ReactNode
138
- return (
139
- <Menu.Item key={Action.key} onClick={Action.buttonProps?.onClick}>
140
- <Icon />
141
- {Action.text}
142
- </Menu.Item>
143
- )
144
- })}
145
- </Menu.Content>
146
- </Menu.Root>
147
- </div>
148
- {type !== "section" && (
149
- <input style={{ display: "none" }} ref={fileInputRef} type="file" accept="image/*" onChange={upload} />
150
- )}
151
- </>
152
- )
153
- })
154
-
155
- FieldActions.displayName = "FieldActions"