@nextsparkjs/theme-default 0.1.0-beta.1
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/about/business.md +49 -0
- package/about/features.json +302 -0
- package/about/team.md +79 -0
- package/api/ai/chat/stream/route.ts +212 -0
- package/api/ai/orchestrator/route.ts +226 -0
- package/api/ai/single-agent/route.ts +291 -0
- package/api/ai/usage/route.ts +122 -0
- package/blocks/benefits/component.tsx +100 -0
- package/blocks/benefits/config.ts +11 -0
- package/blocks/benefits/examples.ts +85 -0
- package/blocks/benefits/fields.ts +156 -0
- package/blocks/benefits/schema.ts +33 -0
- package/blocks/cta-section/component.tsx +100 -0
- package/blocks/cta-section/config.ts +11 -0
- package/blocks/cta-section/examples.ts +41 -0
- package/blocks/cta-section/fields.ts +89 -0
- package/blocks/cta-section/index.ts +6 -0
- package/blocks/cta-section/schema.ts +32 -0
- package/blocks/cta-section/thumbnail.png +1 -0
- package/blocks/faq-accordion/component.tsx +156 -0
- package/blocks/faq-accordion/config.ts +11 -0
- package/blocks/faq-accordion/examples.ts +77 -0
- package/blocks/faq-accordion/fields.ts +119 -0
- package/blocks/faq-accordion/index.ts +6 -0
- package/blocks/faq-accordion/schema.ts +45 -0
- package/blocks/features-grid/component.tsx +112 -0
- package/blocks/features-grid/config.ts +11 -0
- package/blocks/features-grid/examples.ts +63 -0
- package/blocks/features-grid/fields.ts +97 -0
- package/blocks/features-grid/index.ts +6 -0
- package/blocks/features-grid/schema.ts +40 -0
- package/blocks/features-grid/thumbnail.png +1 -0
- package/blocks/hero/component.tsx +100 -0
- package/blocks/hero/config.ts +11 -0
- package/blocks/hero/examples.ts +35 -0
- package/blocks/hero/fields.ts +60 -0
- package/blocks/hero/index.ts +6 -0
- package/blocks/hero/schema.ts +32 -0
- package/blocks/hero/thumbnail.png +1 -0
- package/blocks/hero/thumbnail.png.txt +6 -0
- package/blocks/hero-with-form/component.tsx +232 -0
- package/blocks/hero-with-form/config.ts +11 -0
- package/blocks/hero-with-form/examples.ts +16 -0
- package/blocks/hero-with-form/fields.ts +207 -0
- package/blocks/hero-with-form/index.ts +6 -0
- package/blocks/hero-with-form/schema.ts +54 -0
- package/blocks/jumbotron/component.tsx +136 -0
- package/blocks/jumbotron/config.ts +11 -0
- package/blocks/jumbotron/examples.ts +36 -0
- package/blocks/jumbotron/fields.ts +202 -0
- package/blocks/jumbotron/index.ts +6 -0
- package/blocks/jumbotron/schema.ts +55 -0
- package/blocks/logo-cloud/component.tsx +154 -0
- package/blocks/logo-cloud/config.ts +11 -0
- package/blocks/logo-cloud/examples.ts +34 -0
- package/blocks/logo-cloud/fields.ts +133 -0
- package/blocks/logo-cloud/index.ts +6 -0
- package/blocks/logo-cloud/schema.ts +46 -0
- package/blocks/post-content/component.tsx +197 -0
- package/blocks/post-content/config.ts +11 -0
- package/blocks/post-content/examples.ts +33 -0
- package/blocks/post-content/fields.ts +165 -0
- package/blocks/post-content/index.ts +4 -0
- package/blocks/post-content/schema.ts +46 -0
- package/blocks/pricing-table/component.tsx +154 -0
- package/blocks/pricing-table/config.ts +11 -0
- package/blocks/pricing-table/examples.ts +96 -0
- package/blocks/pricing-table/fields.ts +161 -0
- package/blocks/pricing-table/index.ts +4 -0
- package/blocks/pricing-table/schema.ts +50 -0
- package/blocks/split-content/component.tsx +135 -0
- package/blocks/split-content/config.ts +11 -0
- package/blocks/split-content/examples.ts +38 -0
- package/blocks/split-content/fields.ts +198 -0
- package/blocks/split-content/index.ts +6 -0
- package/blocks/split-content/schema.ts +67 -0
- package/blocks/stats-counter/component.tsx +124 -0
- package/blocks/stats-counter/config.ts +11 -0
- package/blocks/stats-counter/examples.ts +61 -0
- package/blocks/stats-counter/fields.ts +134 -0
- package/blocks/stats-counter/index.ts +6 -0
- package/blocks/stats-counter/schema.ts +47 -0
- package/blocks/testimonials/component.tsx +114 -0
- package/blocks/testimonials/config.ts +11 -0
- package/blocks/testimonials/examples.ts +65 -0
- package/blocks/testimonials/fields.ts +105 -0
- package/blocks/testimonials/index.ts +6 -0
- package/blocks/testimonials/schema.ts +41 -0
- package/blocks/testimonials/thumbnail.png +1 -0
- package/blocks/text-content/component.tsx +97 -0
- package/blocks/text-content/config.ts +11 -0
- package/blocks/text-content/examples.ts +30 -0
- package/blocks/text-content/fields.ts +88 -0
- package/blocks/text-content/index.ts +6 -0
- package/blocks/text-content/schema.ts +30 -0
- package/blocks/text-content/thumbnail.png +1 -0
- package/blocks/timeline/component.tsx +267 -0
- package/blocks/timeline/config.ts +11 -0
- package/blocks/timeline/examples.ts +68 -0
- package/blocks/timeline/fields.ts +147 -0
- package/blocks/timeline/index.ts +6 -0
- package/blocks/timeline/schema.ts +49 -0
- package/blocks/video-hero/component.tsx +270 -0
- package/blocks/video-hero/config.ts +11 -0
- package/blocks/video-hero/examples.ts +24 -0
- package/blocks/video-hero/fields.ts +98 -0
- package/blocks/video-hero/index.ts +6 -0
- package/blocks/video-hero/schema.ts +39 -0
- package/components/ai-chat/ChatPanel.tsx +575 -0
- package/components/ai-chat/ConversationItem.tsx +266 -0
- package/components/ai-chat/ConversationSidebar.tsx +99 -0
- package/components/ai-chat/MarkdownRenderer.tsx +15 -0
- package/components/ai-chat/Message.tsx +42 -0
- package/components/ai-chat/MessageInput.tsx +49 -0
- package/components/ai-chat/MessageList.tsx +46 -0
- package/components/ai-chat/TypingIndicator.tsx +11 -0
- package/config/app.config.ts +367 -0
- package/config/billing.config.ts +349 -0
- package/config/dashboard.config.ts +506 -0
- package/config/dev.config.ts +104 -0
- package/config/features.config.ts +203 -0
- package/config/flows.config.ts +129 -0
- package/config/permissions.config.ts +245 -0
- package/config/theme.config.ts +74 -0
- package/docs/01-overview/01-introduction.md +335 -0
- package/docs/01-overview/02-customization.md +671 -0
- package/docs/02-features/01-components.md +155 -0
- package/docs/02-features/02-styling.md +139 -0
- package/docs/02-features/03-tasks-entity.md +407 -0
- package/docs/03-ai/01-overview.md +211 -0
- package/docs/03-ai/02-customization.md +436 -0
- package/entities/customers/customers.config.ts +75 -0
- package/entities/customers/customers.fields.ts +165 -0
- package/entities/customers/customers.service.ts +516 -0
- package/entities/customers/customers.types.ts +83 -0
- package/entities/customers/messages/en.json +66 -0
- package/entities/customers/messages/es.json +66 -0
- package/entities/customers/migrations/001_customers_table.sql +102 -0
- package/entities/customers/migrations/002_customers_metas.sql +92 -0
- package/entities/pages/messages/en.json +41 -0
- package/entities/pages/messages/es.json +41 -0
- package/entities/pages/migrations/001_pages_table.sql +112 -0
- package/entities/pages/migrations/002_pages_metas.sql +56 -0
- package/entities/pages/migrations/003_add_status.sql +50 -0
- package/entities/pages/pages-management.service.ts +610 -0
- package/entities/pages/pages.config.ts +94 -0
- package/entities/pages/pages.fields.ts +101 -0
- package/entities/pages/pages.service.ts +290 -0
- package/entities/pages/pages.types.ts +124 -0
- package/entities/posts/components/post-header.tsx +97 -0
- package/entities/posts/messages/en.json +55 -0
- package/entities/posts/messages/es.json +55 -0
- package/entities/posts/migrations/001_posts_table.sql +115 -0
- package/entities/posts/migrations/003_add_status.sql +44 -0
- package/entities/posts/migrations/004_entity_taxonomy_relations.sql +129 -0
- package/entities/posts/migrations/006_posts_metas.sql +56 -0
- package/entities/posts/posts.config.ts +101 -0
- package/entities/posts/posts.fields.ts +116 -0
- package/entities/posts/posts.service.ts +376 -0
- package/entities/posts/posts.types.ts +74 -0
- package/entities/tasks/messages/en.json +204 -0
- package/entities/tasks/messages/es.json +204 -0
- package/entities/tasks/migrations/001_tasks_table.sql +105 -0
- package/entities/tasks/migrations/002_task_metas.sql +85 -0
- package/entities/tasks/migrations/sample_data.json +77 -0
- package/entities/tasks/tasks.config.ts +79 -0
- package/entities/tasks/tasks.fields.ts +196 -0
- package/entities/tasks/tasks.service.ts +541 -0
- package/entities/tasks/tasks.types.ts +56 -0
- package/lib/hooks/useAiChat.ts +114 -0
- package/lib/hooks/useConversations.ts +376 -0
- package/lib/hooks/useOrchestratorChat.ts +122 -0
- package/lib/hooks/usePersistentChat.ts +315 -0
- package/lib/hooks/useStreamingChat.ts +127 -0
- package/lib/hooks/useTokenUsage.ts +63 -0
- package/lib/langchain/agents/customer-assistant.md +69 -0
- package/lib/langchain/agents/index.ts +61 -0
- package/lib/langchain/agents/orchestrator.md +59 -0
- package/lib/langchain/agents/page-assistant.md +85 -0
- package/lib/langchain/agents/single-agent.md +46 -0
- package/lib/langchain/agents/task-assistant.md +55 -0
- package/lib/langchain/config.ts +45 -0
- package/lib/langchain/handlers/customer-handler.ts +338 -0
- package/lib/langchain/handlers/page-handler.ts +232 -0
- package/lib/langchain/handlers/task-handler.ts +323 -0
- package/lib/langchain/langchain.config.ts +223 -0
- package/lib/langchain/observability.config.ts +30 -0
- package/lib/langchain/orchestrator.ts +562 -0
- package/lib/langchain/tools/customers.ts +176 -0
- package/lib/langchain/tools/index.ts +10 -0
- package/lib/langchain/tools/orchestrator.ts +92 -0
- package/lib/langchain/tools/pages.ts +289 -0
- package/lib/langchain/tools/tasks.ts +167 -0
- package/lib/scheduled-actions/billing.ts +149 -0
- package/lib/scheduled-actions/index.ts +170 -0
- package/lib/scheduled-actions/webhook.ts +231 -0
- package/lib/selectors.ts +197 -0
- package/messages/de/admin.json +219 -0
- package/messages/de/aiUsage.json +36 -0
- package/messages/de/buttons.json +19 -0
- package/messages/de/categories.json +35 -0
- package/messages/de/common.json +16 -0
- package/messages/de/dev.json +101 -0
- package/messages/de/docs.json +27 -0
- package/messages/de/entities.json +7 -0
- package/messages/de/features.json +119 -0
- package/messages/de/footer.json +22 -0
- package/messages/de/home.json +57 -0
- package/messages/de/index.ts +39 -0
- package/messages/de/mobileNav.json +13 -0
- package/messages/de/navigation.json +8 -0
- package/messages/de/observability.json +74 -0
- package/messages/de/posts.json +54 -0
- package/messages/de/pricing.json +102 -0
- package/messages/de/support.json +9 -0
- package/messages/de/teams.json +8 -0
- package/messages/en/admin.json +219 -0
- package/messages/en/aiUsage.json +36 -0
- package/messages/en/buttons.json +19 -0
- package/messages/en/categories.json +35 -0
- package/messages/en/common.json +16 -0
- package/messages/en/dev.json +106 -0
- package/messages/en/docs.json +27 -0
- package/messages/en/entities.json +7 -0
- package/messages/en/features.json +119 -0
- package/messages/en/footer.json +22 -0
- package/messages/en/home.json +57 -0
- package/messages/en/index.ts +39 -0
- package/messages/en/mobileNav.json +13 -0
- package/messages/en/navigation.json +8 -0
- package/messages/en/observability.json +74 -0
- package/messages/en/posts.json +54 -0
- package/messages/en/pricing.json +102 -0
- package/messages/en/support.json +9 -0
- package/messages/en/teams.json +8 -0
- package/messages/es/admin.json +219 -0
- package/messages/es/aiUsage.json +36 -0
- package/messages/es/buttons.json +19 -0
- package/messages/es/categories.json +35 -0
- package/messages/es/common.json +16 -0
- package/messages/es/dev.json +101 -0
- package/messages/es/docs.json +27 -0
- package/messages/es/entities.json +7 -0
- package/messages/es/features.json +119 -0
- package/messages/es/footer.json +22 -0
- package/messages/es/home.json +57 -0
- package/messages/es/index.ts +39 -0
- package/messages/es/mobileNav.json +13 -0
- package/messages/es/navigation.json +8 -0
- package/messages/es/observability.json +74 -0
- package/messages/es/posts.json +54 -0
- package/messages/es/pricing.json +102 -0
- package/messages/es/support.json +9 -0
- package/messages/es/teams.json +8 -0
- package/messages/fr/admin.json +219 -0
- package/messages/fr/aiUsage.json +36 -0
- package/messages/fr/buttons.json +19 -0
- package/messages/fr/categories.json +35 -0
- package/messages/fr/common.json +16 -0
- package/messages/fr/dev.json +101 -0
- package/messages/fr/docs.json +27 -0
- package/messages/fr/entities.json +7 -0
- package/messages/fr/features.json +119 -0
- package/messages/fr/footer.json +22 -0
- package/messages/fr/home.json +57 -0
- package/messages/fr/index.ts +39 -0
- package/messages/fr/mobileNav.json +13 -0
- package/messages/fr/navigation.json +8 -0
- package/messages/fr/observability.json +74 -0
- package/messages/fr/posts.json +54 -0
- package/messages/fr/pricing.json +102 -0
- package/messages/fr/support.json +9 -0
- package/messages/fr/teams.json +8 -0
- package/messages/it/admin.json +219 -0
- package/messages/it/aiUsage.json +36 -0
- package/messages/it/buttons.json +19 -0
- package/messages/it/categories.json +35 -0
- package/messages/it/common.json +16 -0
- package/messages/it/dev.json +101 -0
- package/messages/it/docs.json +27 -0
- package/messages/it/entities.json +7 -0
- package/messages/it/features.json +119 -0
- package/messages/it/footer.json +22 -0
- package/messages/it/home.json +57 -0
- package/messages/it/index.ts +39 -0
- package/messages/it/mobileNav.json +13 -0
- package/messages/it/navigation.json +8 -0
- package/messages/it/observability.json +74 -0
- package/messages/it/posts.json +54 -0
- package/messages/it/pricing.json +102 -0
- package/messages/it/support.json +9 -0
- package/messages/it/teams.json +8 -0
- package/messages/pt/admin.json +219 -0
- package/messages/pt/aiUsage.json +36 -0
- package/messages/pt/buttons.json +19 -0
- package/messages/pt/categories.json +35 -0
- package/messages/pt/common.json +16 -0
- package/messages/pt/dev.json +101 -0
- package/messages/pt/docs.json +27 -0
- package/messages/pt/entities.json +7 -0
- package/messages/pt/features.json +119 -0
- package/messages/pt/footer.json +22 -0
- package/messages/pt/home.json +57 -0
- package/messages/pt/index.ts +39 -0
- package/messages/pt/mobileNav.json +13 -0
- package/messages/pt/navigation.json +8 -0
- package/messages/pt/observability.json +74 -0
- package/messages/pt/posts.json +54 -0
- package/messages/pt/pricing.json +102 -0
- package/messages/pt/support.json +9 -0
- package/messages/pt/teams.json +8 -0
- package/migrations/089_add_editor_team_role.sql +39 -0
- package/migrations/090_demo_users_teams.sql +540 -0
- package/migrations/091_greek_teams_billing.sql +523 -0
- package/migrations/092_billing_sample_data.sql +774 -0
- package/migrations/093_pages_sample_data.sql +1158 -0
- package/migrations/094_posts_sample_data.sql +278 -0
- package/migrations/095_tasks_sample_data.sql +440 -0
- package/migrations/096_customers_sample_data.sql +358 -0
- package/migrations/097_scheduled_actions_sample_data.sql +111 -0
- package/package.json +22 -0
- package/public/docs/desktop-layout-example.png +0 -0
- package/styles/components.css +11 -0
- package/styles/globals.css +179 -0
- package/templates/(public)/blog/[slug]/page.tsx +65 -0
- package/templates/(public)/layout.tsx +25 -0
- package/templates/(public)/page.tsx +200 -0
- package/templates/(public)/support/page.tsx +321 -0
- package/templates/dashboard/(main)/agent-multi/page.tsx +63 -0
- package/templates/dashboard/(main)/agent-single/page.tsx +142 -0
- package/templates/dashboard/(main)/settings/ai-usage/page.tsx +157 -0
- package/templates/superadmin/ai-observability/[traceId]/page.tsx +27 -0
- package/templates/superadmin/ai-observability/page.tsx +17 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { baseBlockSchema } from '@nextsparkjs/core/types/blocks'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Pricing Plan Item Schema
|
|
6
|
+
* Individual pricing plan in the table
|
|
7
|
+
*/
|
|
8
|
+
const planItemSchema = z.object({
|
|
9
|
+
name: z.string().min(1, 'Plan name is required').max(50),
|
|
10
|
+
price: z.string().min(1, 'Price is required').max(50),
|
|
11
|
+
period: z.string().max(50).optional(),
|
|
12
|
+
description: z.string().max(200).optional(),
|
|
13
|
+
features: z.string().optional(), // Newline-separated features
|
|
14
|
+
ctaText: z.string().max(50).optional(),
|
|
15
|
+
ctaUrl: z.string().optional(),
|
|
16
|
+
isPopular: z.boolean().default(false),
|
|
17
|
+
isDisabled: z.boolean().default(false),
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export type PlanItem = z.infer<typeof planItemSchema>
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Pricing Table Block Schema
|
|
24
|
+
*
|
|
25
|
+
* Extends base schema with:
|
|
26
|
+
* - title: Section title (from base)
|
|
27
|
+
* - subtitle: Section description (from base content field)
|
|
28
|
+
* - plans: Array of pricing plans
|
|
29
|
+
* - columns: Grid layout option (2, 3, 4 columns)
|
|
30
|
+
* - highlightPopular: Whether to highlight popular plan
|
|
31
|
+
*
|
|
32
|
+
* Note: Uses base schema title, content, backgroundColor, className, id
|
|
33
|
+
*/
|
|
34
|
+
export const pricingTableSpecificSchema = z.object({
|
|
35
|
+
// Content: array of pricing plans
|
|
36
|
+
plans: z.array(planItemSchema)
|
|
37
|
+
.min(1, 'At least one plan is required')
|
|
38
|
+
.max(4, 'Maximum 4 plans allowed'),
|
|
39
|
+
|
|
40
|
+
// Design: column layout and highlighting
|
|
41
|
+
columns: z.enum(['2', '3', '4']).default('3'),
|
|
42
|
+
highlightPopular: z.boolean().default(true),
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Complete Pricing Table Block Schema
|
|
47
|
+
*/
|
|
48
|
+
export const schema = baseBlockSchema.merge(pricingTableSpecificSchema)
|
|
49
|
+
|
|
50
|
+
export type PricingTableBlockProps = z.infer<typeof schema>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import Image from 'next/image'
|
|
3
|
+
import { Button } from '@nextsparkjs/core/components/ui/button'
|
|
4
|
+
import { Check } from 'lucide-react'
|
|
5
|
+
import { cn } from '@nextsparkjs/core/lib/utils'
|
|
6
|
+
import { buildSectionClasses } from '@nextsparkjs/core/types/blocks'
|
|
7
|
+
import { sel } from '../../lib/selectors'
|
|
8
|
+
import type { SplitContentBlockProps, BulletPoint } from './schema'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Split Content Block Component
|
|
12
|
+
*
|
|
13
|
+
* Props from 3-tab structure:
|
|
14
|
+
* - Content: subtitle, title, content, image, imageAlt, bulletPoints, cta
|
|
15
|
+
* - Design: backgroundColor, imagePosition, imageStyle, verticalAlign
|
|
16
|
+
* - Advanced: className, id
|
|
17
|
+
*/
|
|
18
|
+
export function SplitContentBlock({
|
|
19
|
+
// Content props
|
|
20
|
+
subtitle,
|
|
21
|
+
title,
|
|
22
|
+
content,
|
|
23
|
+
image,
|
|
24
|
+
imageAlt,
|
|
25
|
+
bulletPoints,
|
|
26
|
+
cta,
|
|
27
|
+
// Design props
|
|
28
|
+
backgroundColor,
|
|
29
|
+
imagePosition = 'left',
|
|
30
|
+
imageStyle = 'rounded',
|
|
31
|
+
verticalAlign = 'center',
|
|
32
|
+
// Advanced props
|
|
33
|
+
className,
|
|
34
|
+
id,
|
|
35
|
+
}: SplitContentBlockProps) {
|
|
36
|
+
// Safe fallback for bulletPoints array
|
|
37
|
+
const safeBulletPoints = bulletPoints ?? []
|
|
38
|
+
|
|
39
|
+
// Build section classes with background and custom className
|
|
40
|
+
const sectionClasses = buildSectionClasses(
|
|
41
|
+
'py-16 px-4 md:py-24',
|
|
42
|
+
{ backgroundColor, className }
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
// Image style classes
|
|
46
|
+
const imageStyleClasses = {
|
|
47
|
+
square: '',
|
|
48
|
+
rounded: 'rounded-lg',
|
|
49
|
+
circle: 'rounded-full',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Vertical alignment classes
|
|
53
|
+
const alignmentClasses = {
|
|
54
|
+
top: 'items-start',
|
|
55
|
+
center: 'items-center',
|
|
56
|
+
bottom: 'items-end',
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Grid order classes based on image position
|
|
60
|
+
const imageOrderClass = imagePosition === 'right' ? 'lg:order-2' : 'lg:order-1'
|
|
61
|
+
const contentOrderClass = imagePosition === 'right' ? 'lg:order-1' : 'lg:order-2'
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
<section id={id} className={sectionClasses} data-cy={sel('blocks.splitContent.container')}>
|
|
65
|
+
<div className="container mx-auto max-w-7xl">
|
|
66
|
+
<div className={cn('grid gap-8 lg:grid-cols-2 lg:gap-12', alignmentClasses[verticalAlign as 'top' | 'center' | 'bottom'])}>
|
|
67
|
+
{/* Image Column */}
|
|
68
|
+
<div className={cn('relative', imageOrderClass)}>
|
|
69
|
+
<div className="relative aspect-[4/3] w-full overflow-hidden">
|
|
70
|
+
<Image
|
|
71
|
+
src={image}
|
|
72
|
+
alt={imageAlt || title || 'Split content image'}
|
|
73
|
+
fill
|
|
74
|
+
className={cn('object-cover', imageStyleClasses[imageStyle as 'square' | 'rounded' | 'circle'])}
|
|
75
|
+
sizes="(max-width: 768px) 100vw, 50vw"
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
{/* Content Column */}
|
|
81
|
+
<div className={cn('flex flex-col justify-center', contentOrderClass)}>
|
|
82
|
+
{subtitle && (
|
|
83
|
+
<p className="mb-2 text-sm font-medium uppercase tracking-wide text-primary">
|
|
84
|
+
{subtitle}
|
|
85
|
+
</p>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{title && (
|
|
89
|
+
<h2 className="mb-4 text-3xl font-bold md:text-4xl lg:text-5xl">
|
|
90
|
+
{title}
|
|
91
|
+
</h2>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{content && (
|
|
95
|
+
<p className="mb-6 text-lg text-muted-foreground">
|
|
96
|
+
{content}
|
|
97
|
+
</p>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
{/* Bullet Points */}
|
|
101
|
+
{safeBulletPoints.length > 0 && (
|
|
102
|
+
<ul className="mb-6 space-y-3">
|
|
103
|
+
{safeBulletPoints.map((point: BulletPoint, index: number) => (
|
|
104
|
+
<li key={index} className="flex items-start gap-3">
|
|
105
|
+
<Check className="mt-1 h-5 w-5 flex-shrink-0 text-primary" />
|
|
106
|
+
<span className="text-base">{point.text}</span>
|
|
107
|
+
</li>
|
|
108
|
+
))}
|
|
109
|
+
</ul>
|
|
110
|
+
)}
|
|
111
|
+
|
|
112
|
+
{/* CTA Button */}
|
|
113
|
+
{cta && cta.text && cta.link && (
|
|
114
|
+
<div className="mt-2">
|
|
115
|
+
<Button
|
|
116
|
+
asChild
|
|
117
|
+
size="lg"
|
|
118
|
+
variant={cta.variant || 'default'}
|
|
119
|
+
>
|
|
120
|
+
<a
|
|
121
|
+
href={cta.link}
|
|
122
|
+
target={cta.target}
|
|
123
|
+
rel={cta.target === '_blank' ? 'noopener noreferrer' : undefined}
|
|
124
|
+
>
|
|
125
|
+
{cta.text}
|
|
126
|
+
</a>
|
|
127
|
+
</Button>
|
|
128
|
+
</div>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</section>
|
|
134
|
+
)
|
|
135
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BlockConfig } from '@nextsparkjs/core/types/blocks'
|
|
2
|
+
|
|
3
|
+
export const config: Omit<BlockConfig, 'schema' | 'fieldDefinitions' | 'Component' | 'examples'> = {
|
|
4
|
+
slug: 'split-content',
|
|
5
|
+
name: 'Split Content',
|
|
6
|
+
description: 'A two-column section with image on one side and text content on the other, with option to reverse the order',
|
|
7
|
+
category: 'content',
|
|
8
|
+
icon: 'LayoutGrid',
|
|
9
|
+
thumbnail: '/theme/blocks/split-content/thumbnail.png',
|
|
10
|
+
scope: ['pages', 'posts']
|
|
11
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { BlockExample } from '@nextsparkjs/core/types/blocks'
|
|
2
|
+
|
|
3
|
+
export const examples: BlockExample[] = [
|
|
4
|
+
{
|
|
5
|
+
name: 'Image Left',
|
|
6
|
+
description: 'Split layout with image on left',
|
|
7
|
+
props: {
|
|
8
|
+
title: 'Built for Teams',
|
|
9
|
+
content: 'Collaborate seamlessly with powerful tools designed for modern workplaces. Share ideas, track progress, and achieve more together.',
|
|
10
|
+
imageUrl: 'https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=800',
|
|
11
|
+
imageAlt: 'Team collaboration',
|
|
12
|
+
imagePosition: 'left',
|
|
13
|
+
cta: {
|
|
14
|
+
text: 'Learn More',
|
|
15
|
+
link: '/features',
|
|
16
|
+
target: '_self',
|
|
17
|
+
},
|
|
18
|
+
backgroundColor: 'white',
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'Image Right',
|
|
23
|
+
description: 'Split layout with image on right',
|
|
24
|
+
props: {
|
|
25
|
+
title: 'Analytics That Matter',
|
|
26
|
+
content: 'Make data-driven decisions with comprehensive analytics and reporting. Gain insights into user behavior, track key metrics, and optimize your strategy.',
|
|
27
|
+
imageUrl: 'https://images.unsplash.com/photo-1551288049-bebda4e38f71?w=800',
|
|
28
|
+
imageAlt: 'Analytics dashboard',
|
|
29
|
+
imagePosition: 'right',
|
|
30
|
+
cta: {
|
|
31
|
+
text: 'View Dashboard',
|
|
32
|
+
link: '/analytics',
|
|
33
|
+
target: '_self',
|
|
34
|
+
},
|
|
35
|
+
backgroundColor: 'gray-50',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
]
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import type { FieldDefinition } from '@nextsparkjs/core/types/blocks'
|
|
2
|
+
import {
|
|
3
|
+
baseDesignFields,
|
|
4
|
+
baseAdvancedFields,
|
|
5
|
+
} from '@nextsparkjs/core/types/blocks'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Split Content Block Field Definitions
|
|
9
|
+
*
|
|
10
|
+
* Organized into 3 tabs:
|
|
11
|
+
* - Content: subtitle, title, content, image, imageAlt, bulletPoints, cta
|
|
12
|
+
* - Design: backgroundColor (from base) + imagePosition, imageStyle, verticalAlign
|
|
13
|
+
* - Advanced: className, id (from base)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Split Content specific content fields
|
|
17
|
+
const splitContentFields: FieldDefinition[] = [
|
|
18
|
+
{
|
|
19
|
+
name: 'subtitle',
|
|
20
|
+
label: 'Subtitle',
|
|
21
|
+
type: 'text',
|
|
22
|
+
tab: 'content',
|
|
23
|
+
required: false,
|
|
24
|
+
placeholder: 'Eyebrow text',
|
|
25
|
+
helpText: 'Optional label or eyebrow text above the title',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
name: 'title',
|
|
29
|
+
label: 'Title',
|
|
30
|
+
type: 'text',
|
|
31
|
+
tab: 'content',
|
|
32
|
+
required: false,
|
|
33
|
+
placeholder: 'Main headline',
|
|
34
|
+
helpText: 'Main section headline',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'content',
|
|
38
|
+
label: 'Content',
|
|
39
|
+
type: 'textarea',
|
|
40
|
+
tab: 'content',
|
|
41
|
+
required: false,
|
|
42
|
+
placeholder: 'Main description text...',
|
|
43
|
+
helpText: 'Main text content/description',
|
|
44
|
+
rows: 4,
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'image',
|
|
48
|
+
label: 'Image',
|
|
49
|
+
type: 'image',
|
|
50
|
+
tab: 'content',
|
|
51
|
+
required: true,
|
|
52
|
+
helpText: 'Featured image (recommended: 800x600px minimum)',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'imageAlt',
|
|
56
|
+
label: 'Image Alt Text',
|
|
57
|
+
type: 'text',
|
|
58
|
+
tab: 'content',
|
|
59
|
+
required: false,
|
|
60
|
+
placeholder: 'Describe the image',
|
|
61
|
+
helpText: 'Alternative text for accessibility',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'bulletPoints',
|
|
65
|
+
label: 'Bullet Points',
|
|
66
|
+
type: 'array',
|
|
67
|
+
tab: 'content',
|
|
68
|
+
required: false,
|
|
69
|
+
description: 'Optional list of bullet points',
|
|
70
|
+
helpText: 'Add up to 10 bullet points',
|
|
71
|
+
maxItems: 10,
|
|
72
|
+
itemFields: [
|
|
73
|
+
{
|
|
74
|
+
name: 'text',
|
|
75
|
+
label: 'Bullet Point',
|
|
76
|
+
type: 'text',
|
|
77
|
+
tab: 'content',
|
|
78
|
+
required: true,
|
|
79
|
+
placeholder: 'Bullet point text',
|
|
80
|
+
maxLength: 200,
|
|
81
|
+
},
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: 'cta',
|
|
86
|
+
label: 'Call to Action',
|
|
87
|
+
type: 'text',
|
|
88
|
+
tab: 'content',
|
|
89
|
+
required: false,
|
|
90
|
+
description: 'Optional CTA button',
|
|
91
|
+
helpText: 'Configure button text, link, target, and variant',
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'cta.text',
|
|
95
|
+
label: 'CTA Text',
|
|
96
|
+
type: 'text',
|
|
97
|
+
tab: 'content',
|
|
98
|
+
required: false,
|
|
99
|
+
placeholder: 'Get Started',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'cta.link',
|
|
103
|
+
label: 'CTA Link',
|
|
104
|
+
type: 'url',
|
|
105
|
+
tab: 'content',
|
|
106
|
+
required: false,
|
|
107
|
+
placeholder: 'https://example.com or /path',
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
name: 'cta.target',
|
|
111
|
+
label: 'CTA Target',
|
|
112
|
+
type: 'select',
|
|
113
|
+
tab: 'content',
|
|
114
|
+
required: false,
|
|
115
|
+
default: '_self',
|
|
116
|
+
options: [
|
|
117
|
+
{ label: 'Same Window', value: '_self' },
|
|
118
|
+
{ label: 'New Window', value: '_blank' },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: 'cta.variant',
|
|
123
|
+
label: 'CTA Variant',
|
|
124
|
+
type: 'select',
|
|
125
|
+
tab: 'content',
|
|
126
|
+
required: false,
|
|
127
|
+
default: 'default',
|
|
128
|
+
helpText: 'Button style variant',
|
|
129
|
+
options: [
|
|
130
|
+
{ label: 'Default', value: 'default' },
|
|
131
|
+
{ label: 'Outline', value: 'outline' },
|
|
132
|
+
{ label: 'Secondary', value: 'secondary' },
|
|
133
|
+
],
|
|
134
|
+
},
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
// Split Content specific design fields
|
|
138
|
+
const splitContentDesignFields: FieldDefinition[] = [
|
|
139
|
+
{
|
|
140
|
+
name: 'imagePosition',
|
|
141
|
+
label: 'Image Position',
|
|
142
|
+
type: 'select',
|
|
143
|
+
tab: 'design',
|
|
144
|
+
required: false,
|
|
145
|
+
default: 'left',
|
|
146
|
+
description: 'Position of the image in the layout',
|
|
147
|
+
options: [
|
|
148
|
+
{ label: 'Left', value: 'left' },
|
|
149
|
+
{ label: 'Right', value: 'right' },
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'imageStyle',
|
|
154
|
+
label: 'Image Style',
|
|
155
|
+
type: 'select',
|
|
156
|
+
tab: 'design',
|
|
157
|
+
required: false,
|
|
158
|
+
default: 'rounded',
|
|
159
|
+
description: 'Image corner style',
|
|
160
|
+
options: [
|
|
161
|
+
{ label: 'Square', value: 'square' },
|
|
162
|
+
{ label: 'Rounded', value: 'rounded' },
|
|
163
|
+
{ label: 'Circle', value: 'circle' },
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
name: 'verticalAlign',
|
|
168
|
+
label: 'Vertical Alignment',
|
|
169
|
+
type: 'select',
|
|
170
|
+
tab: 'design',
|
|
171
|
+
required: false,
|
|
172
|
+
default: 'center',
|
|
173
|
+
description: 'Vertical alignment of content',
|
|
174
|
+
options: [
|
|
175
|
+
{ label: 'Top', value: 'top' },
|
|
176
|
+
{ label: 'Center', value: 'center' },
|
|
177
|
+
{ label: 'Bottom', value: 'bottom' },
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Complete field definitions organized by tab
|
|
184
|
+
*/
|
|
185
|
+
export const fieldDefinitions: FieldDefinition[] = [
|
|
186
|
+
// Content tab: split-content specific fields
|
|
187
|
+
...splitContentFields,
|
|
188
|
+
|
|
189
|
+
// Design tab: base fields + split-content specific
|
|
190
|
+
...baseDesignFields,
|
|
191
|
+
...splitContentDesignFields,
|
|
192
|
+
|
|
193
|
+
// Advanced tab: base fields only
|
|
194
|
+
...baseAdvancedFields,
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
// Alias for compatibility
|
|
198
|
+
export const fields = fieldDefinitions
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import {
|
|
3
|
+
baseBlockSchema,
|
|
4
|
+
ctaSchema,
|
|
5
|
+
type BaseBlockProps,
|
|
6
|
+
} from '@nextsparkjs/core/types/blocks'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Bullet Point Schema
|
|
10
|
+
* Individual bullet point item
|
|
11
|
+
*/
|
|
12
|
+
const bulletPointSchema = z.object({
|
|
13
|
+
text: z.string().min(1, 'Bullet point text is required').max(200),
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export type BulletPoint = z.infer<typeof bulletPointSchema>
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* CTA with Variant Schema
|
|
20
|
+
* Extends base CTA with variant option
|
|
21
|
+
*/
|
|
22
|
+
const ctaWithVariantSchema = ctaSchema.extend({
|
|
23
|
+
variant: z.enum(['default', 'outline', 'secondary']).default('default'),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
export type CTAWithVariant = z.infer<typeof ctaWithVariantSchema>
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Split Content Block Schema
|
|
30
|
+
*
|
|
31
|
+
* Extends base schema with:
|
|
32
|
+
* - subtitle: Optional eyebrow/label above title
|
|
33
|
+
* - image: Featured image URL (required)
|
|
34
|
+
* - imageAlt: Image alt text for accessibility
|
|
35
|
+
* - cta: CTA button with variant option
|
|
36
|
+
* - bulletPoints: Optional list of bullet points
|
|
37
|
+
* - imagePosition: Image position (left/right)
|
|
38
|
+
* - imageStyle: Image corner style (square/rounded/circle)
|
|
39
|
+
* - verticalAlign: Vertical content alignment
|
|
40
|
+
*
|
|
41
|
+
* Note: Uses base schema title, content, backgroundColor, className, id
|
|
42
|
+
*/
|
|
43
|
+
export const splitContentSpecificSchema = z.object({
|
|
44
|
+
// Content fields
|
|
45
|
+
subtitle: z.string().optional(),
|
|
46
|
+
image: z.string().url('Must be a valid image URL').min(1, 'Image is required'),
|
|
47
|
+
imageAlt: z.string().optional(),
|
|
48
|
+
cta: ctaWithVariantSchema.optional(),
|
|
49
|
+
bulletPoints: z.array(bulletPointSchema).max(10, 'Maximum 10 bullet points allowed').optional(),
|
|
50
|
+
|
|
51
|
+
// Design fields
|
|
52
|
+
imagePosition: z.enum(['left', 'right']).default('left'),
|
|
53
|
+
imageStyle: z.enum(['square', 'rounded', 'circle']).default('rounded'),
|
|
54
|
+
verticalAlign: z.enum(['top', 'center', 'bottom']).default('center'),
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Complete Split Content Block Schema
|
|
59
|
+
*/
|
|
60
|
+
export const schema = baseBlockSchema
|
|
61
|
+
.omit({ cta: true }) // Remove base cta
|
|
62
|
+
.merge(splitContentSpecificSchema)
|
|
63
|
+
|
|
64
|
+
export type SplitContentBlockProps = z.infer<typeof schema>
|
|
65
|
+
|
|
66
|
+
// Also export for type-only imports
|
|
67
|
+
export type { BaseBlockProps }
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import { cn } from '@nextsparkjs/core/lib/utils'
|
|
3
|
+
import { buildSectionClasses } from '@nextsparkjs/core/types/blocks'
|
|
4
|
+
import { sel } from '../../lib/selectors'
|
|
5
|
+
import type { StatsCounterBlockProps, StatItem } from './schema'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Stats Counter Block Component
|
|
9
|
+
*
|
|
10
|
+
* Props from 3-tab structure:
|
|
11
|
+
* - Content: title, content, cta, stats
|
|
12
|
+
* - Design: backgroundColor, columns, variant, size
|
|
13
|
+
* - Advanced: className, id
|
|
14
|
+
*/
|
|
15
|
+
export function StatsCounterBlock({
|
|
16
|
+
// Base content props
|
|
17
|
+
title,
|
|
18
|
+
content,
|
|
19
|
+
cta,
|
|
20
|
+
// Stats-specific content
|
|
21
|
+
stats,
|
|
22
|
+
// Base design props
|
|
23
|
+
backgroundColor,
|
|
24
|
+
// Stats-specific design
|
|
25
|
+
columns = '4',
|
|
26
|
+
variant = 'default',
|
|
27
|
+
size = 'md',
|
|
28
|
+
// Base advanced props
|
|
29
|
+
className,
|
|
30
|
+
id,
|
|
31
|
+
}: StatsCounterBlockProps) {
|
|
32
|
+
// Safely handle stats array
|
|
33
|
+
const safeStats = Array.isArray(stats) ? stats : []
|
|
34
|
+
|
|
35
|
+
// Build column classes based on columns prop
|
|
36
|
+
const columnClasses: Record<string, string> = {
|
|
37
|
+
'2': 'sm:grid-cols-2',
|
|
38
|
+
'3': 'sm:grid-cols-2 lg:grid-cols-3',
|
|
39
|
+
'4': 'sm:grid-cols-2 lg:grid-cols-4',
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Size classes for numbers
|
|
43
|
+
const sizeClasses: Record<string, string> = {
|
|
44
|
+
sm: 'text-3xl md:text-4xl',
|
|
45
|
+
md: 'text-4xl md:text-5xl',
|
|
46
|
+
lg: 'text-5xl md:text-6xl lg:text-7xl',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Variant-specific stat item classes
|
|
50
|
+
const getStatItemClasses = () => {
|
|
51
|
+
const baseClasses = 'flex flex-col items-center text-center'
|
|
52
|
+
|
|
53
|
+
switch (variant) {
|
|
54
|
+
case 'cards':
|
|
55
|
+
return cn(baseClasses, 'p-6 rounded-lg border bg-card shadow-sm')
|
|
56
|
+
case 'minimal':
|
|
57
|
+
return cn(baseClasses, 'p-4')
|
|
58
|
+
default:
|
|
59
|
+
return cn(baseClasses, 'p-6')
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build section classes with background and custom className
|
|
64
|
+
const sectionClasses = buildSectionClasses(
|
|
65
|
+
'py-16 px-4 md:py-24',
|
|
66
|
+
{ backgroundColor, className }
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<section id={id} className={sectionClasses} data-cy={sel('blocks.statsCounter.container')}>
|
|
71
|
+
<div className="container mx-auto max-w-6xl">
|
|
72
|
+
{/* Section Header */}
|
|
73
|
+
{(title || content) && (
|
|
74
|
+
<div className="mb-12 text-center">
|
|
75
|
+
{title && (
|
|
76
|
+
<h2 className="mb-4 text-4xl font-bold md:text-5xl">
|
|
77
|
+
{title}
|
|
78
|
+
</h2>
|
|
79
|
+
)}
|
|
80
|
+
{content && (
|
|
81
|
+
<p className="mx-auto max-w-2xl text-lg text-muted-foreground">
|
|
82
|
+
{content}
|
|
83
|
+
</p>
|
|
84
|
+
)}
|
|
85
|
+
</div>
|
|
86
|
+
)}
|
|
87
|
+
|
|
88
|
+
{/* Stats Grid */}
|
|
89
|
+
<div className={cn('grid gap-8', columnClasses[columns] || columnClasses['4'])}>
|
|
90
|
+
{safeStats.map((stat: StatItem, index: number) => (
|
|
91
|
+
<div key={index} className={getStatItemClasses()}>
|
|
92
|
+
<div className={cn('font-bold mb-2', sizeClasses[size] || sizeClasses['md'])}>
|
|
93
|
+
{stat.prefix && (
|
|
94
|
+
<span className="text-primary">{stat.prefix}</span>
|
|
95
|
+
)}
|
|
96
|
+
{stat.value}
|
|
97
|
+
{stat.suffix && (
|
|
98
|
+
<span className="text-primary">{stat.suffix}</span>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
<div className="text-sm md:text-base text-muted-foreground font-medium">
|
|
102
|
+
{stat.label}
|
|
103
|
+
</div>
|
|
104
|
+
</div>
|
|
105
|
+
))}
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{/* Optional CTA */}
|
|
109
|
+
{cta && (
|
|
110
|
+
<div className="mt-12 text-center">
|
|
111
|
+
<a
|
|
112
|
+
href={cta.link}
|
|
113
|
+
target={cta.target}
|
|
114
|
+
rel={cta.target === '_blank' ? 'noopener noreferrer' : undefined}
|
|
115
|
+
className="inline-flex items-center justify-center rounded-md bg-primary px-8 py-3 text-sm font-medium text-primary-foreground hover:bg-primary/90 transition-colors"
|
|
116
|
+
>
|
|
117
|
+
{cta.text}
|
|
118
|
+
</a>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</div>
|
|
122
|
+
</section>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { BlockConfig } from '@nextsparkjs/core/types/blocks'
|
|
2
|
+
|
|
3
|
+
export const config: Omit<BlockConfig, 'schema' | 'fieldDefinitions' | 'Component' | 'examples'> = {
|
|
4
|
+
slug: 'stats-counter',
|
|
5
|
+
name: 'Stats Counter',
|
|
6
|
+
description: 'A section displaying key metrics/statistics with large numbers and labels (e.g., "10,000+ Customers", "99% Uptime")',
|
|
7
|
+
category: 'stats',
|
|
8
|
+
icon: 'TrendingUp',
|
|
9
|
+
thumbnail: '/theme/blocks/stats-counter/thumbnail.png',
|
|
10
|
+
scope: ['pages', 'posts']
|
|
11
|
+
}
|