@nextlyhq/plugin-form-builder 0.0.2-alpha.34 → 0.0.2-alpha.35
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/README.md +4 -5
- package/dist/admin/index.d.ts +101 -303
- package/dist/admin/index.js +3600 -3243
- package/dist/admin/index.js.map +1 -1
- package/dist/chunk-I4VFGPNR.js +506 -0
- package/dist/chunk-I4VFGPNR.js.map +1 -0
- package/dist/index.d.ts +4 -4
- package/dist/index.js +417 -571
- package/dist/index.js.map +1 -1
- package/dist/{index-BRumOEsE.d.ts → types-C-DcuYGF.d.ts} +61 -93
- package/package.json +22 -30
- package/dist/CheckboxFieldEditor-K4T6QX52.js +0 -121
- package/dist/CheckboxFieldEditor-K4T6QX52.js.map +0 -1
- package/dist/DateFieldEditor-JFJFAVOH.js +0 -187
- package/dist/DateFieldEditor-JFJFAVOH.js.map +0 -1
- package/dist/EmailFieldEditor-ZZ4VSKKB.js +0 -138
- package/dist/EmailFieldEditor-ZZ4VSKKB.js.map +0 -1
- package/dist/NumberFieldEditor-UISAYR6Z.js +0 -175
- package/dist/NumberFieldEditor-UISAYR6Z.js.map +0 -1
- package/dist/RadioFieldEditor-6UQFH4WT.js +0 -165
- package/dist/RadioFieldEditor-6UQFH4WT.js.map +0 -1
- package/dist/SelectFieldEditor-PSO4KHV4.js +0 -213
- package/dist/SelectFieldEditor-PSO4KHV4.js.map +0 -1
- package/dist/TextFieldEditor-5EU64EA5.js +0 -187
- package/dist/TextFieldEditor-5EU64EA5.js.map +0 -1
- package/dist/TextareaFieldEditor-773G3LNM.js +0 -190
- package/dist/TextareaFieldEditor-773G3LNM.js.map +0 -1
- package/dist/admin/index.css +0 -91
- package/dist/admin/index.css.map +0 -1
- package/dist/chunk-2LLUO3TV.js +0 -547
- package/dist/chunk-2LLUO3TV.js.map +0 -1
- package/dist/chunk-XTCEPJ7B.js +0 -256
- package/dist/chunk-XTCEPJ7B.js.map +0 -1
- package/dist/components/index.d.ts +0 -3
- package/dist/components/index.js +0 -9
- package/dist/components/index.js.map +0 -1
- package/dist/styles/form-builder.css +0 -128
- package/dist/styles/submissions-filter.css +0 -74
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/submissions/SubmissionList.tsx","../src/components/submissions/SubmissionDetail.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Submission List Component\n *\n * Admin view for listing and managing form submissions.\n * Displays submissions in a filterable table with bulk actions.\n *\n * @module components/submissions/SubmissionList\n * @since 0.1.0\n */\n\n\"use client\";\n\nimport { DataTableView } from \"@nextlyhq/plugin-sdk/admin\";\nimport type {\n DataTableSelection,\n NextlyColumn,\n RowAction,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport type React from \"react\";\nimport { useState, useCallback, useMemo } from \"react\";\n\nimport type { SubmissionDocument, FormDocument, FormField } from \"../../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Props for the SubmissionList component.\n */\nexport interface SubmissionListProps {\n /** Form document containing field definitions */\n form: FormDocument;\n\n /** Array of submission documents to display */\n submissions: SubmissionDocument[];\n\n /** Whether data is currently loading */\n isLoading?: boolean;\n\n /** Callback when submission status is changed */\n onStatusChange?: (\n id: string,\n status: \"new\" | \"read\" | \"archived\"\n ) => Promise<void>;\n\n /** Callback when submission is deleted */\n onDelete?: (id: string) => Promise<void>;\n\n /** Callback when bulk delete is triggered */\n onBulkDelete?: (ids: string[]) => Promise<void>;\n\n /** Callback when bulk status change is triggered */\n onBulkStatusChange?: (\n ids: string[],\n status: \"new\" | \"read\" | \"archived\"\n ) => Promise<void>;\n\n /** Callback when export is triggered */\n onExport?: (format: \"csv\" | \"json\") => void;\n\n /** Callback when a submission is clicked for detail view */\n onViewDetail?: (submission: SubmissionDocument) => void;\n}\n\n/**\n * Status filter options.\n */\ntype StatusFilter = \"all\" | \"new\" | \"read\" | \"archived\";\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Format a value for display in the table.\n */\nfunction formatValue(value: unknown): string {\n if (value === null || value === undefined) return \"-\";\n if (Array.isArray(value)) return value.join(\", \");\n if (typeof value === \"boolean\") return value ? \"Yes\" : \"No\";\n if (value instanceof Date) return value.toLocaleDateString();\n let str: string;\n if (typeof value === \"object\") {\n str = JSON.stringify(value);\n } else {\n // eslint-disable-next-line @typescript-eslint/no-base-to-string -- value narrowed to primitive above; rule doesn't follow control flow on unknown\n str = String(value);\n }\n return str.length > 50 ? str.slice(0, 50) + \"...\" : str;\n}\n\n/**\n * Format a date for display.\n */\nfunction formatDate(date: Date | string): string {\n const d = typeof date === \"string\" ? new Date(date) : date;\n return d.toLocaleString();\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Submission List Component\n *\n * Displays form submissions in a table with filtering, selection, and actions.\n *\n * @example\n * ```tsx\n * <SubmissionList\n * form={form}\n * submissions={submissions}\n * onStatusChange={handleStatusChange}\n * onDelete={handleDelete}\n * onExport={handleExport}\n * onViewDetail={handleViewDetail}\n * />\n * ```\n */\nexport function SubmissionList({\n form,\n submissions,\n isLoading = false,\n onStatusChange,\n onDelete,\n onBulkDelete,\n onBulkStatusChange,\n onExport,\n onViewDetail,\n}: SubmissionListProps): React.ReactElement {\n // -------------------------------------------------------------------------\n // State\n // -------------------------------------------------------------------------\n\n const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());\n const [filter, setFilter] = useState<StatusFilter>(\"all\");\n const [processingIds, setProcessingIds] = useState<Set<string>>(new Set());\n\n // -------------------------------------------------------------------------\n // Computed Values\n // -------------------------------------------------------------------------\n\n /** Get display columns from form fields (first 3 fields) */\n const displayFields = useMemo((): FormField[] => {\n return form.fields.filter(f => f.type !== \"hidden\").slice(0, 3);\n }, [form.fields]);\n\n /** Filter submissions by status */\n const filteredSubmissions = useMemo((): SubmissionDocument[] => {\n if (filter === \"all\") return submissions;\n return submissions.filter(sub => sub.status === filter);\n }, [submissions, filter]);\n\n /** Check if some submissions are selected */\n const someSelected = selectedIds.size > 0;\n\n // -------------------------------------------------------------------------\n // Handlers\n // -------------------------------------------------------------------------\n\n /** Toggle selection for a single submission */\n const toggleSelect = useCallback((id: string) => {\n setSelectedIds(prev => {\n const next = new Set(prev);\n if (next.has(id)) {\n next.delete(id);\n } else {\n next.add(id);\n }\n return next;\n });\n }, []);\n\n /** Handle status change for a single submission */\n const handleStatusChange = useCallback(\n async (id: string, status: \"new\" | \"read\" | \"archived\") => {\n if (!onStatusChange) return;\n\n setProcessingIds(prev => new Set(prev).add(id));\n try {\n await onStatusChange(id, status);\n } finally {\n setProcessingIds(prev => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n }\n },\n [onStatusChange]\n );\n\n /** Handle delete for a single submission */\n const handleDelete = useCallback(\n async (id: string) => {\n if (!onDelete) return;\n if (!window.confirm(\"Are you sure you want to delete this submission?\")) {\n return;\n }\n\n setProcessingIds(prev => new Set(prev).add(id));\n try {\n await onDelete(id);\n setSelectedIds(prev => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n } finally {\n setProcessingIds(prev => {\n const next = new Set(prev);\n next.delete(id);\n return next;\n });\n }\n },\n [onDelete]\n );\n\n /** Handle bulk delete */\n const handleBulkDelete = useCallback(async () => {\n if (!onBulkDelete || selectedIds.size === 0) return;\n if (\n !window.confirm(\n `Are you sure you want to delete ${selectedIds.size} submission(s)?`\n )\n ) {\n return;\n }\n\n const ids = Array.from(selectedIds);\n ids.forEach(id => setProcessingIds(prev => new Set(prev).add(id)));\n\n try {\n await onBulkDelete(ids);\n setSelectedIds(new Set());\n } finally {\n setProcessingIds(new Set());\n }\n }, [onBulkDelete, selectedIds]);\n\n /** Handle bulk status change */\n const handleBulkStatusChange = useCallback(\n async (status: \"new\" | \"read\" | \"archived\") => {\n if (!onBulkStatusChange || selectedIds.size === 0) return;\n\n const ids = Array.from(selectedIds);\n ids.forEach(id => setProcessingIds(prev => new Set(prev).add(id)));\n\n try {\n await onBulkStatusChange(ids, status);\n setSelectedIds(new Set());\n } finally {\n setProcessingIds(new Set());\n }\n },\n [onBulkStatusChange, selectedIds]\n );\n\n // -------------------------------------------------------------------------\n // DataTable wiring (columns, selection, row actions)\n // -------------------------------------------------------------------------\n\n const columns = useMemo<NextlyColumn<SubmissionDocument>[]>(() => {\n const base: NextlyColumn<SubmissionDocument>[] = [\n {\n name: \"id\",\n header: \"ID\",\n cell: ({ row }) => (\n <code className=\"font-mono text-xs text-muted-foreground\">\n {row.id.slice(0, 8)}...\n </code>\n ),\n },\n {\n name: \"status\",\n header: \"Status\",\n cell: ({ row }) => (\n <span className=\"inline-flex items-center rounded-none border border-border bg-muted px-2 py-0.5 text-[11px] font-medium capitalize text-foreground\">\n {row.status}\n </span>\n ),\n },\n {\n name: \"submittedAt\",\n header: \"Submitted\",\n hideOnMobile: true,\n cell: ({ row }) => (\n <span className=\"whitespace-nowrap text-sm text-muted-foreground\">\n {formatDate(row.submittedAt)}\n </span>\n ),\n },\n ];\n const fieldColumns: NextlyColumn<SubmissionDocument>[] = displayFields.map(\n field => ({\n name: `field:${field.name}`,\n header: field.label,\n hideOnMobile: true,\n cell: ({ row }) => (\n <span className=\"text-sm\">{formatValue(row.data[field.name])}</span>\n ),\n })\n );\n return [...base, ...fieldColumns];\n }, [displayFields]);\n\n const selection = useMemo<DataTableSelection<SubmissionDocument>>(\n () => ({\n selectedIds: Array.from(selectedIds),\n onToggle: submission => toggleSelect(submission.id),\n onToggleAll: (rows, allChecked) => {\n const ids = rows.map(r => r.id);\n setSelectedIds(prev => {\n const next = new Set(prev);\n if (allChecked) ids.forEach(id => next.delete(id));\n else ids.forEach(id => next.add(id));\n return next;\n });\n },\n }),\n [selectedIds, toggleSelect]\n );\n\n const rowActions = useCallback((): RowAction<SubmissionDocument>[] => {\n const actions: RowAction<SubmissionDocument>[] = [];\n if (onStatusChange) {\n actions.push(\n {\n id: \"mark-read\",\n label: \"Mark Read\",\n isVisible: r => r.status !== \"read\",\n isDisabled: r => processingIds.has(r.id),\n onSelect: r => void handleStatusChange(r.id, \"read\"),\n },\n {\n id: \"archive\",\n label: \"Archive\",\n isVisible: r => r.status !== \"archived\",\n isDisabled: r => processingIds.has(r.id),\n onSelect: r => void handleStatusChange(r.id, \"archived\"),\n }\n );\n }\n if (onDelete) {\n actions.push({\n id: \"delete\",\n label: \"Delete\",\n destructive: true,\n isDisabled: r => processingIds.has(r.id),\n onSelect: r => void handleDelete(r.id),\n });\n }\n return actions;\n }, [\n onStatusChange,\n onDelete,\n processingIds,\n handleStatusChange,\n handleDelete,\n ]);\n\n // -------------------------------------------------------------------------\n // Render\n // -------------------------------------------------------------------------\n\n return (\n <div className=\"submission-list\">\n {/* Toolbar */}\n <div className=\"submission-list__toolbar\">\n <div className=\"submission-list__filters\">\n <select\n value={filter}\n onChange={e => setFilter(e.target.value as StatusFilter)}\n className=\"submission-list__filter-select\"\n aria-label=\"Filter by status\"\n >\n <option value=\"all\">All Submissions ({submissions.length})</option>\n <option value=\"new\">\n New ({submissions.filter(s => s.status === \"new\").length})\n </option>\n <option value=\"read\">\n Read ({submissions.filter(s => s.status === \"read\").length})\n </option>\n <option value=\"archived\">\n Archived (\n {submissions.filter(s => s.status === \"archived\").length})\n </option>\n </select>\n </div>\n\n <div className=\"submission-list__actions\">\n {/* Bulk Actions */}\n {someSelected && (\n <div className=\"submission-list__bulk-actions\">\n <span className=\"submission-list__selected-count\">\n {selectedIds.size} selected\n </span>\n {onBulkStatusChange && (\n <>\n <button\n type=\"button\"\n onClick={() => void handleBulkStatusChange(\"read\")}\n className=\"submission-list__action-btn\"\n >\n Mark Read\n </button>\n <button\n type=\"button\"\n onClick={() => void handleBulkStatusChange(\"archived\")}\n className=\"submission-list__action-btn\"\n >\n Archive\n </button>\n </>\n )}\n {onBulkDelete && (\n <button\n type=\"button\"\n onClick={() => void handleBulkDelete()}\n className=\"submission-list__action-btn submission-list__action-btn--danger\"\n >\n Delete\n </button>\n )}\n </div>\n )}\n\n {/* Export Actions */}\n {onExport && (\n <div className=\"submission-list__export-actions\">\n <button\n type=\"button\"\n onClick={() => onExport(\"csv\")}\n className=\"submission-list__export-btn\"\n >\n Export CSV\n </button>\n <button\n type=\"button\"\n onClick={() => onExport(\"json\")}\n className=\"submission-list__export-btn\"\n >\n Export JSON\n </button>\n </div>\n )}\n </div>\n </div>\n\n {/* Table (unified admin DataTable via the plugin SDK) */}\n <DataTableView<SubmissionDocument>\n columns={columns}\n rows={filteredSubmissions}\n loading={isLoading}\n selection={selection}\n rowActions={rowActions}\n onRowClick={\n onViewDetail ? submission => onViewDetail(submission) : undefined\n }\n registryKey=\"form-submissions\"\n ariaLabel=\"Form submissions table\"\n emptyMessage=\"No submissions found\"\n />\n\n {/* Summary */}\n <div className=\"submission-list__summary\">\n Showing {filteredSubmissions.length} of {submissions.length} submissions\n </div>\n </div>\n );\n}\n","\"use client\";\n\n/**\n * Submission Detail Component\n *\n * Admin view for viewing and managing a single form submission.\n * Displays all submission data, metadata, and provides status management.\n *\n * @module components/submissions/SubmissionDetail\n * @since 0.1.0\n */\n\n\"use client\";\n\nimport type React from \"react\";\nimport { useState, useCallback } from \"react\";\n\nimport type { SubmissionDocument, FormDocument, FormField } from \"../../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Props for the SubmissionDetail component.\n */\nexport interface SubmissionDetailProps {\n /** Form document containing field definitions */\n form: FormDocument;\n\n /** Submission document to display */\n submission: SubmissionDocument;\n\n /** Callback when status is changed */\n onStatusChange?: (status: \"new\" | \"read\" | \"archived\") => Promise<void>;\n\n /** Callback when notes are updated */\n onNotesUpdate?: (notes: string) => Promise<void>;\n\n /** Callback when back button is clicked */\n onBack?: () => void;\n\n /** Callback when delete is triggered */\n onDelete?: () => Promise<void>;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Stringify a value safely, falling back to JSON for plain objects so we\n * never render the default `[object Object]` representation.\n */\nfunction safeString(value: unknown): string {\n if (typeof value === \"object\" && value !== null) {\n return JSON.stringify(value);\n }\n return String(value);\n}\n\n/**\n * Format a value for display based on field type.\n */\nfunction formatFieldValue(value: unknown, field: FormField): string {\n if (value === null || value === undefined) return \"-\";\n\n switch (field.type) {\n case \"checkbox\":\n return value ? \"Yes\" : \"No\";\n\n case \"select\":\n if (Array.isArray(value)) {\n return value.join(\", \");\n }\n return safeString(value);\n\n case \"file\":\n if (Array.isArray(value)) {\n return value.map(v => safeString(v)).join(\", \");\n }\n return safeString(value);\n\n case \"date\":\n if (value instanceof Date) {\n return value.toLocaleDateString();\n }\n if (typeof value === \"string\") {\n return new Date(value).toLocaleDateString();\n }\n return safeString(value);\n\n case \"time\":\n return safeString(value);\n\n default:\n return safeString(value);\n }\n}\n\n/**\n * Format a date for display.\n */\nfunction formatDate(date: Date | string): string {\n const d = typeof date === \"string\" ? new Date(date) : date;\n return d.toLocaleString();\n}\n\n/**\n * Get status options.\n */\nconst STATUS_OPTIONS: Array<{\n value: \"new\" | \"read\" | \"archived\";\n label: string;\n}> = [\n { value: \"new\", label: \"New\" },\n { value: \"read\", label: \"Read\" },\n { value: \"archived\", label: \"Archived\" },\n];\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * Submission Detail Component\n *\n * Displays full submission data with status management and notes.\n *\n * @example\n * ```tsx\n * <SubmissionDetail\n * form={form}\n * submission={submission}\n * onStatusChange={handleStatusChange}\n * onNotesUpdate={handleNotesUpdate}\n * onBack={handleBack}\n * />\n * ```\n */\nexport function SubmissionDetail({\n form,\n submission,\n onStatusChange,\n onNotesUpdate,\n onBack,\n onDelete,\n}: SubmissionDetailProps): React.ReactElement {\n // -------------------------------------------------------------------------\n // State\n // -------------------------------------------------------------------------\n\n const [isProcessing, setIsProcessing] = useState(false);\n const [notes, setNotes] = useState(\n (submission as unknown as { notes?: string }).notes || \"\"\n );\n const [notesModified, setNotesModified] = useState(false);\n\n // -------------------------------------------------------------------------\n // Handlers\n // -------------------------------------------------------------------------\n\n /** Handle status change */\n const handleStatusChange = useCallback(\n async (event: React.ChangeEvent<HTMLSelectElement>) => {\n if (!onStatusChange) return;\n\n const newStatus = event.target.value as \"new\" | \"read\" | \"archived\";\n setIsProcessing(true);\n\n try {\n await onStatusChange(newStatus);\n } finally {\n setIsProcessing(false);\n }\n },\n [onStatusChange]\n );\n\n /** Handle notes change */\n const handleNotesChange = useCallback(\n (event: React.ChangeEvent<HTMLTextAreaElement>) => {\n setNotes(event.target.value);\n setNotesModified(true);\n },\n []\n );\n\n /** Handle notes save */\n const handleNotesSave = useCallback(async () => {\n if (!onNotesUpdate || !notesModified) return;\n\n setIsProcessing(true);\n try {\n await onNotesUpdate(notes);\n setNotesModified(false);\n } finally {\n setIsProcessing(false);\n }\n }, [onNotesUpdate, notes, notesModified]);\n\n /** Handle delete */\n const handleDelete = useCallback(async () => {\n if (!onDelete) return;\n if (!window.confirm(\"Are you sure you want to delete this submission?\")) {\n return;\n }\n\n setIsProcessing(true);\n try {\n await onDelete();\n } finally {\n setIsProcessing(false);\n }\n }, [onDelete]);\n\n // -------------------------------------------------------------------------\n // Computed Values\n // -------------------------------------------------------------------------\n\n /** Get displayable fields */\n const displayFields = form.fields;\n\n /** Get submission data */\n const data = submission.data;\n\n // -------------------------------------------------------------------------\n // Render\n // -------------------------------------------------------------------------\n\n return (\n <div className=\"submission-detail\">\n {/* Header */}\n <div className=\"submission-detail__header\">\n <div className=\"submission-detail__header-left\">\n {onBack && (\n <button\n type=\"button\"\n onClick={onBack}\n className=\"submission-detail__back-btn\"\n disabled={isProcessing}\n >\n ← Back to List\n </button>\n )}\n <h2 className=\"submission-detail__title\">Submission Details</h2>\n </div>\n\n <div className=\"submission-detail__header-right\">\n {onStatusChange && (\n <div className=\"submission-detail__status-control\">\n <label htmlFor=\"submission-status\">Status:</label>\n <select\n id=\"submission-status\"\n value={submission.status}\n onChange={e => void handleStatusChange(e)}\n disabled={isProcessing}\n className=\"submission-detail__status-select\"\n >\n {STATUS_OPTIONS.map(option => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n </div>\n )}\n\n {onDelete && (\n <button\n type=\"button\"\n onClick={() => void handleDelete()}\n disabled={isProcessing}\n className=\"submission-detail__delete-btn\"\n >\n Delete\n </button>\n )}\n </div>\n </div>\n\n {/* Submission ID */}\n <div className=\"submission-detail__id\">\n <span className=\"submission-detail__id-label\">Submission ID:</span>\n <code className=\"submission-detail__id-value\">{submission.id}</code>\n </div>\n\n {/* Form Fields Data */}\n <div className=\"submission-detail__section\">\n <h3 className=\"submission-detail__section-title\">Submitted Data</h3>\n <div className=\"submission-detail__fields\">\n {displayFields.map(field => (\n <div key={field.name} className=\"submission-detail__field\">\n <div className=\"submission-detail__field-label\">\n {field.label}\n </div>\n <div className=\"submission-detail__field-value\">\n {formatFieldValue(data[field.name], field)}\n </div>\n </div>\n ))}\n\n {displayFields.length === 0 && (\n <div className=\"submission-detail__empty\">No fields to display</div>\n )}\n </div>\n </div>\n\n {/* Notes Section */}\n {onNotesUpdate && (\n <div className=\"submission-detail__section\">\n <h3 className=\"submission-detail__section-title\">Internal Notes</h3>\n <div className=\"submission-detail__notes\">\n <textarea\n value={notes}\n onChange={handleNotesChange}\n placeholder=\"Add internal notes about this submission...\"\n disabled={isProcessing}\n className=\"submission-detail__notes-textarea\"\n rows={4}\n />\n {notesModified && (\n <button\n type=\"button\"\n onClick={() => void handleNotesSave()}\n disabled={isProcessing}\n className=\"submission-detail__notes-save-btn\"\n >\n Save Notes\n </button>\n )}\n </div>\n </div>\n )}\n\n {/* Metadata Section */}\n <div className=\"submission-detail__section\">\n <h3 className=\"submission-detail__section-title\">Metadata</h3>\n <div className=\"submission-detail__metadata\">\n <div className=\"submission-detail__metadata-item\">\n <span className=\"submission-detail__metadata-label\">\n Submitted At:\n </span>\n <span className=\"submission-detail__metadata-value\">\n {formatDate(submission.submittedAt)}\n </span>\n </div>\n\n {submission.ipAddress && (\n <div className=\"submission-detail__metadata-item\">\n <span className=\"submission-detail__metadata-label\">\n IP Address:\n </span>\n <span className=\"submission-detail__metadata-value\">\n <code>{submission.ipAddress}</code>\n </span>\n </div>\n )}\n\n {submission.userAgent && (\n <div className=\"submission-detail__metadata-item\">\n <span className=\"submission-detail__metadata-label\">\n User Agent:\n </span>\n <span className=\"submission-detail__metadata-value submission-detail__metadata-value--truncate\">\n {submission.userAgent}\n </span>\n </div>\n )}\n\n <div className=\"submission-detail__metadata-item\">\n <span className=\"submission-detail__metadata-label\">\n Created At:\n </span>\n <span className=\"submission-detail__metadata-value\">\n {formatDate(submission.createdAt)}\n </span>\n </div>\n\n <div className=\"submission-detail__metadata-item\">\n <span className=\"submission-detail__metadata-label\">\n Updated At:\n </span>\n <span className=\"submission-detail__metadata-value\">\n {formatDate(submission.updatedAt)}\n </span>\n </div>\n </div>\n </div>\n\n {/* Raw Data Section (Expandable) */}\n <details className=\"submission-detail__raw-data\">\n <summary className=\"submission-detail__raw-data-summary\">\n View Raw JSON Data\n </summary>\n <pre className=\"submission-detail__raw-data-content\">\n {JSON.stringify(submission.data, null, 2)}\n </pre>\n </details>\n </div>\n );\n}\n"],"mappings":"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","names":["useState","useCallback","jsx","jsxs","formatDate"]}
|
package/dist/chunk-XTCEPJ7B.js
DELETED
|
@@ -1,256 +0,0 @@
|
|
|
1
|
-
// src/admin/fields/OptionsEditor.tsx
|
|
2
|
-
import {
|
|
3
|
-
DndContext,
|
|
4
|
-
closestCenter,
|
|
5
|
-
KeyboardSensor,
|
|
6
|
-
PointerSensor,
|
|
7
|
-
useSensor,
|
|
8
|
-
useSensors
|
|
9
|
-
} from "@dnd-kit/core";
|
|
10
|
-
import {
|
|
11
|
-
arrayMove,
|
|
12
|
-
SortableContext,
|
|
13
|
-
sortableKeyboardCoordinates,
|
|
14
|
-
useSortable,
|
|
15
|
-
verticalListSortingStrategy
|
|
16
|
-
} from "@dnd-kit/sortable";
|
|
17
|
-
import { CSS } from "@dnd-kit/utilities";
|
|
18
|
-
import { Input } from "@nextlyhq/ui";
|
|
19
|
-
import { GripVertical, Plus, Trash2 } from "lucide-react";
|
|
20
|
-
import { useCallback } from "react";
|
|
21
|
-
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
22
|
-
function generateValueFromLabel(label) {
|
|
23
|
-
return label.toLowerCase().trim().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
24
|
-
}
|
|
25
|
-
function addIdsToOptions(options) {
|
|
26
|
-
return options.map((opt, index) => ({
|
|
27
|
-
...opt,
|
|
28
|
-
id: `opt_${index}_${opt.value}`
|
|
29
|
-
}));
|
|
30
|
-
}
|
|
31
|
-
function removeIdsFromOptions(options) {
|
|
32
|
-
return options.map(({ label, value }) => ({ label, value }));
|
|
33
|
-
}
|
|
34
|
-
function SortableOption({
|
|
35
|
-
option,
|
|
36
|
-
index,
|
|
37
|
-
onLabelChange,
|
|
38
|
-
onValueChange,
|
|
39
|
-
onDelete,
|
|
40
|
-
disabled
|
|
41
|
-
}) {
|
|
42
|
-
const {
|
|
43
|
-
attributes,
|
|
44
|
-
listeners,
|
|
45
|
-
setNodeRef,
|
|
46
|
-
transform,
|
|
47
|
-
transition,
|
|
48
|
-
isDragging
|
|
49
|
-
} = useSortable({ id: option.id });
|
|
50
|
-
const style = {
|
|
51
|
-
transform: CSS.Transform.toString(transform),
|
|
52
|
-
transition,
|
|
53
|
-
opacity: isDragging ? 0.5 : 1
|
|
54
|
-
};
|
|
55
|
-
return /* @__PURE__ */ jsxs(
|
|
56
|
-
"div",
|
|
57
|
-
{
|
|
58
|
-
ref: setNodeRef,
|
|
59
|
-
style,
|
|
60
|
-
className: `options-editor__option ${isDragging ? "options-editor__option--dragging" : ""}`,
|
|
61
|
-
children: [
|
|
62
|
-
/* @__PURE__ */ jsx(
|
|
63
|
-
"button",
|
|
64
|
-
{
|
|
65
|
-
type: "button",
|
|
66
|
-
className: "options-editor__drag-handle",
|
|
67
|
-
disabled,
|
|
68
|
-
"aria-label": `Drag to reorder option ${index + 1}`,
|
|
69
|
-
...attributes,
|
|
70
|
-
...listeners,
|
|
71
|
-
children: /* @__PURE__ */ jsx(GripVertical, { className: "options-editor__drag-icon" })
|
|
72
|
-
}
|
|
73
|
-
),
|
|
74
|
-
/* @__PURE__ */ jsx("div", { className: "options-editor__input-group", children: /* @__PURE__ */ jsx(
|
|
75
|
-
Input,
|
|
76
|
-
{
|
|
77
|
-
type: "text",
|
|
78
|
-
value: option.label,
|
|
79
|
-
onChange: (e) => onLabelChange(option.id, e.target.value),
|
|
80
|
-
placeholder: "Label",
|
|
81
|
-
disabled,
|
|
82
|
-
className: "options-editor__input"
|
|
83
|
-
}
|
|
84
|
-
) }),
|
|
85
|
-
/* @__PURE__ */ jsx("div", { className: "options-editor__input-group", children: /* @__PURE__ */ jsx(
|
|
86
|
-
Input,
|
|
87
|
-
{
|
|
88
|
-
type: "text",
|
|
89
|
-
value: option.value,
|
|
90
|
-
onChange: (e) => onValueChange(option.id, e.target.value),
|
|
91
|
-
placeholder: "value",
|
|
92
|
-
disabled,
|
|
93
|
-
className: "options-editor__input options-editor__input--mono"
|
|
94
|
-
}
|
|
95
|
-
) }),
|
|
96
|
-
/* @__PURE__ */ jsx(
|
|
97
|
-
"button",
|
|
98
|
-
{
|
|
99
|
-
type: "button",
|
|
100
|
-
className: "options-editor__delete-btn",
|
|
101
|
-
onClick: () => onDelete(option.id),
|
|
102
|
-
disabled,
|
|
103
|
-
"aria-label": `Delete option ${option.label || index + 1}`,
|
|
104
|
-
children: /* @__PURE__ */ jsx(Trash2, { className: "options-editor__delete-icon" })
|
|
105
|
-
}
|
|
106
|
-
)
|
|
107
|
-
]
|
|
108
|
-
}
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
function OptionsEditor({
|
|
112
|
-
options,
|
|
113
|
-
onOptionsChange,
|
|
114
|
-
disabled = false
|
|
115
|
-
}) {
|
|
116
|
-
const optionsWithIds = addIdsToOptions(options);
|
|
117
|
-
const sensors = useSensors(
|
|
118
|
-
useSensor(PointerSensor, {
|
|
119
|
-
activationConstraint: {
|
|
120
|
-
distance: 8
|
|
121
|
-
}
|
|
122
|
-
}),
|
|
123
|
-
useSensor(KeyboardSensor, {
|
|
124
|
-
coordinateGetter: sortableKeyboardCoordinates
|
|
125
|
-
})
|
|
126
|
-
);
|
|
127
|
-
const handleAddOption = useCallback(() => {
|
|
128
|
-
const newIndex = options.length + 1;
|
|
129
|
-
const newOption = {
|
|
130
|
-
label: `Option ${newIndex}`,
|
|
131
|
-
value: `option_${newIndex}`
|
|
132
|
-
};
|
|
133
|
-
onOptionsChange([...options, newOption]);
|
|
134
|
-
}, [options, onOptionsChange]);
|
|
135
|
-
const handleLabelChange = useCallback(
|
|
136
|
-
(id, newLabel) => {
|
|
137
|
-
const updatedOptions = optionsWithIds.map((opt) => {
|
|
138
|
-
if (opt.id !== id) return opt;
|
|
139
|
-
const previousAutoValue = generateValueFromLabel(opt.label);
|
|
140
|
-
const shouldAutoGenerate = !opt.value || opt.value === previousAutoValue;
|
|
141
|
-
return {
|
|
142
|
-
...opt,
|
|
143
|
-
label: newLabel,
|
|
144
|
-
value: shouldAutoGenerate ? generateValueFromLabel(newLabel) : opt.value
|
|
145
|
-
};
|
|
146
|
-
});
|
|
147
|
-
onOptionsChange(removeIdsFromOptions(updatedOptions));
|
|
148
|
-
},
|
|
149
|
-
[optionsWithIds, onOptionsChange]
|
|
150
|
-
);
|
|
151
|
-
const handleValueChange = useCallback(
|
|
152
|
-
(id, newValue) => {
|
|
153
|
-
const updatedOptions = optionsWithIds.map(
|
|
154
|
-
(opt) => opt.id === id ? { ...opt, value: newValue } : opt
|
|
155
|
-
);
|
|
156
|
-
onOptionsChange(removeIdsFromOptions(updatedOptions));
|
|
157
|
-
},
|
|
158
|
-
[optionsWithIds, onOptionsChange]
|
|
159
|
-
);
|
|
160
|
-
const handleDeleteOption = useCallback(
|
|
161
|
-
(id) => {
|
|
162
|
-
const updatedOptions = optionsWithIds.filter((opt) => opt.id !== id);
|
|
163
|
-
onOptionsChange(removeIdsFromOptions(updatedOptions));
|
|
164
|
-
},
|
|
165
|
-
[optionsWithIds, onOptionsChange]
|
|
166
|
-
);
|
|
167
|
-
const handleDragEnd = useCallback(
|
|
168
|
-
(event) => {
|
|
169
|
-
const { active, over } = event;
|
|
170
|
-
if (over && active.id !== over.id) {
|
|
171
|
-
const oldIndex = optionsWithIds.findIndex((opt) => opt.id === active.id);
|
|
172
|
-
const newIndex = optionsWithIds.findIndex((opt) => opt.id === over.id);
|
|
173
|
-
const reorderedOptions = arrayMove(optionsWithIds, oldIndex, newIndex);
|
|
174
|
-
onOptionsChange(removeIdsFromOptions(reorderedOptions));
|
|
175
|
-
}
|
|
176
|
-
},
|
|
177
|
-
[optionsWithIds, onOptionsChange]
|
|
178
|
-
);
|
|
179
|
-
return /* @__PURE__ */ jsxs("div", { className: "options-editor", children: [
|
|
180
|
-
/* @__PURE__ */ jsxs("div", { className: "options-editor__header", children: [
|
|
181
|
-
/* @__PURE__ */ jsx("label", { className: "options-editor__label", children: "Options" }),
|
|
182
|
-
/* @__PURE__ */ jsxs(
|
|
183
|
-
"button",
|
|
184
|
-
{
|
|
185
|
-
type: "button",
|
|
186
|
-
className: "options-editor__add-btn",
|
|
187
|
-
onClick: handleAddOption,
|
|
188
|
-
disabled,
|
|
189
|
-
children: [
|
|
190
|
-
/* @__PURE__ */ jsx(Plus, { className: "options-editor__add-icon" }),
|
|
191
|
-
"Add Option"
|
|
192
|
-
]
|
|
193
|
-
}
|
|
194
|
-
)
|
|
195
|
-
] }),
|
|
196
|
-
options.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
197
|
-
/* @__PURE__ */ jsxs("div", { className: "options-editor__column-headers", children: [
|
|
198
|
-
/* @__PURE__ */ jsx("div", { className: "options-editor__column-spacer" }),
|
|
199
|
-
/* @__PURE__ */ jsx("div", { className: "options-editor__column-label", children: "Label (displayed)" }),
|
|
200
|
-
/* @__PURE__ */ jsx("div", { className: "options-editor__column-label", children: "Value (stored)" }),
|
|
201
|
-
/* @__PURE__ */ jsx("div", { className: "options-editor__column-spacer" })
|
|
202
|
-
] }),
|
|
203
|
-
/* @__PURE__ */ jsx(
|
|
204
|
-
DndContext,
|
|
205
|
-
{
|
|
206
|
-
sensors,
|
|
207
|
-
collisionDetection: closestCenter,
|
|
208
|
-
onDragEnd: handleDragEnd,
|
|
209
|
-
children: /* @__PURE__ */ jsx(
|
|
210
|
-
SortableContext,
|
|
211
|
-
{
|
|
212
|
-
items: optionsWithIds.map((opt) => opt.id),
|
|
213
|
-
strategy: verticalListSortingStrategy,
|
|
214
|
-
children: /* @__PURE__ */ jsx("div", { className: "options-editor__list", children: optionsWithIds.map((option, index) => /* @__PURE__ */ jsx(
|
|
215
|
-
SortableOption,
|
|
216
|
-
{
|
|
217
|
-
option,
|
|
218
|
-
index,
|
|
219
|
-
onLabelChange: handleLabelChange,
|
|
220
|
-
onValueChange: handleValueChange,
|
|
221
|
-
onDelete: handleDeleteOption,
|
|
222
|
-
disabled
|
|
223
|
-
},
|
|
224
|
-
option.id
|
|
225
|
-
)) })
|
|
226
|
-
}
|
|
227
|
-
)
|
|
228
|
-
}
|
|
229
|
-
)
|
|
230
|
-
] }) : (
|
|
231
|
-
/* Empty State */
|
|
232
|
-
/* @__PURE__ */ jsxs("div", { className: "options-editor__empty", children: [
|
|
233
|
-
/* @__PURE__ */ jsx("p", { className: "options-editor__empty-text", children: "No options defined" }),
|
|
234
|
-
/* @__PURE__ */ jsxs(
|
|
235
|
-
"button",
|
|
236
|
-
{
|
|
237
|
-
type: "button",
|
|
238
|
-
className: "options-editor__add-first-btn",
|
|
239
|
-
onClick: handleAddOption,
|
|
240
|
-
disabled,
|
|
241
|
-
children: [
|
|
242
|
-
/* @__PURE__ */ jsx(Plus, { className: "options-editor__add-icon" }),
|
|
243
|
-
"Add first option"
|
|
244
|
-
]
|
|
245
|
-
}
|
|
246
|
-
)
|
|
247
|
-
] })
|
|
248
|
-
),
|
|
249
|
-
/* @__PURE__ */ jsx("p", { className: "options-editor__hint", children: "Drag options to reorder. Value is auto-generated from label." })
|
|
250
|
-
] });
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export {
|
|
254
|
-
OptionsEditor
|
|
255
|
-
};
|
|
256
|
-
//# sourceMappingURL=chunk-XTCEPJ7B.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/admin/fields/OptionsEditor.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * Options Editor Component\n *\n * Shared component for editing options arrays in Select and Radio fields.\n * Features:\n * - Add/edit/remove options\n * - Drag-and-drop reordering with @dnd-kit\n * - Auto-generate value from label\n *\n * @module admin/fields/OptionsEditor\n * @since 0.1.0\n */\n\n\"use client\";\n\nimport {\n DndContext,\n closestCenter,\n KeyboardSensor,\n PointerSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from \"@dnd-kit/core\";\nimport {\n arrayMove,\n SortableContext,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { Input } from \"@nextlyhq/ui\";\nimport { GripVertical, Plus, Trash2 } from \"lucide-react\";\nimport { useCallback } from \"react\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Option interface for Select/Radio fields */\nexport interface FieldOption {\n label: string;\n value: string;\n}\n\n/** Internal option with ID for drag-and-drop */\ninterface OptionWithId extends FieldOption {\n id: string;\n}\n\nexport interface OptionsEditorProps {\n /** Current options array */\n options: FieldOption[];\n /** Callback when options change */\n onOptionsChange: (options: FieldOption[]) => void;\n /** Disable editing */\n disabled?: boolean;\n}\n\n// ============================================================================\n// Utility Functions\n// ============================================================================\n\n/**\n * Generate a value from a label (snake_case format)\n */\nfunction generateValueFromLabel(label: string): string {\n return label\n .toLowerCase()\n .trim()\n .replace(/\\s+/g, \"_\")\n .replace(/[^a-z0-9_]/g, \"\");\n}\n\n/**\n * Add IDs to options for DnD tracking\n */\nfunction addIdsToOptions(options: FieldOption[]): OptionWithId[] {\n return options.map((opt, index) => ({\n ...opt,\n id: `opt_${index}_${opt.value}`,\n }));\n}\n\n/**\n * Remove IDs from options when saving\n */\nfunction removeIdsFromOptions(options: OptionWithId[]): FieldOption[] {\n return options.map(({ label, value }) => ({ label, value }));\n}\n\n// ============================================================================\n// SortableOption Component\n// ============================================================================\n\ninterface SortableOptionProps {\n option: OptionWithId;\n index: number;\n onLabelChange: (id: string, label: string) => void;\n onValueChange: (id: string, value: string) => void;\n onDelete: (id: string) => void;\n disabled?: boolean;\n}\n\nfunction SortableOption({\n option,\n index,\n onLabelChange,\n onValueChange,\n onDelete,\n disabled,\n}: SortableOptionProps) {\n const {\n attributes,\n listeners,\n setNodeRef,\n transform,\n transition,\n isDragging,\n } = useSortable({ id: option.id });\n\n const style = {\n transform: CSS.Transform.toString(transform),\n transition,\n opacity: isDragging ? 0.5 : 1,\n };\n\n return (\n <div\n ref={setNodeRef}\n style={style}\n className={`options-editor__option ${isDragging ? \"options-editor__option--dragging\" : \"\"}`}\n >\n {/* Drag Handle */}\n <button\n type=\"button\"\n className=\"options-editor__drag-handle\"\n disabled={disabled}\n aria-label={`Drag to reorder option ${index + 1}`}\n {...attributes}\n {...listeners}\n >\n <GripVertical className=\"options-editor__drag-icon\" />\n </button>\n\n {/* Label Input */}\n <div className=\"options-editor__input-group\">\n <Input\n type=\"text\"\n value={option.label}\n onChange={e => onLabelChange(option.id, e.target.value)}\n placeholder=\"Label\"\n disabled={disabled}\n className=\"options-editor__input\"\n />\n </div>\n\n {/* Value Input */}\n <div className=\"options-editor__input-group\">\n <Input\n type=\"text\"\n value={option.value}\n onChange={e => onValueChange(option.id, e.target.value)}\n placeholder=\"value\"\n disabled={disabled}\n className=\"options-editor__input options-editor__input--mono\"\n />\n </div>\n\n {/* Delete Button */}\n <button\n type=\"button\"\n className=\"options-editor__delete-btn\"\n onClick={() => onDelete(option.id)}\n disabled={disabled}\n aria-label={`Delete option ${option.label || index + 1}`}\n >\n <Trash2 className=\"options-editor__delete-icon\" />\n </button>\n </div>\n );\n}\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\n/**\n * OptionsEditor - Manage options for Select/Radio fields\n *\n * Provides a list of editable options with:\n * - Drag-and-drop reordering\n * - Add/remove options\n * - Auto-generate value from label\n *\n * @example\n * ```tsx\n * <OptionsEditor\n * options={field.options}\n * onOptionsChange={(options) => onUpdate({ options })}\n * />\n * ```\n */\nexport function OptionsEditor({\n options,\n onOptionsChange,\n disabled = false,\n}: OptionsEditorProps) {\n // Convert options to internal format with IDs for DnD\n const optionsWithIds = addIdsToOptions(options);\n\n // DnD sensors with activation distance to prevent accidental drags\n const sensors = useSensors(\n useSensor(PointerSensor, {\n activationConstraint: {\n distance: 8,\n },\n }),\n useSensor(KeyboardSensor, {\n coordinateGetter: sortableKeyboardCoordinates,\n })\n );\n\n // Add a new option\n const handleAddOption = useCallback(() => {\n const newIndex = options.length + 1;\n const newOption: FieldOption = {\n label: `Option ${newIndex}`,\n value: `option_${newIndex}`,\n };\n onOptionsChange([...options, newOption]);\n }, [options, onOptionsChange]);\n\n // Handle label change with auto-generate value\n const handleLabelChange = useCallback(\n (id: string, newLabel: string) => {\n const updatedOptions = optionsWithIds.map(opt => {\n if (opt.id !== id) return opt;\n\n // Auto-generate value if it matches the old auto-generated value or is empty\n const previousAutoValue = generateValueFromLabel(opt.label);\n const shouldAutoGenerate =\n !opt.value || opt.value === previousAutoValue;\n\n return {\n ...opt,\n label: newLabel,\n value: shouldAutoGenerate\n ? generateValueFromLabel(newLabel)\n : opt.value,\n };\n });\n\n onOptionsChange(removeIdsFromOptions(updatedOptions));\n },\n [optionsWithIds, onOptionsChange]\n );\n\n // Handle value change (manual override)\n const handleValueChange = useCallback(\n (id: string, newValue: string) => {\n const updatedOptions = optionsWithIds.map(opt =>\n opt.id === id ? { ...opt, value: newValue } : opt\n );\n onOptionsChange(removeIdsFromOptions(updatedOptions));\n },\n [optionsWithIds, onOptionsChange]\n );\n\n // Delete an option\n const handleDeleteOption = useCallback(\n (id: string) => {\n const updatedOptions = optionsWithIds.filter(opt => opt.id !== id);\n onOptionsChange(removeIdsFromOptions(updatedOptions));\n },\n [optionsWithIds, onOptionsChange]\n );\n\n // Handle drag end for reordering\n const handleDragEnd = useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event;\n\n if (over && active.id !== over.id) {\n const oldIndex = optionsWithIds.findIndex(opt => opt.id === active.id);\n const newIndex = optionsWithIds.findIndex(opt => opt.id === over.id);\n const reorderedOptions = arrayMove(optionsWithIds, oldIndex, newIndex);\n onOptionsChange(removeIdsFromOptions(reorderedOptions));\n }\n },\n [optionsWithIds, onOptionsChange]\n );\n\n return (\n <div className=\"options-editor\">\n {/* Header */}\n <div className=\"options-editor__header\">\n <label className=\"options-editor__label\">Options</label>\n <button\n type=\"button\"\n className=\"options-editor__add-btn\"\n onClick={handleAddOption}\n disabled={disabled}\n >\n <Plus className=\"options-editor__add-icon\" />\n Add Option\n </button>\n </div>\n\n {/* Options List */}\n {options.length > 0 ? (\n <>\n {/* Column Headers */}\n <div className=\"options-editor__column-headers\">\n <div className=\"options-editor__column-spacer\" />\n <div className=\"options-editor__column-label\">\n Label (displayed)\n </div>\n <div className=\"options-editor__column-label\">Value (stored)</div>\n <div className=\"options-editor__column-spacer\" />\n </div>\n\n {/* Sortable List */}\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragEnd={handleDragEnd}\n >\n <SortableContext\n items={optionsWithIds.map(opt => opt.id)}\n strategy={verticalListSortingStrategy}\n >\n <div className=\"options-editor__list\">\n {optionsWithIds.map((option, index) => (\n <SortableOption\n key={option.id}\n option={option}\n index={index}\n onLabelChange={handleLabelChange}\n onValueChange={handleValueChange}\n onDelete={handleDeleteOption}\n disabled={disabled}\n />\n ))}\n </div>\n </SortableContext>\n </DndContext>\n </>\n ) : (\n /* Empty State */\n <div className=\"options-editor__empty\">\n <p className=\"options-editor__empty-text\">No options defined</p>\n <button\n type=\"button\"\n className=\"options-editor__add-first-btn\"\n onClick={handleAddOption}\n disabled={disabled}\n >\n <Plus className=\"options-editor__add-icon\" />\n Add first option\n </button>\n </div>\n )}\n\n {/* Help Text */}\n <p className=\"options-editor__hint\">\n Drag options to reorder. Value is auto-generated from label.\n </p>\n </div>\n );\n}\n\nexport default OptionsEditor;\n"],"mappings":"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","names":[]}
|
package/dist/components/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Form Builder styles
|
|
3
|
-
*
|
|
4
|
-
* The builder UI is driven by Tailwind utilities and @nextlyhq/ui components in its
|
|
5
|
-
* React components. This file holds only the few bespoke rules those can't express:
|
|
6
|
-
* 1. The two-pane builder shell (fixed inspector panel; stacks in a narrow panel)
|
|
7
|
-
* 2. Notifications tab: email-integration card list (hover/variant states)
|
|
8
|
-
*
|
|
9
|
-
* Colors read the admin design tokens directly (`var(--token)`), so the UI looks native
|
|
10
|
-
* and inherits dark mode automatically (the admin flips the tokens under `.adminapp.dark`).
|
|
11
|
-
*
|
|
12
|
-
* @module styles/form-builder
|
|
13
|
-
* @since 0.1.0
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/* ---------------------------------------------------------- builder shell */
|
|
17
|
-
|
|
18
|
-
/* Fixed-width inspector panel beside the fluid canvas. */
|
|
19
|
-
.fb-builder-panel {
|
|
20
|
-
width: 360px;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/* Stack the canvas and inspector when the content panel itself gets narrow
|
|
24
|
-
(`content` is the admin content-area container). */
|
|
25
|
-
@container content (max-width: 800px) {
|
|
26
|
-
.fb-builder-shell {
|
|
27
|
-
flex-direction: column;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
.fb-builder-panel {
|
|
31
|
-
width: 100%;
|
|
32
|
-
border-left: 0;
|
|
33
|
-
border-top: 1px solid var(--border);
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/* ------------------------------------------------------- notifications tab */
|
|
38
|
-
|
|
39
|
-
.form-notifications-tab {
|
|
40
|
-
max-width: 800px;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.form-notifications-list {
|
|
44
|
-
display: flex;
|
|
45
|
-
flex-direction: column;
|
|
46
|
-
gap: 16px;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/* --------------------------------------------------- email integration cards */
|
|
50
|
-
|
|
51
|
-
.form-integration-card {
|
|
52
|
-
display: flex;
|
|
53
|
-
align-items: center;
|
|
54
|
-
justify-content: space-between;
|
|
55
|
-
gap: 16px;
|
|
56
|
-
padding: 14px 16px;
|
|
57
|
-
background: var(--card);
|
|
58
|
-
border: 1px solid var(--border);
|
|
59
|
-
border-radius: var(--radius);
|
|
60
|
-
transition: border-color 0.15s ease;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
.form-integration-card:hover {
|
|
64
|
-
border-color: var(--border-strong);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.form-integration-card--disabled {
|
|
68
|
-
opacity: 0.55;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.form-integration-card-left {
|
|
72
|
-
display: flex;
|
|
73
|
-
align-items: center;
|
|
74
|
-
gap: 12px;
|
|
75
|
-
flex: 1;
|
|
76
|
-
min-width: 0;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
.form-integration-card-info {
|
|
80
|
-
display: flex;
|
|
81
|
-
flex-direction: column;
|
|
82
|
-
gap: 4px;
|
|
83
|
-
min-width: 0;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.form-integration-card-name {
|
|
87
|
-
font-size: 0.9375rem;
|
|
88
|
-
font-weight: 600;
|
|
89
|
-
color: var(--foreground);
|
|
90
|
-
white-space: nowrap;
|
|
91
|
-
overflow: hidden;
|
|
92
|
-
text-overflow: ellipsis;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.form-integration-provider-badge {
|
|
96
|
-
font-size: 0.75rem;
|
|
97
|
-
color: var(--muted-foreground);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
.form-integration-card-actions {
|
|
101
|
-
display: flex;
|
|
102
|
-
gap: 8px;
|
|
103
|
-
flex-shrink: 0;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
.form-integration-btn {
|
|
107
|
-
padding: 6px 12px;
|
|
108
|
-
font-size: 0.75rem;
|
|
109
|
-
font-weight: 500;
|
|
110
|
-
color: var(--muted-foreground);
|
|
111
|
-
background: transparent;
|
|
112
|
-
border: 1px solid var(--border);
|
|
113
|
-
border-radius: var(--radius);
|
|
114
|
-
cursor: pointer;
|
|
115
|
-
transition: all 0.15s ease;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.form-integration-btn--edit:hover {
|
|
119
|
-
color: var(--primary);
|
|
120
|
-
border-color: var(--primary);
|
|
121
|
-
background: color-mix(in srgb, var(--primary) 10%, transparent);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
.form-integration-btn--delete:hover {
|
|
125
|
-
color: var(--destructive);
|
|
126
|
-
border-color: var(--destructive);
|
|
127
|
-
background: color-mix(in srgb, var(--destructive) 10%, transparent);
|
|
128
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Submissions Filter Styles
|
|
3
|
-
*
|
|
4
|
-
* Styles for the SubmissionsFilter component shown above the
|
|
5
|
-
* form-submissions list table. Colors/borders/radius use the admin tokens.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
.submissions-filter {
|
|
9
|
-
margin-bottom: 1rem;
|
|
10
|
-
padding: 1rem;
|
|
11
|
-
background: var(--muted);
|
|
12
|
-
border: 1px solid var(--border);
|
|
13
|
-
border-radius: var(--radius);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.submissions-filter-container {
|
|
17
|
-
display: flex;
|
|
18
|
-
align-items: center;
|
|
19
|
-
gap: 0.75rem;
|
|
20
|
-
flex-wrap: wrap;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
.submissions-filter-label {
|
|
24
|
-
font-size: 0.875rem;
|
|
25
|
-
font-weight: 500;
|
|
26
|
-
color: var(--muted-foreground);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.submissions-filter-select {
|
|
30
|
-
padding: 0.5rem 2rem 0.5rem 0.75rem;
|
|
31
|
-
font-size: 0.875rem;
|
|
32
|
-
border: 1px solid var(--input);
|
|
33
|
-
border-radius: var(--radius);
|
|
34
|
-
background-color: var(--background);
|
|
35
|
-
color: var(--foreground);
|
|
36
|
-
min-width: 200px;
|
|
37
|
-
cursor: pointer;
|
|
38
|
-
appearance: none;
|
|
39
|
-
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
|
40
|
-
background-position: right 0.5rem center;
|
|
41
|
-
background-repeat: no-repeat;
|
|
42
|
-
background-size: 1.5em 1.5em;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
.submissions-filter-select:hover {
|
|
46
|
-
border-color: var(--border-strong);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
.submissions-filter-select:focus {
|
|
50
|
-
outline: none;
|
|
51
|
-
border-color: var(--ring);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.submissions-filter-select:disabled {
|
|
55
|
-
opacity: 0.6;
|
|
56
|
-
cursor: not-allowed;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
.submissions-filter-clear {
|
|
60
|
-
padding: 0.5rem 0.75rem;
|
|
61
|
-
font-size: 0.75rem;
|
|
62
|
-
font-weight: 500;
|
|
63
|
-
color: var(--muted-foreground);
|
|
64
|
-
background: var(--background);
|
|
65
|
-
border: 1px solid var(--input);
|
|
66
|
-
border-radius: var(--radius);
|
|
67
|
-
cursor: pointer;
|
|
68
|
-
transition: all 0.15s ease;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
.submissions-filter-clear:hover {
|
|
72
|
-
background: var(--muted);
|
|
73
|
-
color: var(--foreground);
|
|
74
|
-
}
|