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 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
+ ![STL Banner](./asset/integration.webp "STL Banner")
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,5 @@
1
+ import React from 'react';
2
+ import { TableInputProps } from './type';
3
+ declare const TableInput: React.ForwardRefExoticComponent<TableInputProps & React.RefAttributes<HTMLTextAreaElement>>;
4
+ export default TableInput;
5
+ //# sourceMappingURL=TableInput.d.ts.map
@@ -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 {};
@@ -0,0 +1,4 @@
1
+ export { default as TableInput } from './components/TableInput';
2
+ export { default as schema } from './schema/schema';
3
+ export { default as stlTableBlock } from './schema/schema';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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,3 @@
1
+ export { default as TableInput } from './components/TableInput';
2
+ export { default as schema } from './schema/schema';
3
+ export { default as stlTableBlock } from './schema/schema';
@@ -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,3 @@
1
+ export { default as TableInput } from './components/TableInput';
2
+ export { default as schema } from './schema/schema';
3
+ export { default as stlTableBlock } from './schema/schema';
@@ -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
+ };