southpaw 0.2.7

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 (169) hide show
  1. package/README.md +51 -0
  2. package/api/client.ts +83 -0
  3. package/api/helpers/fs.ts +98 -0
  4. package/api/helpers/stream.ts +6 -0
  5. package/api/index.ts +50 -0
  6. package/api/server.ts +566 -0
  7. package/constants/fonts.ts +1583 -0
  8. package/constants/icons.ts +2124 -0
  9. package/constants/langs.ts +2592 -0
  10. package/constants/ssr-loading.tsx +17 -0
  11. package/contexts/dev.tsx +124 -0
  12. package/hooks/useDebounceCb.ts +21 -0
  13. package/modules/app.tsx +102 -0
  14. package/modules/behaviors.ts +161 -0
  15. package/modules/component.tsx +483 -0
  16. package/modules/config/index.ts +8 -0
  17. package/modules/database.ts +25 -0
  18. package/modules/document.tsx +23 -0
  19. package/modules/error.tsx +16 -0
  20. package/modules/page/autozoom.tsx +92 -0
  21. package/modules/page/browser.tsx +166 -0
  22. package/modules/page/index.tsx +327 -0
  23. package/modules/page/insert-modal.tsx +225 -0
  24. package/modules/page/inspect/index.tsx +237 -0
  25. package/modules/page/inspect/wrapper.tsx +88 -0
  26. package/modules/page/knobs/action.tsx +58 -0
  27. package/modules/page/knobs/color.tsx +78 -0
  28. package/modules/page/knobs/helpers.ts +71 -0
  29. package/modules/page/knobs/icon.tsx +85 -0
  30. package/modules/page/knobs/linker.tsx +188 -0
  31. package/modules/page/knobs/list-json.tsx +149 -0
  32. package/modules/page/knobs/list-text.tsx +129 -0
  33. package/modules/page/knobs/number.tsx +84 -0
  34. package/modules/page/knobs/segmented.tsx +81 -0
  35. package/modules/page/knobs/select.tsx +89 -0
  36. package/modules/page/knobs/size.tsx +106 -0
  37. package/modules/page/knobs/switch.tsx +75 -0
  38. package/modules/page/knobs/text.tsx +93 -0
  39. package/modules/page/left-tabs.tsx +36 -0
  40. package/modules/page/right-tabs.tsx +31 -0
  41. package/modules/page/seo.tsx +69 -0
  42. package/modules/page/tree.tsx +860 -0
  43. package/modules/state.ts +787 -0
  44. package/modules/store.ts +37 -0
  45. package/modules/theme.ts +105 -0
  46. package/modules/translations.ts +15 -0
  47. package/package.json +68 -0
  48. package/scripts/bin.js +607 -0
  49. package/scripts/test.js +145 -0
  50. package/themes/base.tsx +8962 -0
  51. package/themes/index.ts +4 -0
  52. package/themes/m2.ts +39 -0
  53. package/themes/newspaper.ts +39 -0
  54. package/types/api.ts +57 -0
  55. package/types/fiber_node.ts +69 -0
  56. package/types/react_element.ts +14 -0
  57. package/ui/accordion.tsx +70 -0
  58. package/ui/actionicon.tsx +41 -0
  59. package/ui/affix.tsx +14 -0
  60. package/ui/alert.tsx +37 -0
  61. package/ui/anchor.tsx +52 -0
  62. package/ui/arrow.tsx +116 -0
  63. package/ui/aspectratio.tsx +21 -0
  64. package/ui/autocomplete.tsx +34 -0
  65. package/ui/avatar.tsx +36 -0
  66. package/ui/backgroundimage.tsx +16 -0
  67. package/ui/badge.tsx +28 -0
  68. package/ui/blockquote.tsx +15 -0
  69. package/ui/blocks/faq.tsx +145 -0
  70. package/ui/blocks/features.tsx +167 -0
  71. package/ui/blocks/footer.tsx +182 -0
  72. package/ui/blocks/herobanner.tsx +391 -0
  73. package/ui/blocks/index.ts +44 -0
  74. package/ui/blocks/leftnav.tsx +141 -0
  75. package/ui/blocks/pricing.tsx +210 -0
  76. package/ui/blocks/testimonials.tsx +149 -0
  77. package/ui/blocks/uidocs.tsx +1429 -0
  78. package/ui/box.tsx +27 -0
  79. package/ui/breadcrumbs.tsx +33 -0
  80. package/ui/burger.tsx +23 -0
  81. package/ui/button.tsx +85 -0
  82. package/ui/card.tsx +28 -0
  83. package/ui/center.tsx +26 -0
  84. package/ui/checkbox.tsx +35 -0
  85. package/ui/chip.tsx +32 -0
  86. package/ui/closebutton.tsx +15 -0
  87. package/ui/code.tsx +104 -0
  88. package/ui/collapse.tsx +16 -0
  89. package/ui/colorinput.tsx +32 -0
  90. package/ui/colorpicker.tsx +26 -0
  91. package/ui/colorswatch.tsx +17 -0
  92. package/ui/combobox.tsx +80 -0
  93. package/ui/container.tsx +27 -0
  94. package/ui/dialog.tsx +15 -0
  95. package/ui/divider.tsx +58 -0
  96. package/ui/drawer.tsx +28 -0
  97. package/ui/fieldset.tsx +15 -0
  98. package/ui/filebutton.tsx +23 -0
  99. package/ui/fileinput.tsx +36 -0
  100. package/ui/flex.tsx +139 -0
  101. package/ui/focustrap.tsx +15 -0
  102. package/ui/form.tsx +26 -0
  103. package/ui/grid.tsx +69 -0
  104. package/ui/highlight.tsx +15 -0
  105. package/ui/hoverable.tsx +66 -0
  106. package/ui/hovercard.tsx +45 -0
  107. package/ui/icon.tsx +76 -0
  108. package/ui/image.tsx +32 -0
  109. package/ui/index.ts +1355 -0
  110. package/ui/indicator.tsx +42 -0
  111. package/ui/infobox.tsx +200 -0
  112. package/ui/input.tsx +18 -0
  113. package/ui/inviewport.tsx +23 -0
  114. package/ui/jsoninput.tsx +35 -0
  115. package/ui/kbd.tsx +20 -0
  116. package/ui/list.tsx +33 -0
  117. package/ui/loader.tsx +26 -0
  118. package/ui/loadingoverlay.tsx +22 -0
  119. package/ui/mark.tsx +15 -0
  120. package/ui/menu.tsx +89 -0
  121. package/ui/modal.tsx +27 -0
  122. package/ui/multiselect.tsx +42 -0
  123. package/ui/nativeselect.tsx +17 -0
  124. package/ui/navlink.tsx +40 -0
  125. package/ui/notification.tsx +25 -0
  126. package/ui/numberformatter.tsx +15 -0
  127. package/ui/numberinput.tsx +37 -0
  128. package/ui/overlay.tsx +15 -0
  129. package/ui/pagination.tsx +21 -0
  130. package/ui/paper.tsx +33 -0
  131. package/ui/passwordinput.tsx +36 -0
  132. package/ui/pill.tsx +17 -0
  133. package/ui/pillsinput.tsx +37 -0
  134. package/ui/pininput.tsx +30 -0
  135. package/ui/popover.tsx +43 -0
  136. package/ui/portal.tsx +15 -0
  137. package/ui/progress.tsx +49 -0
  138. package/ui/props.ts +91 -0
  139. package/ui/propsDef.ts +38 -0
  140. package/ui/radio.tsx +30 -0
  141. package/ui/rating.tsx +25 -0
  142. package/ui/ringprogress.tsx +17 -0
  143. package/ui/scrollarea.tsx +27 -0
  144. package/ui/segmentedcontrol.tsx +38 -0
  145. package/ui/select.tsx +43 -0
  146. package/ui/simplegrid.tsx +23 -0
  147. package/ui/skeleton.tsx +15 -0
  148. package/ui/slider.tsx +31 -0
  149. package/ui/space.tsx +19 -0
  150. package/ui/spoiler.tsx +23 -0
  151. package/ui/stepper.tsx +63 -0
  152. package/ui/switch.tsx +29 -0
  153. package/ui/table.tsx +93 -0
  154. package/ui/tabs.tsx +80 -0
  155. package/ui/tagsinput.tsx +19 -0
  156. package/ui/terminal.tsx +44 -0
  157. package/ui/text.tsx +168 -0
  158. package/ui/textarea.tsx +36 -0
  159. package/ui/textinput.tsx +34 -0
  160. package/ui/timeline.tsx +30 -0
  161. package/ui/title.tsx +42 -0
  162. package/ui/tooltip.tsx +32 -0
  163. package/ui/transition.tsx +21 -0
  164. package/ui/tree.tsx +42 -0
  165. package/ui/typographystylesprovider.tsx +20 -0
  166. package/ui/unstyledbutton.tsx +18 -0
  167. package/ui/video.tsx +16 -0
  168. package/ui/visuallyhidden.tsx +15 -0
  169. package/utils/index.ts +76 -0
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Southpaw
2
+
3
+ A behavior-driven Next.js framework.
4
+
5
+ Southpaw is a superset of Next.js that enforces a strict separation between **UI (Blocks)**, **Logic (Behaviors)**, and **Pages**. It comes with a visual editor, AI-powered code generation, and a built-in component library.
6
+
7
+ ## Quick Start
8
+
9
+ Create a new Southpaw project:
10
+
11
+ ```bash
12
+ npx southpaw
13
+ ```
14
+
15
+ You'll be prompted to choose a project name and template. Then:
16
+
17
+ ```bash
18
+ cd your-project
19
+ npm install
20
+ npm run dev
21
+ ```
22
+
23
+ ## CLI Commands
24
+
25
+ | Command | Description |
26
+ | --- | --- |
27
+ | `npx southpaw` | Create a new project from a template |
28
+ | `npx southpaw generate-block` | AI-powered block (component) generation |
29
+ | `npx southpaw generate-behavior` | Scaffold a new behavior |
30
+ | `npx southpaw generate-page` | Scaffold a new page |
31
+ | `npx southpaw test` | Run route-level tests with Puppeteer |
32
+
33
+ ## Core Concepts
34
+
35
+ ### Behaviors
36
+ Manage global or shared state with `create_behaviors`. Define `state`, `actions`, `when` (action labels), `then` (state labels), and `given` (test scenarios).
37
+
38
+ ### Blocks
39
+ Reusable UI components built with `create_component`. Define `propsDef` for visual editor integration, local `state`, `computed` values, `methods`, and `lifecycle` hooks.
40
+
41
+ ### Pages
42
+ Connect blocks and behaviors using `create_page`. Access behaviors via `this.do` and `this.get`, and translations via `this.trans`.
43
+
44
+ ## Requirements
45
+
46
+ - Node.js >= 18
47
+ - Next.js >= 15.2.4
48
+
49
+ ## License
50
+
51
+ MIT
package/api/client.ts ADDED
@@ -0,0 +1,83 @@
1
+ import { ApiType, StreamCallback, StreamIterator } from "../types/api";
2
+ import { UTILS } from "../utils";
3
+
4
+ const post = async <
5
+ T extends Extract<keyof ApiType, string>,
6
+ K extends Extract<keyof ApiType[T], string>,
7
+ >(
8
+ parentSlug: T,
9
+ childSlug: K,
10
+ //@ts-expect-error Whatever
11
+ args?: Parameters<ApiType[T][K]>[0] = {},
12
+ //@ts-expect-error Whatever
13
+ ): Promise<ReturnType<ApiType[T][K]>> => {
14
+ const result = await fetch(
15
+ `/api/dev/${String(parentSlug)}/${String(childSlug)}`,
16
+ {
17
+ method: "POST",
18
+ body: JSON.stringify(args),
19
+ },
20
+ );
21
+ const isJson = result.headers
22
+ .get("Content-Type")
23
+ ?.startsWith("application/json;");
24
+ if (isJson) {
25
+ const json = await result.json();
26
+ UTILS.event.dispatch("next-ide-notification", {
27
+ type: json.type,
28
+ message: json.message,
29
+ });
30
+ return json;
31
+ } else {
32
+ const stream = result.body as StreamIterator;
33
+ const fn: StreamCallback = async (cb) => {
34
+ for await (const chunk of stream) {
35
+ const decoder = new TextDecoder();
36
+ const value = decoder.decode(chunk, { stream: true });
37
+ cb(value, stream);
38
+ }
39
+ };
40
+ return fn as any;
41
+ }
42
+ };
43
+ export const FETCH: ApiType = {
44
+ application: {},
45
+ page: {
46
+ async writeSEO(args) {
47
+ const result = await post("page", "writeSEO", args);
48
+ return Promise.resolve(result);
49
+ },
50
+ async insertNode(args) {
51
+ const result = await post("page", "insertNode", args);
52
+ return Promise.resolve(result);
53
+ },
54
+ async duplicateNode(args) {
55
+ const result = await post("page", "duplicateNode", args);
56
+ return Promise.resolve(result);
57
+ },
58
+ async deleteNode(args) {
59
+ const result = await post("page", "deleteNode", args);
60
+ return Promise.resolve(result);
61
+ },
62
+ async moveUpNode(args) {
63
+ const result = await post("page", "moveUpNode", args);
64
+ return Promise.resolve(result);
65
+ },
66
+ async moveDownNode(args) {
67
+ const result = await post("page", "moveDownNode", args);
68
+ return Promise.resolve(result);
69
+ },
70
+ async moveNode(args) {
71
+ const result = await post("page", "moveNode", args);
72
+ return Promise.resolve(result);
73
+ },
74
+ async setPropNode(args) {
75
+ const result = await post("page", "setPropNode", args);
76
+ return Promise.resolve(result);
77
+ },
78
+ async getRawPropsNode(args) {
79
+ const result = await post("page", "getRawPropsNode", args);
80
+ return Promise.resolve(result);
81
+ },
82
+ },
83
+ };
@@ -0,0 +1,98 @@
1
+ import path, { ParsedPath } from "path";
2
+ import fs from "fs";
3
+
4
+ export type GlobFile = {
5
+ name: string;
6
+ url: string;
7
+ content: string;
8
+ parsedPath: ParsedPath;
9
+ type:
10
+ | "page"
11
+ | "component"
12
+ | "public"
13
+ | "api"
14
+ | "module"
15
+ | "behavior"
16
+ | "translation";
17
+ };
18
+ export function isPathPage(url: string) {
19
+ const normalizedUrl = normalizeSeparator(url);
20
+ const nextPageRegex = /src\/pages\/.*?\.(tsx|jsx)$/;
21
+ return nextPageRegex.test(normalizedUrl);
22
+ }
23
+ export function isPathComponent(url: string) {
24
+ const normalizedUrl = normalizeSeparator(url);
25
+ const nextComponentsRegex = /src\/components\/.*?\.(tsx|jsx)$/;
26
+ return nextComponentsRegex.test(normalizedUrl);
27
+ }
28
+ export function isPathBehavior(url: string) {
29
+ const normalizedUrl = normalizeSeparator(url);
30
+ const nextBehaviorRegex = /src\/behaviors\/.*?\.(ts)$/;
31
+ return nextBehaviorRegex.test(normalizedUrl);
32
+ }
33
+ export function isPathTranslation(url: string) {
34
+ const normalizedUrl = normalizeSeparator(url);
35
+ const nextTranslationRegex = /src\/translations\/.*?\.(ts)$/;
36
+ return nextTranslationRegex.test(normalizedUrl);
37
+ }
38
+ export function isPathApi(url: string) {
39
+ const normalizedUrl = normalizeSeparator(url);
40
+ const nextApiRegex = /src\/pages\/api\/[^/]+\.(js|ts)$/;
41
+ return nextApiRegex.test(normalizedUrl);
42
+ }
43
+ export function normalizeSeparator(url: string) {
44
+ return url.replace(/\\/g, "/");
45
+ }
46
+ export async function readDir(dirPath: string) {
47
+ const fs = await import("fs/promises");
48
+ return fs.readdir(dirPath);
49
+ }
50
+ export async function readPages(): Promise<GlobFile[]> {
51
+ const fastGlob = await import("fast-glob");
52
+ const pages: GlobFile[] = fastGlob
53
+ .sync(["src/pages/**/*"], {
54
+ onlyFiles: true,
55
+ ignore: [
56
+ "src/pages/api/**/*",
57
+ "src/pages/_app.tsx",
58
+ "src/pages/_document.tsx",
59
+ ],
60
+ })
61
+ .map((url) => {
62
+ const parsedPath = path.parse(url);
63
+ const content = fs
64
+ .readFileSync(url, "utf-8")
65
+ // .replace(/^\s*[\r]/gm, "") // remove empty lines
66
+ .trim();
67
+ return {
68
+ name: parsedPath.name,
69
+ content,
70
+ url,
71
+ parsedPath,
72
+ type: "page",
73
+ };
74
+ });
75
+ return pages;
76
+ }
77
+ export async function readModules(): Promise<GlobFile[]> {
78
+ const fastGlob = await import("fast-glob");
79
+ const modules: GlobFile[] = fastGlob
80
+ .sync(["node_modules/southpaw/modules/**/*"], {
81
+ onlyFiles: true,
82
+ })
83
+ .map((url) => {
84
+ const parsedPath = path.parse(url);
85
+ const content = fs
86
+ .readFileSync(url, "utf-8")
87
+ // .replace(/^\s*[\r]/gm, "") // remove empty lines
88
+ .trim();
89
+ return {
90
+ name: parsedPath.name,
91
+ content,
92
+ url,
93
+ parsedPath,
94
+ type: "module",
95
+ };
96
+ });
97
+ return modules;
98
+ }
@@ -0,0 +1,6 @@
1
+ export const encodeControllerChunk = (
2
+ controller: ReadableStreamDefaultController,
3
+ chunk: string
4
+ ) => {
5
+ controller.enqueue(new TextEncoder().encode(chunk));
6
+ };
package/api/index.ts ADDED
@@ -0,0 +1,50 @@
1
+ import type { NextApiRequest, NextApiResponse } from "next";
2
+ import { API } from "./server";
3
+
4
+ export async function handler<T extends keyof typeof API = keyof typeof API>(
5
+ req: NextApiRequest,
6
+ res: NextApiResponse
7
+ ) {
8
+ const { dev } = req.query;
9
+
10
+ if (dev) {
11
+ const parentSlug = dev[0];
12
+ const genreSlug = dev[1] as T;
13
+ const actionSlug = dev[2] as keyof (typeof API)[T];
14
+ if (parentSlug === "dev" && genreSlug && actionSlug) {
15
+ const action = API[genreSlug][actionSlug] as unknown as (
16
+ arg: unknown
17
+ ) => unknown;
18
+ try {
19
+ if (!req.body) {
20
+ throw new Error("body is missing");
21
+ }
22
+ const result = await action(JSON.parse(req.body));
23
+ if (result instanceof ReadableStream) {
24
+ result.pipeTo(
25
+ new WritableStream({
26
+ write(chunk: Uint8Array) {
27
+ res.write(chunk);
28
+ },
29
+ close() {
30
+ res.end();
31
+ },
32
+ })
33
+ );
34
+ } else {
35
+ res.status(200).json(result);
36
+ }
37
+ } catch (error) {
38
+ res.status(500).json({
39
+ type: "error",
40
+ message: (error as Error).message,
41
+ payload: {},
42
+ });
43
+ }
44
+ } else {
45
+ res.status(500).json({});
46
+ }
47
+ } else {
48
+ res.status(500).json({});
49
+ }
50
+ }