rankrunners-cms 0.0.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.
Files changed (49) hide show
  1. package/CLAUDE.md +106 -0
  2. package/README.md +15 -0
  3. package/package.json +39 -0
  4. package/src/CaptchaBadge.tsx +72 -0
  5. package/src/editor/blocks/Blank/index.tsx +15 -0
  6. package/src/editor/blocks/Blank/styles.module.css +4 -0
  7. package/src/editor/blocks/Button/index.tsx +46 -0
  8. package/src/editor/blocks/Card/index.tsx +67 -0
  9. package/src/editor/blocks/Card/styles.module.css +54 -0
  10. package/src/editor/blocks/Container/index.tsx +36 -0
  11. package/src/editor/blocks/Flex/index.tsx +82 -0
  12. package/src/editor/blocks/Flex/styles.module.css +9 -0
  13. package/src/editor/blocks/Grid/index.tsx +53 -0
  14. package/src/editor/blocks/Grid/styles.module.css +11 -0
  15. package/src/editor/blocks/Heading/index.tsx +69 -0
  16. package/src/editor/blocks/Hero/Hero.tsx +107 -0
  17. package/src/editor/blocks/Hero/client.tsx +204 -0
  18. package/src/editor/blocks/Hero/index.tsx +2 -0
  19. package/src/editor/blocks/Hero/quotes.ts +46 -0
  20. package/src/editor/blocks/Hero/server.tsx +7 -0
  21. package/src/editor/blocks/Hero/styles.module.css +116 -0
  22. package/src/editor/blocks/Logos/index.tsx +77 -0
  23. package/src/editor/blocks/Logos/styles.module.css +13 -0
  24. package/src/editor/blocks/Paragraph/index.tsx +95 -0
  25. package/src/editor/blocks/Paragraph/styles.module.css +4 -0
  26. package/src/editor/blocks/Space/index.tsx +45 -0
  27. package/src/editor/blocks/Space/styles.module.css +14 -0
  28. package/src/editor/blocks/Stats/index.tsx +58 -0
  29. package/src/editor/blocks/Stats/styles.module.css +64 -0
  30. package/src/editor/blocks/Text/index.tsx +75 -0
  31. package/src/editor/blocks/Text/styles.module.css +4 -0
  32. package/src/editor/components/Layout/index.tsx +160 -0
  33. package/src/editor/components/Layout/styles.module.css +3 -0
  34. package/src/editor/components/Section/index.tsx +31 -0
  35. package/src/editor/components/Section/styles.module.css +23 -0
  36. package/src/editor/index.tsx +63 -0
  37. package/src/editor/options.ts +37 -0
  38. package/src/editor/types.ts +60 -0
  39. package/src/editor/utils/generateId.ts +2 -0
  40. package/src/editor/utils/getClassNameFactory.ts +63 -0
  41. package/src/index.css +1 -0
  42. package/src/index.ts +6 -0
  43. package/src/libs/redirect.ts +10 -0
  44. package/src/sitemap/handleRobotsTxt.ts +19 -0
  45. package/src/sitemap/handleSitemap.ts +25 -0
  46. package/src/sitemap/index.ts +2 -0
  47. package/src/styles.d.ts +4 -0
  48. package/tsconfig.json +42 -0
  49. package/tsdown.config.ts +15 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,106 @@
1
+
2
+ Default to using Bun instead of Node.js.
3
+
4
+ - Use `bun <file>` instead of `node <file>` or `ts-node <file>`
5
+ - Use `bun test` instead of `jest` or `vitest`
6
+ - Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
7
+ - Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
8
+ - Use `bun run <script>` instead of `npm run <script>` or `yarn run <script>` or `pnpm run <script>`
9
+ - Bun automatically loads .env, so don't use dotenv.
10
+
11
+ ## APIs
12
+
13
+ - `Bun.serve()` supports WebSockets, HTTPS, and routes. Don't use `express`.
14
+ - `bun:sqlite` for SQLite. Don't use `better-sqlite3`.
15
+ - `Bun.redis` for Redis. Don't use `ioredis`.
16
+ - `Bun.sql` for Postgres. Don't use `pg` or `postgres.js`.
17
+ - `WebSocket` is built-in. Don't use `ws`.
18
+ - Prefer `Bun.file` over `node:fs`'s readFile/writeFile
19
+ - Bun.$`ls` instead of execa.
20
+
21
+ ## Testing
22
+
23
+ Use `bun test` to run tests.
24
+
25
+ ```ts#index.test.ts
26
+ import { test, expect } from "bun:test";
27
+
28
+ test("hello world", () => {
29
+ expect(1).toBe(1);
30
+ });
31
+ ```
32
+
33
+ ## Frontend
34
+
35
+ Use HTML imports with `Bun.serve()`. Don't use `vite`. HTML imports fully support React, CSS, Tailwind.
36
+
37
+ Server:
38
+
39
+ ```ts#index.ts
40
+ import index from "./index.html"
41
+
42
+ Bun.serve({
43
+ routes: {
44
+ "/": index,
45
+ "/api/users/:id": {
46
+ GET: (req) => {
47
+ return new Response(JSON.stringify({ id: req.params.id }));
48
+ },
49
+ },
50
+ },
51
+ // optional websocket support
52
+ websocket: {
53
+ open: (ws) => {
54
+ ws.send("Hello, world!");
55
+ },
56
+ message: (ws, message) => {
57
+ ws.send(message);
58
+ },
59
+ close: (ws) => {
60
+ // handle close
61
+ }
62
+ },
63
+ development: {
64
+ hmr: true,
65
+ console: true,
66
+ }
67
+ })
68
+ ```
69
+
70
+ HTML files can import .tsx, .jsx or .js files directly and Bun's bundler will transpile & bundle automatically. `<link>` tags can point to stylesheets and Bun's CSS bundler will bundle.
71
+
72
+ ```html#index.html
73
+ <html>
74
+ <body>
75
+ <h1>Hello, world!</h1>
76
+ <script type="module" src="./frontend.tsx"></script>
77
+ </body>
78
+ </html>
79
+ ```
80
+
81
+ With the following `frontend.tsx`:
82
+
83
+ ```tsx#frontend.tsx
84
+ import React from "react";
85
+
86
+ // import .css files directly and it works
87
+ import './index.css';
88
+
89
+ import { createRoot } from "react-dom/client";
90
+
91
+ const root = createRoot(document.body);
92
+
93
+ export default function Frontend() {
94
+ return <h1>Hello, world!</h1>;
95
+ }
96
+
97
+ root.render(<Frontend />);
98
+ ```
99
+
100
+ Then, run index.ts
101
+
102
+ ```sh
103
+ bun --hot ./index.ts
104
+ ```
105
+
106
+ For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`.
package/README.md ADDED
@@ -0,0 +1,15 @@
1
+ # rankrunners-cms
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun run index.ts
13
+ ```
14
+
15
+ This project was created using `bun init` in bun v1.3.0. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "rankrunners-cms",
3
+ "type": "module",
4
+ "version": "0.0.1",
5
+ "scripts": {
6
+ "build": "bun run --bun tsdown",
7
+ "watch": "bun run --bun tsdown --watch"
8
+ },
9
+ "types": "dist/types/index.d.ts",
10
+ "module": "dist/index.js",
11
+ "peerDependencies": {
12
+ "react": "^19.2.0",
13
+ "@measured/puck": "^0.20.2",
14
+ "next": "^16.0.1"
15
+ },
16
+ "peerDependenciesMeta": {
17
+ "next": {
18
+ "optional": true
19
+ }
20
+ },
21
+ "devDependencies": {
22
+ "@bosh-code/tsdown-plugin-inject-css": "^2.0.0",
23
+ "@bosh-code/tsdown-plugin-tailwindcss": "^1.0.1",
24
+ "@swc/core": "^1.15.0",
25
+ "@types/react": "^19.2.2",
26
+ "bun-types": "^1.3.0",
27
+ "tsdown": "^0.15.9",
28
+ "typescript": "^5.9.3"
29
+ },
30
+ "dependencies": {
31
+ "classnames": "^2.5.1",
32
+ "lucide-react": "^0.552.0",
33
+ "react": "^19.2.0",
34
+ "tailwindcss": "^4.1.15"
35
+ },
36
+ "trustedDependencies": [
37
+ "@swc/core"
38
+ ]
39
+ }
@@ -0,0 +1,72 @@
1
+ import React, { useState } from 'react';
2
+
3
+ const CAPTCHA_EXPLANATION =
4
+ 'RankRunners Captcha helps protect this site from spam and abuse. It works by analyzing user behavior to distinguish between humans and bots without interrupting the user experience.';
5
+ const RANKRUNNERS_LOGO_URL =
6
+ 'https://aplushandymanservicesaz.com/assets/rankrunners-ico.webp';
7
+
8
+ export const CaptchaBadge: React.FC = () => {
9
+ const [isHovered, setIsHovered] = useState(false);
10
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
11
+
12
+ const handleMouseEnter = () => setIsHovered(true);
13
+ const handleMouseLeave = () => setIsHovered(false);
14
+ const handleLearnMoreClick = () => setIsDialogOpen(true);
15
+ const handleCloseDialog = () => setIsDialogOpen(false);
16
+
17
+ return (
18
+ <>
19
+ <div
20
+ className={`fixed bottom-4 z-50 flex items-center p-2 bg-white border border-gray-300 border-1.5 shadow-xl transition-all duration-300 ease-in-out ${
21
+ isHovered
22
+ ? 'w-64 rounded-lg right-0'
23
+ : 'w-26 rounded-lg right-[-48px]'
24
+ } overflow-hidden`}
25
+ onMouseEnter={handleMouseEnter}
26
+ onMouseLeave={handleMouseLeave}
27
+ >
28
+ <img
29
+ src={RANKRUNNERS_LOGO_URL}
30
+ alt="RankRunners Logo"
31
+ className="w-10 h-10 rounded-full flex-shrink-0"
32
+ />
33
+ <div
34
+ className={`flex w-full justify-center transition-all duration-300 ease-in-out flex flex-col ${
35
+ isHovered
36
+ ? 'translate-x-0 opacity-100'
37
+ : 'translate-x-full opacity-0'
38
+ }`}
39
+ >
40
+ <div className="flex flex-col justify-center items-center">
41
+ <span className="text-sm font-medium text-gray-700 whitespace-nowrap">
42
+ Protected by RankRunners
43
+ </span>
44
+ <button
45
+ className="ml-2 text-blue-600 text-sm hover:underline cursor-pointer"
46
+ onClick={handleLearnMoreClick}
47
+ >
48
+ Learn more
49
+ </button>
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ {isDialogOpen && (
55
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-[100]">
56
+ <div className="bg-white p-6 rounded-lg shadow-xl max-w-sm mx-auto">
57
+ <h3 className="text-lg font-bold mb-4">
58
+ About RankRunners Captcha
59
+ </h3>
60
+ <p className="text-gray-700 mb-4">{CAPTCHA_EXPLANATION}</p>
61
+ <button
62
+ className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
63
+ onClick={handleCloseDialog}
64
+ >
65
+ Close
66
+ </button>
67
+ </div>
68
+ </div>
69
+ )}
70
+ </>
71
+ );
72
+ };
@@ -0,0 +1,15 @@
1
+ import styles from './styles.module.css'
2
+ import type { ComponentConfig } from '@measured/puck'
3
+ import getClassNameFactory from '../../utils/getClassNameFactory'
4
+
5
+ const getClassName = getClassNameFactory('Blank', styles)
6
+
7
+ export type BlankProps = {}
8
+
9
+ export const Blank: ComponentConfig<BlankProps> = {
10
+ fields: {},
11
+ defaultProps: {},
12
+ render: () => {
13
+ return <div className={getClassName()}></div>
14
+ },
15
+ }
@@ -0,0 +1,4 @@
1
+ .Blank {
2
+ background: hotpink;
3
+ padding: 16px;
4
+ }
@@ -0,0 +1,46 @@
1
+ import { Button as _Button } from '@measured/puck'
2
+ import type { ComponentConfig } from '@measured/puck'
3
+
4
+ export type ButtonProps = {
5
+ label: string
6
+ href: string
7
+ variant: 'primary' | 'secondary'
8
+ }
9
+
10
+ export const Button: ComponentConfig<ButtonProps> = {
11
+ label: 'Button',
12
+ fields: {
13
+ label: {
14
+ type: 'text',
15
+ placeholder: 'Lorem ipsum...',
16
+ contentEditable: true,
17
+ },
18
+ href: { type: 'text' },
19
+ variant: {
20
+ type: 'radio',
21
+ options: [
22
+ { label: 'primary', value: 'primary' },
23
+ { label: 'secondary', value: 'secondary' },
24
+ ],
25
+ },
26
+ },
27
+ defaultProps: {
28
+ label: 'Button',
29
+ href: '#',
30
+ variant: 'primary',
31
+ },
32
+ render: ({ href, variant, label, puck }) => {
33
+ return (
34
+ <div>
35
+ <_Button
36
+ href={puck.isEditing ? '#' : href}
37
+ variant={variant}
38
+ size="large"
39
+ tabIndex={puck.isEditing ? -1 : undefined}
40
+ >
41
+ {label}
42
+ </_Button>
43
+ </div>
44
+ )
45
+ },
46
+ }
@@ -0,0 +1,67 @@
1
+ // import { DynamicIcon, dynamicIconImports } from 'lucide-react/dynamic'
2
+ import { withLayout } from '../../components/Layout'
3
+ import styles from './styles.module.css'
4
+ import type { ComponentConfig } from '@measured/puck'
5
+ import type { WithLayout } from '../../components/Layout'
6
+ import getClassNameFactory from '../../utils/getClassNameFactory'
7
+
8
+ const getClassName = getClassNameFactory('Card', styles)
9
+
10
+ const iconOptions: Array<{ label: string; value: string }> = []
11
+ // Object.keys(dynamicIconImports).map((iconName) => ({
12
+ // label: iconName,
13
+ // value: iconName,
14
+ // }))
15
+
16
+ export type CardProps = WithLayout<{
17
+ title: string
18
+ description: string
19
+ icon?: string
20
+ mode: 'flat' | 'card'
21
+ }>
22
+
23
+ const CardInner: ComponentConfig<CardProps> = {
24
+ fields: {
25
+ title: {
26
+ type: 'text',
27
+ contentEditable: true,
28
+ },
29
+ description: {
30
+ type: 'textarea',
31
+ contentEditable: true,
32
+ },
33
+ icon: {
34
+ type: 'select',
35
+ options: iconOptions,
36
+ },
37
+ mode: {
38
+ type: 'radio',
39
+ options: [
40
+ { label: 'card', value: 'card' },
41
+ { label: 'flat', value: 'flat' },
42
+ ],
43
+ },
44
+ },
45
+ defaultProps: {
46
+ title: 'Title',
47
+ description: 'Description',
48
+ icon: 'Feather',
49
+ mode: 'flat',
50
+ },
51
+ render: ({ title, icon, description, mode }) => {
52
+ return (
53
+ <div className={getClassName({ [mode]: mode })}>
54
+ <div className={getClassName('inner')}>
55
+ {/* <div className={getClassName('icon')}>
56
+ <DynamicIcon name={icon as 'replace'} />
57
+ </div>*/}
58
+
59
+ <div className={getClassName('title')}>{title}</div>
60
+ <div className={getClassName('description')}>{description}</div>
61
+ </div>
62
+ </div>
63
+ )
64
+ },
65
+ }
66
+
67
+ export const Card = withLayout(CardInner)
@@ -0,0 +1,54 @@
1
+ .Card {
2
+ height: 100%;
3
+ }
4
+
5
+ .Card--card {
6
+ background: white;
7
+ box-shadow: rgba(140, 152, 164, 0.25) 0px 3px 6px 0px;
8
+ border-radius: 8px;
9
+ max-width: 100%;
10
+ }
11
+
12
+ .Card-inner {
13
+ align-items: center;
14
+ display: flex;
15
+ gap: 16px;
16
+ flex-direction: column;
17
+ }
18
+
19
+ .Card--card .Card-inner {
20
+ align-items: flex-start;
21
+ padding: 24px;
22
+ }
23
+
24
+ .Card-icon {
25
+ border-radius: 256px;
26
+ background: var(--puck-color-azure-09);
27
+ color: var(--puck-color-azure-06);
28
+ display: flex;
29
+ justify-content: center;
30
+ align-items: center;
31
+ width: 64px;
32
+ height: 64px;
33
+ }
34
+
35
+ .Card-title {
36
+ font-size: 22px;
37
+ text-align: center;
38
+ }
39
+
40
+ .Card--card .Card-title {
41
+ text-align: left;
42
+ }
43
+
44
+ .Card-description {
45
+ font-size: 16px;
46
+ line-height: 1.5;
47
+ color: var(--puck-color-grey-05);
48
+ text-align: center;
49
+ font-weight: 300;
50
+ }
51
+
52
+ .Card--card .Card-description {
53
+ text-align: left;
54
+ }
@@ -0,0 +1,36 @@
1
+ import { spacingRemOptions } from '../../options'
2
+ import type { ComponentConfig, Slot } from '@measured/puck'
3
+
4
+ export type ContainerProps = {
5
+ content?: Slot
6
+ margins: string
7
+ }
8
+
9
+ export const Container: ComponentConfig<ContainerProps> = {
10
+ label: 'Container',
11
+ fields: {
12
+ content: {
13
+ label: 'Content',
14
+ type: 'slot',
15
+ },
16
+ margins: {
17
+ label: 'Container Margins',
18
+ type: 'select',
19
+ options: spacingRemOptions,
20
+ },
21
+ },
22
+ defaultProps: {
23
+ margins: '78rem',
24
+ },
25
+ render: ({ margins, puck, content: Content }) => {
26
+ return (
27
+ <div
28
+ ref={puck.dragRef}
29
+ className={'mx-auto py-8 px-[2.5rem]'}
30
+ style={{ maxWidth: margins }}
31
+ >
32
+ {Content && <Content />}
33
+ </div>
34
+ )
35
+ },
36
+ }
@@ -0,0 +1,82 @@
1
+ import { Section } from '../../components/Section'
2
+ import { withLayout } from '../../components/Layout'
3
+ import styles from './styles.module.css'
4
+ import type { ComponentConfig, Slot } from '@measured/puck'
5
+ import type { WithLayout } from '../../components/Layout'
6
+ import getClassNameFactory from '../../utils/getClassNameFactory'
7
+
8
+ const getClassName = getClassNameFactory('Flex', styles)
9
+
10
+ export type FlexProps = WithLayout<{
11
+ justifyContent: 'start' | 'center' | 'end'
12
+ direction: 'row' | 'column'
13
+ gap: number
14
+ wrap: 'wrap' | 'nowrap'
15
+ items: Slot
16
+ }>
17
+
18
+ const FlexInternal: ComponentConfig<FlexProps> = {
19
+ fields: {
20
+ direction: {
21
+ label: 'Direction',
22
+ type: 'radio',
23
+ options: [
24
+ { label: 'Row', value: 'row' },
25
+ { label: 'Column', value: 'column' },
26
+ ],
27
+ },
28
+ justifyContent: {
29
+ label: 'Justify Content',
30
+ type: 'radio',
31
+ options: [
32
+ { label: 'Start', value: 'start' },
33
+ { label: 'Center', value: 'center' },
34
+ { label: 'End', value: 'end' },
35
+ ],
36
+ },
37
+ gap: {
38
+ label: 'Gap',
39
+ type: 'number',
40
+ min: 0,
41
+ },
42
+ wrap: {
43
+ label: 'Wrap',
44
+ type: 'radio',
45
+ options: [
46
+ { label: 'true', value: 'wrap' },
47
+ { label: 'false', value: 'nowrap' },
48
+ ],
49
+ },
50
+ items: {
51
+ type: 'slot',
52
+ },
53
+ },
54
+ defaultProps: {
55
+ justifyContent: 'start',
56
+ direction: 'row',
57
+ gap: 24,
58
+ wrap: 'wrap',
59
+ layout: {
60
+ grow: true,
61
+ },
62
+ items: [],
63
+ },
64
+ render: ({ justifyContent, direction, gap, wrap, items: Items }) => {
65
+ return (
66
+ <Section style={{ height: '100%' }}>
67
+ <Items
68
+ className={getClassName()}
69
+ style={{
70
+ justifyContent,
71
+ flexDirection: direction,
72
+ gap,
73
+ flexWrap: wrap,
74
+ }}
75
+ disallow={['Hero', 'Stats']}
76
+ />
77
+ </Section>
78
+ )
79
+ },
80
+ }
81
+
82
+ export const Flex = withLayout(FlexInternal)
@@ -0,0 +1,9 @@
1
+ .Flex {
2
+ display: flex;
3
+ flex-wrap: wrap;
4
+ height: 100%;
5
+ }
6
+
7
+ .Flex-item {
8
+ flex: 1;
9
+ }
@@ -0,0 +1,53 @@
1
+ import { Section } from '../../components/Section'
2
+ import { withLayout } from '../../components/Layout'
3
+ import styles from './styles.module.css'
4
+ import type { ComponentConfig, Slot } from '@measured/puck'
5
+ import getClassNameFactory from '../../utils/getClassNameFactory'
6
+
7
+ const getClassName = getClassNameFactory('Grid', styles)
8
+
9
+ export type GridProps = {
10
+ numColumns: number
11
+ gap: number
12
+ items: Slot
13
+ }
14
+
15
+ export const GridInternal: ComponentConfig<GridProps> = {
16
+ fields: {
17
+ numColumns: {
18
+ type: 'number',
19
+ label: 'Number of columns',
20
+ min: 1,
21
+ max: 12,
22
+ },
23
+ gap: {
24
+ label: 'Gap',
25
+ type: 'number',
26
+ min: 0,
27
+ },
28
+ items: {
29
+ type: 'slot',
30
+ },
31
+ },
32
+ defaultProps: {
33
+ numColumns: 4,
34
+ gap: 24,
35
+ items: [],
36
+ },
37
+ render: ({ gap, numColumns, items: Items }) => {
38
+ return (
39
+ <Section>
40
+ <Items
41
+ disallow={['Hero', 'Stats']}
42
+ className={getClassName()}
43
+ style={{
44
+ gap,
45
+ gridTemplateColumns: `repeat(${numColumns}, 1fr)`,
46
+ }}
47
+ />
48
+ </Section>
49
+ )
50
+ },
51
+ }
52
+
53
+ export const Grid = withLayout(GridInternal)
@@ -0,0 +1,11 @@
1
+ .Grid {
2
+ display: flex;
3
+ flex-direction: column;
4
+ width: auto;
5
+ }
6
+
7
+ @media (min-width: 768px) {
8
+ .Grid {
9
+ display: grid;
10
+ }
11
+ }
@@ -0,0 +1,69 @@
1
+ import { Section } from '../../components/Section'
2
+ import { withLayout } from '../../components/Layout'
3
+ import type { ComponentConfig } from '@measured/puck'
4
+ import type { WithLayout } from '../../components/Layout'
5
+
6
+ export type HeadingProps = WithLayout<{
7
+ align: 'left' | 'center' | 'right'
8
+ text?: string
9
+ level: '1' | '2' | '3'
10
+ }>
11
+
12
+ const levelOptions = [
13
+ { label: '1', value: '1' },
14
+ { label: '2', value: '2' },
15
+ { label: '3', value: '3' },
16
+ ]
17
+
18
+ const HeadingInternal: ComponentConfig<HeadingProps> = {
19
+ fields: {
20
+ text: {
21
+ label: 'Text',
22
+ type: 'textarea',
23
+ contentEditable: true,
24
+ },
25
+ level: {
26
+ label: 'Level',
27
+ type: 'select',
28
+ options: levelOptions,
29
+ },
30
+ align: {
31
+ label: 'Text Alignment',
32
+ type: 'radio',
33
+ options: [
34
+ { label: 'Left', value: 'left' },
35
+ { label: 'Center', value: 'center' },
36
+ { label: 'Right', value: 'right' },
37
+ ],
38
+ },
39
+ },
40
+ defaultProps: {
41
+ align: 'left',
42
+ level: '1',
43
+ text: 'Heading',
44
+ layout: {
45
+ padding: '8px',
46
+ },
47
+ },
48
+ render: ({ align, text, level }) => {
49
+ const Tag: any = `h${level}`
50
+ const className =
51
+ level === '1'
52
+ ? 'text-[40px] uppercase text-[#232549]'
53
+ : level === '2'
54
+ ? 'text-[28px] font-[700] leading-[32px] text-[#232549]'
55
+ : 'text-[26px] font-semibold text-[#232549]'
56
+
57
+ return (
58
+ <Section>
59
+ <Tag className={className}>
60
+ <span style={{ display: 'block', textAlign: align, width: '100%' }}>
61
+ {text}
62
+ </span>
63
+ </Tag>
64
+ </Section>
65
+ )
66
+ },
67
+ }
68
+
69
+ export const Heading = withLayout(HeadingInternal)