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,237 @@
1
+ import { Center, Tabs, TreeNodeData } from "@mantine/core";
2
+ import { FC, useCallback, useEffect, useState } from "react";
3
+ import { useContextDev } from "southpaw/contexts/dev";
4
+ import { PropsDef } from "southpaw/modules/component";
5
+ import { PageBehaviors } from "..";
6
+ import { KnobColor } from "../knobs/color";
7
+ import { KnobNumber } from "../knobs/number";
8
+ import { KnobSegmented } from "../knobs/segmented";
9
+ import { KnobSelect } from "../knobs/select";
10
+ import { KnobSwitch } from "../knobs/switch";
11
+ import { KnobText } from "../knobs/text";
12
+ import { KnobListText } from "../knobs/list-text";
13
+ import { KnobListJson } from "../knobs/list-json";
14
+ import { KnobAction } from "../knobs/action";
15
+ import InspectWrapper from "./wrapper";
16
+ import { KnobSize } from "../knobs/size";
17
+ import { KnobIcon } from "../knobs/icon";
18
+ import { UI } from "../../../ui";
19
+ import { Blocks } from "../../../ui/blocks";
20
+ import { Components } from "@/components";
21
+
22
+ const NodeInspector: FC<{ node: TreeNodeData; behaviors: PageBehaviors }> = ({
23
+ node,
24
+ behaviors,
25
+ }) => {
26
+ const [propsDef, setPropsDef] = useState<PropsDef>({});
27
+ const label = String(node.label || "");
28
+ const [category, name] = label.split(".") as [
29
+ "UI" | "Blocks" | "Components",
30
+ keyof typeof UI | keyof typeof Blocks | keyof typeof Components,
31
+ ];
32
+ const propDefs = Object.entries(propsDef);
33
+ const refresh = useCallback(async () => {
34
+ switch (category) {
35
+ case "UI":
36
+ const { default: UiComp } = await import(`southpaw/ui/${name}`);
37
+ setPropsDef(UiComp.params.propsDef);
38
+ break;
39
+ case "Blocks":
40
+ const { default: BlocksComp } = await import(
41
+ `southpaw/ui/blocks/${name}`
42
+ );
43
+ setPropsDef(BlocksComp.params.propsDef);
44
+ break;
45
+ case "Components":
46
+ const { default: ComponentsComp } = await import(
47
+ `@/components/${name}`
48
+ );
49
+ setPropsDef(ComponentsComp.params.propsDef);
50
+ break;
51
+ }
52
+ }, [category, name]);
53
+ useEffect(() => {
54
+ refresh();
55
+ }, [node, refresh]);
56
+
57
+ if (propDefs.length === 0) {
58
+ return <Center>No prop def found</Center>;
59
+ }
60
+ return (
61
+ <InspectWrapper node={node} behaviors={behaviors}>
62
+ {({ gettersData, node, rawProps, ready }) => {
63
+ return (
64
+ <>
65
+ {propDefs.map(([propName, value]) => {
66
+ switch (value.type) {
67
+ case "color":
68
+ return (
69
+ <KnobColor
70
+ key={propName}
71
+ label={value.label}
72
+ propName={propName}
73
+ gettersData={gettersData}
74
+ node={node}
75
+ rawProps={rawProps}
76
+ ready={ready}
77
+ />
78
+ );
79
+ case "size":
80
+ return (
81
+ <KnobSize
82
+ key={propName}
83
+ label={value.label}
84
+ propName={propName}
85
+ gettersData={gettersData}
86
+ node={node}
87
+ rawProps={rawProps}
88
+ ready={ready}
89
+ />
90
+ );
91
+ case "icon":
92
+ return (
93
+ <KnobIcon
94
+ key={propName}
95
+ label={value.label}
96
+ propName={propName}
97
+ gettersData={gettersData}
98
+ node={node}
99
+ rawProps={rawProps}
100
+ ready={ready}
101
+ />
102
+ );
103
+ case "number":
104
+ return (
105
+ <KnobNumber
106
+ key={propName}
107
+ label={value.label}
108
+ propName={propName}
109
+ gettersData={gettersData}
110
+ node={node}
111
+ rawProps={rawProps}
112
+ ready={ready}
113
+ min={value.min}
114
+ max={value.max}
115
+ step={value.step}
116
+ />
117
+ );
118
+ case "segmented":
119
+ return (
120
+ <KnobSegmented
121
+ key={propName}
122
+ label={value.label}
123
+ options={value.options}
124
+ propName={propName}
125
+ gettersData={gettersData}
126
+ node={node}
127
+ rawProps={rawProps}
128
+ ready={ready}
129
+ />
130
+ );
131
+ case "select":
132
+ return (
133
+ <KnobSelect
134
+ key={propName}
135
+ label={value.label}
136
+ options={value.options}
137
+ propName={propName}
138
+ gettersData={gettersData}
139
+ node={node}
140
+ rawProps={rawProps}
141
+ ready={ready}
142
+ />
143
+ );
144
+ case "switch":
145
+ return (
146
+ <KnobSwitch
147
+ key={propName}
148
+ label={value.label}
149
+ propName={propName}
150
+ gettersData={gettersData}
151
+ node={node}
152
+ rawProps={rawProps}
153
+ ready={ready}
154
+ />
155
+ );
156
+ case "text":
157
+ return (
158
+ <KnobText
159
+ key={propName}
160
+ label={value.label}
161
+ multiline={value.multiline}
162
+ propName={propName}
163
+ gettersData={gettersData}
164
+ node={node}
165
+ rawProps={rawProps}
166
+ ready={ready}
167
+ />
168
+ );
169
+ case "list-text":
170
+ return (
171
+ <KnobListText
172
+ key={propName}
173
+ label={value.label}
174
+ propName={propName}
175
+ gettersData={gettersData}
176
+ node={node}
177
+ rawProps={rawProps}
178
+ ready={ready}
179
+ />
180
+ );
181
+ case "list-json":
182
+ return (
183
+ <KnobListJson
184
+ key={propName}
185
+ label={value.label}
186
+ propName={propName}
187
+ gettersData={gettersData}
188
+ node={node}
189
+ rawProps={rawProps}
190
+ ready={ready}
191
+ />
192
+ );
193
+ case "action":
194
+ return (
195
+ <KnobAction
196
+ key={propName}
197
+ label={value.label}
198
+ propName={propName}
199
+ gettersData={gettersData}
200
+ node={node}
201
+ rawProps={rawProps}
202
+ ready={ready}
203
+ behaviors={behaviors}
204
+ />
205
+ );
206
+ }
207
+ })}
208
+ </>
209
+ );
210
+ }}
211
+ </InspectWrapper>
212
+ );
213
+ };
214
+ type Props = {
215
+ behaviors: PageBehaviors;
216
+ };
217
+ export const TabPageInspect: FC<Props> = ({ behaviors }) => {
218
+ const { state } = useContextDev();
219
+ const selectedNode = state.tree.selectedNode;
220
+ return (
221
+ <Tabs.Panel
222
+ key={selectedNode?.value}
223
+ value="inspect"
224
+ p="xs"
225
+ w={400}
226
+ style={{
227
+ overflow: "auto",
228
+ }}
229
+ >
230
+ {selectedNode ? (
231
+ <NodeInspector node={selectedNode} behaviors={behaviors} />
232
+ ) : (
233
+ <div>Select a node</div>
234
+ )}
235
+ </Tabs.Panel>
236
+ );
237
+ };
@@ -0,0 +1,88 @@
1
+ import { ComboboxData, Flex, LoadingOverlay } from "@mantine/core";
2
+ import { FC, ReactNode, useEffect, useState } from "react";
3
+ import { FETCH } from "southpaw/api/client";
4
+ import { TNode } from "southpaw/contexts/dev";
5
+ import { PageBehaviors } from "..";
6
+ import { translations } from "@/translations";
7
+
8
+ type Props = {
9
+ node: TNode;
10
+ behaviors: PageBehaviors;
11
+ children: (args: {
12
+ gettersData: ComboboxData;
13
+ node: TNode;
14
+ rawProps: Record<string, string>;
15
+ ready: boolean;
16
+ }) => ReactNode;
17
+ };
18
+ const InspectWrapper: FC<Props> = ({ node, behaviors, children }) => {
19
+ const [ready, setReady] = useState(false);
20
+ const [rawProps, setRawProps] = useState<Record<string, string>>({});
21
+ const gettersData: ComboboxData = [
22
+ ...Object.keys(behaviors.definitions).map((storeName) => {
23
+ const defs =
24
+ behaviors.definitions[storeName as keyof typeof behaviors.definitions];
25
+ const items = Object.keys(defs["then"]).map((key) => {
26
+ return {
27
+ label: key,
28
+ value: JSON.stringify({
29
+ type: "get",
30
+ storeName,
31
+ key,
32
+ }),
33
+ };
34
+ });
35
+ return { group: storeName, items };
36
+ }),
37
+ {
38
+ group: "Translations",
39
+ items: Object.entries(translations).flatMap(([category, categoryTranslations]) => {
40
+ const actualTranslations = (categoryTranslations as any).translations || {};
41
+ return Object.keys(actualTranslations).map((key) => {
42
+ return {
43
+ label: `${category}.${key}`,
44
+ value: JSON.stringify({
45
+ type: "trans",
46
+ storeName: category,
47
+ key,
48
+ }),
49
+ };
50
+ });
51
+ }),
52
+ },
53
+ ];
54
+
55
+ useEffect(() => {
56
+ setReady(false);
57
+ const source = JSON.parse(node.value);
58
+ FETCH.page
59
+ .getRawPropsNode({
60
+ source,
61
+ })
62
+ .then(({ payload: { rawProps } }) => {
63
+ setRawProps(rawProps);
64
+ setReady(true);
65
+ });
66
+ }, [node.value]);
67
+
68
+ if (!ready) {
69
+ return (
70
+ <Flex>
71
+ <LoadingOverlay />
72
+ </Flex>
73
+ );
74
+ }
75
+
76
+ return (
77
+ <>
78
+ {children({
79
+ gettersData,
80
+ node,
81
+ rawProps,
82
+ ready,
83
+ })}
84
+ </>
85
+ );
86
+ };
87
+
88
+ export default InspectWrapper;
@@ -0,0 +1,58 @@
1
+ import { ComboboxData, Fieldset, Select } from "@mantine/core";
2
+ import { FC, useEffect, useState } from "react";
3
+ import { TNode } from "southpaw/contexts/dev";
4
+ import { useDebounceCb } from "southpaw/hooks/useDebounceCb";
5
+ import { KNOB_DEBOUNCE, refreshProp } from "./helpers";
6
+ import { PageBehaviors } from "..";
7
+
8
+ export const KnobAction: FC<{
9
+ gettersData: ComboboxData;
10
+ node: TNode;
11
+ rawProps: Record<string, string>;
12
+ ready: boolean;
13
+ propName: string;
14
+ label: string;
15
+ behaviors: PageBehaviors;
16
+ }> = ({ node, rawProps, ready, propName, label, behaviors }) => {
17
+ const [value, setValue] = useState<string>();
18
+
19
+ const options = Object.entries(behaviors.definitions).flatMap(
20
+ ([behaviorName, definition]) => {
21
+ return Object.keys(definition.when).map((actionName) => ({
22
+ label: `${behaviorName}: ${actionName}`,
23
+ value: `{() => this.do.${behaviorName}("${actionName}")()}`,
24
+ }));
25
+ },
26
+ );
27
+
28
+ const refreshFile = useDebounceCb(async (value: unknown) => {
29
+ await refreshProp(value as string | undefined, node, propName);
30
+ }, KNOB_DEBOUNCE);
31
+
32
+ useEffect(() => {
33
+ if (!ready) {
34
+ return;
35
+ }
36
+ setValue(rawProps[propName]);
37
+ }, [ready, rawProps, propName]);
38
+
39
+ return (
40
+ <Fieldset legend={label} style={{ border: "none" }} px={0} py={10}>
41
+ <Select
42
+ flex={1}
43
+ data={[
44
+ {
45
+ label: "Select an action",
46
+ value: "",
47
+ },
48
+ ...options,
49
+ ]}
50
+ value={value || ""}
51
+ onChange={(value) => {
52
+ setValue(value || undefined);
53
+ refreshFile(value || undefined);
54
+ }}
55
+ />
56
+ </Fieldset>
57
+ );
58
+ };
@@ -0,0 +1,78 @@
1
+ import { ComboboxData, useMantineTheme, ColorInput } from "@mantine/core";
2
+ import { FC, useEffect, useState } from "react";
3
+ import { TNode } from "southpaw/contexts/dev";
4
+ import { useDebounceCb } from "southpaw/hooks/useDebounceCb";
5
+ import { isRawPropGet, KNOB_DEBOUNCE, refreshProp } from "./helpers";
6
+ import { Linker } from "./linker";
7
+
8
+ export const KnobColor: FC<{
9
+ gettersData: ComboboxData;
10
+ node: TNode;
11
+ rawProps: Record<string, string>;
12
+ ready: boolean;
13
+ propName: string;
14
+ label: string;
15
+ }> = ({ gettersData, node, rawProps, ready, propName, label }) => {
16
+ const [colorValue, setColorValue] = useState<string>();
17
+ const [rawGetter, setRawGetter] = useState<{
18
+ type: "get" | "trans";
19
+ storeName: string;
20
+ key: string;
21
+ }>();
22
+ const theme = useMantineTheme();
23
+ const props = node.nodeProps || {};
24
+
25
+ const colorNames = Object.keys(theme.colors);
26
+ const swatches = colorNames.map((color) => ({
27
+ color,
28
+ value: theme.colors[color][5],
29
+ varName: `--mantine-color-${color}-5`,
30
+ }));
31
+
32
+ const getColorValue = (value: string) =>
33
+ colorNames.includes(value) ? theme.colors[value][5] : value;
34
+
35
+ const refreshFile = useDebounceCb(
36
+ async (value: unknown) => {
37
+ const swatch = swatches.find((s) => s.value === value);
38
+ const propValue = swatch ? swatch.color : value;
39
+ await refreshProp(`"${propValue}"`, node, propName);
40
+ },
41
+ KNOB_DEBOUNCE
42
+ );
43
+
44
+ useEffect(() => {
45
+ const getter = isRawPropGet(rawProps[propName]);
46
+ setRawGetter(getter || undefined);
47
+ }, [rawProps[propName]]);
48
+
49
+ useEffect(() => {
50
+ if (ready) {
51
+ setColorValue(getColorValue(props[propName]));
52
+ }
53
+ }, [ready, props[propName]]);
54
+
55
+ return (
56
+ <Linker
57
+ gettersData={gettersData}
58
+ node={node}
59
+ propName={propName}
60
+ rawGetter={rawGetter}
61
+ setRawGetter={setRawGetter}
62
+ value={colorValue}
63
+ label={label}
64
+ >
65
+ <ColorInput
66
+ flex={1}
67
+ disabled={Boolean(rawGetter)}
68
+ format="hex"
69
+ value={colorValue}
70
+ onChange={(value) => {
71
+ setColorValue(value);
72
+ refreshFile(value);
73
+ }}
74
+ swatches={swatches.map((s) => s.value)}
75
+ />
76
+ </Linker>
77
+ );
78
+ };
@@ -0,0 +1,71 @@
1
+ import { FETCH } from "southpaw/api/client";
2
+ import { TNode } from "southpaw/contexts/dev";
3
+
4
+ export const KNOB_DEBOUNCE = 500;
5
+ export const refreshProp = async (
6
+ newValue: string | undefined,
7
+ node: TNode,
8
+ propName: string,
9
+ ) => {
10
+ await FETCH.page.setPropNode({
11
+ source: JSON.parse(node.value),
12
+ attrs: [
13
+ {
14
+ name: propName,
15
+ newValue,
16
+ },
17
+ ],
18
+ });
19
+ };
20
+ export const isRawPropGet = (value: string) => {
21
+ const getRegex =
22
+ /(\{this\.get\.([a-zA-Z_$][a-zA-Z0-9_$]*)\(\s*["']([^"']+)["']\s*\)\})/gm;
23
+ const transRegex =
24
+ /(\{this\.trans\.([a-zA-Z_$][a-zA-Z0-9_$]*)\(\s*["']([^"']+)["']\s*\)\})/gm;
25
+
26
+ const getMatch = getRegex.exec(value || "");
27
+ if (getMatch) {
28
+ return {
29
+ type: "get" as const,
30
+ storeName: getMatch[2],
31
+ key: getMatch[3],
32
+ };
33
+ }
34
+
35
+ const transMatch = transRegex.exec(value || "");
36
+ if (transMatch) {
37
+ return {
38
+ type: "trans" as const,
39
+ storeName: transMatch[2],
40
+ key: transMatch[3],
41
+ };
42
+ }
43
+
44
+ return undefined;
45
+ };
46
+
47
+ export const isRawPropAction = (value: string) => {
48
+ const regex =
49
+ /(\{this\.when\.([a-zA-Z_$][a-zA-Z0-9_$]*)\(\s*["']([^"']+)["']\s*\)\})/gm;
50
+ const match = regex.exec(value || "");
51
+ if (!match) {
52
+ return undefined;
53
+ }
54
+ const [, , behaviorName, actionName] = match;
55
+
56
+ return {
57
+ behaviorName,
58
+ actionName,
59
+ };
60
+ };
61
+
62
+ export const isPropResponsive = (value: unknown) => {
63
+ const isObject =
64
+ typeof value === "object" && !Array.isArray(value) && value !== null;
65
+ if (isObject) {
66
+ return Object.keys(isObject).some((key) =>
67
+ ["xs", "sm", "md", "lg", "xl"].includes(key),
68
+ );
69
+ }
70
+ return false;
71
+ };
@@ -0,0 +1,85 @@
1
+ import { ComboboxData, Group, Select, SelectProps } from "@mantine/core";
2
+ import { FC, useEffect, useState } from "react";
3
+ import { TNode } from "southpaw/contexts/dev";
4
+ import { useDebounceCb } from "southpaw/hooks/useDebounceCb";
5
+ import { isRawPropGet, KNOB_DEBOUNCE, refreshProp } from "./helpers";
6
+ import { Linker } from "./linker";
7
+ import { ICONS } from "../../../constants/icons";
8
+ import { IconCheck } from "@tabler/icons-react";
9
+ import { UI } from "../../../ui";
10
+
11
+ const renderOption: SelectProps["renderOption"] = ({ option, checked }) => (
12
+ <Group flex="1" gap="xs">
13
+ <UI.icon icon={option.value as (typeof ICONS)[number]} />
14
+ {option.label}
15
+ {checked && <IconCheck />}
16
+ </Group>
17
+ );
18
+
19
+ export const KnobIcon: FC<{
20
+ gettersData: ComboboxData;
21
+ node: TNode;
22
+ rawProps: Record<string, string>;
23
+ ready: boolean;
24
+ propName: string;
25
+ label: string;
26
+ }> = ({ gettersData, node, rawProps, ready, propName, label }) => {
27
+ const [value, setValue] = useState<string>();
28
+ const [rawGetter, setRawGetter] = useState<{
29
+ type: "get" | "trans";
30
+ storeName: string;
31
+ key: string;
32
+ }>();
33
+
34
+ const props = node.nodeProps || {};
35
+ const rawGetterStr = JSON.stringify(rawGetter);
36
+
37
+ const refreshFile = useDebounceCb(
38
+ async (value: unknown) => {
39
+ await refreshProp(value ? `"${value}"` : undefined, node, propName);
40
+ },
41
+ KNOB_DEBOUNCE,
42
+ );
43
+
44
+ useEffect(() => {
45
+ const getter = isRawPropGet(rawProps[propName]);
46
+ if (getter) {
47
+ setRawGetter(getter);
48
+ } else {
49
+ setRawGetter(undefined);
50
+ }
51
+ }, [rawProps[propName]]);
52
+
53
+ useEffect(() => {
54
+ if (!ready) {
55
+ return;
56
+ }
57
+ setValue(props[propName]);
58
+ }, [ready, props[propName]]);
59
+
60
+ return (
61
+ <Linker
62
+ gettersData={gettersData}
63
+ node={node}
64
+ propName={propName}
65
+ rawGetter={rawGetter}
66
+ setRawGetter={setRawGetter}
67
+ value={value}
68
+ label={label}
69
+ >
70
+ <Select
71
+ flex={1}
72
+ disabled={Boolean(rawGetterStr)}
73
+ data={ICONS.map((icon) => ({ value: icon, label: icon }))}
74
+ renderOption={renderOption}
75
+ value={value || ""}
76
+ onChange={(value) => {
77
+ setValue(value || undefined);
78
+ refreshFile(value || undefined);
79
+ }}
80
+ limit={20}
81
+ searchable
82
+ />
83
+ </Linker>
84
+ );
85
+ };