Skip to content

Commit 834596c

Browse files
authored
Merge pull request #20 from objectstack-ai/copilot/add-full-form-component
2 parents 57424b3 + 6c93e07 commit 834596c

26 files changed

Lines changed: 1508 additions & 176 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ jobs:
9797

9898
- name: Check for build artifacts
9999
run: |
100-
if [ ! -d "packages/protocol/dist" ]; then
100+
if [ ! -d "packages/core/dist" ]; then
101101
echo "Protocol package build failed"
102102
exit 1
103103
fi

.github/workflows/pr-checks.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ on:
44
pull_request:
55
types: [opened, synchronize, reopened]
66

7+
permissions:
8+
contents: read
9+
pull-requests: write
10+
issues: write
11+
712
jobs:
813
validate:
914
name: Validate PR

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
auto-install-peers=true

apps/playground/eslint.config.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import js from '@eslint/js'
2+
import globals from 'globals'
3+
import reactHooks from 'eslint-plugin-react-hooks'
4+
import reactRefresh from 'eslint-plugin-react-refresh'
5+
import tseslint from 'typescript-eslint'
6+
7+
export default tseslint.config(
8+
{ ignores: ['dist'] },
9+
{
10+
extends: [js.configs.recommended, ...tseslint.configs.recommended],
11+
files: ['**/*.{ts,tsx}'],
12+
languageOptions: {
13+
ecmaVersion: 2020,
14+
globals: globals.browser,
15+
},
16+
plugins: {
17+
'react-hooks': reactHooks,
18+
'react-refresh': reactRefresh,
19+
},
20+
rules: {
21+
...reactHooks.configs.recommended.rules,
22+
'react-refresh/only-export-components': [
23+
'warn',
24+
{ allowConstantExport: true },
25+
],
26+
},
27+
},
28+
)

apps/playground/package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,20 @@
1919
"react-dom": "^18.3.1"
2020
},
2121
"devDependencies": {
22+
"@eslint/js": "^9.39.2",
2223
"@types/react": "^18.3.12",
2324
"@types/react-dom": "^18.3.1",
2425
"@vitejs/plugin-react": "^5.1.1",
2526
"autoprefixer": "^10.4.23",
27+
"eslint": "^9.39.2",
28+
"eslint-plugin-react-hooks": "^7.0.1",
29+
"eslint-plugin-react-refresh": "^0.4.26",
30+
"globals": "^16.5.0",
2631
"postcss": "^8.5.6",
2732
"tailwindcss": "^3.4.19",
2833
"tailwindcss-animate": "^1.0.7",
2934
"typescript": "~5.9.3",
35+
"typescript-eslint": "^8.53.0",
3036
"vite": "^7.2.4"
3137
}
3238
}

apps/playground/src/App.tsx

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,26 +9,34 @@ type ViewportSize = 'desktop' | 'tablet' | 'mobile';
99
export default function Playground() {
1010
const [selectedExample, setSelectedExample] = useState<ExampleKey>('dashboard');
1111
const [code, setCode] = useState(examples['dashboard']);
12+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
1213
const [schema, setSchema] = useState<any>(null);
1314
const [jsonError, setJsonError] = useState<string | null>(null);
1415
const [viewportSize, setViewportSize] = useState<ViewportSize>('desktop');
1516
const [copied, setCopied] = useState(false);
1617

17-
// Real-time JSON parsing
18-
useEffect(() => {
18+
const updateSchema = (newCode: string) => {
1919
try {
20-
const parsed = JSON.parse(code);
20+
const parsed = JSON.parse(newCode);
2121
setSchema(parsed);
2222
setJsonError(null);
2323
} catch (e) {
2424
setJsonError((e as Error).message);
2525
// Keep previous schema on error
2626
}
27-
}, [code]);
27+
};
28+
29+
// Initial parse usage
30+
useEffect(() => {
31+
updateSchema(code);
32+
// eslint-disable-next-line react-hooks/exhaustive-deps
33+
}, []); // Run once on mount
2834

2935
const handleExampleChange = (key: ExampleKey) => {
3036
setSelectedExample(key);
31-
setCode(examples[key]);
37+
const newCode = examples[key];
38+
setCode(newCode);
39+
updateSchema(newCode);
3240
};
3341

3442
const handleCopySchema = async () => {
@@ -118,7 +126,11 @@ export default function Playground() {
118126
<div className="flex-1 overflow-hidden">
119127
<textarea
120128
value={code}
121-
onChange={(e) => setCode(e.target.value)}
129+
onChange={(e) => {
130+
const val = e.target.value;
131+
setCode(val);
132+
updateSchema(val);
133+
}}
122134
className="w-full h-full p-4 font-mono text-sm resize-none focus:outline-none border-0 bg-background"
123135
spellCheck={false}
124136
style={{

apps/playground/src/data/examples.ts

Lines changed: 137 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,142 @@ export const examples = {
608608
]
609609
}`,
610610

611+
'airtable-form': `{
612+
"type": "div",
613+
"className": "max-w-4xl space-y-6",
614+
"body": [
615+
{
616+
"type": "div",
617+
"className": "space-y-2",
618+
"body": [
619+
{
620+
"type": "text",
621+
"content": "Airtable-Style Feature-Complete Form",
622+
"className": "text-3xl font-bold"
623+
},
624+
{
625+
"type": "text",
626+
"content": "A comprehensive form component with validation, multi-column layout, and conditional fields",
627+
"className": "text-muted-foreground"
628+
}
629+
]
630+
},
631+
{
632+
"type": "card",
633+
"className": "shadow-sm",
634+
"body": {
635+
"type": "form",
636+
"className": "p-6",
637+
"submitLabel": "Create Project",
638+
"cancelLabel": "Reset",
639+
"showCancel": true,
640+
"columns": 2,
641+
"validationMode": "onBlur",
642+
"resetOnSubmit": false,
643+
"defaultValues": {
644+
"projectType": "personal",
645+
"priority": "medium",
646+
"notifications": true
647+
},
648+
"fields": [
649+
{
650+
"name": "projectName",
651+
"label": "Project Name",
652+
"type": "input",
653+
"required": true,
654+
"placeholder": "Enter project name",
655+
"validation": {
656+
"minLength": {
657+
"value": 3,
658+
"message": "Project name must be at least 3 characters"
659+
}
660+
}
661+
},
662+
{
663+
"name": "projectType",
664+
"label": "Project Type",
665+
"type": "select",
666+
"required": true,
667+
"options": [
668+
{ "label": "Personal", "value": "personal" },
669+
{ "label": "Team", "value": "team" },
670+
{ "label": "Enterprise", "value": "enterprise" }
671+
]
672+
},
673+
{
674+
"name": "teamSize",
675+
"label": "Team Size",
676+
"type": "input",
677+
"inputType": "number",
678+
"placeholder": "Number of team members",
679+
"condition": {
680+
"field": "projectType",
681+
"in": ["team", "enterprise"]
682+
},
683+
"validation": {
684+
"min": {
685+
"value": 2,
686+
"message": "Team must have at least 2 members"
687+
}
688+
}
689+
},
690+
{
691+
"name": "budget",
692+
"label": "Budget",
693+
"type": "input",
694+
"inputType": "number",
695+
"placeholder": "Project budget",
696+
"condition": {
697+
"field": "projectType",
698+
"equals": "enterprise"
699+
}
700+
},
701+
{
702+
"name": "priority",
703+
"label": "Priority Level",
704+
"type": "select",
705+
"required": true,
706+
"options": [
707+
{ "label": "Low", "value": "low" },
708+
{ "label": "Medium", "value": "medium" },
709+
{ "label": "High", "value": "high" },
710+
{ "label": "Critical", "value": "critical" }
711+
]
712+
},
713+
{
714+
"name": "deadline",
715+
"label": "Deadline",
716+
"type": "input",
717+
"inputType": "date",
718+
"condition": {
719+
"field": "priority",
720+
"in": ["high", "critical"]
721+
}
722+
},
723+
{
724+
"name": "description",
725+
"label": "Project Description",
726+
"type": "textarea",
727+
"placeholder": "Describe your project goals and objectives",
728+
"validation": {
729+
"maxLength": {
730+
"value": 500,
731+
"message": "Description must not exceed 500 characters"
732+
}
733+
}
734+
},
735+
{
736+
"name": "notifications",
737+
"label": "Enable Notifications",
738+
"type": "checkbox",
739+
"description": "Receive updates about project progress"
740+
}
741+
]
742+
}
743+
}
744+
]
745+
}`,
746+
611747
'simple-page': `{
612748
"type": "div",
613749
"className": "space-y-4",
@@ -733,6 +869,6 @@ export type ExampleKey = keyof typeof examples;
733869
export const exampleCategories = {
734870
'Primitives': ['simple-page', 'input-states', 'button-variants'],
735871
'Layouts': ['grid-layout', 'dashboard', 'tabs-demo'],
736-
'Forms': ['form-demo'],
872+
'Forms': ['form-demo', 'airtable-form'],
737873
'Data Display': ['calendar-view']
738874
};

apps/playground/tailwind.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import tailwindcssAnimate from "tailwindcss-animate";
2+
13
/** @type {import('tailwindcss').Config} */
24
export default {
35
darkMode: ["class"],
@@ -57,5 +59,5 @@ export default {
5759
}
5860
}
5961
},
60-
plugins: [require("tailwindcss-animate")],
62+
plugins: [tailwindcssAnimate],
6163
}

0 commit comments

Comments
 (0)