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
@@ -0,0 +1,166 @@
1
+ import {
2
+ Box,
3
+ Center,
4
+ Flex,
5
+ SegmentedControl,
6
+ useMantineTheme,
7
+ } from "@mantine/core";
8
+ import { useRouter } from "next/router";
9
+ import { useEffect, useRef, useState } from "react";
10
+ import { useContextDev } from "southpaw/contexts/dev";
11
+
12
+ export const PageBrowser = () => {
13
+ const theme = useMantineTheme();
14
+ const router = useRouter();
15
+ const [breakpoint, setBreakpoint] =
16
+ useState<keyof typeof theme.breakpoints>("xs");
17
+ const src = `${router.asPath}${router.asPath.includes("?") ? "&" : "?"}isPreview=true`;
18
+ const iframeRef = useRef<HTMLIFrameElement>(null);
19
+ const outlineRef = useRef<HTMLDivElement>(null);
20
+ const { state } = useContextDev();
21
+ const selectedNode = state.tree.selectedNode;
22
+
23
+ useEffect(() => {
24
+ if (!iframeRef.current) return;
25
+ const iframe = iframeRef.current;
26
+
27
+ let animationFrameId: number;
28
+ let targetEl: Element | null = null;
29
+
30
+ const findTarget = () => {
31
+ if (!selectedNode) return null;
32
+ const doc = iframe.contentDocument;
33
+ if (!doc) return null;
34
+ const elements = Array.from(doc.querySelectorAll("*"));
35
+ for (const el of elements) {
36
+ const key = Object.keys(el).find((k) => k.startsWith("__reactFiber$"));
37
+ if (key) {
38
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
39
+ let curr = (el as any)[key];
40
+ while (curr) {
41
+ if (
42
+ curr._debugSource &&
43
+ JSON.stringify(curr._debugSource) === selectedNode.value
44
+ ) {
45
+ return el;
46
+ }
47
+ curr = curr.return;
48
+ }
49
+ }
50
+ }
51
+ return null;
52
+ };
53
+
54
+ const updateRect = () => {
55
+ animationFrameId = requestAnimationFrame(updateRect);
56
+ const outline = outlineRef.current;
57
+ if (!outline) return;
58
+
59
+ if (!selectedNode) {
60
+ outline.style.display = "none";
61
+ return;
62
+ }
63
+
64
+ if (!targetEl || !targetEl.isConnected) {
65
+ targetEl = findTarget();
66
+ }
67
+
68
+ if (targetEl) {
69
+ const rect = targetEl.getBoundingClientRect();
70
+ outline.style.display = "block";
71
+ outline.style.top = `${rect.top}px`;
72
+ outline.style.left = `${rect.left}px`;
73
+ outline.style.width = `${rect.width}px`;
74
+ outline.style.height = `${rect.height}px`;
75
+ } else {
76
+ outline.style.display = "none";
77
+ }
78
+ };
79
+ updateRect();
80
+ return () => cancelAnimationFrame(animationFrameId);
81
+ }, [selectedNode]);
82
+
83
+ if (!src) return null;
84
+ return (
85
+ <Flex direction="column" flex={1} style={{ overflow: "auto" }}>
86
+ <Box
87
+ p="sm"
88
+ bg="var(--mantine-color-body)"
89
+ style={{
90
+ borderBottom: "1px solid var(--mantine-color-gray-2)",
91
+ fontFamily: "var(--mantine-font-family)",
92
+ }}
93
+ >
94
+ <SegmentedControl
95
+ value={breakpoint}
96
+ onChange={setBreakpoint}
97
+ data={[
98
+ {
99
+ label: "Xs",
100
+ value: "xs",
101
+ },
102
+ {
103
+ label: "Sm",
104
+ value: "sm",
105
+ },
106
+ {
107
+ label: "Md",
108
+ value: "md",
109
+ },
110
+ {
111
+ label: "Lg",
112
+ value: "lg",
113
+ },
114
+ {
115
+ label: "Xl",
116
+ value: "xl",
117
+ },
118
+ ]}
119
+ />
120
+ </Box>
121
+ <Center
122
+ flex={1}
123
+ style={{
124
+ "--s": "86px",
125
+ "--c1": "var(--mantine-color-gray-light)",
126
+ "--c2": "var(--mantine-color-body)",
127
+ "--_g": "#0000 90deg,var(--c1) 0",
128
+ background:
129
+ "conic-gradient(from 90deg at 2px 2px,var(--_g)), conic-gradient(from 90deg at 1px 1px,var(--_g)), var(--c2)",
130
+ backgroundSize:
131
+ "var(--s) var(--s), calc(var(--s)/5) calc(var(--s)/5)",
132
+ }}
133
+ >
134
+ <Box
135
+ style={{
136
+ position: "relative",
137
+ height: "100%",
138
+ width: breakpoint === "xl" ? "100%" : theme.breakpoints[breakpoint],
139
+ }}
140
+ >
141
+ <Box
142
+ ref={iframeRef}
143
+ component="iframe"
144
+ h="100%"
145
+ w="100%"
146
+ style={{
147
+ border: "none",
148
+ }}
149
+ src={src}
150
+ />
151
+ <Box
152
+ ref={outlineRef}
153
+ style={{
154
+ position: "absolute",
155
+ display: "none",
156
+ pointerEvents: "none",
157
+ border: "2px solid var(--mantine-color-blue-filled)",
158
+ zIndex: 9999,
159
+ transition: "all 0.1s ease",
160
+ }}
161
+ />
162
+ </Box>
163
+ </Center>
164
+ </Flex>
165
+ );
166
+ };
@@ -0,0 +1,327 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import {
3
+ FunctionComponent,
4
+ ReactNode,
5
+ useEffect,
6
+ useReducer,
7
+ useState,
8
+ ReactElement,
9
+ FC,
10
+ Dispatch,
11
+ SetStateAction,
12
+ } from "react";
13
+ import { renderToString } from "react-dom/server";
14
+ import Head from "next/head";
15
+ import { NextRouter, useRouter } from "next/router";
16
+ import { Flex, useMantineTheme } from "@mantine/core";
17
+
18
+ import { ReactElementType } from "../../types/react_element";
19
+ import { _ErrorBoundary } from "../error";
20
+
21
+ import { PageLeftTabs } from "./left-tabs";
22
+ import { PageRightTabs } from "./right-tabs";
23
+ import { PageBrowser } from "./browser";
24
+ import { useMediaQuery } from "@mantine/hooks";
25
+
26
+ const ClientOnly = ({ children }: { children: ReactNode }) => {
27
+ const [mounted, setMounted] = useState(false);
28
+ useEffect(() => {
29
+ setMounted(true);
30
+ }, []);
31
+ return mounted ? children : null;
32
+ };
33
+
34
+ type PropsType = {
35
+ behaviors: Behaviors;
36
+ trans: (key: keyof Translations) => string;
37
+ lang: Translations[keyof Translations]["lang"];
38
+ setTransLang: (lang: Translations[keyof Translations]["lang"]) => void;
39
+ colorScheme: "dark" | "light";
40
+ setColorScheme: Dispatch<SetStateAction<"dark" | "light">>;
41
+ };
42
+
43
+ export type PageBehaviors = {
44
+ get: {
45
+ [K in keyof Behaviors]: Behaviors[K]["get"];
46
+ };
47
+ do: {
48
+ [K in keyof Behaviors]: Behaviors[K]["do"];
49
+ };
50
+ given: {
51
+ [K in keyof Behaviors]: Behaviors[K]["given"];
52
+ };
53
+ when: {
54
+ [K in keyof Behaviors]: Behaviors[K]["when"];
55
+ };
56
+ then: {
57
+ [K in keyof Behaviors]: Behaviors[K]["then"];
58
+ };
59
+ definitions: {
60
+ [K in keyof Behaviors]: Behaviors[K]["definitions"];
61
+ };
62
+ };
63
+ type PageProps = Record<string, unknown>;
64
+ export type PageRenderThisType = {
65
+ props: PageProps;
66
+ router: NextRouter;
67
+ trans: {
68
+ [TK in keyof Translations]: <
69
+ K extends keyof Translations[TK]["translations"],
70
+ >(
71
+ key: K,
72
+ ...args: K extends keyof Translations[TK]["translations"]
73
+ ? Translations[TK]["translations"][K] extends (...args: any) => any
74
+ ? Parameters<Translations[TK]["translations"][K]>
75
+ : never
76
+ : never
77
+ ) => string;
78
+ };
79
+ devError: (error: ReactElement) => void;
80
+ lang: Translations[keyof Translations]["lang"];
81
+ setTransLang: (lang: Translations[keyof Translations]["lang"]) => void;
82
+ colorScheme: "dark" | "light";
83
+ setColorScheme: Dispatch<SetStateAction<"dark" | "light">>;
84
+ do: PageBehaviors["do"];
85
+ get: PageBehaviors["get"];
86
+ given: PageBehaviors["given"];
87
+ when: PageBehaviors["when"];
88
+ then: PageBehaviors["then"];
89
+ definitions: PageBehaviors["definitions"];
90
+ mediaQueries: {
91
+ isXs: boolean;
92
+ isSm: boolean;
93
+ isMd: boolean;
94
+ isLg: boolean;
95
+ isXl: boolean;
96
+ };
97
+ };
98
+
99
+ type TddType = Record<
100
+ string,
101
+ (this: {
102
+ given: {
103
+ [K in keyof Behaviors]: Behaviors[K]["given"];
104
+ };
105
+ when: {
106
+ [K in keyof Behaviors]: Behaviors[K]["when"];
107
+ };
108
+ then: {
109
+ [K in keyof Behaviors]: Behaviors[K]["then"];
110
+ };
111
+ }) => Promise<void>
112
+ >;
113
+
114
+ export type PageSeoType = {
115
+ title: string;
116
+ description: string;
117
+ };
118
+
119
+ type LifecycleBindings = Omit<
120
+ PageRenderThisType,
121
+ "do" | "get" | "definitions"
122
+ > & {
123
+ given: PageBehaviors["given"];
124
+ when: PageBehaviors["when"];
125
+ then: PageBehaviors["then"];
126
+ };
127
+ export interface PageParams {
128
+ name: string;
129
+ description: string;
130
+ seo: PageSeoType;
131
+ lifecycle?: {
132
+ onMount?: (this: LifecycleBindings) => void;
133
+ onUnmount?: (this: LifecycleBindings) => void;
134
+ onRender?: (this: Pick<LifecycleBindings, "props">) => void;
135
+ };
136
+ tdd?: TddType;
137
+ render: (this: PageRenderThisType) => ReactNode;
138
+ }
139
+
140
+ export const create_page = (params: PageParams) => {
141
+ const Fn: FC = (_props) => {
142
+ const router = useRouter();
143
+ const [errorHtml, setErrorHtml] = useState("");
144
+ const [isIframe, setIsIframe] = useState(false);
145
+ useEffect(() => {
146
+ setIsIframe(window.self !== window.top);
147
+ }, []);
148
+ const theme = useMantineTheme();
149
+ const breakpoints = theme.breakpoints;
150
+
151
+ const isXs = useMediaQuery(`(max-width: ${breakpoints.xs})`);
152
+ const isSm = useMediaQuery(`(max-width: ${breakpoints.sm})`);
153
+ const isMd = useMediaQuery(`(max-width: ${breakpoints.md})`);
154
+ const isLg = useMediaQuery(`(max-width: ${breakpoints.lg})`);
155
+ const isXl = useMediaQuery(`(min-width: ${breakpoints.xl})`);
156
+
157
+ const tdd = params.tdd || {};
158
+ const seo = params.seo;
159
+ const mediaQueries = {
160
+ isXs: Boolean(isXs),
161
+ isSm: Boolean(isSm),
162
+ isMd: Boolean(isMd),
163
+ isLg: Boolean(isLg),
164
+ isXl: Boolean(isXl),
165
+ };
166
+ const [, forceUpdate] = useReducer((x) => x + 1, 0);
167
+ const {
168
+ behaviors,
169
+ trans,
170
+ setTransLang,
171
+ colorScheme,
172
+ setColorScheme,
173
+ lang,
174
+ ...props
175
+ } = _props as Omit<PropsType, "trans"> & {
176
+ trans: PageRenderThisType["trans"];
177
+ };
178
+
179
+ const devError = (error: ReactElement) => {
180
+ setErrorHtml(renderToString(error));
181
+ };
182
+
183
+ const pageBehaviors = Object.entries(behaviors).reduce(
184
+ (acc: PageBehaviors, [k, value]) => {
185
+ const key = k as keyof Behaviors;
186
+ acc.do[key] = value.do;
187
+ acc.get[key] = value.get;
188
+ acc.given[key] = value.given;
189
+ acc.when[key] = value.when;
190
+ acc.then[key] = value.then;
191
+ // TODO: type Behaviors["definitions"] more precisely
192
+ (acc.definitions as any)[key] = (value as any).definitions;
193
+ return acc;
194
+ },
195
+ {
196
+ do: {},
197
+ get: {},
198
+ given: {},
199
+ when: {},
200
+ then: {},
201
+ definitions: {},
202
+ } as PageBehaviors,
203
+ );
204
+ const bindings: PageRenderThisType = {
205
+ props,
206
+ router,
207
+ do: pageBehaviors.do,
208
+ get: pageBehaviors.get,
209
+ given: pageBehaviors.given,
210
+ when: pageBehaviors.when,
211
+ then: pageBehaviors.then,
212
+ definitions: pageBehaviors.definitions,
213
+ devError,
214
+ trans: trans as PageRenderThisType["trans"],
215
+ lang,
216
+ setTransLang,
217
+ colorScheme,
218
+ setColorScheme,
219
+
220
+ mediaQueries,
221
+ };
222
+ const render = params.render.bind(bindings) as FunctionComponent;
223
+
224
+ useEffect(() => {
225
+ const lifecycleBindings: LifecycleBindings = {
226
+ props,
227
+ router,
228
+ given: pageBehaviors.given,
229
+ when: pageBehaviors.when,
230
+ then: pageBehaviors.then,
231
+ devError,
232
+ trans,
233
+ lang,
234
+ setTransLang,
235
+ colorScheme,
236
+ setColorScheme,
237
+ mediaQueries,
238
+ };
239
+ params.lifecycle?.onMount?.bind(lifecycleBindings)();
240
+ document.addEventListener("--page-update", forceUpdate);
241
+ return () => {
242
+ params.lifecycle?.onUnmount?.bind(lifecycleBindings)();
243
+ document.removeEventListener("--page-update", forceUpdate);
244
+ };
245
+ }, []);
246
+
247
+ useEffect(() => {
248
+ const lifecycleBindings = { props };
249
+ params.lifecycle?.onRender?.bind(lifecycleBindings)();
250
+ });
251
+
252
+ if (errorHtml) {
253
+ return <_ErrorBoundary errorHtml={errorHtml} />;
254
+ }
255
+ const isTest =
256
+ typeof navigator !== "undefined" && navigator.webdriver === true;
257
+ const isDev =
258
+ !Object.keys(router.query).includes("isPreview") &&
259
+ !router.asPath.includes("isPreview") &&
260
+ process.env.NODE_ENV === "development" &&
261
+ !isTest;
262
+ const jsx = render(props) as ReactElementType;
263
+ if (isTest) {
264
+ //@ts-expect-error TODO: fix this
265
+ globalThis.tdd = Object.keys(tdd).reduce(
266
+ (acc, name) => {
267
+ acc[name] = tdd[name].bind(pageBehaviors);
268
+ return acc;
269
+ },
270
+ {} as Record<string, () => Promise<void>>,
271
+ );
272
+ }
273
+ if (!isDev) {
274
+ return (
275
+ <>
276
+ <Head>
277
+ <title>{seo.title}</title>
278
+ <meta name="description" content={seo.description} />
279
+ <meta
280
+ name="viewport"
281
+ content="width=device-width, initial-scale=1"
282
+ />
283
+ <link rel="icon" href="/favicon.ico" />
284
+ </Head>
285
+ {jsx}
286
+ </>
287
+ );
288
+ }
289
+ return (
290
+ <>
291
+ <Head>
292
+ <title>{seo.title}</title>
293
+ <meta name="description" content={seo.description} />
294
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
295
+ <link rel="icon" href="/favicon.ico" />
296
+ </Head>
297
+ <ClientOnly>
298
+ {(isIframe ||
299
+ (typeof window !== "undefined" &&
300
+ window.location.search.includes("isPreview"))) &&
301
+ jsx}
302
+ {!isIframe &&
303
+ !(
304
+ typeof window !== "undefined" &&
305
+ window.location.search.includes("isPreview")
306
+ ) && (
307
+ <Flex h="100vh">
308
+ <PageLeftTabs
309
+ jsx={jsx}
310
+ params={params as unknown as PageParams}
311
+ pageBehaviors={pageBehaviors}
312
+ />
313
+ <PageBrowser />
314
+ <PageRightTabs
315
+ jsx={jsx}
316
+ params={params as unknown as PageParams}
317
+ pageBehaviors={pageBehaviors}
318
+ />
319
+ </Flex>
320
+ )}
321
+ </ClientOnly>
322
+ </>
323
+ );
324
+ };
325
+ Fn.displayName = params.name;
326
+ return Fn;
327
+ };
@@ -0,0 +1,225 @@
1
+ import {
2
+ Button,
3
+ Card,
4
+ Flex,
5
+ Modal,
6
+ ScrollArea,
7
+ Tabs,
8
+ Text,
9
+ TextInput,
10
+ } from "@mantine/core";
11
+ import { Dispatch, FC, SetStateAction, useState } from "react";
12
+ import { FETCH } from "southpaw/api/client";
13
+ import { useContextDev } from "southpaw/contexts/dev";
14
+ import { AutoZoomContainer } from "./autozoom";
15
+ import { UI } from "../../ui";
16
+ import { Blocks } from "../../ui/blocks";
17
+ import { Components } from "@/components";
18
+
19
+ type TCategory = "UI" | "Blocks" | "Components";
20
+ const ModalList: FC<{
21
+ category: TCategory;
22
+ setSelected: Dispatch<SetStateAction<string | undefined>>;
23
+ selected: string | undefined;
24
+ onInsert: () => Promise<void>;
25
+ }> = ({ category, onInsert, selected, setSelected }) => {
26
+ const [search, setSearch] = useState("");
27
+ const List = {
28
+ UI,
29
+ Blocks,
30
+ Components,
31
+ }[category];
32
+ return (
33
+ <>
34
+ <TextInput
35
+ value={search}
36
+ onChange={(event) => {
37
+ setSearch(event.target.value);
38
+ }}
39
+ placeholder="Search placeholder"
40
+ my="sm"
41
+ />
42
+ <ScrollArea offsetScrollbars>
43
+ <Flex h={400} wrap="wrap" p={5} gap="lg">
44
+ {Object.entries(List)
45
+ .filter(([name]) => {
46
+ return name.toLowerCase().includes(search.toLowerCase());
47
+ })
48
+ .map(([name, value]) => {
49
+ const isSelected = selected === name;
50
+ const Component = value;
51
+ return (
52
+ <Card
53
+ key={name}
54
+ shadow="sm"
55
+ radius="md"
56
+ withBorder
57
+ w={225}
58
+ h={225}
59
+ flex="none"
60
+ onClick={() => {
61
+ setSelected(name);
62
+ }}
63
+ onDoubleClick={onInsert}
64
+ p={4}
65
+ styles={{
66
+ root: {
67
+ outline: isSelected
68
+ ? "3px solid var(--mantine-color-dark-5)"
69
+ : "",
70
+ outlineOffset: "0px",
71
+ userSelect: "none",
72
+ },
73
+ }}
74
+ >
75
+ <UI.inviewport>
76
+ {(inViewport, ref) => {
77
+ return (
78
+ <Flex
79
+ ref={ref}
80
+ p={2}
81
+ justify="center"
82
+ align="center"
83
+ bg="gray.0"
84
+ flex={1}
85
+ styles={{
86
+ root: {
87
+ pointerEvents: "none",
88
+ overflow: "hidden",
89
+ position: "relative",
90
+ },
91
+ }}
92
+ >
93
+ {inViewport && (
94
+ <AutoZoomContainer>
95
+ <Component />
96
+ </AutoZoomContainer>
97
+ )}
98
+ </Flex>
99
+ );
100
+ }}
101
+ </UI.inviewport>
102
+ <Text p="sm" fw={500} tt="capitalize">
103
+ {name}
104
+ </Text>
105
+ </Card>
106
+ );
107
+ })}
108
+ </Flex>
109
+ </ScrollArea>
110
+ </>
111
+ );
112
+ };
113
+
114
+ export const ModalInsert: FC = ({}) => {
115
+ const { state, dispatch } = useContextDev();
116
+ const [tab, setTab] = useState<TCategory>("UI");
117
+ // const [promptValue, setPromptValue] = useState(
118
+ // `A UI.text with Hello World`
119
+ // // `A list ui element with 3 items UI.text with text "Hello 1", "Hello 2" and "Hello 3"`
120
+ // // `A UI.flex which showcases the features of the Southpaw framework, use UI.list, UI.alert and other elements to write the section`
121
+ // // `A UI text which contains a short presentation of the inventor of headlights`
122
+ // );
123
+ const [inserting, setInserting] = useState(false);
124
+ const [selected, setSelected] = useState<string | undefined>(undefined);
125
+ const opened = state.modals.insert;
126
+ const insertMode = state.tree.menuNode.insertMode;
127
+ const menuNode = state.tree.menuNode.node;
128
+ const onClose = () => {
129
+ dispatch({
130
+ type: "MODALS_CLOSE",
131
+ args: {
132
+ name: "insert",
133
+ },
134
+ });
135
+ setSelected(undefined);
136
+ };
137
+
138
+ // const unwrapTsxFence = (response: string): string => {
139
+ // const match = response.match(/```tsx\s*([\s\S]*?)```/);
140
+ // return match ? match[1].trim() : response;
141
+ // };
142
+
143
+ const onInsert = async () => {
144
+ setInserting(true);
145
+ const key = selected!;
146
+ const code = `<${tab}.${key} />`;
147
+ setSelected(undefined);
148
+ await FETCH.page.insertNode({
149
+ code,
150
+ source: JSON.parse(menuNode.value),
151
+ insertMode,
152
+ });
153
+ setInserting(false);
154
+ dispatch({
155
+ type: "MODALS_CLOSE",
156
+ args: {
157
+ name: "insert",
158
+ },
159
+ });
160
+ };
161
+
162
+ return (
163
+ <Modal
164
+ opened={opened}
165
+ onClose={onClose}
166
+ title="Insert"
167
+ size="xl"
168
+ overlayProps={{
169
+ backgroundOpacity: 0.3,
170
+ }}
171
+ >
172
+ <Tabs
173
+ value={tab}
174
+ onChange={(value) => {
175
+ if (value) {
176
+ setInserting(false);
177
+ setSelected(undefined);
178
+ setTab(value as typeof tab);
179
+ }
180
+ }}
181
+ orientation="horizontal"
182
+ >
183
+ <Tabs.List>
184
+ <Tabs.Tab value="UI">UI</Tabs.Tab>
185
+ <Tabs.Tab value="Blocks">Blocks</Tabs.Tab>
186
+ <Tabs.Tab value="Components">Components</Tabs.Tab>
187
+ </Tabs.List>
188
+ <Tabs.Panel value="UI">
189
+ <ModalList
190
+ category="UI"
191
+ onInsert={onInsert}
192
+ selected={selected}
193
+ setSelected={setSelected}
194
+ />
195
+ </Tabs.Panel>
196
+ <Tabs.Panel value="Blocks">
197
+ <ModalList
198
+ category="Blocks"
199
+ onInsert={onInsert}
200
+ selected={selected}
201
+ setSelected={setSelected}
202
+ />
203
+ </Tabs.Panel>
204
+ <Tabs.Panel value="Components">
205
+ <ModalList
206
+ category="Components"
207
+ onInsert={onInsert}
208
+ selected={selected}
209
+ setSelected={setSelected}
210
+ />
211
+ </Tabs.Panel>
212
+ </Tabs>
213
+
214
+ <Button
215
+ onClick={onInsert}
216
+ disabled={!selected}
217
+ mt="md"
218
+ color="dark"
219
+ loading={inserting}
220
+ >
221
+ Insert
222
+ </Button>
223
+ </Modal>
224
+ );
225
+ };