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
@@ -0,0 +1,45 @@
1
+ import { spacingOptions } from '../../options'
2
+ import styles from './styles.module.css'
3
+ import type { ComponentConfig } from '@measured/puck'
4
+ import getClassNameFactory from '../../utils/getClassNameFactory'
5
+
6
+ const getClassName = getClassNameFactory('Space', styles)
7
+
8
+ export type SpaceProps = {
9
+ direction?: '' | 'vertical' | 'horizontal'
10
+ size: string
11
+ }
12
+
13
+ export const Space: ComponentConfig<SpaceProps> = {
14
+ label: 'Space',
15
+ fields: {
16
+ size: {
17
+ label: 'Size',
18
+ type: 'select',
19
+ options: spacingOptions,
20
+ },
21
+ direction: {
22
+ label: 'Direction',
23
+ type: 'radio',
24
+ options: [
25
+ { value: 'vertical', label: 'Vertical' },
26
+ { value: 'horizontal', label: 'Horizontal' },
27
+ { value: '', label: 'Both' },
28
+ ],
29
+ },
30
+ },
31
+ defaultProps: {
32
+ direction: '',
33
+ size: '24px',
34
+ },
35
+ inline: true,
36
+ render: ({ direction, size, puck }) => {
37
+ return (
38
+ <div
39
+ ref={puck.dragRef}
40
+ className={getClassName(direction ? { [direction]: direction } : {})}
41
+ style={{ '--size': size } as any}
42
+ />
43
+ )
44
+ },
45
+ }
@@ -0,0 +1,14 @@
1
+ .Space {
2
+ display: block;
3
+ height: var(--size);
4
+ width: var(--size);
5
+ }
6
+
7
+ .Space--vertical {
8
+ width: 100%;
9
+ }
10
+
11
+ .Space--horizontal {
12
+ width: var(--size);
13
+ height: 100%;
14
+ }
@@ -0,0 +1,58 @@
1
+ import { Section } from '../../components/Section'
2
+ import styles from './styles.module.css'
3
+ import type { ComponentConfig } from '@measured/puck'
4
+ import getClassNameFactory from '../../utils/getClassNameFactory'
5
+
6
+ const getClassName = getClassNameFactory('Stats', styles)
7
+
8
+ export type StatsProps = {
9
+ items: Array<{
10
+ title: string
11
+ description: string
12
+ }>
13
+ }
14
+
15
+ export const Stats: ComponentConfig<StatsProps> = {
16
+ fields: {
17
+ items: {
18
+ type: 'array',
19
+ getItemSummary: (item, i) => item.title || `Feature #${i}`,
20
+ defaultItemProps: {
21
+ title: 'Stat',
22
+ description: '1,000',
23
+ },
24
+ arrayFields: {
25
+ title: {
26
+ type: 'text',
27
+ contentEditable: true,
28
+ },
29
+ description: {
30
+ type: 'text',
31
+ contentEditable: true,
32
+ },
33
+ },
34
+ },
35
+ },
36
+ defaultProps: {
37
+ items: [
38
+ {
39
+ title: 'Stat',
40
+ description: '1,000',
41
+ },
42
+ ],
43
+ },
44
+ render: ({ items }) => {
45
+ return (
46
+ <Section className={getClassName()} maxWidth={'916px'}>
47
+ <div className={getClassName('items')}>
48
+ {items.map((item, i) => (
49
+ <div key={i} className={getClassName('item')}>
50
+ <div className={getClassName('label')}>{item.title}</div>
51
+ <div className={getClassName('value')}>{item.description}</div>
52
+ </div>
53
+ ))}
54
+ </div>
55
+ </Section>
56
+ )
57
+ },
58
+ }
@@ -0,0 +1,64 @@
1
+ .Stats-items {
2
+ background-image: linear-gradient(
3
+ 120deg,
4
+ var(--puck-color-azure-03) 0%,
5
+ var(--puck-color-azure-05) 100%
6
+ );
7
+ border-radius: 24px;
8
+ display: grid;
9
+ grid-template-columns: 1fr;
10
+ grid-gap: 72px;
11
+ align-items: center;
12
+ justify-content: space-between;
13
+ margin: 0 auto;
14
+ max-width: 768px;
15
+ padding: 64px 16px;
16
+ }
17
+
18
+ @media (min-width: 768px) {
19
+ .Stats-items {
20
+ padding: 64px 24px;
21
+ }
22
+ }
23
+
24
+ @media (min-width: 1024px) {
25
+ .Stats-items {
26
+ grid-template-columns: 1fr 1fr;
27
+ padding: 128px 24px;
28
+ max-width: 100%;
29
+ }
30
+ }
31
+
32
+ .Stats-item {
33
+ display: flex;
34
+ flex-direction: column;
35
+ align-items: center;
36
+ color: white;
37
+ gap: 8px;
38
+ width: 100%;
39
+ text-align: center;
40
+ }
41
+
42
+ .Stats-icon {
43
+ border-radius: 256px;
44
+ background: var(--puck-color-azure-09);
45
+ color: var(--puck-color-azure-06);
46
+ display: flex;
47
+ justify-content: center;
48
+ align-items: center;
49
+ width: 64px;
50
+ height: 64px;
51
+ }
52
+
53
+ .Stats-label {
54
+ font-size: 22px;
55
+ text-align: center;
56
+ font-weight: 600;
57
+ opacity: 0.8;
58
+ }
59
+
60
+ .Stats-value {
61
+ font-size: 72px;
62
+ line-height: 1;
63
+ font-weight: 700;
64
+ }
@@ -0,0 +1,75 @@
1
+ import { ALargeSmall, AlignLeft } from 'lucide-react'
2
+
3
+ import { Section } from '../../components/Section'
4
+ import { withLayout } from '../../components/Layout'
5
+ import type { ComponentConfig } from '@measured/puck'
6
+ import type { WithLayout } from '../../components/Layout'
7
+
8
+ export type TextProps = WithLayout<{
9
+ align: 'left' | 'center' | 'right'
10
+ text?: string
11
+ padding?: string
12
+ size?: 's' | 'm'
13
+ maxWidth?: string
14
+ }>
15
+
16
+ const TextInner: ComponentConfig<TextProps> = {
17
+ fields: {
18
+ text: {
19
+ label: 'Text',
20
+ type: 'textarea',
21
+ contentEditable: true,
22
+ },
23
+ size: {
24
+ label: 'Size',
25
+ type: 'select',
26
+ labelIcon: <ALargeSmall size={16} />,
27
+ options: [
28
+ { label: 'S', value: 's' },
29
+ { label: 'M', value: 'm' },
30
+ ],
31
+ },
32
+ align: {
33
+ label: 'Text Alignment',
34
+ type: 'radio',
35
+ labelIcon: <AlignLeft size={16} />,
36
+ options: [
37
+ { label: 'Left', value: 'left' },
38
+ { label: 'Center', value: 'center' },
39
+ { label: 'Right', value: 'right' },
40
+ ],
41
+ },
42
+ maxWidth: { label: 'Maximum Width', type: 'text' },
43
+ },
44
+ defaultProps: {
45
+ align: 'left',
46
+ text: 'Text',
47
+ size: 'm',
48
+ },
49
+ render: ({ align, text, size, maxWidth }) => {
50
+ return (
51
+ <Section maxWidth={maxWidth}>
52
+ <span
53
+ style={{
54
+ display: 'flex',
55
+ textAlign: align,
56
+ width: '100%',
57
+ fontSize: size === 'm' ? '20px' : '16px',
58
+ fontWeight: 300,
59
+ maxWidth,
60
+ justifyContent:
61
+ align === 'center'
62
+ ? 'center'
63
+ : align === 'right'
64
+ ? 'flex-end'
65
+ : 'flex-start',
66
+ }}
67
+ >
68
+ {text}
69
+ </span>
70
+ </Section>
71
+ )
72
+ },
73
+ }
74
+
75
+ export const Text = withLayout(TextInner)
@@ -0,0 +1,4 @@
1
+ .Text {
2
+ line-height: 1.5;
3
+ padding: 0px;
4
+ }
@@ -0,0 +1,160 @@
1
+ import { forwardRef } from 'react'
2
+ import { spacingOptions } from '../../options'
3
+ import styles from './styles.module.css'
4
+ import type {
5
+ ComponentConfig,
6
+ DefaultComponentProps,
7
+ ObjectField,
8
+ } from '@measured/puck'
9
+ import type { CSSProperties, ReactNode } from 'react'
10
+ import getClassNameFactory from '../../utils/getClassNameFactory'
11
+
12
+ const getClassName = getClassNameFactory('Layout', styles)
13
+
14
+ type LayoutFieldProps = {
15
+ padding?: string
16
+ spanCol?: number
17
+ spanRow?: number
18
+ grow?: boolean
19
+ }
20
+
21
+ // eslint-disable-next-line @typescript-eslint/naming-convention
22
+ export type WithLayout<Props extends DefaultComponentProps> = Props & {
23
+ layout?: LayoutFieldProps
24
+ }
25
+
26
+ type LayoutProps = WithLayout<{
27
+ children: ReactNode
28
+ className?: string
29
+ style?: CSSProperties
30
+ }>
31
+
32
+ export const layoutField: ObjectField<LayoutFieldProps> = {
33
+ type: 'object',
34
+ objectFields: {
35
+ spanCol: {
36
+ label: 'Grid Columns',
37
+ type: 'number',
38
+ min: 1,
39
+ max: 12,
40
+ },
41
+ spanRow: {
42
+ label: 'Grid Rows',
43
+ type: 'number',
44
+ min: 1,
45
+ max: 12,
46
+ },
47
+ grow: {
48
+ label: 'Flex Grow',
49
+ type: 'radio',
50
+ options: [
51
+ { label: 'true', value: true },
52
+ { label: 'false', value: false },
53
+ ],
54
+ },
55
+ padding: {
56
+ type: 'select',
57
+ label: 'Vertical Padding',
58
+ options: [{ label: '0px', value: '0px' }, ...spacingOptions],
59
+ },
60
+ },
61
+ }
62
+
63
+ const Layout = forwardRef<HTMLDivElement, LayoutProps>(
64
+ ({ children, className, layout, style }, ref) => {
65
+ return (
66
+ <div
67
+ className={className}
68
+ style={{
69
+ gridColumn: layout?.spanCol
70
+ ? `span ${Math.max(Math.min(layout.spanCol, 12), 1)}`
71
+ : undefined,
72
+ gridRow: layout?.spanRow
73
+ ? `span ${Math.max(Math.min(layout.spanRow, 12), 1)}`
74
+ : undefined,
75
+ paddingTop: layout?.padding,
76
+ paddingBottom: layout?.padding,
77
+ flex: layout?.grow ? '1 1 0' : undefined,
78
+ ...style,
79
+ }}
80
+ ref={ref}
81
+ >
82
+ {children}
83
+ </div>
84
+ )
85
+ },
86
+ )
87
+
88
+ Layout.displayName = 'Layout'
89
+
90
+ export { Layout }
91
+
92
+ export function withLayout<
93
+ // eslint-disable-next-line @typescript-eslint/naming-convention
94
+ ThisComponentConfig extends ComponentConfig<any> = ComponentConfig,
95
+ >(componentConfig: ThisComponentConfig): ThisComponentConfig {
96
+ return {
97
+ ...componentConfig,
98
+ fields: {
99
+ ...componentConfig.fields,
100
+ layout: layoutField,
101
+ },
102
+ defaultProps: {
103
+ ...componentConfig.defaultProps,
104
+ layout: {
105
+ spanCol: 1,
106
+ spanRow: 1,
107
+ padding: '0px',
108
+ grow: false,
109
+ ...componentConfig.defaultProps?.layout,
110
+ },
111
+ },
112
+ resolveFields: (_, params) => {
113
+ if (params.parent?.type === 'Grid') {
114
+ return {
115
+ ...componentConfig.fields,
116
+ layout: {
117
+ ...layoutField,
118
+ objectFields: {
119
+ spanCol: layoutField.objectFields.spanCol,
120
+ spanRow: layoutField.objectFields.spanRow,
121
+ padding: layoutField.objectFields.padding,
122
+ },
123
+ },
124
+ }
125
+ }
126
+ if (params.parent?.type === 'Flex') {
127
+ return {
128
+ ...componentConfig.fields,
129
+ layout: {
130
+ ...layoutField,
131
+ objectFields: {
132
+ grow: layoutField.objectFields.grow,
133
+ padding: layoutField.objectFields.padding,
134
+ },
135
+ },
136
+ }
137
+ }
138
+
139
+ return {
140
+ ...componentConfig.fields,
141
+ layout: {
142
+ ...layoutField,
143
+ objectFields: {
144
+ padding: layoutField.objectFields.padding,
145
+ },
146
+ },
147
+ }
148
+ },
149
+ inline: true,
150
+ render: (props) => (
151
+ <Layout
152
+ className={getClassName()}
153
+ layout={props.layout as LayoutFieldProps}
154
+ ref={props.puck.dragRef}
155
+ >
156
+ {componentConfig.render(props)}
157
+ </Layout>
158
+ ),
159
+ }
160
+ }
@@ -0,0 +1,3 @@
1
+ .Layout {
2
+ display: block;
3
+ }
@@ -0,0 +1,31 @@
1
+ import { forwardRef } from 'react'
2
+ import styles from './styles.module.css'
3
+ import type { CSSProperties, ReactNode } from 'react'
4
+ import getClassNameFactory from '../../utils/getClassNameFactory'
5
+
6
+ const getClassName = getClassNameFactory('Section', styles)
7
+
8
+ export type SectionProps = {
9
+ className?: string
10
+ children: ReactNode
11
+ maxWidth?: string
12
+ style?: CSSProperties
13
+ }
14
+
15
+ export const Section = forwardRef<HTMLDivElement, SectionProps>(
16
+ ({ children, className, maxWidth = '1280px', style = {} }, ref) => {
17
+ return (
18
+ <div
19
+ className={`${getClassName()}${className ? ` ${className}` : ''}`}
20
+ style={{
21
+ ...style,
22
+ }}
23
+ ref={ref}
24
+ >
25
+ <div className={getClassName('inner')} style={{ maxWidth }}>
26
+ {children}
27
+ </div>
28
+ </div>
29
+ )
30
+ },
31
+ )
@@ -0,0 +1,23 @@
1
+ .Section:not(.Section .Section) {
2
+ padding-inline-start: 16px;
3
+ padding-inline-end: 16px;
4
+ }
5
+
6
+ @media (min-width: 768px) {
7
+ .Section:not(.Section .Section) {
8
+ padding-inline-start: 24px;
9
+ padding-inline-end: 24px;
10
+ }
11
+ }
12
+
13
+ .Section-inner {
14
+ margin-inline-start: auto;
15
+ margin-inline-end: auto;
16
+ height: 100%;
17
+ width: 100%;
18
+ }
19
+
20
+ .Section .Section .Section-inner {
21
+ margin-inline-start: 0;
22
+ margin-inline-end: 0;
23
+ }
@@ -0,0 +1,63 @@
1
+ import { Button } from "./blocks/Button";
2
+ import { Card } from "./blocks/Card";
3
+ import { Grid } from "./blocks/Grid";
4
+ import { Hero } from "./blocks/Hero";
5
+ import { Heading } from "./blocks/Heading";
6
+ import { Flex } from "./blocks/Flex";
7
+ import { Logos } from "./blocks/Logos";
8
+ import { Stats } from "./blocks/Stats";
9
+ import { Text } from "./blocks/Text";
10
+ import { Space } from "./blocks/Space";
11
+
12
+ import { Paragraph } from "./blocks/Paragraph";
13
+ import { Container } from "./blocks/Container";
14
+ import type { CategoriesType, CMSUserConfig, ComponentsType } from "./types";
15
+ import type { RootConfig } from "@measured/puck";
16
+
17
+ export * from "./types";
18
+
19
+ export const defaultCategories: CategoriesType<
20
+ ["layout", "typography", "interactive", "other"]
21
+ > = {
22
+ layout: {
23
+ components: ["Grid", "Flex", "Space", "Container"],
24
+ },
25
+ typography: {
26
+ components: ["Heading", "Text", "Paragraph"],
27
+ },
28
+ interactive: {
29
+ title: "Actions",
30
+ components: ["Button"],
31
+ },
32
+ other: {
33
+ title: "Other",
34
+ components: ["Card", "Hero", "Logos", "Stats"],
35
+ },
36
+ };
37
+
38
+ export const defaultComponents: ComponentsType = {
39
+ Button,
40
+ Card,
41
+ Grid,
42
+ Container,
43
+ Hero,
44
+ Heading,
45
+ Flex,
46
+ Logos,
47
+ Stats,
48
+ Text,
49
+ Paragraph,
50
+ Space,
51
+ };
52
+
53
+ // We avoid the name config as next gets confused
54
+ export const createCMSConfig = <K, C extends string[]>(
55
+ root: RootConfig,
56
+ categories: CategoriesType<C>,
57
+ components: ComponentsType
58
+ ): CMSUserConfig<K, C> =>
59
+ ({
60
+ root,
61
+ categories: { ...defaultCategories, ...categories },
62
+ components: { ...defaultComponents, ...components },
63
+ } as unknown as CMSUserConfig<K, C>);
@@ -0,0 +1,37 @@
1
+ export const spacingOptions = [
2
+ { label: '8px', value: '8px' },
3
+ { label: '16px', value: '16px' },
4
+ { label: '24px', value: '24px' },
5
+ { label: '32px', value: '32px' },
6
+ { label: '40px', value: '40px' },
7
+ { label: '48px', value: '48px' },
8
+ { label: '56px', value: '56px' },
9
+ { label: '64px', value: '64px' },
10
+ { label: '72px', value: '72px' },
11
+ { label: '80px', value: '80px' },
12
+ { label: '88px', value: '88px' },
13
+ { label: '96px', value: '96px' },
14
+ { label: '104px', value: '104px' },
15
+ { label: '112px', value: '112px' },
16
+ { label: '120px', value: '120px' },
17
+ { label: '128px', value: '128px' },
18
+ { label: '136px', value: '136px' },
19
+ { label: '144px', value: '144px' },
20
+ { label: '152px', value: '152px' },
21
+ { label: '160px', value: '160px' },
22
+ ]
23
+
24
+ export const spacingRemOptions = [
25
+ { label: '78rem', value: '78rem' },
26
+ { label: '64rem', value: '64rem' },
27
+ { label: '48rem', value: '48rem' },
28
+ { label: '42rem', value: '42rem' },
29
+ { label: '36rem', value: '36rem' },
30
+ { label: '32rem', value: '32rem' },
31
+ { label: '28rem', value: '28rem' },
32
+ { label: '24rem', value: '24rem' },
33
+ { label: '20rem', value: '20rem' },
34
+ { label: '16rem', value: '16rem' },
35
+ { label: '12rem', value: '12rem' },
36
+ { label: '8rem', value: '8rem' },
37
+ ]
@@ -0,0 +1,60 @@
1
+ import type { ParagraphProps } from "./blocks/Paragraph";
2
+ import type { ButtonProps } from "./blocks/Button";
3
+ import type { CardProps } from "./blocks/Card";
4
+ import type { GridProps } from "./blocks/Grid";
5
+ import type { HeroProps } from "./blocks/Hero";
6
+ import type { HeadingProps } from "./blocks/Heading";
7
+ import type { FlexProps } from "./blocks/Flex";
8
+ import type { LogosProps } from "./blocks/Logos";
9
+ import type { StatsProps } from "./blocks/Stats";
10
+ import type { TextProps } from "./blocks/Text";
11
+ import type { SpaceProps } from "./blocks/Space";
12
+ import type {
13
+ Config,
14
+ Data,
15
+ DefaultComponentProps,
16
+ RootConfig,
17
+ } from "@measured/puck";
18
+ import type { ContainerProps } from "./blocks/Container";
19
+
20
+ export type CategoryType = {
21
+ title?: string;
22
+ components: string[];
23
+ };
24
+
25
+ export type CategoriesType<C extends string[]> = Record<
26
+ C[number],
27
+ CategoryType
28
+ >;
29
+
30
+ export type ComponentsType = Record<string, DefaultComponentProps>;
31
+
32
+ export type DefaultComponents = {
33
+ Button: ButtonProps;
34
+ Card: CardProps;
35
+ Grid: GridProps;
36
+ Container: ContainerProps;
37
+ Hero: HeroProps;
38
+ Heading: HeadingProps;
39
+ Flex: FlexProps;
40
+ Logos: LogosProps;
41
+ Stats: StatsProps;
42
+ Text: TextProps;
43
+ Paragraph: ParagraphProps;
44
+ Space: SpaceProps;
45
+ };
46
+
47
+ export type CMSUserConfig<Components, ExtraCategories> = Config<{
48
+ components: DefaultComponents & Components;
49
+ root: RootConfig;
50
+ categories: ["layout", "typography", "interactive", "other"] &
51
+ ExtraCategories;
52
+ fields: {
53
+ userField: {
54
+ type: "userField";
55
+ option: boolean;
56
+ };
57
+ };
58
+ }>;
59
+
60
+ export type CMSUserData<Components> = Data<DefaultComponents & Components>;
@@ -0,0 +1,2 @@
1
+ export const generateId = (type?: string | number) =>
2
+ type ? `${type}-${crypto.randomUUID()}` : crypto.randomUUID()