@asheeui/puck 0.0.0-stage → 2.3.0

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 (154) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +10 -0
  3. package/README.md +126 -3
  4. package/dist/index.d.ts +22 -0
  5. package/dist/index.d.ts.map +1 -0
  6. package/dist/index.js +23 -0
  7. package/dist/index.js.map +1 -0
  8. package/dist/index.native.d.ts +22 -0
  9. package/dist/index.native.d.ts.map +1 -0
  10. package/dist/index.native.js +22 -0
  11. package/dist/index.native.js.map +1 -0
  12. package/dist/native/blocks/content.d.ts +19 -0
  13. package/dist/native/blocks/content.d.ts.map +1 -0
  14. package/dist/native/blocks/content.js +29 -0
  15. package/dist/native/blocks/content.js.map +1 -0
  16. package/dist/native/blocks/layout.d.ts +42 -0
  17. package/dist/native/blocks/layout.d.ts.map +1 -0
  18. package/dist/native/blocks/layout.js +33 -0
  19. package/dist/native/blocks/layout.js.map +1 -0
  20. package/dist/native/blocks/navigation.d.ts +19 -0
  21. package/dist/native/blocks/navigation.d.ts.map +1 -0
  22. package/dist/native/blocks/navigation.js +38 -0
  23. package/dist/native/blocks/navigation.js.map +1 -0
  24. package/dist/native/blocks/sections.d.ts +30 -0
  25. package/dist/native/blocks/sections.d.ts.map +1 -0
  26. package/dist/native/blocks/sections.js +60 -0
  27. package/dist/native/blocks/sections.js.map +1 -0
  28. package/dist/native/blocks/utility.d.ts +13 -0
  29. package/dist/native/blocks/utility.d.ts.map +1 -0
  30. package/dist/native/blocks/utility.js +18 -0
  31. package/dist/native/blocks/utility.js.map +1 -0
  32. package/dist/native/config.d.ts +19 -0
  33. package/dist/native/config.d.ts.map +1 -0
  34. package/dist/native/config.js +45 -0
  35. package/dist/native/config.js.map +1 -0
  36. package/dist/native/index.d.ts +18 -0
  37. package/dist/native/index.d.ts.map +1 -0
  38. package/dist/native/index.js +18 -0
  39. package/dist/native/index.js.map +1 -0
  40. package/dist/native/render.d.ts +39 -0
  41. package/dist/native/render.d.ts.map +1 -0
  42. package/dist/native/render.js +71 -0
  43. package/dist/native/render.js.map +1 -0
  44. package/dist/native/root.d.ts +21 -0
  45. package/dist/native/root.d.ts.map +1 -0
  46. package/dist/native/root.js +28 -0
  47. package/dist/native/root.js.map +1 -0
  48. package/dist/shared/adapters.d.ts +52 -0
  49. package/dist/shared/adapters.d.ts.map +1 -0
  50. package/dist/shared/adapters.js +57 -0
  51. package/dist/shared/adapters.js.map +1 -0
  52. package/dist/shared/blocks/content.d.ts +42 -0
  53. package/dist/shared/blocks/content.d.ts.map +1 -0
  54. package/dist/shared/blocks/content.js +44 -0
  55. package/dist/shared/blocks/content.js.map +1 -0
  56. package/dist/shared/blocks/index.d.ts +42 -0
  57. package/dist/shared/blocks/index.d.ts.map +1 -0
  58. package/dist/shared/blocks/index.js +81 -0
  59. package/dist/shared/blocks/index.js.map +1 -0
  60. package/dist/shared/blocks/layout.d.ts +52 -0
  61. package/dist/shared/blocks/layout.d.ts.map +1 -0
  62. package/dist/shared/blocks/layout.js +51 -0
  63. package/dist/shared/blocks/layout.js.map +1 -0
  64. package/dist/shared/blocks/navigation.d.ts +74 -0
  65. package/dist/shared/blocks/navigation.d.ts.map +1 -0
  66. package/dist/shared/blocks/navigation.js +86 -0
  67. package/dist/shared/blocks/navigation.js.map +1 -0
  68. package/dist/shared/blocks/sections.d.ts +142 -0
  69. package/dist/shared/blocks/sections.d.ts.map +1 -0
  70. package/dist/shared/blocks/sections.js +166 -0
  71. package/dist/shared/blocks/sections.js.map +1 -0
  72. package/dist/shared/blocks/utility.d.ts +31 -0
  73. package/dist/shared/blocks/utility.d.ts.map +1 -0
  74. package/dist/shared/blocks/utility.js +36 -0
  75. package/dist/shared/blocks/utility.js.map +1 -0
  76. package/dist/shared/fields.d.ts +103 -0
  77. package/dist/shared/fields.d.ts.map +1 -0
  78. package/dist/shared/fields.js +162 -0
  79. package/dist/shared/fields.js.map +1 -0
  80. package/dist/shared/index.d.ts +14 -0
  81. package/dist/shared/index.d.ts.map +1 -0
  82. package/dist/shared/index.js +14 -0
  83. package/dist/shared/index.js.map +1 -0
  84. package/dist/shared/types.d.ts +235 -0
  85. package/dist/shared/types.d.ts.map +1 -0
  86. package/dist/shared/types.js +20 -0
  87. package/dist/shared/types.js.map +1 -0
  88. package/dist/web/blocks/content.d.ts +10 -0
  89. package/dist/web/blocks/content.d.ts.map +1 -0
  90. package/dist/web/blocks/content.js +28 -0
  91. package/dist/web/blocks/content.js.map +1 -0
  92. package/dist/web/blocks/layout.d.ts +27 -0
  93. package/dist/web/blocks/layout.d.ts.map +1 -0
  94. package/dist/web/blocks/layout.js +28 -0
  95. package/dist/web/blocks/layout.js.map +1 -0
  96. package/dist/web/blocks/navigation.d.ts +10 -0
  97. package/dist/web/blocks/navigation.d.ts.map +1 -0
  98. package/dist/web/blocks/navigation.js +38 -0
  99. package/dist/web/blocks/navigation.js.map +1 -0
  100. package/dist/web/blocks/sections.d.ts +22 -0
  101. package/dist/web/blocks/sections.d.ts.map +1 -0
  102. package/dist/web/blocks/sections.js +60 -0
  103. package/dist/web/blocks/sections.js.map +1 -0
  104. package/dist/web/blocks/utility.d.ts +6 -0
  105. package/dist/web/blocks/utility.d.ts.map +1 -0
  106. package/dist/web/blocks/utility.js +19 -0
  107. package/dist/web/blocks/utility.js.map +1 -0
  108. package/dist/web/config.d.ts +65 -0
  109. package/dist/web/config.d.ts.map +1 -0
  110. package/dist/web/config.js +35 -0
  111. package/dist/web/config.js.map +1 -0
  112. package/dist/web/index.d.ts +19 -0
  113. package/dist/web/index.d.ts.map +1 -0
  114. package/dist/web/index.js +20 -0
  115. package/dist/web/index.js.map +1 -0
  116. package/dist/web/page.d.ts +24 -0
  117. package/dist/web/page.d.ts.map +1 -0
  118. package/dist/web/page.js +27 -0
  119. package/dist/web/page.js.map +1 -0
  120. package/dist/web/root.d.ts +9 -0
  121. package/dist/web/root.d.ts.map +1 -0
  122. package/dist/web/root.js +26 -0
  123. package/dist/web/root.js.map +1 -0
  124. package/package.json +119 -3
  125. package/src/index.native.ts +22 -0
  126. package/src/index.ts +24 -0
  127. package/src/native/blocks/content.tsx +55 -0
  128. package/src/native/blocks/layout.tsx +72 -0
  129. package/src/native/blocks/navigation.tsx +71 -0
  130. package/src/native/blocks/sections.tsx +174 -0
  131. package/src/native/blocks/utility.tsx +27 -0
  132. package/src/native/config.tsx +66 -0
  133. package/src/native/index.ts +18 -0
  134. package/src/native/render.tsx +113 -0
  135. package/src/native/root.tsx +35 -0
  136. package/src/shared/adapters.ts +82 -0
  137. package/src/shared/blocks/content.ts +80 -0
  138. package/src/shared/blocks/index.ts +100 -0
  139. package/src/shared/blocks/layout.ts +82 -0
  140. package/src/shared/blocks/navigation.ts +168 -0
  141. package/src/shared/blocks/sections.ts +322 -0
  142. package/src/shared/blocks/utility.ts +65 -0
  143. package/src/shared/fields.ts +199 -0
  144. package/src/shared/index.ts +14 -0
  145. package/src/shared/types.ts +278 -0
  146. package/src/web/blocks/content.tsx +55 -0
  147. package/src/web/blocks/layout.tsx +73 -0
  148. package/src/web/blocks/navigation.tsx +72 -0
  149. package/src/web/blocks/sections.tsx +177 -0
  150. package/src/web/blocks/utility.tsx +31 -0
  151. package/src/web/config.tsx +141 -0
  152. package/src/web/index.ts +21 -0
  153. package/src/web/page.tsx +40 -0
  154. package/src/web/root.tsx +34 -0
@@ -0,0 +1,174 @@
1
+ /**
2
+ * The platform marketing section blocks.
3
+ *
4
+ * Each one is a thin adapter over the framework component of the same name, so the
5
+ * builder edits the component's own props rather than a parallel model of them — and it
6
+ * edits them once, for both platforms: the label, the fields and the defaults come from
7
+ * `src/shared`, and what is written here is only how the component is drawn on a device.
8
+ */
9
+
10
+ import {
11
+ CTA,
12
+ FeatureGrid,
13
+ Hero,
14
+ PricingCard,
15
+ Testimonials,
16
+ } from "@asheeui/native";
17
+ import type {
18
+ AsheeBlock,
19
+ CtaBlockProps,
20
+ FeatureGridBlockProps,
21
+ HeroBlockProps,
22
+ PricingCardBlockProps,
23
+ TestimonialsBlockProps,
24
+ } from "../../shared";
25
+ import {
26
+ CTA_SPEC,
27
+ FEATURE_GRID_SPEC,
28
+ HERO_SPEC,
29
+ PRICING_CARD_SPEC,
30
+ TESTIMONIALS_SPEC,
31
+ toAction,
32
+ toColumns,
33
+ } from "../../shared";
34
+
35
+ /**
36
+ * The leading statement of a page.
37
+ */
38
+ export const heroBlock: AsheeBlock<HeroBlockProps> = {
39
+ ...HERO_SPEC,
40
+ render: ({
41
+ eyebrow,
42
+ title,
43
+ description,
44
+ primaryAction,
45
+ secondaryAction,
46
+ align,
47
+ background,
48
+ contentWidth,
49
+ }) => (
50
+ <Hero
51
+ eyebrow={eyebrow || undefined}
52
+ title={title ?? ""}
53
+ description={description || undefined}
54
+ primaryAction={toAction(primaryAction)}
55
+ secondaryAction={toAction(secondaryAction)}
56
+ align={align ?? "start"}
57
+ background={background ?? "none"}
58
+ containerSize={contentWidth ?? "lg"}
59
+ />
60
+ ),
61
+ };
62
+
63
+ /**
64
+ * The closing call to action of a page.
65
+ */
66
+ export const ctaBlock: AsheeBlock<CtaBlockProps> = {
67
+ ...CTA_SPEC,
68
+ render: ({
69
+ eyebrow,
70
+ title,
71
+ description,
72
+ primaryAction,
73
+ secondaryAction,
74
+ align,
75
+ background,
76
+ panel,
77
+ }) => (
78
+ <CTA
79
+ eyebrow={eyebrow || undefined}
80
+ title={title ?? ""}
81
+ description={description || undefined}
82
+ primaryAction={toAction(primaryAction)}
83
+ secondaryAction={toAction(secondaryAction)}
84
+ align={align ?? "center"}
85
+ background={background ?? "none"}
86
+ panel={panel ?? "bordered"}
87
+ />
88
+ ),
89
+ };
90
+
91
+ /**
92
+ * A band of features laid out in a responsive grid.
93
+ */
94
+ export const featureGridBlock: AsheeBlock<FeatureGridBlockProps> = {
95
+ ...FEATURE_GRID_SPEC,
96
+ render: ({
97
+ eyebrow,
98
+ title,
99
+ description,
100
+ items,
101
+ columnsMd,
102
+ columnsLg,
103
+ align,
104
+ background,
105
+ }) => (
106
+ <FeatureGrid
107
+ eyebrow={eyebrow || undefined}
108
+ title={title || undefined}
109
+ description={description || undefined}
110
+ align={align ?? "center"}
111
+ background={background ?? "none"}
112
+ columnsMd={toColumns(columnsMd)}
113
+ columnsLg={toColumns(columnsLg)}
114
+ items={(items ?? []).map((item, index) => ({
115
+ id: index,
116
+ title: item.title ?? "",
117
+ description: item.description || undefined,
118
+ }))}
119
+ />
120
+ ),
121
+ };
122
+
123
+ /**
124
+ * A band of attributed customer quotes.
125
+ */
126
+ export const testimonialsBlock: AsheeBlock<TestimonialsBlockProps> = {
127
+ ...TESTIMONIALS_SPEC,
128
+ render: ({ eyebrow, title, description, items, align }) => (
129
+ <Testimonials
130
+ eyebrow={eyebrow || undefined}
131
+ title={title || undefined}
132
+ description={description || undefined}
133
+ align={align ?? "center"}
134
+ items={(items ?? []).map((item, index) => ({
135
+ id: index,
136
+ quote: item.quote ?? "",
137
+ name: item.name ?? "",
138
+ role: item.role || undefined,
139
+ }))}
140
+ />
141
+ ),
142
+ };
143
+
144
+ /**
145
+ * One plan in a pricing table.
146
+ */
147
+ export const pricingCardBlock: AsheeBlock<PricingCardBlockProps> = {
148
+ ...PRICING_CARD_SPEC,
149
+ render: ({
150
+ badge,
151
+ name,
152
+ price,
153
+ period,
154
+ description,
155
+ features,
156
+ highlighted,
157
+ primaryAction,
158
+ }) => (
159
+ <PricingCard
160
+ badge={badge || undefined}
161
+ name={name ?? ""}
162
+ price={price ?? ""}
163
+ period={period || undefined}
164
+ description={description || undefined}
165
+ highlighted={highlighted ?? false}
166
+ features={(features ?? []).map((feature, index) => ({
167
+ id: index,
168
+ label: feature.label ?? "",
169
+ included: feature.included !== false,
170
+ }))}
171
+ primaryAction={toAction(primaryAction)}
172
+ />
173
+ ),
174
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The platform utility blocks.
3
+ *
4
+ * The empty state is the one utility block a marketing or application page needs from a
5
+ * builder: a region that has nothing in it yet, which a non-developer can word and
6
+ * justify without touching code. The fields and the defaults come from `src/shared`.
7
+ */
8
+
9
+ import { EmptyState } from "@asheeui/native";
10
+ import type { AsheeBlock, EmptyStateBlockProps } from "../../shared";
11
+ import { EMPTY_STATE_SPEC, toAction } from "../../shared";
12
+
13
+ /**
14
+ * A region with nothing in it.
15
+ */
16
+ export const emptyStateBlock: AsheeBlock<EmptyStateBlockProps> = {
17
+ ...EMPTY_STATE_SPEC,
18
+ render: ({ title, description, type, panel, primaryAction }) => (
19
+ <EmptyState
20
+ title={title ?? ""}
21
+ description={description || undefined}
22
+ type={type ?? "info"}
23
+ panel={panel ?? false}
24
+ primaryAction={toAction(primaryAction)}
25
+ />
26
+ ),
27
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * The AsheeUI registry, on the platform.
3
+ *
4
+ * The native twin of the web configuration: every block a stored page can name, the
5
+ * categories they are filed under, and the page shell a stored page renders into. It
6
+ * exists so that a device renders the page the editor composed — not a second page
7
+ * model of it — and so that adding a block is one addition in `src/shared` plus one
8
+ * drawing per platform.
9
+ *
10
+ * The category map and the block labels come from `src/shared`, which is what keeps the
11
+ * two registries honest: neither can offer a category the other does not, and neither
12
+ * can offer a block the other has not.
13
+ */
14
+
15
+ import type { AsheeNativePuckConfig } from "../shared";
16
+ import { PUCK_CATEGORIES } from "../shared";
17
+ import { headingBlock, textBlock } from "./blocks/content";
18
+ import { columnsBlock, sectionBlock } from "./blocks/layout";
19
+ import { footerBlock, navbarBlock } from "./blocks/navigation";
20
+ import {
21
+ ctaBlock,
22
+ featureGridBlock,
23
+ heroBlock,
24
+ pricingCardBlock,
25
+ testimonialsBlock,
26
+ } from "./blocks/sections";
27
+ import { emptyStateBlock } from "./blocks/utility";
28
+ import { PuckRoot } from "./root";
29
+
30
+ /**
31
+ * The blocks a stored page can name.
32
+ *
33
+ * The assertion is deliberate and it is the one place this package asserts rather than
34
+ * checks: a registry holds blocks of different props, so the collection's type is the
35
+ * open one, while each entry keeps its own type where it is declared. The package's own
36
+ * tests assert the two properties that matter here — that every block in the registry
37
+ * renders, and that the category map names exactly the blocks that exist.
38
+ */
39
+ const components = {
40
+ Section: sectionBlock,
41
+ Columns: columnsBlock,
42
+ Heading: headingBlock,
43
+ Text: textBlock,
44
+ Navbar: navbarBlock,
45
+ Footer: footerBlock,
46
+ Hero: heroBlock,
47
+ CTA: ctaBlock,
48
+ FeatureGrid: featureGridBlock,
49
+ Testimonials: testimonialsBlock,
50
+ PricingCard: pricingCardBlock,
51
+ EmptyState: emptyStateBlock,
52
+ } as unknown as AsheeNativePuckConfig["components"];
53
+
54
+ /**
55
+ * The native registry a stored page is rendered with.
56
+ */
57
+ export const asheeNativePuckConfig: AsheeNativePuckConfig = {
58
+ components,
59
+ categories: PUCK_CATEGORIES,
60
+ root: {
61
+ label: "Page",
62
+ fields: {},
63
+ defaultProps: {},
64
+ render: ({ children }) => <PuckRoot>{children}</PuckRoot>,
65
+ },
66
+ };
@@ -0,0 +1,18 @@
1
+ /**
2
+ * The platform half of the Puck integration.
3
+ *
4
+ * It is the shared layer plus this platform's drawings, and the renderer that turns a
5
+ * stored page into a screen. Nothing here imports React DOM, Tailwind or the editor: a
6
+ * device reaches this file without loading a single byte of the web edition, which is
7
+ * the property the whole split exists for.
8
+ */
9
+
10
+ export * from "../shared";
11
+ export * from "./blocks/content";
12
+ export * from "./blocks/layout";
13
+ export * from "./blocks/navigation";
14
+ export * from "./blocks/sections";
15
+ export * from "./blocks/utility";
16
+ export * from "./config";
17
+ export * from "./render";
18
+ export * from "./root";
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Rendering a stored page, on the platform.
3
+ *
4
+ * A device has no editor, so what it needs from a published document is a renderer:
5
+ * walk the blocks the editor stored, resolve each drop zone into the nodes that belong
6
+ * there, and draw each block through the registry the two platforms share. That is all
7
+ * this is — the platform's counterpart of the editor's own `Render`, which is why the
8
+ * two entries export a component of the same name and an application calls one.
9
+ *
10
+ * Two properties are worth stating. A block the registry does not know is dropped rather
11
+ * than thrown on, because a page saved by a newer editor should not crash an older
12
+ * application. And a key the document omits resolves to the block's default value, so a
13
+ * partial payload — a page saved before a field existed — renders the same way the
14
+ * editor would have shown it.
15
+ */
16
+
17
+ import { Fragment, type ReactNode } from "react";
18
+ import type {
19
+ AsheeBlock,
20
+ AsheeNativePuckConfig,
21
+ AsheePuckBlockData,
22
+ AsheePuckData,
23
+ } from "../shared";
24
+ import { asheeNativePuckConfig } from "./config";
25
+
26
+ /**
27
+ * Render a list of blocks.
28
+ *
29
+ * @param content - The blocks, in order.
30
+ * @param config - The registry to render them with.
31
+ * @param keyPrefix - A prefix that keeps a nested block's key unique among its siblings.
32
+ * @returns The rendered blocks.
33
+ */
34
+ function renderBlocks(
35
+ content: AsheePuckBlockData[],
36
+ config: AsheeNativePuckConfig,
37
+ keyPrefix: string,
38
+ ): ReactNode[] {
39
+ return content.map((item, index) =>
40
+ renderBlock(item, config, `${keyPrefix}-${index}`),
41
+ );
42
+ }
43
+
44
+ /**
45
+ * Render one block, resolving its drop zones first.
46
+ *
47
+ * @param item - The block as the document stored it.
48
+ * @param config - The registry to render it with.
49
+ * @param key - A key unique among the block's siblings.
50
+ * @returns The rendered block, or null when the registry does not know its type.
51
+ */
52
+ function renderBlock(
53
+ item: AsheePuckBlockData,
54
+ config: AsheeNativePuckConfig,
55
+ key: string,
56
+ ): ReactNode {
57
+ const block = config.components[item.type] as
58
+ | AsheeBlock<Record<string, unknown>>
59
+ | undefined;
60
+
61
+ if (!block) return null;
62
+
63
+ const props: Record<string, unknown> = {
64
+ ...block.defaultProps,
65
+ ...item.props,
66
+ };
67
+
68
+ // A drop zone's children are stored under the zone's own key, so the fields are what
69
+ // says which keys hold blocks rather than values.
70
+ for (const [name, field] of Object.entries(block.fields ?? {})) {
71
+ if (field?.type !== "slot") continue;
72
+
73
+ const children = item.props?.[name];
74
+ props[name] = renderBlocks(
75
+ Array.isArray(children) ? (children as AsheePuckBlockData[]) : [],
76
+ config,
77
+ `${key}-${name}`,
78
+ );
79
+ }
80
+
81
+ return <Fragment key={key}>{block.render(props)}</Fragment>;
82
+ }
83
+
84
+ /**
85
+ * Props of {@link PuckPage}.
86
+ */
87
+ export interface PuckPageProps {
88
+ /** The page the builder published. */
89
+ data: AsheePuckData;
90
+
91
+ /** The registry to render it with. Defaults to the AsheeUI configuration. */
92
+ config?: AsheeNativePuckConfig;
93
+ }
94
+
95
+ /**
96
+ * Render a stored page.
97
+ *
98
+ * @param props - The stored page, and optionally the registry to render it with.
99
+ * @returns The rendered page.
100
+ *
101
+ * @example
102
+ * ```tsx
103
+ * <PuckPage data={page} />
104
+ * ```
105
+ */
106
+ export function PuckPage({
107
+ data,
108
+ config = asheeNativePuckConfig,
109
+ }: PuckPageProps): ReactNode {
110
+ const children = renderBlocks(data.content ?? [], config, "root");
111
+
112
+ return config.root.render({ children });
113
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * The page shell a stored page renders into, on the platform.
3
+ *
4
+ * A block resolves its theme through the framework provider, so the shell supplies one
5
+ * when the application has not. An existing provider is respected rather than nested, so
6
+ * an application's own configuration keeps applying to a built page — a second provider
7
+ * would replace the resolved configuration with the framework's own defaults.
8
+ *
9
+ * The shell is the platform's own view rather than a re-implementation of the web's
10
+ * `<div>`: a page fills the screen it is drawn in, and the background it paints is the
11
+ * framework's background role.
12
+ */
13
+
14
+ import { AsheeNativeConfigContext, AsheeNativeProvider } from "@asheeui/native";
15
+ import { useContext } from "react";
16
+ import { View } from "react-native";
17
+ import type { AsheePuckRootProps } from "../shared";
18
+
19
+ /**
20
+ * The published page's root.
21
+ *
22
+ * @param props - The page the builder composed.
23
+ * @returns The page, inside the framework's provider when the application has none.
24
+ */
25
+ export function PuckRoot({ children }: AsheePuckRootProps) {
26
+ const existingConfig = useContext(AsheeNativeConfigContext);
27
+
28
+ const page = <View className="bg-background flex-1 w-full">{children}</View>;
29
+
30
+ return existingConfig ? (
31
+ page
32
+ ) : (
33
+ <AsheeNativeProvider>{page}</AsheeNativeProvider>
34
+ );
35
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Adapters between builder values and framework props.
3
+ *
4
+ * A builder holds plain strings, because that is what a form control produces. The
5
+ * framework's props are unions of named tokens. These adapters are the single place
6
+ * where one becomes the other, so a block's render function stays a mapping of names
7
+ * rather than a parsing exercise, and a value that is not accepted by the component
8
+ * resolves to undefined rather than to a wrong class.
9
+ *
10
+ * Both renderers use them, which is why they live with the specs rather than with
11
+ * either platform: a value the web accepts and the platform rejects would be two
12
+ * different pages built from one document.
13
+ */
14
+
15
+ import type { GridColumns, Variant } from "@asheeui/core";
16
+
17
+ /**
18
+ * One action as a builder holds it.
19
+ *
20
+ * The keys are required because a field set is a form the builder fills in, and
21
+ * `defaultProps` supplies a complete value for every block that offers one. A
22
+ * published payload can still arrive with a key missing, which is why the adapter that
23
+ * reads it accepts a partial value.
24
+ */
25
+ export type BuilderAction = {
26
+ /** Wording of the action. */
27
+ label: string;
28
+
29
+ /** Destination of the action. */
30
+ href: string;
31
+
32
+ /** Emphasis of the action. */
33
+ variant: string;
34
+ };
35
+
36
+ /** The variants an action accepts, used to validate a builder value. */
37
+ const ACTION_VARIANTS: readonly string[] = [
38
+ "solid",
39
+ "faded",
40
+ "bordered",
41
+ "ghost",
42
+ "underlined",
43
+ ];
44
+
45
+ /**
46
+ * Turn a builder action into a configured action.
47
+ *
48
+ * @param action - The action the builder holds, which may be missing a key when it
49
+ * comes from a stored page rather than from the editor.
50
+ * @returns The action the framework renders, or undefined when it has no wording,
51
+ * because an action without a name cannot be read or clicked.
52
+ */
53
+ export function toAction(action: Partial<BuilderAction> | undefined) {
54
+ if (!action?.label) {
55
+ return undefined;
56
+ }
57
+
58
+ return {
59
+ label: action.label,
60
+ href: action.href,
61
+ variant:
62
+ action.variant && ACTION_VARIANTS.includes(action.variant)
63
+ ? (action.variant as Variant)
64
+ : undefined,
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Turn a builder column count into a grid column count.
70
+ *
71
+ * @param value - The column count as a string, which is what a select field holds.
72
+ * @returns The numeric column count, or undefined when the value is not one the grid
73
+ * accepts.
74
+ */
75
+ export function toColumns(value: string | undefined): GridColumns | undefined {
76
+ if (!value) return undefined;
77
+
78
+ const parsed = Number(value);
79
+ if (![1, 2, 3, 4, 6, 12].includes(parsed)) return undefined;
80
+
81
+ return parsed as GridColumns;
82
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The content blocks, stated once for both renderers.
3
+ *
4
+ * These are the blocks that place words on a page: a section heading and a paragraph.
5
+ * What is stated here is everything but the drawing — the label a builder shows, the
6
+ * fields it fills in and the value each starts as — because the drawing is the one part
7
+ * that is the platform's: the web composes a `Section` and a `Typography`, the platform
8
+ * composes its own two. Everything else would otherwise be stated twice and could
9
+ * drift.
10
+ */
11
+
12
+ import {
13
+ ALIGNMENT_OPTIONS,
14
+ selectField,
15
+ textareaField,
16
+ textField,
17
+ } from "../fields";
18
+ import type { AsheeBlockSpec } from "../types";
19
+
20
+ /**
21
+ * The props of the heading block.
22
+ */
23
+ export type HeadingBlockProps = {
24
+ /** Short label above the heading. */
25
+ eyebrow?: string;
26
+
27
+ /** The heading itself. */
28
+ title?: string;
29
+
30
+ /** Supporting sentence under the heading. */
31
+ description?: string;
32
+
33
+ /** Horizontal alignment of the block. */
34
+ align?: "start" | "center";
35
+ };
36
+
37
+ /**
38
+ * A section heading with an optional eyebrow and description.
39
+ */
40
+ export const HEADING_SPEC: AsheeBlockSpec<HeadingBlockProps> = {
41
+ label: "Heading",
42
+ fields: {
43
+ eyebrow: textField("Eyebrow", "Why teams switch"),
44
+ title: textField("Heading", "Everything in one place"),
45
+ description: textareaField("Description"),
46
+ align: selectField("Alignment", ALIGNMENT_OPTIONS),
47
+ },
48
+ defaultProps: {
49
+ eyebrow: "",
50
+ title: "Everything in one place",
51
+ description: "",
52
+ align: "start",
53
+ },
54
+ };
55
+
56
+ /**
57
+ * The props of the text block.
58
+ */
59
+ export type TextBlockProps = {
60
+ /** The paragraph. */
61
+ text?: string;
62
+
63
+ /** Alignment of the paragraph. */
64
+ align?: "start" | "center";
65
+ };
66
+
67
+ /**
68
+ * A paragraph of body text.
69
+ */
70
+ export const TEXT_SPEC: AsheeBlockSpec<TextBlockProps> = {
71
+ label: "Text",
72
+ fields: {
73
+ text: textareaField("Text", "Write the paragraph here."),
74
+ align: selectField("Alignment", ALIGNMENT_OPTIONS),
75
+ },
76
+ defaultProps: {
77
+ text: "Write the paragraph here.",
78
+ align: "start",
79
+ },
80
+ };
@@ -0,0 +1,100 @@
1
+ /**
2
+ * The block specs, gathered.
3
+ *
4
+ * One module states the set: the names a stored page uses, the order a builder lists
5
+ * them in, and the categories they are filed under. A block that is added without a
6
+ * category, or a category that names a block nobody implemented, is caught by the
7
+ * package's own tests rather than by an editor that silently shows nothing — which is
8
+ * why the name list and the category map live beside the specs rather than in either
9
+ * renderer.
10
+ */
11
+
12
+ import type { AsheeBlockSpec, AsheePuckCategories } from "../types";
13
+ import { HEADING_SPEC, TEXT_SPEC } from "./content";
14
+ import { COLUMNS_SPEC, SECTION_SPEC } from "./layout";
15
+ import { FOOTER_SPEC, NAVBAR_SPEC } from "./navigation";
16
+ import {
17
+ CTA_SPEC,
18
+ FEATURE_GRID_SPEC,
19
+ HERO_SPEC,
20
+ PRICING_CARD_SPEC,
21
+ TESTIMONIALS_SPEC,
22
+ } from "./sections";
23
+ import { EMPTY_STATE_SPEC } from "./utility";
24
+
25
+ export * from "./content";
26
+ export * from "./layout";
27
+ export * from "./navigation";
28
+ export * from "./sections";
29
+ export * from "./utility";
30
+
31
+ /**
32
+ * The blocks a stored page can name, in the order a builder lists them.
33
+ */
34
+ export const PUCK_BLOCK_NAMES = [
35
+ "Section",
36
+ "Columns",
37
+ "Heading",
38
+ "Text",
39
+ "Navbar",
40
+ "Footer",
41
+ "Hero",
42
+ "CTA",
43
+ "FeatureGrid",
44
+ "Testimonials",
45
+ "PricingCard",
46
+ "EmptyState",
47
+ ] as const;
48
+
49
+ /**
50
+ * The name of one block, which is also its key in a page's content.
51
+ */
52
+ export type PuckBlockName = (typeof PUCK_BLOCK_NAMES)[number];
53
+
54
+ /**
55
+ * The specs, by block name.
56
+ *
57
+ * This is the one place the set is complete: a name without a spec, or a spec without a
58
+ * name, is a compile error rather than an editor that silently shows nothing, and both
59
+ * registries read their labels and fields from what is here. A consumer building its own
60
+ * registry — an application that wants a subset of the blocks, or one of its own beside
61
+ * them — can start from the same specs rather than restating the fields.
62
+ */
63
+ export const PUCK_SPECS: Record<
64
+ PuckBlockName,
65
+ AsheeBlockSpec<Record<string, unknown>>
66
+ > = {
67
+ Section: SECTION_SPEC,
68
+ Columns: COLUMNS_SPEC,
69
+ Heading: HEADING_SPEC,
70
+ Text: TEXT_SPEC,
71
+ Navbar: NAVBAR_SPEC,
72
+ Footer: FOOTER_SPEC,
73
+ Hero: HERO_SPEC,
74
+ CTA: CTA_SPEC,
75
+ FeatureGrid: FEATURE_GRID_SPEC,
76
+ Testimonials: TESTIMONIALS_SPEC,
77
+ PricingCard: PRICING_CARD_SPEC,
78
+ EmptyState: EMPTY_STATE_SPEC,
79
+ };
80
+
81
+ /**
82
+ * The categories a block can be filed under.
83
+ *
84
+ * A block that appears in none of them is listed by the builder's own fallback
85
+ * category, which is why the list stays explicit rather than complete by accident.
86
+ */
87
+ export const PUCK_CATEGORIES: AsheePuckCategories = {
88
+ Layout: { title: "Layout", components: ["Section", "Columns"] },
89
+ Navigation: { title: "Navigation", components: ["Navbar", "Footer"] },
90
+ Hero: { title: "Hero", components: ["Hero"] },
91
+ Content: { title: "Content", components: ["Heading", "Text"] },
92
+ Features: { title: "Features", components: ["FeatureGrid"] },
93
+ CTA: { title: "CTA", components: ["CTA"] },
94
+ Pricing: { title: "Pricing", components: ["PricingCard"] },
95
+ Testimonials: {
96
+ title: "Testimonials",
97
+ components: ["Testimonials"],
98
+ },
99
+ Utility: { title: "Utility", components: ["EmptyState"] },
100
+ };