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.
- package/CLAUDE.md +106 -0
- package/README.md +15 -0
- package/package.json +39 -0
- package/src/CaptchaBadge.tsx +72 -0
- package/src/editor/blocks/Blank/index.tsx +15 -0
- package/src/editor/blocks/Blank/styles.module.css +4 -0
- package/src/editor/blocks/Button/index.tsx +46 -0
- package/src/editor/blocks/Card/index.tsx +67 -0
- package/src/editor/blocks/Card/styles.module.css +54 -0
- package/src/editor/blocks/Container/index.tsx +36 -0
- package/src/editor/blocks/Flex/index.tsx +82 -0
- package/src/editor/blocks/Flex/styles.module.css +9 -0
- package/src/editor/blocks/Grid/index.tsx +53 -0
- package/src/editor/blocks/Grid/styles.module.css +11 -0
- package/src/editor/blocks/Heading/index.tsx +69 -0
- package/src/editor/blocks/Hero/Hero.tsx +107 -0
- package/src/editor/blocks/Hero/client.tsx +204 -0
- package/src/editor/blocks/Hero/index.tsx +2 -0
- package/src/editor/blocks/Hero/quotes.ts +46 -0
- package/src/editor/blocks/Hero/server.tsx +7 -0
- package/src/editor/blocks/Hero/styles.module.css +116 -0
- package/src/editor/blocks/Logos/index.tsx +77 -0
- package/src/editor/blocks/Logos/styles.module.css +13 -0
- package/src/editor/blocks/Paragraph/index.tsx +95 -0
- package/src/editor/blocks/Paragraph/styles.module.css +4 -0
- package/src/editor/blocks/Space/index.tsx +45 -0
- package/src/editor/blocks/Space/styles.module.css +14 -0
- package/src/editor/blocks/Stats/index.tsx +58 -0
- package/src/editor/blocks/Stats/styles.module.css +64 -0
- package/src/editor/blocks/Text/index.tsx +75 -0
- package/src/editor/blocks/Text/styles.module.css +4 -0
- package/src/editor/components/Layout/index.tsx +160 -0
- package/src/editor/components/Layout/styles.module.css +3 -0
- package/src/editor/components/Section/index.tsx +31 -0
- package/src/editor/components/Section/styles.module.css +23 -0
- package/src/editor/index.tsx +63 -0
- package/src/editor/options.ts +37 -0
- package/src/editor/types.ts +60 -0
- package/src/editor/utils/generateId.ts +2 -0
- package/src/editor/utils/getClassNameFactory.ts +63 -0
- package/src/index.css +1 -0
- package/src/index.ts +6 -0
- package/src/libs/redirect.ts +10 -0
- package/src/sitemap/handleRobotsTxt.ts +19 -0
- package/src/sitemap/handleSitemap.ts +25 -0
- package/src/sitemap/index.ts +2 -0
- package/src/styles.d.ts +4 -0
- package/tsconfig.json +42 -0
- 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,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,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,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)
|