sanity-plugin-stl-table 0.1.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/LICENSE +21 -0
- package/README.md +194 -0
- package/dist/components/TableInput.d.ts +5 -0
- package/dist/components/TableInput.d.ts.map +1 -0
- package/dist/components/TableInput.js +51 -0
- package/dist/components/TablePreview.d.ts +8 -0
- package/dist/components/TablePreview.d.ts.map +1 -0
- package/dist/components/TablePreview.js +27 -0
- package/dist/components/type.d.ts +14 -0
- package/dist/components/type.d.ts.map +1 -0
- package/dist/components/type.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/schema/schema.d.ts +33 -0
- package/dist/schema/schema.d.ts.map +1 -0
- package/dist/schema/schema.js +36 -0
- package/package.json +63 -0
- package/src/components/TableInput.tsx +88 -0
- package/src/components/TablePreview.tsx +49 -0
- package/src/components/type.ts +16 -0
- package/src/index.ts +3 -0
- package/src/schema/schema.ts +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Yashraj Yadav
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
# Sanity Plugin: Structured Table (STL)
|
|
2
|
+
|
|
3
|
+
A Sanity Studio plugin for creating advanced, structured tables using the **Structured Table Language (STL)**.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
This plugin empowers your content creators to build complex tables with features like row/column spanning, headers, and rich styling, effectively overcoming the limitations of standard portable text tables.
|
|
8
|
+
|
|
9
|
+
## ✨ Features
|
|
10
|
+
|
|
11
|
+
- **STL Editor**: A dedicated input component for writing and managing Structured Table Language (STL).
|
|
12
|
+
- **Live Preview**: Real-time preview of your table within the Sanity Studio.
|
|
13
|
+
- **Advanced Layouts**: Support for colspans, rowspans, and complex header structures.
|
|
14
|
+
- **SSR Friendly**: Designed to work seamlessly with Server-Side Rendering (especially Next.js) via the core `structured-table` package.
|
|
15
|
+
|
|
16
|
+
## 📦 Installation
|
|
17
|
+
|
|
18
|
+
### 1. Install the Plugin
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install sanity-plugin-stl-table
|
|
22
|
+
# or
|
|
23
|
+
yarn add sanity-plugin-stl-table
|
|
24
|
+
# or
|
|
25
|
+
pnpm add sanity-plugin-stl-table
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> **Note**: This plugin requires `react` >= 18 and `sanity` >= 3.0.0.
|
|
29
|
+
|
|
30
|
+
### 2. Setup Table Render Components (Required for Studio)
|
|
31
|
+
|
|
32
|
+
To enable the interactive table preview within Sanity Studio, you need to install the CLI and generate the React components.
|
|
33
|
+
|
|
34
|
+
First, install the CLI tool:
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install structured-table-cli
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Then, run the following command to download the pre-built React table components:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npx stl-cli add react
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Optional:** You can specify a custom path for the components:
|
|
47
|
+
```bash
|
|
48
|
+
npx stl-cli add react --path ./schemaTypes/components
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 3. Register Components in Sanity Config
|
|
52
|
+
|
|
53
|
+
After generating the components, you must register them in your `sanity.config.ts`. Import the register file (usually found in the folder where components were installed) at the top of your config.
|
|
54
|
+
|
|
55
|
+
For example, if installed in `schemaTypes/components`:
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// sanity.config.ts
|
|
59
|
+
import './schemaTypes/components/register' // Base path depends on where you installed it
|
|
60
|
+
import { defineConfig } from 'sanity'
|
|
61
|
+
import { stlTableBlock } from 'sanity-plugin-stl-table'
|
|
62
|
+
|
|
63
|
+
export default defineConfig({
|
|
64
|
+
// ... configuration
|
|
65
|
+
})
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 🚀 How to Use
|
|
69
|
+
|
|
70
|
+
### 1. Register the Schema
|
|
71
|
+
|
|
72
|
+
Import the `stlTableBlock` schema definition and add it to your Sanity Studio configuration `types` array.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
// sanity.config.ts
|
|
76
|
+
import { defineConfig } from 'sanity'
|
|
77
|
+
import { stlTableBlock } from 'sanity-plugin-stl-table'
|
|
78
|
+
|
|
79
|
+
export default defineConfig({
|
|
80
|
+
// ...
|
|
81
|
+
schema: {
|
|
82
|
+
types: [
|
|
83
|
+
// ... other types
|
|
84
|
+
stlTableBlock,
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. Use in Portable Text or as a Field
|
|
91
|
+
|
|
92
|
+
You can now use the `stlTableBlock` type in your portable text editors or as a standalone field in your documents.
|
|
93
|
+
|
|
94
|
+
**In Portable Text:**
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
// schemas/blockContent.ts (or similar)
|
|
98
|
+
export default {
|
|
99
|
+
title: 'Block Content',
|
|
100
|
+
name: 'blockContent',
|
|
101
|
+
type: 'array',
|
|
102
|
+
of: [
|
|
103
|
+
{ type: 'block' },
|
|
104
|
+
// Add the table block
|
|
105
|
+
{ type: 'stlTableBlock' },
|
|
106
|
+
],
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**As a Field:**
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
export default {
|
|
114
|
+
name: 'productSpecification',
|
|
115
|
+
title: 'Product Specification',
|
|
116
|
+
type: 'document',
|
|
117
|
+
fields: [
|
|
118
|
+
{
|
|
119
|
+
name: 'title',
|
|
120
|
+
type: 'string',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'specsTable',
|
|
124
|
+
title: 'Specifications Table',
|
|
125
|
+
type: 'stlTableBlock',
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 💻 Frontend Implementation
|
|
132
|
+
|
|
133
|
+
To render the structured tables on your frontend (e.g., Next.js, Remix), follow these steps:
|
|
134
|
+
|
|
135
|
+
### 1. Install Dependencies
|
|
136
|
+
|
|
137
|
+
Install the core package and the CLI (if you haven't already on the frontend repo), then generate the React components.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npm install structured-table structured-table-cli && npx stl-cli add react
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 2. Render in Portable Text
|
|
144
|
+
|
|
145
|
+
Use the generated components within your Portable Text configuration. Here is an example of how to implement the `stlTableBlock`.
|
|
146
|
+
|
|
147
|
+
```tsx
|
|
148
|
+
import * as STLReact from './components/react' // Path to your generated components
|
|
149
|
+
import { STL } from 'structured-table'
|
|
150
|
+
|
|
151
|
+
const myPortableTextComponents = {
|
|
152
|
+
types: {
|
|
153
|
+
stlTableBlock: ({ value }: {
|
|
154
|
+
value: {
|
|
155
|
+
_key: string;
|
|
156
|
+
_type: string;
|
|
157
|
+
stlString: string;
|
|
158
|
+
}
|
|
159
|
+
}) => {
|
|
160
|
+
// Parse the STL string
|
|
161
|
+
const parsedSTL = STL.parse(value.stlString);
|
|
162
|
+
|
|
163
|
+
// Render the table
|
|
164
|
+
return <STLReact.Table data={parsedSTL} className='border' />
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Usage in your PortableText component
|
|
170
|
+
// <PortableText value={data.body} components={myPortableTextComponents} />
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## 🛠 How it Works
|
|
174
|
+
|
|
175
|
+
1. **Data Storage**: The table data is stored as a string in the **Structured Table Language (STL)** format. This is a concise, human-readable format designed for table representation.
|
|
176
|
+
2. **Input Component**: When editing, the plugin provides a custom `TableInput` interface that allows you to input and edit the STL code directly, with immediate visual feedback.
|
|
177
|
+
* It uses `TableInput` component to handle user interaction.
|
|
178
|
+
* It saves the raw STL string + an optional caption.
|
|
179
|
+
3. **Preview**: Inside the Studio, a `TablePreview` component renders the STL string so editors can see exactly what the table looks like without leaving the CMS.
|
|
180
|
+
4. **Frontend Rendering**: On your frontend application (e.g., Next.js), you use the `structured-table` package to parse and render this STL string.
|
|
181
|
+
|
|
182
|
+
## 💡 What is it used for?
|
|
183
|
+
|
|
184
|
+
Standard Sanity tables are great for simple key-value pairs or basic grids. However, they often struggle with:
|
|
185
|
+
|
|
186
|
+
- **Merged Cells**: Row spans and column spans.
|
|
187
|
+
- **Complex Headers**: Multi-level headers, headers in columns, or non-standard grid layouts.
|
|
188
|
+
- **Rich Styling**: Needing specific alignment, button cells, or complex formatting that a simple grid doesn't support.
|
|
189
|
+
|
|
190
|
+
**Sanity Plugin Structured Table** solves this by leveraging STL, allowing you to define table structures as flexibly as you would in HTML, but with a syntax designed for data entry and maintainability.
|
|
191
|
+
|
|
192
|
+
## 📄 License
|
|
193
|
+
|
|
194
|
+
MIT © [Yashraj Yadav](https://github.com/ameghcoder)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableInput.d.ts","sourceRoot":"","sources":["../../src/components/TableInput.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA+B,MAAM,OAAO,CAAC;AAKpD,OAAO,EAAiB,eAAe,EAAE,MAAM,QAAQ,CAAC;AAExD,QAAA,MAAM,UAAU,6FA8Ed,CAAC;AAEH,eAAe,UAAU,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { useCallback, useMemo } from 'react';
|
|
3
|
+
import { PatchEvent, set, unset } from 'sanity';
|
|
4
|
+
import { FormField } from 'sanity';
|
|
5
|
+
import { Card, Text, TextArea, Stack } from '@sanity/ui';
|
|
6
|
+
import { STL, getRenderer } from 'structured-table';
|
|
7
|
+
const TableInput = React.forwardRef((props, ref) => {
|
|
8
|
+
const { schemaType, value, onChange, onFocus, onBlur, readOnly } = props;
|
|
9
|
+
// 1. Convert the STL string (value) to JSON (SanityTable) for the preview
|
|
10
|
+
const tableData = useMemo(() => {
|
|
11
|
+
try {
|
|
12
|
+
if (!value)
|
|
13
|
+
return null;
|
|
14
|
+
return STL.parse(value);
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
console.error("STL Parsing Error:", error);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}, [value]);
|
|
21
|
+
// 2. Handle changes from the textarea
|
|
22
|
+
const handleTextChange = useCallback((event) => {
|
|
23
|
+
const nextValue = event.currentTarget.value;
|
|
24
|
+
// Using PatchEvent to update the Sanity document
|
|
25
|
+
if (nextValue) {
|
|
26
|
+
onChange(PatchEvent.from(set(nextValue)));
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
onChange(PatchEvent.from(unset())); // Remove the field if empty
|
|
30
|
+
}
|
|
31
|
+
}, [onChange]);
|
|
32
|
+
const hasContent = useMemo(() => {
|
|
33
|
+
if (!tableData)
|
|
34
|
+
return false;
|
|
35
|
+
const hasHeader = (tableData.header?.cells?.length ?? 0) > 0;
|
|
36
|
+
const hasBody = tableData.body?.length > 0;
|
|
37
|
+
const hasFooter = (tableData.footer?.cells?.length ?? 0) > 0;
|
|
38
|
+
return hasHeader || hasBody || hasFooter;
|
|
39
|
+
}, [tableData]);
|
|
40
|
+
const { Table } = getRenderer("react");
|
|
41
|
+
return (_jsx(Stack, { space: 3, children: _jsx(FormField
|
|
42
|
+
// description={schemaType?.description}
|
|
43
|
+
// title={schemaType?.title}
|
|
44
|
+
, {
|
|
45
|
+
// description={schemaType?.description}
|
|
46
|
+
// title={schemaType?.title}
|
|
47
|
+
__unstable_presence: props['__unstable_presence'], inputId: props['inputId'] || '', children: _jsxs(Stack, { space: 4, children: [_jsx(Card, { padding: 0, border: true, children: _jsx(TextArea, { ref: ref, value: value || '', onChange: handleTextChange, onFocus: onFocus, onBlur: onBlur, readOnly: readOnly, rows: 10, placeholder: "[header]... | ... [body]... | ...", style: { fontFamily: 'monospace', fontSize: '0.9em' } }) }), _jsx(Card, { padding: 3, border: true, tone: "transparent", radius: 2, children: _jsxs(Stack, { space: 3, children: [_jsx(Text, { size: 1, weight: "bold", muted: true, children: "Live Preview" }), hasContent && tableData ? (
|
|
48
|
+
// 3. Render the preview using your custom React component
|
|
49
|
+
_jsx(Table, { className: "border", data: tableData })) : (_jsxs(Text, { size: 1, muted: true, children: ["Start by defining a section (e.g. ", _jsx("code", { children: "[header]" }), " or ", _jsx("code", { children: "[body]" }), ") to see the preview."] }))] }) })] }) }) }));
|
|
50
|
+
});
|
|
51
|
+
export default TableInput;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { PreviewProps } from 'sanity';
|
|
2
|
+
interface TablePreviewProps extends PreviewProps {
|
|
3
|
+
title?: string;
|
|
4
|
+
subtitle?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function TablePreview(props: TablePreviewProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=TablePreview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TablePreview.d.ts","sourceRoot":"","sources":["../../src/components/TablePreview.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAKtC,UAAU,iBAAkB,SAAQ,YAAY;IAC5C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAID,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,2CAmCpD"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
import { Card, Text, Stack } from '@sanity/ui';
|
|
4
|
+
import { STL, getRenderer } from 'structured-table';
|
|
5
|
+
export function TablePreview(props) {
|
|
6
|
+
const { title, subtitle: stlString } = props;
|
|
7
|
+
const tableData = useMemo(() => {
|
|
8
|
+
if (!stlString)
|
|
9
|
+
return null;
|
|
10
|
+
try {
|
|
11
|
+
return STL.parse(stlString);
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}, [stlString]);
|
|
17
|
+
const hasContent = useMemo(() => {
|
|
18
|
+
if (!tableData)
|
|
19
|
+
return false;
|
|
20
|
+
const hasHeader = (tableData.header?.cells?.length ?? 0) > 0;
|
|
21
|
+
const hasBody = tableData.body?.length > 0;
|
|
22
|
+
const hasFooter = (tableData.footer?.cells?.length ?? 0) > 0;
|
|
23
|
+
return hasHeader || hasBody || hasFooter;
|
|
24
|
+
}, [tableData]);
|
|
25
|
+
const { Table } = getRenderer("react");
|
|
26
|
+
return (_jsx(Card, { children: _jsx(Stack, { space: 3, children: hasContent && tableData ? (_jsx(Table, { className: "border", data: tableData })) : (_jsx(Text, { size: 1, muted: true, children: "Empty Table" })) }) }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { PatchEvent } from "sanity";
|
|
2
|
+
export interface TableInputProps {
|
|
3
|
+
schemaType?: any;
|
|
4
|
+
value?: string;
|
|
5
|
+
onChange: (patch: PatchEvent) => void;
|
|
6
|
+
elementProps?: any;
|
|
7
|
+
readOnly?: boolean;
|
|
8
|
+
onFocus: () => void;
|
|
9
|
+
onBlur: () => void;
|
|
10
|
+
}
|
|
11
|
+
export type ReactRenderer = {
|
|
12
|
+
Table: React.ComponentType<any>;
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=type.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"type.d.ts","sourceRoot":"","sources":["../../src/components/type.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAEpC,MAAM,WAAW,eAAe;IAC9B,UAAU,CAAC,EAAE,GAAG,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,CAAC;IAEtC,YAAY,CAAC,EAAE,GAAG,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC;CACjC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { TablePreview } from "../components/TablePreview";
|
|
2
|
+
declare const _default: {
|
|
3
|
+
name: string;
|
|
4
|
+
title: string;
|
|
5
|
+
type: string;
|
|
6
|
+
icon: () => string;
|
|
7
|
+
fields: ({
|
|
8
|
+
title: string;
|
|
9
|
+
name: string;
|
|
10
|
+
type: string;
|
|
11
|
+
description: string;
|
|
12
|
+
components: {
|
|
13
|
+
input: import("react").ForwardRefExoticComponent<import("../components/type").TableInputProps & import("react").RefAttributes<HTMLTextAreaElement>>;
|
|
14
|
+
};
|
|
15
|
+
} | {
|
|
16
|
+
name: string;
|
|
17
|
+
title: string;
|
|
18
|
+
type: string;
|
|
19
|
+
description?: undefined;
|
|
20
|
+
components?: undefined;
|
|
21
|
+
})[];
|
|
22
|
+
components: {
|
|
23
|
+
preview: typeof TablePreview;
|
|
24
|
+
};
|
|
25
|
+
preview: {
|
|
26
|
+
select: {
|
|
27
|
+
title: string;
|
|
28
|
+
subtitle: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export default _default;
|
|
33
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/schema/schema.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAE1D,wBAiCE"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import TableInput from "../components/TableInput";
|
|
2
|
+
import { TablePreview } from "../components/TablePreview";
|
|
3
|
+
export default {
|
|
4
|
+
// Use a unique name for this block type
|
|
5
|
+
name: "stlTableBlock",
|
|
6
|
+
title: "Structured Table Block",
|
|
7
|
+
type: "object",
|
|
8
|
+
icon: () => "📊", // A simple icon for the editor's insert menu
|
|
9
|
+
fields: [
|
|
10
|
+
{
|
|
11
|
+
title: "Table Data (STL Format)",
|
|
12
|
+
name: "stlString",
|
|
13
|
+
type: "string",
|
|
14
|
+
description: "Enter your Structured Table Language (STL) here.",
|
|
15
|
+
// This is the field that will use custom input component
|
|
16
|
+
components: {
|
|
17
|
+
input: TableInput,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "caption",
|
|
22
|
+
title: "Table Caption",
|
|
23
|
+
type: "string",
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
components: {
|
|
27
|
+
preview: TablePreview,
|
|
28
|
+
},
|
|
29
|
+
// A preview for how it looks *inside* the text editor.
|
|
30
|
+
preview: {
|
|
31
|
+
select: {
|
|
32
|
+
title: "caption",
|
|
33
|
+
subtitle: "stlString",
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sanity-plugin-stl-table",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Sanity Studio plugin for creating Advance table with Structured Table Language (STL).",
|
|
5
|
+
"licenses": [
|
|
6
|
+
{
|
|
7
|
+
"type": "MIT",
|
|
8
|
+
"url": "https://github.com/ameghcoder/sanity-plugin-stl-table/blob/main/LICENSE"
|
|
9
|
+
}
|
|
10
|
+
],
|
|
11
|
+
"author": {
|
|
12
|
+
"name": "Yashraj Yadav",
|
|
13
|
+
"url": "https://github.com/ameghcoder"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"sanity studio",
|
|
17
|
+
"sanity table plugin",
|
|
18
|
+
"sanity tables",
|
|
19
|
+
"advance tables in sanity",
|
|
20
|
+
"stl table plugin"
|
|
21
|
+
],
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/ameghcoder/sanity-plugin-stl-table"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/ameghcoder/sanity-plugin-stl-table/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/ameghcoder/sanity-plugin-stl-table",
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "dist/index.cjs",
|
|
32
|
+
"module": "dist/index.js",
|
|
33
|
+
"types": "dist/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"require": "./dist/index.cjs",
|
|
38
|
+
"types": "./dist/index.d.ts"
|
|
39
|
+
},
|
|
40
|
+
"./package.json": "./package.json"
|
|
41
|
+
},
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"src"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc -p tsconfig.build.json",
|
|
48
|
+
"prepublishOnly": "npm run build"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@sanity/ui": "^2.0.0 || ^3.0.0",
|
|
52
|
+
"react": ">=18",
|
|
53
|
+
"sanity": "^3.0.0 || ^4.0.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/react": "^18.0.0",
|
|
57
|
+
"sanity": "^4.21.1",
|
|
58
|
+
"typescript": "^5.4.0"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"structured-table": "^0.1.1"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
|
+
import { PatchEvent, set, unset } from 'sanity';
|
|
3
|
+
import { FormField } from 'sanity';
|
|
4
|
+
import { Card, Text, TextArea, Stack } from '@sanity/ui';
|
|
5
|
+
import { STL, getRenderer, SanityTable } from 'structured-table';
|
|
6
|
+
import { ReactRenderer, TableInputProps } from './type';
|
|
7
|
+
|
|
8
|
+
const TableInput = React.forwardRef<HTMLTextAreaElement, TableInputProps>((props, ref) => {
|
|
9
|
+
const { schemaType, value, onChange, onFocus, onBlur, readOnly } = props;
|
|
10
|
+
|
|
11
|
+
// 1. Convert the STL string (value) to JSON (SanityTable) for the preview
|
|
12
|
+
const tableData: SanityTable | null = useMemo(() => {
|
|
13
|
+
try {
|
|
14
|
+
if (!value) return null;
|
|
15
|
+
return STL.parse(value);
|
|
16
|
+
} catch (error) {
|
|
17
|
+
console.error("STL Parsing Error:", error);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}, [value]);
|
|
21
|
+
|
|
22
|
+
// 2. Handle changes from the textarea
|
|
23
|
+
const handleTextChange = useCallback((event: React.ChangeEvent<HTMLTextAreaElement>) => {
|
|
24
|
+
const nextValue = event.currentTarget.value;
|
|
25
|
+
|
|
26
|
+
// Using PatchEvent to update the Sanity document
|
|
27
|
+
if (nextValue) {
|
|
28
|
+
onChange(PatchEvent.from(set(nextValue)));
|
|
29
|
+
} else {
|
|
30
|
+
onChange(PatchEvent.from(unset())); // Remove the field if empty
|
|
31
|
+
}
|
|
32
|
+
}, [onChange]);
|
|
33
|
+
|
|
34
|
+
const hasContent = useMemo(() => {
|
|
35
|
+
if (!tableData) return false;
|
|
36
|
+
const hasHeader = (tableData.header?.cells?.length ?? 0) > 0;
|
|
37
|
+
const hasBody = tableData.body?.length > 0;
|
|
38
|
+
const hasFooter = (tableData.footer?.cells?.length ?? 0) > 0;
|
|
39
|
+
return hasHeader || hasBody || hasFooter;
|
|
40
|
+
}, [tableData]);
|
|
41
|
+
|
|
42
|
+
const { Table } = getRenderer<ReactRenderer>("react");
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<Stack space={3}>
|
|
46
|
+
<FormField
|
|
47
|
+
// description={schemaType?.description}
|
|
48
|
+
// title={schemaType?.title}
|
|
49
|
+
__unstable_presence={props['__unstable_presence' as keyof TableInputProps]} // Handle internal props safely
|
|
50
|
+
inputId={props['inputId' as keyof TableInputProps] || ''}
|
|
51
|
+
>
|
|
52
|
+
<Stack space={4}>
|
|
53
|
+
{/* === A. Input Area for STL Text === */}
|
|
54
|
+
<Card padding={0} border>
|
|
55
|
+
<TextArea
|
|
56
|
+
ref={ref}
|
|
57
|
+
value={value || ''}
|
|
58
|
+
onChange={handleTextChange}
|
|
59
|
+
onFocus={onFocus}
|
|
60
|
+
onBlur={onBlur}
|
|
61
|
+
readOnly={readOnly}
|
|
62
|
+
rows={10}
|
|
63
|
+
placeholder="[header]... | ... [body]... | ..."
|
|
64
|
+
style={{ fontFamily: 'monospace', fontSize: '0.9em' }}
|
|
65
|
+
/>
|
|
66
|
+
</Card>
|
|
67
|
+
|
|
68
|
+
{/* === B. Live Preview Area === */}
|
|
69
|
+
<Card padding={3} border tone="transparent" radius={2}>
|
|
70
|
+
<Stack space={3}>
|
|
71
|
+
<Text size={1} weight="bold" muted>Live Preview</Text>
|
|
72
|
+
{hasContent && tableData ? (
|
|
73
|
+
// 3. Render the preview using your custom React component
|
|
74
|
+
<Table className="border" data={tableData} />
|
|
75
|
+
) : (
|
|
76
|
+
<Text size={1} muted>
|
|
77
|
+
Start by defining a section (e.g. <code>[header]</code> or <code>[body]</code>) to see the preview.
|
|
78
|
+
</Text>
|
|
79
|
+
)}
|
|
80
|
+
</Stack>
|
|
81
|
+
</Card>
|
|
82
|
+
</Stack>
|
|
83
|
+
</FormField>
|
|
84
|
+
</Stack>
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export default TableInput;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React, { useMemo } from 'react';
|
|
2
|
+
import { PreviewProps } from 'sanity';
|
|
3
|
+
import { Card, Text, Stack } from '@sanity/ui';
|
|
4
|
+
import { STL, getRenderer } from 'structured-table';
|
|
5
|
+
import { ReactRenderer } from './type';
|
|
6
|
+
|
|
7
|
+
interface TablePreviewProps extends PreviewProps {
|
|
8
|
+
title?: string;
|
|
9
|
+
subtitle?: string; // This will contain the stlString
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
export function TablePreview(props: TablePreviewProps) {
|
|
15
|
+
const { title, subtitle: stlString } = props;
|
|
16
|
+
|
|
17
|
+
const tableData = useMemo(() => {
|
|
18
|
+
if (!stlString) return null;
|
|
19
|
+
try {
|
|
20
|
+
return STL.parse(stlString);
|
|
21
|
+
} catch (e) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}, [stlString]);
|
|
25
|
+
|
|
26
|
+
const hasContent = useMemo(() => {
|
|
27
|
+
if (!tableData) return false;
|
|
28
|
+
const hasHeader = (tableData.header?.cells?.length ?? 0) > 0;
|
|
29
|
+
const hasBody = tableData.body?.length > 0;
|
|
30
|
+
const hasFooter = (tableData.footer?.cells?.length ?? 0) > 0;
|
|
31
|
+
return hasHeader || hasBody || hasFooter;
|
|
32
|
+
}, [tableData]);
|
|
33
|
+
|
|
34
|
+
const { Table } = getRenderer<ReactRenderer>("react");
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<Card>
|
|
38
|
+
<Stack space={3}>
|
|
39
|
+
{hasContent && tableData ? (
|
|
40
|
+
<Table className="border" data={tableData} />
|
|
41
|
+
) : (
|
|
42
|
+
<Text size={1} muted>
|
|
43
|
+
Empty Table
|
|
44
|
+
</Text>
|
|
45
|
+
)}
|
|
46
|
+
</Stack>
|
|
47
|
+
</Card>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PatchEvent } from "sanity";
|
|
2
|
+
|
|
3
|
+
export interface TableInputProps {
|
|
4
|
+
schemaType?: any; // Schema type definition
|
|
5
|
+
value?: string; // The raw STL string value stored in Sanity
|
|
6
|
+
onChange: (patch: PatchEvent) => void;
|
|
7
|
+
// Sanity v3 injects these
|
|
8
|
+
elementProps?: any;
|
|
9
|
+
readOnly?: boolean;
|
|
10
|
+
onFocus: () => void;
|
|
11
|
+
onBlur: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type ReactRenderer = {
|
|
15
|
+
Table: React.ComponentType<any>;
|
|
16
|
+
};
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import TableInput from "../components/TableInput";
|
|
2
|
+
import { TablePreview } from "../components/TablePreview";
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
// Use a unique name for this block type
|
|
6
|
+
name: "stlTableBlock",
|
|
7
|
+
title: "Structured Table Block",
|
|
8
|
+
type: "object",
|
|
9
|
+
icon: () => "📊", // A simple icon for the editor's insert menu
|
|
10
|
+
fields: [
|
|
11
|
+
{
|
|
12
|
+
title: "Table Data (STL Format)",
|
|
13
|
+
name: "stlString",
|
|
14
|
+
type: "string",
|
|
15
|
+
description: "Enter your Structured Table Language (STL) here.",
|
|
16
|
+
// This is the field that will use custom input component
|
|
17
|
+
components: {
|
|
18
|
+
input: TableInput,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: "caption",
|
|
23
|
+
title: "Table Caption",
|
|
24
|
+
type: "string",
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
components: {
|
|
28
|
+
preview: TablePreview,
|
|
29
|
+
},
|
|
30
|
+
// A preview for how it looks *inside* the text editor.
|
|
31
|
+
preview: {
|
|
32
|
+
select: {
|
|
33
|
+
title: "caption",
|
|
34
|
+
subtitle: "stlString",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
};
|