|
6 | 6 |
|
7 | 7 | import React, { useState, useMemo, useRef, useEffect } from 'react'; |
8 | 8 | import { createRoot } from 'react-dom/client'; |
9 | | -import { MonkeyTable, type MonkeyTableHandle } from '@monkeytab/browser'; |
| 9 | +import { MonkeyTable, MonkeyTableFromConfig, type MonkeyTableHandle, type MonkeyTableConfig } from '@monkeytab/browser'; |
10 | 10 | import type { Value, PresenceUser, RemoteChangeEvent } from '@monkeytab/browser'; |
11 | 11 |
|
12 | 12 | // --------------------------------------------------------------------------- |
@@ -833,8 +833,131 @@ function applyToLocalRows( |
833 | 833 | } |
834 | 834 | } |
835 | 835 |
|
| 836 | +// --------------------------------------------------------------------------- |
| 837 | +// Example: Config-driven table — one JSON blob renders the whole thing |
| 838 | +// |
| 839 | +// Shows <MonkeyTableFromConfig> in action. Everything you'd normally spread |
| 840 | +// across columns + props + options lives in one serializable object that |
| 841 | +// round-trips through JSON.stringify. Non-serializable pieces (here: onChange) |
| 842 | +// stay as ordinary React props on the wrapper. |
| 843 | +// --------------------------------------------------------------------------- |
| 844 | + |
| 845 | +const FROM_CONFIG_INITIAL: MonkeyTableConfig = { |
| 846 | + schemaVersion: 1, |
| 847 | + columns: [ |
| 848 | + { id: 'Name' }, |
| 849 | + { id: 'Email', type: 'Email' }, |
| 850 | + { id: 'Role', type: 'SingleSelect', options: { |
| 851 | + options: [ |
| 852 | + { value: 'Engineer', label: 'Engineer', color: '#dbeafe' }, |
| 853 | + { value: 'Designer', label: 'Designer', color: '#fce7f3' }, |
| 854 | + { value: 'Manager', label: 'Manager', color: '#dcfce7' }, |
| 855 | + ], |
| 856 | + } }, |
| 857 | + { id: 'Salary', type: 'Number', options: { format: 'currency', precision: 0 } }, |
| 858 | + { id: 'Joined', type: 'Date', options: { dateFormat: 'iso' } }, |
| 859 | + { id: 'Active', type: 'Boolean' }, |
| 860 | + { id: 'Website', type: 'URL' }, |
| 861 | + ], |
| 862 | + rows: [ |
| 863 | + { Name: 'Alice Chen', Email: 'alice@acme.com', Role: 'Engineer', Salary: 95000, Joined: '2022-03-15', Active: true, Website: 'https://alicechen.dev' }, |
| 864 | + { Name: 'Bob Smith', Email: 'bob@acme.com', Role: 'Designer', Salary: 82000, Joined: '2021-07-01', Active: true, Website: 'https://bobsmith.design' }, |
| 865 | + { Name: 'Carol Wu', Email: 'carol@acme.com', Role: 'Manager', Salary: 110000, Joined: '2020-11-20', Active: false, Website: 'https://carolwu.io' }, |
| 866 | + ], |
| 867 | + settings: { |
| 868 | + editable: true, |
| 869 | + height: 'auto', |
| 870 | + rowHeight: 'medium', |
| 871 | + pageSize: 25, |
| 872 | + locale: 'en-US', |
| 873 | + currencyCode: 'USD', |
| 874 | + showRowNumbers: true, |
| 875 | + }, |
| 876 | +}; |
| 877 | + |
| 878 | +function FromConfigExample({ locale, language, compactMode }: { locale: string; language: string; compactMode: boolean }) { |
| 879 | + const [config, setConfig] = useState<MonkeyTableConfig>(FROM_CONFIG_INITIAL); |
| 880 | + const [configText, setConfigText] = useState(() => JSON.stringify(FROM_CONFIG_INITIAL, null, 2)); |
| 881 | + const [parseError, setParseError] = useState<string | null>(null); |
| 882 | + |
| 883 | + // Merge the tab-level locale/compact controls onto the config's own settings |
| 884 | + // so the top-bar switches still work for this tab. |
| 885 | + const effectiveConfig = useMemo<MonkeyTableConfig>(() => ({ |
| 886 | + ...config, |
| 887 | + settings: { |
| 888 | + ...config.settings, |
| 889 | + locale, |
| 890 | + language, |
| 891 | + compactMode, |
| 892 | + }, |
| 893 | + }), [config, locale, language, compactMode]); |
| 894 | + |
| 895 | + const handleConfigChange = (text: string) => { |
| 896 | + setConfigText(text); |
| 897 | + try { |
| 898 | + const parsed = JSON.parse(text) as MonkeyTableConfig; |
| 899 | + setConfig(parsed); |
| 900 | + setParseError(null); |
| 901 | + } catch (err) { |
| 902 | + setParseError(err instanceof Error ? err.message : String(err)); |
| 903 | + } |
| 904 | + }; |
| 905 | + |
| 906 | + const handleRowsChange = (rows: Array<Record<string, Value>>) => { |
| 907 | + // Reflect live edits back into the config so the JSON pane stays in sync. |
| 908 | + setConfig((prev) => ({ ...prev, rows })); |
| 909 | + setConfigText(JSON.stringify({ ...config, rows }, null, 2)); |
| 910 | + }; |
| 911 | + |
| 912 | + return ( |
| 913 | + <div className="example"> |
| 914 | + <h2>From Config</h2> |
| 915 | + <div className="desc"> |
| 916 | + One <code>MonkeyTableConfig</code> blob — <code>{`{ columns, rows, settings }`}</code> — rendered by{' '} |
| 917 | + <code><MonkeyTableFromConfig></code>. Edit the JSON on the right and the table re-renders live. |
| 918 | + Edit cells in the table and the JSON updates to match. |
| 919 | + </div> |
| 920 | + <div style={{ display: 'grid', gridTemplateColumns: '1fr 440px', gap: 16, alignItems: 'stretch' }}> |
| 921 | + <div className="table-container" style={{ height: 420 }}> |
| 922 | + <MonkeyTableFromConfig |
| 923 | + config={effectiveConfig} |
| 924 | + onChange={handleRowsChange} |
| 925 | + /> |
| 926 | + </div> |
| 927 | + <div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}> |
| 928 | + <div style={{ fontSize: 12, fontWeight: 600, color: '#374151' }}>config (live JSON)</div> |
| 929 | + <textarea |
| 930 | + value={configText} |
| 931 | + onChange={(e) => handleConfigChange(e.target.value)} |
| 932 | + spellCheck={false} |
| 933 | + style={{ |
| 934 | + flex: 1, |
| 935 | + minHeight: 380, |
| 936 | + fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace', |
| 937 | + fontSize: 12, |
| 938 | + lineHeight: 1.5, |
| 939 | + padding: 10, |
| 940 | + borderRadius: 6, |
| 941 | + border: `1px solid ${parseError ? '#ef4444' : '#d1d5db'}`, |
| 942 | + background: '#fafafa', |
| 943 | + color: '#111827', |
| 944 | + resize: 'vertical', |
| 945 | + }} |
| 946 | + /> |
| 947 | + {parseError && ( |
| 948 | + <div style={{ fontSize: 12, color: '#b91c1c' }}> |
| 949 | + JSON parse error: {parseError} — table still shows the last valid config. |
| 950 | + </div> |
| 951 | + )} |
| 952 | + </div> |
| 953 | + </div> |
| 954 | + </div> |
| 955 | + ); |
| 956 | +} |
| 957 | + |
836 | 958 | const TABS: Array<{ id: string; label: string; private?: boolean }> = [ |
837 | 959 | { id: 'editable', label: 'Editable' }, |
| 960 | + { id: 'from-config', label: 'From Config' }, |
838 | 961 | { id: 'async-crud', label: 'Async CRUD' }, |
839 | 962 | { id: 'multiplayer', label: 'Multiplayer' }, |
840 | 963 | { id: 'height', label: 'Height Modes' }, |
@@ -1011,6 +1134,10 @@ function App() { |
1011 | 1134 | </div> |
1012 | 1135 | )} |
1013 | 1136 |
|
| 1137 | + {tab === 'from-config' && ( |
| 1138 | + <FromConfigExample locale={locale} language={language} compactMode={compactMode} /> |
| 1139 | + )} |
| 1140 | + |
1014 | 1141 | {tab === 'height' && ( |
1015 | 1142 | <HeightModesExample locale={locale} language={language} compactMode={compactMode} /> |
1016 | 1143 | )} |
|
0 commit comments