mirror of
https://github.com/oarkflow/mq.git
synced 2025-10-06 00:16:49 +08:00
update
This commit is contained in:
@@ -1,113 +1,528 @@
|
|||||||
{
|
{
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"page_title": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Main Page Title",
|
||||||
|
"order": 1,
|
||||||
|
"ui": {
|
||||||
|
"element": "h1",
|
||||||
|
"class": "text-3xl font-bold text-center mb-6",
|
||||||
|
"name": "page_title",
|
||||||
|
"content": "Comprehensive HTML Form Demo"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"intro_paragraph": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Introduction",
|
||||||
|
"order": 2,
|
||||||
|
"ui": {
|
||||||
|
"element": "p",
|
||||||
|
"class": "text-gray-600 mb-4",
|
||||||
|
"name": "intro",
|
||||||
|
"content": "This form demonstrates all supported HTML DOM elements in the renderer."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"divider": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 3,
|
||||||
|
"ui": {
|
||||||
|
"element": "hr",
|
||||||
|
"class": "my-6 border-gray-300"
|
||||||
|
}
|
||||||
|
},
|
||||||
"first_name": {
|
"first_name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "First Name",
|
"title": "First Name",
|
||||||
"order": 1,
|
"placeholder": "Enter your first name",
|
||||||
|
"order": 10,
|
||||||
"ui": {
|
"ui": {
|
||||||
"element": "input",
|
"element": "input",
|
||||||
|
"type": "text",
|
||||||
"class": "form-group",
|
"class": "form-group",
|
||||||
"name": "first_name"
|
"name": "first_name",
|
||||||
|
"autocomplete": "given-name",
|
||||||
|
"maxlength": "50"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"last_name": {
|
"last_name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"title": "Last Name",
|
"title": "Last Name",
|
||||||
"order": 2,
|
"placeholder": "Enter your last name",
|
||||||
|
"order": 11,
|
||||||
"ui": {
|
"ui": {
|
||||||
"element": "input",
|
"element": "input",
|
||||||
|
"type": "text",
|
||||||
"class": "form-group",
|
"class": "form-group",
|
||||||
"name": "last_name"
|
"name": "last_name",
|
||||||
|
"autocomplete": "family-name",
|
||||||
|
"maxlength": "50"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"email": {
|
"email": {
|
||||||
"type": "email",
|
"type": "email",
|
||||||
"title": "Email Address",
|
"title": "Email Address",
|
||||||
"order": 3,
|
"placeholder": "your.email@example.com",
|
||||||
|
"order": 12,
|
||||||
"ui": {
|
"ui": {
|
||||||
"element": "input",
|
"element": "input",
|
||||||
"type": "email",
|
"type": "email",
|
||||||
"class": "form-group",
|
"class": "form-group",
|
||||||
"name": "email"
|
"name": "email",
|
||||||
|
"autocomplete": "email"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"user_type": {
|
"password": {
|
||||||
"type": "string",
|
"type": "password",
|
||||||
"title": "User Type",
|
"title": "Password",
|
||||||
"order": 4,
|
"placeholder": "Enter a secure password",
|
||||||
"ui": {
|
"order": 13,
|
||||||
"element": "select",
|
|
||||||
"class": "form-group",
|
|
||||||
"name": "user_type",
|
|
||||||
"options": [ "new", "premium", "standard" ]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"priority": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Priority Level",
|
|
||||||
"order": 5,
|
|
||||||
"ui": {
|
|
||||||
"element": "select",
|
|
||||||
"class": "form-group",
|
|
||||||
"name": "priority",
|
|
||||||
"options": [ "low", "medium", "high", "urgent" ]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"subject": {
|
|
||||||
"type": "string",
|
|
||||||
"title": "Subject",
|
|
||||||
"order": 6,
|
|
||||||
"ui": {
|
"ui": {
|
||||||
"element": "input",
|
"element": "input",
|
||||||
|
"type": "password",
|
||||||
"class": "form-group",
|
"class": "form-group",
|
||||||
"name": "subject"
|
"name": "password",
|
||||||
|
"minlength": "8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"message": {
|
"age": {
|
||||||
"type": "textarea",
|
"type": "number",
|
||||||
"title": "Message",
|
"title": "Age",
|
||||||
"order": 7,
|
"order": 14,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "number",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "age",
|
||||||
|
"min": "18",
|
||||||
|
"max": "120"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"birth_date": {
|
||||||
|
"type": "date",
|
||||||
|
"title": "Birth Date",
|
||||||
|
"order": 15,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "date",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "birth_date"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"website": {
|
||||||
|
"type": "url",
|
||||||
|
"title": "Personal Website",
|
||||||
|
"placeholder": "https://example.com",
|
||||||
|
"order": 16,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "url",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "website"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"phone": {
|
||||||
|
"type": "tel",
|
||||||
|
"title": "Phone Number",
|
||||||
|
"placeholder": "+1-555-123-4567",
|
||||||
|
"order": 17,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "tel",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "phone"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"favorite_color": {
|
||||||
|
"type": "color",
|
||||||
|
"title": "Favorite Color",
|
||||||
|
"order": 18,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "color",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "favorite_color",
|
||||||
|
"value": "#3B82F6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"satisfaction": {
|
||||||
|
"type": "range",
|
||||||
|
"title": "Satisfaction Level",
|
||||||
|
"order": 19,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "range",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "satisfaction",
|
||||||
|
"min": "1",
|
||||||
|
"max": "10",
|
||||||
|
"value": "5"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profile_picture": {
|
||||||
|
"type": "file",
|
||||||
|
"title": "Profile Picture",
|
||||||
|
"order": 20,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "file",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "profile_picture",
|
||||||
|
"accept": "image/*"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"newsletter": {
|
||||||
|
"type": "boolean",
|
||||||
|
"title": "Subscribe to Newsletter",
|
||||||
|
"order": 21,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "checkbox",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "newsletter",
|
||||||
|
"value": "yes"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"gender": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Gender",
|
||||||
|
"order": 22,
|
||||||
|
"ui": {
|
||||||
|
"element": "select",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "gender",
|
||||||
|
"options": [
|
||||||
|
{ "value": "", "text": "Select Gender", "selected": true },
|
||||||
|
{ "value": "male", "text": "Male" },
|
||||||
|
{ "value": "female", "text": "Female" },
|
||||||
|
{ "value": "other", "text": "Other" },
|
||||||
|
{ "value": "prefer_not_to_say", "text": "Prefer not to say" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"country": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Country",
|
||||||
|
"order": 23,
|
||||||
|
"ui": {
|
||||||
|
"element": "select",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "country",
|
||||||
|
"options": [
|
||||||
|
{ "value": "us", "text": "United States" },
|
||||||
|
{ "value": "ca", "text": "Canada" },
|
||||||
|
{ "value": "uk", "text": "United Kingdom" },
|
||||||
|
{ "value": "de", "text": "Germany" },
|
||||||
|
{ "value": "fr", "text": "France" },
|
||||||
|
{ "value": "jp", "text": "Japan" },
|
||||||
|
{ "value": "au", "text": "Australia" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"skills": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Skills",
|
||||||
|
"order": 24,
|
||||||
|
"ui": {
|
||||||
|
"element": "select",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "skills",
|
||||||
|
"multiple": true,
|
||||||
|
"options": [
|
||||||
|
{ "value": "javascript", "text": "JavaScript" },
|
||||||
|
{ "value": "python", "text": "Python" },
|
||||||
|
{ "value": "go", "text": "Go" },
|
||||||
|
{ "value": "rust", "text": "Rust" },
|
||||||
|
{ "value": "java", "text": "Java" },
|
||||||
|
{ "value": "csharp", "text": "C#" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bio": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Biography",
|
||||||
|
"placeholder": "Tell us about yourself...",
|
||||||
|
"order": 25,
|
||||||
"ui": {
|
"ui": {
|
||||||
"element": "textarea",
|
"element": "textarea",
|
||||||
"class": "form-group",
|
"class": "form-group",
|
||||||
"name": "message"
|
"name": "bio",
|
||||||
|
"rows": "4",
|
||||||
|
"cols": "50",
|
||||||
|
"maxlength": "500"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"section_header": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 30,
|
||||||
|
"ui": {
|
||||||
|
"element": "h2",
|
||||||
|
"class": "text-2xl font-semibold mt-8 mb-4",
|
||||||
|
"content": "Additional Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"experience_fieldset": {
|
||||||
|
"type": "object",
|
||||||
|
"order": 31,
|
||||||
|
"ui": {
|
||||||
|
"element": "fieldset",
|
||||||
|
"class": "border border-gray-300 rounded p-4 mb-4",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"ui": {
|
||||||
|
"element": "legend",
|
||||||
|
"class": "font-medium px-2",
|
||||||
|
"content": "Work Experience"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Years of Experience",
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "number",
|
||||||
|
"name": "years_experience",
|
||||||
|
"class": "form-group",
|
||||||
|
"min": "0",
|
||||||
|
"max": "50"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Current Position",
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "text",
|
||||||
|
"name": "current_position",
|
||||||
|
"class": "form-group",
|
||||||
|
"placeholder": "e.g., Software Engineer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"technologies_datalist": {
|
||||||
|
"type": "string",
|
||||||
|
"title": "Preferred Technology",
|
||||||
|
"order": 32,
|
||||||
|
"ui": {
|
||||||
|
"element": "input",
|
||||||
|
"type": "text",
|
||||||
|
"class": "form-group",
|
||||||
|
"name": "preferred_tech",
|
||||||
|
"list": "technologies",
|
||||||
|
"placeholder": "Start typing..."
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tech_datalist": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 33,
|
||||||
|
"ui": {
|
||||||
|
"element": "datalist",
|
||||||
|
"id": "technologies",
|
||||||
|
"options": [
|
||||||
|
"React", "Vue.js", "Angular", "Node.js", "Express",
|
||||||
|
"Django", "Flask", "Spring Boot", "ASP.NET", "Laravel"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"completion_progress": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 34,
|
||||||
|
"ui": {
|
||||||
|
"element": "div",
|
||||||
|
"class": "mb-4",
|
||||||
|
"contentHTML": "<label for='form_progress'>Form Completion:</label><progress id='form_progress' value='70' max='100' class='w-full'>70%</progress>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rating_meter": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 35,
|
||||||
|
"ui": {
|
||||||
|
"element": "div",
|
||||||
|
"class": "mb-4",
|
||||||
|
"contentHTML": "<label for='rating'>Overall Rating:</label><meter id='rating' value='8' min='0' max='10' optimum='9' class='w-full'>8 out of 10</meter>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"media_section": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 40,
|
||||||
|
"ui": {
|
||||||
|
"element": "section",
|
||||||
|
"class": "mt-8 mb-4",
|
||||||
|
"contentHTML": "<h3 class='text-xl font-medium mb-4'>Media Examples</h3>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"demo_image": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 41,
|
||||||
|
"ui": {
|
||||||
|
"element": "figure",
|
||||||
|
"class": "mb-4",
|
||||||
|
"contentHTML": "<img src='https://placehold.co/300x200/3B82F6/ffffff?text=Demo+Image' alt='Demo placeholder image' class='rounded border' /><figcaption class='text-sm text-gray-600 mt-2'>Sample image with caption</figcaption>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"table_section": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 50,
|
||||||
|
"ui": {
|
||||||
|
"element": "div",
|
||||||
|
"class": "mt-8 mb-4",
|
||||||
|
"contentHTML": "<h3 class='text-xl font-medium mb-4'>Data Table Example</h3><table class='min-w-full border border-gray-300'><thead class='bg-gray-50'><tr><th class='border border-gray-300 px-4 py-2'>Name</th><th class='border border-gray-300 px-4 py-2'>Role</th><th class='border border-gray-300 px-4 py-2'>Experience</th></tr></thead><tbody><tr><td class='border border-gray-300 px-4 py-2'>John Doe</td><td class='border border-gray-300 px-4 py-2'>Developer</td><td class='border border-gray-300 px-4 py-2'>5 years</td></tr><tr><td class='border border-gray-300 px-4 py-2'>Jane Smith</td><td class='border border-gray-300 px-4 py-2'>Designer</td><td class='border border-gray-300 px-4 py-2'>3 years</td></tr></tbody></table>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"list_examples": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 60,
|
||||||
|
"ui": {
|
||||||
|
"element": "div",
|
||||||
|
"class": "mt-8 mb-4",
|
||||||
|
"contentHTML": "<h3 class='text-xl font-medium mb-4'>List Examples</h3><div class='grid grid-cols-1 md:grid-cols-3 gap-4'><div><h4 class='font-medium mb-2'>Unordered List:</h4><ul class='list-disc list-inside space-y-1'><li>First item</li><li>Second item</li><li>Third item</li></ul></div><div><h4 class='font-medium mb-2'>Ordered List:</h4><ol class='list-decimal list-inside space-y-1'><li>Step one</li><li>Step two</li><li>Step three</li></ol></div><div><h4 class='font-medium mb-2'>Description List:</h4><dl class='space-y-2'><dt class='font-medium'>Term 1:</dt><dd class='ml-4 text-gray-600'>Definition 1</dd><dt class='font-medium'>Term 2:</dt><dd class='ml-4 text-gray-600'>Definition 2</dd></dl></div></div>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interactive_details": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 70,
|
||||||
|
"ui": {
|
||||||
|
"element": "details",
|
||||||
|
"class": "border border-gray-300 rounded p-4 mb-4",
|
||||||
|
"contentHTML": "<summary class='font-medium cursor-pointer'>Click to expand advanced options</summary><div class='mt-4 space-y-4'><div class='form-group'><label for='advanced_setting_1'>Advanced Setting 1:</label><input type='text' id='advanced_setting_1' name='advanced_setting_1' class='w-full border rounded px-3 py-2' /></div><div class='form-group'><label for='advanced_setting_2'>Advanced Setting 2:</label><select id='advanced_setting_2' name='advanced_setting_2' class='w-full border rounded px-3 py-2'><option value='option1'>Option 1</option><option value='option2'>Option 2</option></select></div></div>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"code_example": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 80,
|
||||||
|
"ui": {
|
||||||
|
"element": "div",
|
||||||
|
"class": "mt-8 mb-4",
|
||||||
|
"contentHTML": "<h3 class='text-xl font-medium mb-4'>Code Example</h3><pre class='bg-gray-100 p-4 rounded overflow-x-auto'><code class='text-sm'>function greetUser(name) {\n return `Hello, ${name}!`;\n}\n\nconsole.log(greetUser('World'));</code></pre>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text_formatting": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 90,
|
||||||
|
"ui": {
|
||||||
|
"element": "div",
|
||||||
|
"class": "mt-8 mb-4",
|
||||||
|
"contentHTML": "<h3 class='text-xl font-medium mb-4'>Text Formatting Examples</h3><p class='mb-4'>This paragraph contains various text formatting: <strong>bold text</strong>, <em>italic text</em>, <mark>highlighted text</mark>, <small>small text</small>, <del>deleted text</del>, <ins>inserted text</ins>, <sup>superscript</sup>, <sub>subscript</sub>, and <abbr title='HyperText Markup Language'>HTML</abbr> abbreviation.</p><blockquote class='border-l-4 border-blue-500 pl-4 italic text-gray-600 mb-4'>This is a blockquote that can contain longer quoted text with proper styling and indentation.</blockquote><address class='not-italic text-gray-600'>Contact: <a href='mailto:demo@example.com' class='text-blue-600 hover:underline'>demo@example.com</a></address>"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"break_line": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 95,
|
||||||
|
"ui": {
|
||||||
|
"element": "br"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"final_divider": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 96,
|
||||||
|
"ui": {
|
||||||
|
"element": "hr",
|
||||||
|
"class": "my-8 border-gray-300"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"footer_note": {
|
||||||
|
"type": "string",
|
||||||
|
"order": 97,
|
||||||
|
"ui": {
|
||||||
|
"element": "footer",
|
||||||
|
"class": "text-center text-gray-500 text-sm",
|
||||||
|
"contentHTML": "<p>This comprehensive form demonstrates the full capabilities of the enhanced JSON Schema renderer.</p>"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [ "first_name", "last_name", "email", "user_type", "priority", "subject", "message" ],
|
"required": [
|
||||||
|
"first_name", "last_name", "email", "age", "birth_date", "gender", "bio"
|
||||||
|
],
|
||||||
"form": {
|
"form": {
|
||||||
"class": "form-horizontal",
|
"class": "max-w-4xl mx-auto p-6 bg-white shadow-lg rounded-lg",
|
||||||
"action": "/process?task_id={{task_id}}&next=true",
|
"action": "/process?task_id={{task_id}}&form=comprehensive",
|
||||||
"method": "POST",
|
"method": "POST",
|
||||||
"enctype": "application/x-www-form-urlencoded",
|
"enctype": "multipart/form-data",
|
||||||
"groups": [
|
"groups": [
|
||||||
{
|
{
|
||||||
"title": {
|
"title": {
|
||||||
"text": "User Information",
|
"text": "Header Section",
|
||||||
"class": "text-lg font-semibold mb-2"
|
"class": "sr-only"
|
||||||
},
|
},
|
||||||
"fields": [ "first_name", "last_name", "email" ],
|
"fields": [ "page_title", "intro_paragraph", "divider" ],
|
||||||
"class": "flex gap-2 items-center justify-between"
|
"class": "mb-8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": {
|
"title": {
|
||||||
"text": "Details",
|
"text": "Personal Information",
|
||||||
"class": "text-lg font-semibold mb-2"
|
"class": "text-2xl font-semibold mb-6 text-gray-800"
|
||||||
},
|
},
|
||||||
"fields": [ "user_type", "priority", "subject", "message" ],
|
"fields": [
|
||||||
"class": "flex gap-2 items-center justify-between"
|
"first_name", "last_name", "email", "password", "age",
|
||||||
|
"birth_date", "website", "phone", "favorite_color",
|
||||||
|
"satisfaction", "profile_picture", "newsletter"
|
||||||
|
],
|
||||||
|
"class": "grid grid-cols-1 md:grid-cols-2 gap-4 mb-8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": {
|
||||||
|
"text": "Preferences & Background",
|
||||||
|
"class": "text-2xl font-semibold mb-6 text-gray-800"
|
||||||
|
},
|
||||||
|
"fields": [ "gender", "country", "skills", "bio" ],
|
||||||
|
"class": "space-y-4 mb-8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": {
|
||||||
|
"text": "Professional Details",
|
||||||
|
"class": "sr-only"
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
"section_header", "experience_fieldset", "technologies_datalist",
|
||||||
|
"tech_datalist", "completion_progress", "rating_meter"
|
||||||
|
],
|
||||||
|
"class": "mb-8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": {
|
||||||
|
"text": "Media & Content Examples",
|
||||||
|
"class": "sr-only"
|
||||||
|
},
|
||||||
|
"fields": [ "media_section", "demo_image" ],
|
||||||
|
"class": "mb-8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": {
|
||||||
|
"text": "Data & Text Examples",
|
||||||
|
"class": "sr-only"
|
||||||
|
},
|
||||||
|
"fields": [
|
||||||
|
"table_section", "list_examples", "interactive_details",
|
||||||
|
"code_example", "text_formatting"
|
||||||
|
],
|
||||||
|
"class": "mb-8"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": {
|
||||||
|
"text": "Footer",
|
||||||
|
"class": "sr-only"
|
||||||
|
},
|
||||||
|
"fields": [ "break_line", "final_divider", "footer_note" ],
|
||||||
|
"class": "mt-8"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"submit": {
|
"submit": {
|
||||||
"type": "submit",
|
"type": "submit",
|
||||||
"label": "Submit",
|
"label": "Submit Complete Form",
|
||||||
"class": "btn btn-primary px-2 py-1"
|
"class": "bg-blue-600 hover:bg-blue-700 text-white font-medium py-3 px-6 rounded-lg transition-colors duration-200 mr-4"
|
||||||
},
|
},
|
||||||
"reset": {
|
"reset": {
|
||||||
"type": "reset",
|
"type": "reset",
|
||||||
"label": "Reset",
|
"label": "Reset All Fields",
|
||||||
"class": "btn btn-secondary px-2 py-1"
|
"class": "bg-gray-500 hover:bg-gray-600 text-white font-medium py-3 px-6 rounded-lg transition-colors duration-200 mr-4"
|
||||||
|
},
|
||||||
|
"buttons": [
|
||||||
|
{
|
||||||
|
"type": "button",
|
||||||
|
"label": "Save Draft",
|
||||||
|
"class": "bg-green-600 hover:bg-green-700 text-white font-medium py-3 px-6 rounded-lg transition-colors duration-200",
|
||||||
|
"onclick": "saveDraft()"
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +1,212 @@
|
|||||||
/* Normalize and style form controls */
|
/* CSS Reset and Base Styles */
|
||||||
body {
|
* {
|
||||||
font-family: 'Arial', sans-serif;
|
box-sizing: border-box;
|
||||||
background-color: #f8f9fa;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.6;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Arial', 'Helvetica', sans-serif;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
color: #333;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Typography and Text Elements */
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 { font-size: 2.5rem; }
|
||||||
|
h2 { font-size: 2rem; }
|
||||||
|
h3 { font-size: 1.75rem; }
|
||||||
|
h4 { font-size: 1.5rem; }
|
||||||
|
h5 { font-size: 1.25rem; }
|
||||||
|
h6 { font-size: 1rem; }
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline text elements */
|
||||||
|
strong, b {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
em, i {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
background-color: #fff3cd;
|
||||||
|
padding: 0.125rem 0.25rem;
|
||||||
|
border-radius: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
del {
|
||||||
|
text-decoration: line-through;
|
||||||
|
color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
ins {
|
||||||
|
text-decoration: underline;
|
||||||
|
color: #28a745;
|
||||||
|
background-color: #d4edda;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub, sup {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub { bottom: -0.25rem; }
|
||||||
|
sup { top: -0.5rem; }
|
||||||
|
|
||||||
|
abbr {
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 0.125rem 0.25rem;
|
||||||
|
border-radius: 0.125rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #e83e8c;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
font-size: inherit;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 1rem 0;
|
||||||
|
padding: 1rem;
|
||||||
|
border-left: 4px solid #007bff;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
cite {
|
||||||
|
font-style: italic;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
font-style: normal;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
time {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Links */
|
||||||
|
a {
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #0056b3;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:focus {
|
||||||
|
outline: 2px solid #007bff;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:visited {
|
||||||
|
color: #6f42c1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Lists */
|
||||||
|
ul, ol {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
padding-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
list-style-type: disc;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
list-style-type: decimal;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul ul, ol ol, ul ol, ol ul {
|
||||||
|
margin-bottom: 0;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Nested list styles */
|
||||||
|
ul ul { list-style-type: circle; }
|
||||||
|
ul ul ul { list-style-type: square; }
|
||||||
|
|
||||||
|
ol ol { list-style-type: lower-alpha; }
|
||||||
|
ol ol ol { list-style-type: lower-roman; }
|
||||||
|
|
||||||
|
/* Description lists */
|
||||||
|
dl {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-left: 1rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Form Elements */
|
||||||
.form-group {
|
.form-group {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.required {
|
.required {
|
||||||
color: #dc3545; /* Bootstrap's danger color */
|
color: #dc3545;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group label {
|
.form-group label {
|
||||||
@@ -22,10 +216,35 @@ body {
|
|||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group input[type="text"],
|
/* Input elements */
|
||||||
.form-group input[type="email"],
|
input[type="text"],
|
||||||
.form-group select,
|
input[type="email"],
|
||||||
.form-group textarea {
|
input[type="password"],
|
||||||
|
input[type="number"],
|
||||||
|
input[type="tel"],
|
||||||
|
input[type="url"],
|
||||||
|
input[type="search"],
|
||||||
|
input[type="date"],
|
||||||
|
input[type="time"],
|
||||||
|
input[type="datetime-local"],
|
||||||
|
input[type="month"],
|
||||||
|
input[type="week"],
|
||||||
|
input[type="color"],
|
||||||
|
input[type="file"],
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-family: inherit;
|
||||||
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure form-group input styles apply to all types including password */
|
||||||
|
.form-group input[type="password"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
@@ -34,8 +253,19 @@ body {
|
|||||||
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
|
box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input:focus,
|
||||||
|
select:focus,
|
||||||
|
textarea:focus {
|
||||||
|
border-color: #007bff;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Specific focus styles for form-group inputs */
|
||||||
|
.form-group input[type="password"]:focus,
|
||||||
.form-group input[type="text"]:focus,
|
.form-group input[type="text"]:focus,
|
||||||
.form-group input[type="email"]:focus,
|
.form-group input[type="email"]:focus,
|
||||||
|
.form-group input[type="number"]:focus,
|
||||||
.form-group select:focus,
|
.form-group select:focus,
|
||||||
.form-group textarea:focus {
|
.form-group textarea:focus {
|
||||||
border-color: #007bff;
|
border-color: #007bff;
|
||||||
@@ -43,51 +273,208 @@ body {
|
|||||||
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
box-shadow: 0 0 5px rgba(0, 123, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group select {
|
input:invalid {
|
||||||
|
border-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
input:invalid:focus {
|
||||||
|
box-shadow: 0 0 5px rgba(220, 53, 69, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Range input */
|
||||||
|
input[type="range"] {
|
||||||
|
width: 100%;
|
||||||
|
height: 0.5rem;
|
||||||
|
background: #ddd;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
outline: none;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
appearance: none;
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
background: #007bff;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
width: 1.5rem;
|
||||||
|
height: 1.5rem;
|
||||||
|
background: #007bff;
|
||||||
|
border-radius: 50%;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Checkbox and radio */
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] {
|
||||||
|
width: auto;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
border-radius: 0.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select styling */
|
||||||
|
select {
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background: url('data:image/svg+xml;charset=US-ASCII,%3Csvg xmlns%3D%22http%3A//www.w3.org/2000/svg%22 viewBox%3D%220 0 4 5%22%3E%3Cpath fill%3D%22%23000%22 d%3D%22M2 0L0 2h4z%22/%3E%3C/svg%3E') no-repeat right 0.75rem center;
|
background: url('data:image/svg+xml;charset=US-ASCII,%3Csvg xmlns%3D%22http%3A//www.w3.org/2000/svg%22 viewBox%3D%220 0 4 5%22%3E%3Cpath fill%3D%22%23000%22 d%3D%22M2 0L0 2h4z%22/%3E%3C/svg%3E') no-repeat right 0.75rem center;
|
||||||
background-size: 0.5rem;
|
background-size: 0.5rem;
|
||||||
|
padding-right: 2.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group textarea {
|
select[multiple] {
|
||||||
|
background-image: none;
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
height: auto;
|
||||||
|
min-height: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
optgroup {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Textarea */
|
||||||
|
textarea {
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
|
min-height: 6rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group .form-control-error {
|
/* Fieldset and Legend */
|
||||||
color: #dc3545;
|
fieldset {
|
||||||
font-size: 0.875rem;
|
border: 1px solid #ccc;
|
||||||
margin-top: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Progress and Meter */
|
||||||
|
progress, meter {
|
||||||
|
width: 100%;
|
||||||
|
height: 1.5rem;
|
||||||
|
appearance: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress::-webkit-progress-bar,
|
||||||
|
meter::-webkit-meter-bar {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress::-webkit-progress-value {
|
||||||
|
background-color: #007bff;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
meter::-webkit-meter-optimum-value {
|
||||||
|
background-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
meter::-webkit-meter-suboptimum-value {
|
||||||
|
background-color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
meter::-webkit-meter-even-less-good-value {
|
||||||
|
background-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Output */
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.375rem 0.75rem;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Datalist styling (limited browser support) */
|
||||||
|
datalist {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Buttons */
|
/* Buttons */
|
||||||
button {
|
button,
|
||||||
|
input[type="button"],
|
||||||
|
input[type="submit"],
|
||||||
|
input[type="reset"] {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.75rem 1.5rem;
|
padding: 0.75rem 1.5rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-family: inherit;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.3s ease, transform 0.2s ease;
|
text-align: center;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover,
|
||||||
transform: scale(1.05);
|
input[type="button"]:hover,
|
||||||
|
input[type="submit"]:hover,
|
||||||
|
input[type="reset"]:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:active {
|
button:active,
|
||||||
transform: scale(0.95);
|
input[type="button"]:active,
|
||||||
|
input[type="submit"]:active,
|
||||||
|
input[type="reset"]:active {
|
||||||
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
button:disabled {
|
button:disabled,
|
||||||
|
input[type="button"]:disabled,
|
||||||
|
input[type="submit"]:disabled,
|
||||||
|
input[type="reset"]:disabled {
|
||||||
background-color: #ccc;
|
background-color: #ccc;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Primary Button */
|
button:focus,
|
||||||
|
input[type="button"]:focus,
|
||||||
|
input[type="submit"]:focus,
|
||||||
|
input[type="reset"]:focus {
|
||||||
|
outline: 2px solid #007bff;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Button variants */
|
||||||
.btn-primary {
|
.btn-primary {
|
||||||
color: #fff;
|
|
||||||
background-color: #007bff;
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-primary:hover {
|
.btn-primary:hover {
|
||||||
@@ -98,10 +485,9 @@ button:disabled {
|
|||||||
background-color: #004085;
|
background-color: #004085;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Secondary Button */
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
color: #fff;
|
|
||||||
background-color: #6c757d;
|
background-color: #6c757d;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:hover {
|
.btn-secondary:hover {
|
||||||
@@ -112,40 +498,283 @@ button:disabled {
|
|||||||
background-color: #4e555b;
|
background-color: #4e555b;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Additional layout-specific styles */
|
.btn-success {
|
||||||
.bg-gray-100 {
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-success:hover {
|
||||||
|
background-color: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger {
|
||||||
|
background-color: #dc3545;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-danger:hover {
|
||||||
|
background-color: #c82333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning {
|
||||||
|
background-color: #ffc107;
|
||||||
|
color: #212529;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-warning:hover {
|
||||||
|
background-color: #e0a800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info {
|
||||||
|
background-color: #17a2b8;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-info:hover {
|
||||||
|
background-color: #138496;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-light {
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
|
color: #212529;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-white {
|
.btn-light:hover {
|
||||||
background-color: #fff;
|
background-color: #e2e6ea;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-gray-200 {
|
.btn-dark {
|
||||||
background-color: #e9ecef;
|
background-color: #343a40;
|
||||||
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shadow-md {
|
.btn-dark:hover {
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
background-color: #23272b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rounded {
|
/* Tables */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-top: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:hover {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
vertical-align: bottom;
|
||||||
|
border-bottom: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody + tbody {
|
||||||
|
border-top: 2px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Table variants */
|
||||||
|
.table-striped tbody tr:nth-of-type(odd) {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-bordered {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-bordered th,
|
||||||
|
.table-bordered td {
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Images and Media */
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rounded-lg {
|
figure {
|
||||||
border-radius: 0.5rem;
|
margin: 1rem 0;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-xl {
|
figcaption {
|
||||||
font-size: 1.25rem;
|
font-size: 0.875rem;
|
||||||
font-weight: bold;
|
color: #666;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.font-bold {
|
audio, video {
|
||||||
font-weight: bold;
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mb-4 {
|
canvas {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
fill: currentColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sectioning Elements */
|
||||||
|
article, section, nav, aside {
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
header, footer {
|
||||||
|
padding: 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
main {
|
||||||
|
min-height: calc(100vh - 200px);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive Elements */
|
||||||
|
details {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 1px solid #dee2e6;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
padding: 0.5rem;
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary:hover {
|
||||||
|
background-color: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open] summary {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog {
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog::backdrop {
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Embedded Content */
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
embed, object {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Custom Layout Utilities (Non-Tailwind) */
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: -0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error states */
|
||||||
|
.form-control-error {
|
||||||
|
color: #dc3545;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-invalid {
|
||||||
|
border-color: #dc3545;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-valid {
|
||||||
|
border-color: #28a745;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Print styles */
|
||||||
|
@media print {
|
||||||
|
* {
|
||||||
|
box-shadow: none !important;
|
||||||
|
text-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, a:visited {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title]:after {
|
||||||
|
content: " (" attr(title) ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
pre, blockquote {
|
||||||
|
border: 1px solid #999;
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead {
|
||||||
|
display: table-header-group;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr, img {
|
||||||
|
page-break-inside: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
p, h2, h3 {
|
||||||
|
orphans: 3;
|
||||||
|
widows: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2, h3 {
|
||||||
|
page-break-after: avoid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -5,8 +5,150 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A single template for the entire group structure
|
||||||
|
const groupTemplateStr = `
|
||||||
|
<div class="form-group-container">
|
||||||
|
{{if .Title.Text}}
|
||||||
|
{{if .Title.Class}}
|
||||||
|
<div class="{{.Title.Class}}">{{.Title.Text}}</div>
|
||||||
|
{{else}}
|
||||||
|
<h3 class="group-title">{{.Title.Text}}</h3>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
<div class="{{.GroupClass}}">{{.FieldsHTML}}</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
|
||||||
|
// Templates for field rendering - now supports all HTML DOM elements
|
||||||
|
var fieldTemplates = map[string]string{
|
||||||
|
// Form elements
|
||||||
|
"input": `<div class="{{.Class}}">{{.LabelHTML}}<input {{.AllAttributes}} />{{.ContentHTML}}</div>`,
|
||||||
|
"textarea": `<div class="{{.Class}}">{{.LabelHTML}}<textarea {{.AllAttributes}}>{{.Content}}</textarea>{{.ContentHTML}}</div>`,
|
||||||
|
"select": `<div class="{{.Class}}">{{.LabelHTML}}<select {{.AllAttributes}}>{{.OptionsHTML}}</select>{{.ContentHTML}}</div>`,
|
||||||
|
"button": `<button {{.AllAttributes}}>{{.Content}}</button>`,
|
||||||
|
"option": `<option {{.AllAttributes}}>{{.Content}}</option>`,
|
||||||
|
"optgroup": `<optgroup {{.AllAttributes}}>{{.OptionsHTML}}</optgroup>`,
|
||||||
|
"label": `<label {{.AllAttributes}}>{{.Content}}</label>`,
|
||||||
|
"fieldset": `<fieldset {{.AllAttributes}}>{{.ContentHTML}}</fieldset>`,
|
||||||
|
"legend": `<legend {{.AllAttributes}}>{{.Content}}</legend>`,
|
||||||
|
"datalist": `<datalist {{.AllAttributes}}>{{.OptionsHTML}}</datalist>`,
|
||||||
|
"output": `<output {{.AllAttributes}}>{{.Content}}</output>`,
|
||||||
|
"progress": `<progress {{.AllAttributes}}>{{.Content}}</progress>`,
|
||||||
|
"meter": `<meter {{.AllAttributes}}>{{.Content}}</meter>`,
|
||||||
|
|
||||||
|
// Text content elements
|
||||||
|
"h1": `<h1 {{.AllAttributes}}>{{.Content}}</h1>`,
|
||||||
|
"h2": `<h2 {{.AllAttributes}}>{{.Content}}</h2>`,
|
||||||
|
"h3": `<h3 {{.AllAttributes}}>{{.Content}}</h3>`,
|
||||||
|
"h4": `<h4 {{.AllAttributes}}>{{.Content}}</h4>`,
|
||||||
|
"h5": `<h5 {{.AllAttributes}}>{{.Content}}</h5>`,
|
||||||
|
"h6": `<h6 {{.AllAttributes}}>{{.Content}}</h6>`,
|
||||||
|
"p": `<p {{.AllAttributes}}>{{.Content}}</p>`,
|
||||||
|
"div": `<div {{.AllAttributes}}>{{.ContentHTML}}</div>`,
|
||||||
|
"span": `<span {{.AllAttributes}}>{{.Content}}</span>`,
|
||||||
|
"pre": `<pre {{.AllAttributes}}>{{.Content}}</pre>`,
|
||||||
|
"code": `<code {{.AllAttributes}}>{{.Content}}</code>`,
|
||||||
|
"blockquote": `<blockquote {{.AllAttributes}}>{{.ContentHTML}}</blockquote>`,
|
||||||
|
"cite": `<cite {{.AllAttributes}}>{{.Content}}</cite>`,
|
||||||
|
"strong": `<strong {{.AllAttributes}}>{{.Content}}</strong>`,
|
||||||
|
"em": `<em {{.AllAttributes}}>{{.Content}}</em>`,
|
||||||
|
"small": `<small {{.AllAttributes}}>{{.Content}}</small>`,
|
||||||
|
"mark": `<mark {{.AllAttributes}}>{{.Content}}</mark>`,
|
||||||
|
"del": `<del {{.AllAttributes}}>{{.Content}}</del>`,
|
||||||
|
"ins": `<ins {{.AllAttributes}}>{{.Content}}</ins>`,
|
||||||
|
"sub": `<sub {{.AllAttributes}}>{{.Content}}</sub>`,
|
||||||
|
"sup": `<sup {{.AllAttributes}}>{{.Content}}</sup>`,
|
||||||
|
"abbr": `<abbr {{.AllAttributes}}>{{.Content}}</abbr>`,
|
||||||
|
"address": `<address {{.AllAttributes}}>{{.ContentHTML}}</address>`,
|
||||||
|
"time": `<time {{.AllAttributes}}>{{.Content}}</time>`,
|
||||||
|
|
||||||
|
// List elements
|
||||||
|
"ul": `<ul {{.AllAttributes}}>{{.ContentHTML}}</ul>`,
|
||||||
|
"ol": `<ol {{.AllAttributes}}>{{.ContentHTML}}</ol>`,
|
||||||
|
"li": `<li {{.AllAttributes}}>{{.Content}}</li>`,
|
||||||
|
"dl": `<dl {{.AllAttributes}}>{{.ContentHTML}}</dl>`,
|
||||||
|
"dt": `<dt {{.AllAttributes}}>{{.Content}}</dt>`,
|
||||||
|
"dd": `<dd {{.AllAttributes}}>{{.Content}}</dd>`,
|
||||||
|
|
||||||
|
// Links and media
|
||||||
|
"a": `<a {{.AllAttributes}}>{{.Content}}</a>`,
|
||||||
|
"img": `<img {{.AllAttributes}} />`,
|
||||||
|
"figure": `<figure {{.AllAttributes}}>{{.ContentHTML}}</figure>`,
|
||||||
|
"figcaption": `<figcaption {{.AllAttributes}}>{{.Content}}</figcaption>`,
|
||||||
|
"audio": `<audio {{.AllAttributes}}>{{.ContentHTML}}</audio>`,
|
||||||
|
"video": `<video {{.AllAttributes}}>{{.ContentHTML}}</video>`,
|
||||||
|
"source": `<source {{.AllAttributes}} />`,
|
||||||
|
"track": `<track {{.AllAttributes}} />`,
|
||||||
|
|
||||||
|
// Table elements
|
||||||
|
"table": `<table {{.AllAttributes}}>{{.ContentHTML}}</table>`,
|
||||||
|
"caption": `<caption {{.AllAttributes}}>{{.Content}}</caption>`,
|
||||||
|
"thead": `<thead {{.AllAttributes}}>{{.ContentHTML}}</thead>`,
|
||||||
|
"tbody": `<tbody {{.AllAttributes}}>{{.ContentHTML}}</tbody>`,
|
||||||
|
"tfoot": `<tfoot {{.AllAttributes}}>{{.ContentHTML}}</tfoot>`,
|
||||||
|
"tr": `<tr {{.AllAttributes}}>{{.ContentHTML}}</tr>`,
|
||||||
|
"th": `<th {{.AllAttributes}}>{{.Content}}</th>`,
|
||||||
|
"td": `<td {{.AllAttributes}}>{{.Content}}</td>`,
|
||||||
|
"colgroup": `<colgroup {{.AllAttributes}}>{{.ContentHTML}}</colgroup>`,
|
||||||
|
"col": `<col {{.AllAttributes}} />`,
|
||||||
|
|
||||||
|
// Sectioning elements
|
||||||
|
"article": `<article {{.AllAttributes}}>{{.ContentHTML}}</article>`,
|
||||||
|
"section": `<section {{.AllAttributes}}>{{.ContentHTML}}</section>`,
|
||||||
|
"nav": `<nav {{.AllAttributes}}>{{.ContentHTML}}</nav>`,
|
||||||
|
"aside": `<aside {{.AllAttributes}}>{{.ContentHTML}}</aside>`,
|
||||||
|
"header": `<header {{.AllAttributes}}>{{.ContentHTML}}</header>`,
|
||||||
|
"footer": `<footer {{.AllAttributes}}>{{.ContentHTML}}</footer>`,
|
||||||
|
"main": `<main {{.AllAttributes}}>{{.ContentHTML}}</main>`,
|
||||||
|
|
||||||
|
// Interactive elements
|
||||||
|
"details": `<details {{.AllAttributes}}>{{.ContentHTML}}</details>`,
|
||||||
|
"summary": `<summary {{.AllAttributes}}>{{.Content}}</summary>`,
|
||||||
|
"dialog": `<dialog {{.AllAttributes}}>{{.ContentHTML}}</dialog>`,
|
||||||
|
|
||||||
|
// Embedded content
|
||||||
|
"iframe": `<iframe {{.AllAttributes}}>{{.Content}}</iframe>`,
|
||||||
|
"embed": `<embed {{.AllAttributes}} />`,
|
||||||
|
"object": `<object {{.AllAttributes}}>{{.ContentHTML}}</object>`,
|
||||||
|
"param": `<param {{.AllAttributes}} />`,
|
||||||
|
"picture": `<picture {{.AllAttributes}}>{{.ContentHTML}}</picture>`,
|
||||||
|
"canvas": `<canvas {{.AllAttributes}}>{{.Content}}</canvas>`,
|
||||||
|
"svg": `<svg {{.AllAttributes}}>{{.ContentHTML}}</svg>`,
|
||||||
|
|
||||||
|
// Meta elements
|
||||||
|
"br": `<br {{.AllAttributes}} />`,
|
||||||
|
"hr": `<hr {{.AllAttributes}} />`,
|
||||||
|
"wbr": `<wbr {{.AllAttributes}} />`,
|
||||||
|
|
||||||
|
// Generic template for any unlisted element
|
||||||
|
"generic": `<{{.Element}} {{.AllAttributes}}>{{.ContentHTML}}</{{.Element}}>`,
|
||||||
|
"void": `<{{.Element}} {{.AllAttributes}} />`,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Void elements that don't have closing tags
|
||||||
|
var voidElements = map[string]bool{
|
||||||
|
"area": true, "base": true, "br": true, "col": true, "embed": true,
|
||||||
|
"hr": true, "img": true, "input": true, "link": true, "meta": true,
|
||||||
|
"param": true, "source": true, "track": true, "wbr": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var standardAttrs = []string{
|
||||||
|
"id", "class", "name", "type", "value", "placeholder", "href", "src",
|
||||||
|
"alt", "title", "target", "rel", "role", "tabindex", "accesskey",
|
||||||
|
"contenteditable", "draggable", "hidden", "spellcheck", "translate",
|
||||||
|
"autocomplete", "autofocus", "disabled", "readonly", "required",
|
||||||
|
"multiple", "checked", "selected", "defer", "async", "loop", "muted",
|
||||||
|
"controls", "autoplay", "preload", "poster", "width", "height",
|
||||||
|
"rows", "cols", "size", "maxlength", "minlength", "min", "max",
|
||||||
|
"step", "pattern", "accept", "capture", "form", "formaction",
|
||||||
|
"formenctype", "formmethod", "formnovalidate", "formtarget",
|
||||||
|
"colspan", "rowspan", "headers", "scope", "start", "reversed",
|
||||||
|
"datetime", "open", "label", "high", "low", "optimum", "span",
|
||||||
|
}
|
||||||
|
|
||||||
// FieldInfo represents metadata for a field extracted from JSONSchema
|
// FieldInfo represents metadata for a field extracted from JSONSchema
|
||||||
type FieldInfo struct {
|
type FieldInfo struct {
|
||||||
Name string
|
Name string
|
||||||
@@ -198,21 +340,6 @@ func parseGroupsFromSchema(schema map[string]any) []GroupInfo {
|
|||||||
// renderGroup generates HTML for a single group
|
// renderGroup generates HTML for a single group
|
||||||
func renderGroup(group GroupInfo) string {
|
func renderGroup(group GroupInfo) string {
|
||||||
var groupHTML bytes.Buffer
|
var groupHTML bytes.Buffer
|
||||||
|
|
||||||
// A single template for the entire group structure
|
|
||||||
const groupTemplateStr = `
|
|
||||||
<div class="form-group-container">
|
|
||||||
{{if .Title.Text}}
|
|
||||||
{{if .Title.Class}}
|
|
||||||
<div class="{{.Title.Class}}">{{.Title.Text}}</div>
|
|
||||||
{{else}}
|
|
||||||
<h3 class="group-title">{{.Title.Text}}</h3>
|
|
||||||
{{end}}
|
|
||||||
{{end}}
|
|
||||||
<div class="{{.GroupClass}}">{{.FieldsHTML}}</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
|
|
||||||
// Render fields
|
// Render fields
|
||||||
var fieldsHTML bytes.Buffer
|
var fieldsHTML bytes.Buffer
|
||||||
for _, field := range group.Fields {
|
for _, field := range group.Fields {
|
||||||
@@ -236,123 +363,327 @@ func renderGroup(group GroupInfo) string {
|
|||||||
return groupHTML.String()
|
return groupHTML.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Templates for field rendering
|
|
||||||
var fieldTemplates = map[string]string{
|
|
||||||
"input": `<div class="{{.Class}}"><label for="{{.Name}}">{{.Title}}</label><input type="{{.InputType}}" id="{{.Name}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{.Required}} {{.AdditionalAttributes}} /></div>`,
|
|
||||||
"textarea": `<div class="{{.Class}}"><label for="{{.Name}}">{{.Title}}</label><textarea id="{{.Name}}" name="{{.Name}}" placeholder="{{.Placeholder}}" {{.Required}} {{.AdditionalAttributes}}></textarea></div>`,
|
|
||||||
"select": `<div class="{{.Class}}"><label for="{{.Name}}">{{.Title}}</label><select id="{{.Name}}" name="{{.Name}}" {{.Required}} {{.AdditionalAttributes}}>{{.OptionsHTML}}</select></div>`,
|
|
||||||
"h": `<{{.Control}} class="{{.Class}}" id="{{.Name}}" {{.AdditionalAttributes}}>{{.Title}}</{{.Control}}>`,
|
|
||||||
"p": `<p class="{{.Class}}" id="{{.Name}}" {{.AdditionalAttributes}}>{{.Title}}</p>`,
|
|
||||||
"a": `<a class="{{.Class}}" id="{{.Name}}" href="{{.Href}}" {{.AdditionalAttributes}}>{{.Title}}</a>`,
|
|
||||||
"button": `<button type="{{.Type}}" class="{{.Class}}">{{.Label}}</button>`,
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderField(field FieldInfo) string {
|
func renderField(field FieldInfo) string {
|
||||||
ui, ok := field.Definition["ui"].(map[string]any)
|
ui, ok := field.Definition["ui"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
control, _ := ui["element"].(string)
|
element, _ := ui["element"].(string)
|
||||||
class, _ := ui["class"].(string)
|
if element == "" {
|
||||||
name, _ := ui["name"].(string)
|
return ""
|
||||||
title, _ := field.Definition["title"].(string)
|
|
||||||
placeholder, _ := field.Definition["placeholder"].(string)
|
|
||||||
|
|
||||||
isRequired, _ := field.Definition["isRequired"].(bool)
|
|
||||||
required := ""
|
|
||||||
titleHTML := title
|
|
||||||
if isRequired {
|
|
||||||
required = "required"
|
|
||||||
titleHTML += ` <span class="required">*</span>`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
inputType := "text"
|
// Build all attributes
|
||||||
if uiType, ok := ui["type"].(string); ok {
|
allAttributes := buildAllAttributes(field, ui)
|
||||||
inputType = uiType
|
|
||||||
} else if fieldType, ok := field.Definition["type"].(string); ok && fieldType == "email" {
|
|
||||||
inputType = "email"
|
|
||||||
}
|
|
||||||
|
|
||||||
var additionalAttributes bytes.Buffer
|
// Get content
|
||||||
for key, value := range field.Definition {
|
content := getFieldContent(field.Definition, ui)
|
||||||
switch key {
|
contentHTML := getFieldContentHTML(field.Definition, ui)
|
||||||
case "title", "ui", "placeholder", "type", "order", "isRequired":
|
|
||||||
continue
|
// Generate label if needed
|
||||||
default:
|
labelHTML := generateLabel(field, ui)
|
||||||
additionalAttributes.WriteString(fmt.Sprintf(` %s="%v"`, key, value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data := map[string]any{
|
data := map[string]any{
|
||||||
"Class": class,
|
"Element": element,
|
||||||
"Name": name,
|
"AllAttributes": template.HTMLAttr(allAttributes),
|
||||||
"Title": template.HTML(titleHTML),
|
"Content": content,
|
||||||
"Placeholder": placeholder,
|
"ContentHTML": template.HTML(contentHTML),
|
||||||
"Required": required,
|
"LabelHTML": template.HTML(labelHTML),
|
||||||
"AdditionalAttributes": template.HTML(additionalAttributes.String()),
|
"Class": getUIValue(ui, "class"),
|
||||||
"InputType": inputType,
|
"OptionsHTML": template.HTML(generateOptions(ui)),
|
||||||
"Control": control,
|
|
||||||
"Href": "",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle options for select
|
// Use specific template if available, otherwise use generic
|
||||||
if control == "select" {
|
var tmplStr string
|
||||||
options, _ := ui["options"].([]any)
|
if template, exists := fieldTemplates[element]; exists {
|
||||||
var optionsHTML bytes.Buffer
|
tmplStr = template
|
||||||
for _, option := range options {
|
} else if voidElements[element] {
|
||||||
optionsHTML.WriteString(fmt.Sprintf(`<option value="%v">%v</option>`, option, option))
|
tmplStr = fieldTemplates["void"]
|
||||||
}
|
} else {
|
||||||
data["OptionsHTML"] = template.HTML(optionsHTML.String())
|
tmplStr = fieldTemplates["generic"]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle href for links
|
tmpl := template.Must(template.New(element).Parse(tmplStr))
|
||||||
if control == "a" {
|
|
||||||
if href, ok := ui["href"].(string); ok {
|
|
||||||
data["Href"] = href
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmplStr, ok := fieldTemplates[control]; ok {
|
|
||||||
tmpl := template.Must(template.New(control).Parse(tmplStr))
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
tmpl.Execute(&buf, data)
|
if err := tmpl.Execute(&buf, data); err != nil {
|
||||||
return buf.String()
|
return ""
|
||||||
}
|
}
|
||||||
// Handle generic 'h' template for h1-h6
|
|
||||||
if control[0] == 'h' && len(control) == 2 && control[1] >= '1' && control[1] <= '6' {
|
|
||||||
data["Control"] = control
|
|
||||||
tmpl := template.Must(template.New("h").Parse(fieldTemplates["h"]))
|
|
||||||
var buf bytes.Buffer
|
|
||||||
tmpl.Execute(&buf, data)
|
|
||||||
return buf.String()
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildAllAttributes(field FieldInfo, ui map[string]any) string {
|
||||||
|
var attributes []string
|
||||||
|
|
||||||
|
// Add standard attributes from ui
|
||||||
|
for _, attr := range standardAttrs {
|
||||||
|
if value, exists := ui[attr]; exists {
|
||||||
|
if attr == "class" && value == "" {
|
||||||
|
continue // Skip empty class
|
||||||
|
}
|
||||||
|
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, attr, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle required field
|
||||||
|
if isRequired, ok := field.Definition["isRequired"].(bool); ok && isRequired {
|
||||||
|
if !contains(attributes, "required=") {
|
||||||
|
attributes = append(attributes, `required="required"`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle input type based on field type
|
||||||
|
element, _ := ui["element"].(string)
|
||||||
|
if element == "input" {
|
||||||
|
if inputType := getInputType(field.Definition, ui); inputType != "" {
|
||||||
|
if !contains(attributes, "type=") {
|
||||||
|
attributes = append(attributes, fmt.Sprintf(`type="%s"`, inputType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add data-* and aria-* attributes
|
||||||
|
for key, value := range ui {
|
||||||
|
if strings.HasPrefix(key, "data-") || strings.HasPrefix(key, "aria-") {
|
||||||
|
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, key, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add custom attributes from field definition (excluding known schema properties)
|
||||||
|
excludeFields := map[string]bool{
|
||||||
|
"type": true, "title": true, "ui": true, "placeholder": true,
|
||||||
|
"order": true, "isRequired": true, "content": true, "children": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range field.Definition {
|
||||||
|
if !excludeFields[key] && !strings.HasPrefix(key, "ui") {
|
||||||
|
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, key, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(attributes, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getInputType(fieldDef map[string]any, ui map[string]any) string {
|
||||||
|
// Check ui type first
|
||||||
|
if uiType, ok := ui["type"].(string); ok {
|
||||||
|
return uiType
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map schema types to input types
|
||||||
|
if fieldType, ok := fieldDef["type"].(string); ok {
|
||||||
|
switch fieldType {
|
||||||
|
case "email":
|
||||||
|
return "email"
|
||||||
|
case "password":
|
||||||
|
return "password"
|
||||||
|
case "number", "integer":
|
||||||
|
return "number"
|
||||||
|
case "boolean":
|
||||||
|
return "checkbox"
|
||||||
|
case "date":
|
||||||
|
return "date"
|
||||||
|
case "time":
|
||||||
|
return "time"
|
||||||
|
case "datetime":
|
||||||
|
return "datetime-local"
|
||||||
|
case "url":
|
||||||
|
return "url"
|
||||||
|
case "tel":
|
||||||
|
return "tel"
|
||||||
|
case "color":
|
||||||
|
return "color"
|
||||||
|
case "range":
|
||||||
|
return "range"
|
||||||
|
case "file":
|
||||||
|
return "file"
|
||||||
|
case "hidden":
|
||||||
|
return "hidden"
|
||||||
|
default:
|
||||||
|
return "text"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "text"
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFieldContent(fieldDef map[string]any, ui map[string]any) string {
|
||||||
|
// Check for content in ui first
|
||||||
|
if content, ok := ui["content"].(string); ok {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for content in field definition
|
||||||
|
if content, ok := fieldDef["content"].(string); ok {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use title as fallback for some elements
|
||||||
|
if title, ok := fieldDef["title"].(string); ok {
|
||||||
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getFieldContentHTML(fieldDef map[string]any, ui map[string]any) string {
|
||||||
|
// Check for HTML content in ui
|
||||||
|
if contentHTML, ok := ui["contentHTML"].(string); ok {
|
||||||
|
return contentHTML
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for children elements
|
||||||
|
if children, ok := ui["children"].([]any); ok {
|
||||||
|
return renderChildren(children)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderChildren(children []any) string {
|
||||||
|
var result strings.Builder
|
||||||
|
for _, child := range children {
|
||||||
|
if childMap, ok := child.(map[string]any); ok {
|
||||||
|
// Create a temporary field info for the child
|
||||||
|
childField := FieldInfo{
|
||||||
|
Name: getMapValue(childMap, "name", ""),
|
||||||
|
Definition: childMap,
|
||||||
|
}
|
||||||
|
result.WriteString(renderField(childField))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateLabel(field FieldInfo, ui map[string]any) string {
|
||||||
|
// Check if label should be generated
|
||||||
|
if showLabel, ok := ui["showLabel"].(bool); !showLabel && ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
title, _ := field.Definition["title"].(string)
|
||||||
|
if title == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
name := getUIValue(ui, "name")
|
||||||
|
if name == "" {
|
||||||
|
name = field.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if field is required
|
||||||
|
isRequired, _ := field.Definition["isRequired"].(bool)
|
||||||
|
requiredSpan := ""
|
||||||
|
if isRequired {
|
||||||
|
requiredSpan = ` <span class="required">*</span>`
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(`<label for="%s">%s%s</label>`, name, title, requiredSpan)
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateOptions(ui map[string]any) string {
|
||||||
|
options, ok := ui["options"].([]any)
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var optionsHTML strings.Builder
|
||||||
|
for _, option := range options {
|
||||||
|
if optionMap, ok := option.(map[string]any); ok {
|
||||||
|
// Complex option with attributes
|
||||||
|
value := getMapValue(optionMap, "value", "")
|
||||||
|
text := getMapValue(optionMap, "text", value)
|
||||||
|
selected := ""
|
||||||
|
if isSelected, ok := optionMap["selected"].(bool); ok && isSelected {
|
||||||
|
selected = ` selected="selected"`
|
||||||
|
}
|
||||||
|
disabled := ""
|
||||||
|
if isDisabled, ok := optionMap["disabled"].(bool); ok && isDisabled {
|
||||||
|
disabled = ` disabled="disabled"`
|
||||||
|
}
|
||||||
|
optionsHTML.WriteString(fmt.Sprintf(`<option value="%s"%s%s>%s</option>`,
|
||||||
|
value, selected, disabled, text))
|
||||||
|
} else {
|
||||||
|
// Simple option (just value)
|
||||||
|
optionsHTML.WriteString(fmt.Sprintf(`<option value="%v">%v</option>`, option, option))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return optionsHTML.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUIValue(ui map[string]any, key string) string {
|
||||||
|
if value, ok := ui[key].(string); ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMapValue(m map[string]any, key, defaultValue string) string {
|
||||||
|
if value, ok := m[key].(string); ok {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(slice []string, substr string) bool {
|
||||||
|
for _, item := range slice {
|
||||||
|
if strings.Contains(item, substr) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// renderButtons generates HTML for form buttons
|
// renderButtons generates HTML for form buttons
|
||||||
func renderButtons(formConfig map[string]any) string {
|
func renderButtons(formConfig map[string]any) string {
|
||||||
var buttonsHTML bytes.Buffer
|
var buttonsHTML bytes.Buffer
|
||||||
tmpl := template.Must(template.New("button").Parse(fieldTemplates["button"]))
|
|
||||||
|
|
||||||
if submitConfig, ok := formConfig["submit"].(map[string]any); ok {
|
if submitConfig, ok := formConfig["submit"].(map[string]any); ok {
|
||||||
data := map[string]any{
|
buttonHTML := renderButtonFromConfig(submitConfig, "submit")
|
||||||
"Type": submitConfig["type"],
|
buttonsHTML.WriteString(buttonHTML)
|
||||||
"Class": submitConfig["class"],
|
|
||||||
"Label": submitConfig["label"],
|
|
||||||
}
|
|
||||||
tmpl.Execute(&buttonsHTML, data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if resetConfig, ok := formConfig["reset"].(map[string]any); ok {
|
if resetConfig, ok := formConfig["reset"].(map[string]any); ok {
|
||||||
data := map[string]any{
|
buttonHTML := renderButtonFromConfig(resetConfig, "reset")
|
||||||
"Type": resetConfig["type"],
|
buttonsHTML.WriteString(buttonHTML)
|
||||||
"Class": resetConfig["class"],
|
}
|
||||||
"Label": resetConfig["label"],
|
|
||||||
|
// Support for additional custom buttons
|
||||||
|
if buttons, ok := formConfig["buttons"].([]any); ok {
|
||||||
|
for _, button := range buttons {
|
||||||
|
if buttonMap, ok := button.(map[string]any); ok {
|
||||||
|
buttonType := getMapValue(buttonMap, "type", "button")
|
||||||
|
buttonHTML := renderButtonFromConfig(buttonMap, buttonType)
|
||||||
|
buttonsHTML.WriteString(buttonHTML)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
tmpl.Execute(&buttonsHTML, data)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buttonsHTML.String()
|
return buttonsHTML.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func renderButtonFromConfig(config map[string]any, defaultType string) string {
|
||||||
|
var attributes []string
|
||||||
|
|
||||||
|
buttonType := getMapValue(config, "type", defaultType)
|
||||||
|
attributes = append(attributes, fmt.Sprintf(`type="%s"`, buttonType))
|
||||||
|
|
||||||
|
if class := getMapValue(config, "class", ""); class != "" {
|
||||||
|
attributes = append(attributes, fmt.Sprintf(`class="%s"`, class))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add other button attributes
|
||||||
|
for key, value := range config {
|
||||||
|
switch key {
|
||||||
|
case "type", "class", "label", "content":
|
||||||
|
continue // Already handled
|
||||||
|
default:
|
||||||
|
attributes = append(attributes, fmt.Sprintf(`%s="%v"`, key, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
content := getMapValue(config, "label", getMapValue(config, "content", "Button"))
|
||||||
|
|
||||||
|
return fmt.Sprintf(`<button %s>%s</button>`,
|
||||||
|
strings.Join(attributes, " "), content)
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user