Frontend:
- Add comprehensive Puck component library (Layout, Content, Booking, Contact)
- Add Services component with usePublicServices hook integration
- Add 150+ icons to IconList component organized by category
- Add preview modal with viewport toggles (desktop/tablet/mobile)
- Add draft save/discard functionality with localStorage persistence
- Add draft status indicator in PageEditor toolbar
- Fix useSites hooks to use correct API URLs (/pages/{id}/)
Backend:
- Add SiteConfig model for theme, header, footer configuration
- Add Page SEO fields (meta_title, meta_description, og_image, etc.)
- Add puck_data validation for component structure
- Add create_missing_sites management command
- Fix PageViewSet to use EntitlementService for permissions
- Add comprehensive tests for site builder functionality
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
98 lines
2.7 KiB
TypeScript
98 lines
2.7 KiB
TypeScript
import React from 'react';
|
|
import type { ComponentConfig } from '@measured/puck';
|
|
import { DropZone } from '@measured/puck';
|
|
import type { ColumnsProps } from '../../types';
|
|
|
|
const COLUMN_CONFIGS = {
|
|
'2': { count: 2, classes: 'grid-cols-1 md:grid-cols-2' },
|
|
'3': { count: 3, classes: 'grid-cols-1 md:grid-cols-3' },
|
|
'4': { count: 4, classes: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-4' },
|
|
'2-1': { count: 2, classes: 'grid-cols-1 md:grid-cols-3', colSpans: ['md:col-span-2', 'md:col-span-1'] },
|
|
'1-2': { count: 2, classes: 'grid-cols-1 md:grid-cols-3', colSpans: ['md:col-span-1', 'md:col-span-2'] },
|
|
};
|
|
|
|
const GAP_CLASSES = {
|
|
none: 'gap-0',
|
|
small: 'gap-4',
|
|
medium: 'gap-6',
|
|
large: 'gap-8',
|
|
};
|
|
|
|
const ALIGN_CLASSES = {
|
|
top: 'items-start',
|
|
center: 'items-center',
|
|
bottom: 'items-end',
|
|
stretch: 'items-stretch',
|
|
};
|
|
|
|
export const Columns: ComponentConfig<ColumnsProps> = {
|
|
label: 'Columns',
|
|
fields: {
|
|
columns: {
|
|
type: 'select',
|
|
options: [
|
|
{ label: '2 Columns', value: '2' },
|
|
{ label: '3 Columns', value: '3' },
|
|
{ label: '4 Columns', value: '4' },
|
|
{ label: '2:1 Ratio', value: '2-1' },
|
|
{ label: '1:2 Ratio', value: '1-2' },
|
|
],
|
|
},
|
|
gap: {
|
|
type: 'select',
|
|
options: [
|
|
{ label: 'None', value: 'none' },
|
|
{ label: 'Small', value: 'small' },
|
|
{ label: 'Medium', value: 'medium' },
|
|
{ label: 'Large', value: 'large' },
|
|
],
|
|
},
|
|
verticalAlign: {
|
|
type: 'select',
|
|
options: [
|
|
{ label: 'Top', value: 'top' },
|
|
{ label: 'Center', value: 'center' },
|
|
{ label: 'Bottom', value: 'bottom' },
|
|
{ label: 'Stretch', value: 'stretch' },
|
|
],
|
|
},
|
|
stackOnMobile: {
|
|
type: 'radio',
|
|
label: 'Stack on Mobile',
|
|
options: [
|
|
{ label: 'Yes', value: true },
|
|
{ label: 'No', value: false },
|
|
],
|
|
},
|
|
},
|
|
defaultProps: {
|
|
columns: '2',
|
|
gap: 'medium',
|
|
verticalAlign: 'top',
|
|
stackOnMobile: true,
|
|
},
|
|
render: ({ columns, gap, verticalAlign, stackOnMobile }) => {
|
|
const config = COLUMN_CONFIGS[columns] || COLUMN_CONFIGS['2'];
|
|
const gapClass = GAP_CLASSES[gap] || GAP_CLASSES.medium;
|
|
const alignClass = ALIGN_CLASSES[verticalAlign] || ALIGN_CLASSES.top;
|
|
|
|
// Generate column elements
|
|
const columnElements = Array.from({ length: config.count }).map((_, index) => {
|
|
const colSpan = config.colSpans?.[index] || '';
|
|
return (
|
|
<div key={index} className={colSpan}>
|
|
<DropZone zone={`column-${index}`} />
|
|
</div>
|
|
);
|
|
});
|
|
|
|
return (
|
|
<div className={`grid ${config.classes} ${gapClass} ${alignClass}`}>
|
|
{columnElements}
|
|
</div>
|
|
);
|
|
},
|
|
};
|
|
|
|
export default Columns;
|