@sanity/table 2.0.0 → 3.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.
@@ -1,260 +0,0 @@
1
- /* eslint-disable consistent-return */
2
- import { AddIcon } from '@sanity/icons';
3
- import { Box, Button, Card, Dialog, Flex, Inline, Text } from '@sanity/ui';
4
- import { uuid } from '@sanity/uuid';
5
- import { type FormEvent, useState } from 'react';
6
- import { type ObjectInputProps, set, unset } from 'sanity';
7
-
8
- import { TableInput } from './TableInput';
9
- import { TableMenu } from './TableMenu';
10
-
11
- const deepClone: <T>(data: T) => T =
12
- globalThis.structuredClone ?? (data => JSON.parse(JSON.stringify(data)));
13
-
14
- export interface TableValue {
15
- _type: 'table';
16
- rows: TableRow[];
17
- }
18
-
19
- export type TableProps = ObjectInputProps<TableValue>;
20
-
21
- export type TableRow = {
22
- _type: string;
23
- _key: string;
24
- cells: string[];
25
- };
26
-
27
- // TODO refactor deeplone stuff to use proper patches
28
- // TODO use callback all the things
29
-
30
- export const TableComponent = (props: TableProps & { rowType?: string }) => {
31
- const { rowType = 'tableRow', value, onChange } = props;
32
- const [dialog, setDialog] = useState<{
33
- type: string;
34
- callback: () => void;
35
- } | null>(null);
36
-
37
- const updateValue = (v?: Omit<TableValue, '_type'>) => {
38
- return onChange(set(v));
39
- };
40
-
41
- const resetValue = () => {
42
- return onChange(unset());
43
- };
44
-
45
- const createTable = () => {
46
- const newValue: Omit<TableValue, '_type'> = {
47
- rows: [
48
- {
49
- _type: rowType,
50
- _key: uuid(),
51
- cells: ['', ''],
52
- },
53
- {
54
- _type: rowType,
55
- _key: uuid(),
56
- cells: ['', ''],
57
- },
58
- ],
59
- };
60
- return updateValue({ ...value, ...newValue });
61
- };
62
-
63
- const confirmRemoveTable = () => {
64
- setDialog({ type: 'table', callback: removeTable });
65
- };
66
-
67
- const removeTable = () => {
68
- resetValue();
69
- setDialog(null);
70
- };
71
-
72
- const addRows = (count = 1) => {
73
- if (!value) {
74
- return;
75
- }
76
- const newValue = deepClone(value);
77
- // Calculate the column count from the first row
78
- const columnCount = value?.rows[0].cells.length ?? 0;
79
- for (let i = 0; i < count; i++) {
80
- // Add as many cells as we have columns
81
- newValue.rows.push({
82
- _type: rowType,
83
- _key: uuid(),
84
- cells: Array(columnCount).fill(''),
85
- });
86
- }
87
- // eslint-disable-next-line consistent-return
88
- return updateValue(newValue);
89
- };
90
-
91
- const addRowAt = (index = 0) => {
92
- if (!value) {
93
- return;
94
- }
95
- const newValue = deepClone(value);
96
- // Calculate the column count from the first row
97
- const columnCount = value.rows[0].cells.length;
98
-
99
- newValue.rows.splice(index, 0, {
100
- _type: rowType,
101
- _key: uuid(),
102
- cells: Array(columnCount).fill(''),
103
- });
104
-
105
- // eslint-disable-next-line consistent-return
106
- return updateValue(newValue);
107
- };
108
-
109
- const removeRow = (index: number) => {
110
- if (!value) {
111
- return;
112
- }
113
- const newValue = deepClone(value);
114
- newValue.rows.splice(index, 1);
115
- updateValue(newValue);
116
- setDialog(null);
117
- };
118
-
119
- const confirmRemoveRow = (index: number) => {
120
- if (!value) {
121
- return;
122
- }
123
- if (value.rows.length <= 1) return confirmRemoveTable();
124
- return setDialog({ type: 'row', callback: () => removeRow(index) });
125
- };
126
-
127
- const confirmRemoveColumn = (index: number) => {
128
- if (!value) {
129
- return;
130
- }
131
- if (value.rows[0].cells.length <= 1) return confirmRemoveTable();
132
- return setDialog({ type: 'column', callback: () => removeColumn(index) });
133
- };
134
-
135
- const addColumns = (count: number) => {
136
- if (!value) {
137
- return;
138
- }
139
- const newValue = deepClone(value);
140
- // Add a cell to each of the rows
141
- newValue.rows.forEach((_, i) => {
142
- for (let j = 0; j < count; j++) {
143
- newValue.rows[i].cells.push('');
144
- }
145
- });
146
- return updateValue(newValue);
147
- };
148
-
149
- const addColumnAt = (index: number) => {
150
- if (!value) {
151
- return;
152
- }
153
- const newValue = deepClone(value);
154
-
155
- newValue.rows.forEach((_, i) => {
156
- newValue.rows[i].cells.splice(index, 0, '');
157
- });
158
-
159
- return updateValue(newValue);
160
- };
161
-
162
- const removeColumn = (index: number) => {
163
- if (!value) {
164
- return;
165
- }
166
- const newValue = deepClone(value);
167
- newValue.rows.forEach(row => {
168
- row.cells.splice(index, 1);
169
- });
170
- updateValue(newValue);
171
- setDialog(null);
172
- };
173
-
174
- const updateCell = (
175
- e: FormEvent<HTMLInputElement>,
176
- rowIndex: number,
177
- cellIndex: number
178
- ) => {
179
- if (!value) {
180
- return;
181
- }
182
- const newValue = deepClone(value);
183
- newValue.rows[rowIndex].cells[cellIndex] = (
184
- e.target as HTMLInputElement
185
- ).value;
186
- return updateValue(newValue);
187
- };
188
-
189
- return (
190
- <div>
191
- {dialog && (
192
- <Dialog
193
- header={`Remove ${dialog.type}`}
194
- id="dialog-remove"
195
- onClose={() => setDialog(null)}
196
- zOffset={1000}
197
- >
198
- <Card padding={4}>
199
- <Text>Are you sure you want to remove this {dialog.type}?</Text>
200
- <Box marginTop={4}>
201
- <Inline space={1} style={{ textAlign: 'right' }}>
202
- <Button
203
- text="Cancel"
204
- mode="ghost"
205
- onClick={() => setDialog(null)}
206
- />
207
- <Button
208
- text="Confirm"
209
- tone="critical"
210
- onClick={() => dialog.callback()}
211
- />
212
- </Inline>
213
- </Box>
214
- </Card>
215
- </Dialog>
216
- )}
217
- <Box>
218
- <Flex justify="flex-end">
219
- {value?.rows?.length && (
220
- <TableMenu
221
- addColumns={addColumns}
222
- addColumnAt={addColumnAt}
223
- addRows={addRows}
224
- addRowAt={addRowAt}
225
- remove={confirmRemoveTable}
226
- placement="left"
227
- />
228
- )}
229
- </Flex>
230
- </Box>
231
- {value?.rows?.length && (
232
- <TableInput
233
- rows={value.rows}
234
- removeRow={confirmRemoveRow}
235
- removeColumn={confirmRemoveColumn}
236
- updateCell={updateCell}
237
- />
238
- )}
239
- {(!value || !value?.rows?.length) && (
240
- <Inline space={1}>
241
- <Button
242
- fontSize={1}
243
- padding={3}
244
- icon={AddIcon}
245
- text="Create Table"
246
- tone="primary"
247
- mode="ghost"
248
- onClick={createTable}
249
- />
250
- </Inline>
251
- )}
252
- </div>
253
- );
254
- };
255
-
256
- export function createTableComponent(rowType: string) {
257
- return function Table(props: TableProps) {
258
- return <TableComponent {...props} rowType={rowType} />;
259
- };
260
- }
@@ -1,14 +0,0 @@
1
- export function TableIcon() {
2
- return (
3
- <svg
4
- width="1em"
5
- height="1em"
6
- viewBox="0 0 25 25"
7
- fill="none"
8
- stroke="currentColor"
9
- strokeWidth="1.2"
10
- >
11
- <path d="M3 3h18v18H3zM21 9H3M21 15H3M12 3v18" />
12
- </svg>
13
- );
14
- }
@@ -1,78 +0,0 @@
1
- import { RemoveIcon } from '@sanity/icons';
2
- import { Box, Button, TextInput } from '@sanity/ui';
3
- import type { FormEvent } from 'react';
4
-
5
- import type { TableRow } from './TableComponent';
6
-
7
- interface TableInputProps {
8
- rows: TableRow[];
9
- updateCell: (
10
- e: FormEvent<HTMLInputElement>,
11
- rowIndex: number,
12
- cellIndex: number
13
- ) => void;
14
- removeRow: (index: number) => void;
15
- removeColumn: (index: number) => void;
16
- }
17
-
18
- export const TableInput = (props: TableInputProps) => {
19
- const updateCell = props.updateCell;
20
-
21
- const renderRowCell = (rowIndex: number) =>
22
- function RowCell(cell: string, cellIndex: number) {
23
- return (
24
- <td key={`cell-${rowIndex}-${cellIndex}`}>
25
- <TextInput
26
- fontSize={1}
27
- padding={3}
28
- value={cell}
29
- onChange={e => updateCell(e, rowIndex, cellIndex)}
30
- />
31
- </td>
32
- );
33
- };
34
-
35
- const renderRow = (row: TableRow, rowIndex: number) => {
36
- const renderCell = renderRowCell(rowIndex);
37
-
38
- return (
39
- <tr key={`row-${rowIndex}`}>
40
- {row.cells.map(renderCell)}
41
- {
42
- <td key={rowIndex}>
43
- <Box marginLeft={1} style={{ textAlign: 'center' }}>
44
- <Button
45
- icon={RemoveIcon}
46
- padding={2}
47
- onClick={() => props.removeRow(rowIndex)}
48
- mode="bleed"
49
- />
50
- </Box>
51
- </td>
52
- }
53
- </tr>
54
- );
55
- };
56
-
57
- return (
58
- <table style={{ width: '100%' }}>
59
- <tbody>
60
- {props.rows.map(renderRow)}
61
- <tr>
62
- {(props.rows[0]?.cells || []).map((_, i) => (
63
- <td key={i}>
64
- <Box marginTop={1} style={{ textAlign: 'center' }}>
65
- <Button
66
- icon={RemoveIcon}
67
- padding={2}
68
- onClick={() => props.removeColumn(i)}
69
- mode="bleed"
70
- />
71
- </Box>
72
- </td>
73
- ))}
74
- </tr>
75
- </tbody>
76
- </table>
77
- );
78
- };
@@ -1,144 +0,0 @@
1
- import { AddIcon, ControlsIcon, WarningOutlineIcon } from '@sanity/icons';
2
- import {
3
- Box,
4
- Button,
5
- Card,
6
- Dialog,
7
- Inline,
8
- Menu,
9
- MenuButton,
10
- MenuDivider,
11
- MenuItem,
12
- Placement,
13
- TextInput,
14
- } from '@sanity/ui';
15
- import { type FormEventHandler, useState } from 'react';
16
-
17
- interface TableMenuProps {
18
- addColumns: (count: number) => void;
19
- addColumnAt: (index: number) => void;
20
- addRows: (count: number) => void;
21
- addRowAt: (index: number) => void;
22
- remove: () => void;
23
- placement: Placement;
24
- }
25
-
26
- export const TableMenu = (props: TableMenuProps) => {
27
- const { remove: handleRemove } = props;
28
- const [dialog, setDialog] = useState<{
29
- type: string;
30
- callback: (count: number) => void;
31
- } | null>(null);
32
-
33
- const [count, setCount] = useState<string | undefined>('');
34
-
35
- const updateCount: FormEventHandler<HTMLInputElement> = e => {
36
- setCount(e.currentTarget.value);
37
- };
38
-
39
- const addRows = () => {
40
- setDialog({ type: 'rows', callback: c => props.addRows(c) });
41
- };
42
-
43
- const addRowAt = () => {
44
- setDialog({ type: 'rows', callback: index => props.addRowAt(index) });
45
- };
46
-
47
- const addColumns = () => {
48
- setDialog({
49
- type: 'columns',
50
- callback: c => props.addColumns(c),
51
- });
52
- };
53
-
54
- const addColumnsAt = () => {
55
- setDialog({ type: 'columns', callback: index => props.addColumnAt(index) });
56
- };
57
-
58
- const onConfirm = () => {
59
- const parsedCount = parseInt(count ?? '0', 10);
60
-
61
- if (parsedCount < 100) {
62
- setDialog(null);
63
- dialog?.callback(parsedCount);
64
- setCount(undefined);
65
- }
66
- };
67
-
68
- return (
69
- <>
70
- {dialog && (
71
- <Dialog
72
- header={`Add ${dialog.type}`}
73
- id="dialog-add"
74
- onClose={() => setDialog(null)}
75
- zOffset={1000}
76
- >
77
- <Card padding={4}>
78
- <TextInput
79
- style={{ textAlign: 'left' }}
80
- fontSize={2}
81
- padding={3}
82
- type="number"
83
- value={count}
84
- onChange={updateCount}
85
- />
86
- <Box marginTop={4}>
87
- <Inline space={1} style={{ textAlign: 'right' }}>
88
- <Button
89
- text="Cancel"
90
- mode="ghost"
91
- onClick={() => setDialog(null)}
92
- />
93
- <Button text="Confirm" tone="critical" onClick={onConfirm} />
94
- </Inline>
95
- </Box>
96
- </Card>
97
- </Dialog>
98
- )}
99
- <MenuButton
100
- button={
101
- <Button icon={ControlsIcon} fontSize={1} padding={2} mode="ghost" />
102
- }
103
- id="menu-button-example"
104
- menu={
105
- <Menu>
106
- <MenuItem
107
- icon={AddIcon}
108
- fontSize={1}
109
- text="Add Row(s)"
110
- onClick={addRows}
111
- />
112
- <MenuItem
113
- icon={AddIcon}
114
- fontSize={1}
115
- text="Add Row At Index"
116
- onClick={addRowAt}
117
- />
118
- <MenuItem
119
- icon={AddIcon}
120
- fontSize={1}
121
- text="Add Column(s)"
122
- onClick={addColumns}
123
- />
124
- <MenuItem
125
- icon={AddIcon}
126
- fontSize={1}
127
- text="Add Column At Index"
128
- onClick={addColumnsAt}
129
- />
130
- <MenuDivider />
131
- <MenuItem
132
- icon={WarningOutlineIcon}
133
- fontSize={1}
134
- text="Remove"
135
- tone="critical"
136
- onClick={handleRemove}
137
- />
138
- </Menu>
139
- }
140
- popover={{ placement: props.placement }}
141
- />
142
- </>
143
- );
144
- };
@@ -1,58 +0,0 @@
1
- import { Box, Card, Grid, Inline, Label, Text } from '@sanity/ui';
2
- import type { PreviewProps } from 'sanity';
3
-
4
- import type { TableRow } from './TableComponent';
5
- import { TableIcon } from './TableIcon';
6
-
7
- interface ValueProps {
8
- rows?: TableRow[];
9
- title?: string;
10
- }
11
-
12
- const Table = ({ rows }: { rows: TableRow[] }) => {
13
- const numCols = rows.length === 0 ? 0 : rows[0].cells.length;
14
-
15
- return (
16
- <Grid columns={numCols} padding={2}>
17
- {rows.map(row =>
18
- row.cells.map((cell, i) => (
19
- <Card
20
- key={row._key + i}
21
- padding={2}
22
- style={{ outline: '1px solid #DFE2E9' }}
23
- >
24
- <Text style={{ textOverflow: 'elipsis' }}>{cell}</Text>
25
- </Card>
26
- ))
27
- )}
28
- </Grid>
29
- );
30
- };
31
-
32
- export const TablePreview = (props: ValueProps & PreviewProps) => {
33
- const { schemaType, rows = [], title = 'Title missing' } = props;
34
-
35
- return (
36
- <>
37
- <Box padding={3}>
38
- <Inline space={3}>
39
- <Card>
40
- <Label size={4}>
41
- <TableIcon />
42
- </Label>
43
- </Card>
44
- <Card>
45
- <Text>{schemaType?.title ?? title}</Text>
46
- </Card>
47
- </Inline>
48
- </Box>
49
- <Box padding={2}>
50
- {rows.length === 0 ? (
51
- <Label muted>Empty Table</Label>
52
- ) : (
53
- <Table rows={rows} />
54
- )}
55
- </Box>
56
- </>
57
- );
58
- };
package/src/index.ts DELETED
@@ -1,73 +0,0 @@
1
- import { definePlugin, defineType } from 'sanity';
2
-
3
- import {
4
- createTableComponent,
5
- TableComponent,
6
- } from './components/TableComponent';
7
- import { TablePreview } from './components/TablePreview';
8
- export type {
9
- TableProps,
10
- TableRow,
11
- TableValue,
12
- } from './components/TableComponent';
13
-
14
- export { TableComponent, TablePreview };
15
-
16
- export interface TableConfig {
17
- rowType?: string;
18
- }
19
-
20
- export const table = definePlugin<TableConfig | void>(config => {
21
- const tableRowSchema = defineType({
22
- title: 'Table Row',
23
- name: config?.rowType || 'tableRow',
24
- type: 'object',
25
- fields: [
26
- {
27
- name: 'cells',
28
- type: 'array',
29
- of: [{ type: 'string' }],
30
- },
31
- ],
32
- });
33
-
34
- const tableSchema = defineType({
35
- title: 'Table',
36
- name: 'table',
37
- type: 'object',
38
- fields: [
39
- {
40
- name: 'rows',
41
- type: 'array',
42
- of: [
43
- {
44
- type: tableRowSchema.name,
45
- },
46
- ],
47
- },
48
- ],
49
- components: {
50
- /* eslint-disable @typescript-eslint/no-explicit-any */
51
- input: createTableComponent(tableRowSchema.name) as any,
52
- preview: TablePreview as any,
53
- /* eslint-enable @typescript-eslint/no-explicit-any */
54
- },
55
- preview: {
56
- select: {
57
- rows: 'rows',
58
- title: 'title',
59
- },
60
- prepare: ({ title, rows = [] }) => ({
61
- title,
62
- rows,
63
- }),
64
- },
65
- });
66
-
67
- return {
68
- name: 'table',
69
- schema: {
70
- types: [tableRowSchema, tableSchema],
71
- },
72
- };
73
- });
@@ -1,11 +0,0 @@
1
- const { showIncompatiblePluginDialog } = require('@sanity/incompatible-plugin');
2
- const { name, version, sanityExchangeUrl } = require('./package.json');
3
-
4
- export default showIncompatiblePluginDialog({
5
- name: name,
6
- versions: {
7
- v3: version,
8
- v2: undefined,
9
- },
10
- sanityExchangeUrl,
11
- });