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,188 @@
1
+ import {
2
+ Button,
3
+ Combobox,
4
+ ComboboxData,
5
+ ComboboxItem,
6
+ ComboboxItemGroup,
7
+ Fieldset,
8
+ Group,
9
+ Tooltip,
10
+ useCombobox,
11
+ } from "@mantine/core";
12
+ import {
13
+ Dispatch,
14
+ FC,
15
+ ReactNode,
16
+ SetStateAction,
17
+ useEffect,
18
+ useState,
19
+ } from "react";
20
+ import { refreshProp } from "./helpers";
21
+ import { TNode } from "southpaw/contexts/dev";
22
+ import { IconCheck, IconLineDashed, IconLink } from "@tabler/icons-react";
23
+
24
+ type Props = {
25
+ children: ReactNode;
26
+ node: TNode;
27
+ label: string;
28
+ propName: string;
29
+ value: unknown;
30
+ gettersData: ComboboxData;
31
+ rawGetter:
32
+ | {
33
+ type: "get" | "trans";
34
+ storeName: string;
35
+ key: string;
36
+ }
37
+ | undefined;
38
+ setRawGetter: Dispatch<
39
+ SetStateAction<
40
+ | {
41
+ type: "get" | "trans";
42
+ storeName: string;
43
+ key: string;
44
+ }
45
+ | undefined
46
+ >
47
+ >;
48
+ };
49
+ export const Linker: FC<Props> = ({
50
+ children,
51
+ label,
52
+ gettersData,
53
+ rawGetter,
54
+ setRawGetter,
55
+ node,
56
+ propName,
57
+ value,
58
+ }) => {
59
+ const [search, setSearch] = useState("");
60
+ const [selectedItem, setSelectedItem] = useState<string>(
61
+ JSON.stringify(rawGetter)
62
+ );
63
+ const isValueDefined = typeof value !== "undefined" && value !== "";
64
+
65
+ const combobox = useCombobox({
66
+ onDropdownClose: () => {
67
+ combobox.resetSelectedOption();
68
+ combobox.focusTarget();
69
+ setSearch("");
70
+ },
71
+
72
+ onDropdownOpen: () => {
73
+ combobox.focusSearchInput();
74
+ },
75
+ });
76
+
77
+ useEffect(() => {
78
+ setSelectedItem(JSON.stringify(rawGetter));
79
+ }, [rawGetter]);
80
+
81
+ return (
82
+ <Fieldset legend={label} style={{ border: "none" }} px={0} py={10}>
83
+ <Group align="center">
84
+ <Combobox
85
+ store={combobox}
86
+ width={250}
87
+ position="bottom-start"
88
+ withArrow
89
+ onOptionSubmit={(val) => {
90
+ const shouldToggleOff = val === selectedItem;
91
+ if (!shouldToggleOff) {
92
+ setSelectedItem(val);
93
+ combobox.closeDropdown();
94
+ const getter = JSON.parse(val) as {
95
+ type: "get" | "trans";
96
+ storeName: string;
97
+ key: string;
98
+ };
99
+ setRawGetter(getter);
100
+ refreshProp(
101
+ getter.type === "get"
102
+ ? `{this.get.${getter.storeName}("${getter.key}")}`
103
+ : `{this.trans.${getter.storeName}("${getter.key}")}`,
104
+ node,
105
+ propName
106
+ );
107
+ } else {
108
+ setRawGetter(undefined);
109
+ refreshProp(
110
+ isValueDefined ? `"${value}"` : undefined,
111
+ node,
112
+ propName
113
+ );
114
+ }
115
+ }}
116
+ >
117
+ <Combobox.Target withAriaAttributes={false}>
118
+ <Tooltip label="Attach a getter to the prop">
119
+ <Button
120
+ size="sm"
121
+ variant={rawGetter ? "filled" : "outline"}
122
+ color={
123
+ rawGetter ? "var(--mantine-primary-color-filled)" : "gray"
124
+ }
125
+ onClick={() => combobox.toggleDropdown()}
126
+ leftSection={rawGetter?.key}
127
+ p={!rawGetter ? 0 : undefined}
128
+ style={
129
+ !rawGetter
130
+ ? {
131
+ aspectRatio: "1 / 1",
132
+ }
133
+ : undefined
134
+ }
135
+ >
136
+ {rawGetter ? (
137
+ <IconLink size={18} />
138
+ ) : (
139
+ <IconLineDashed size={18} />
140
+ )}
141
+ </Button>
142
+ </Tooltip>
143
+ </Combobox.Target>
144
+
145
+ <Combobox.Dropdown>
146
+ <Combobox.Search
147
+ value={search}
148
+ onChange={(event) => setSearch(event.currentTarget.value)}
149
+ placeholder="Search a getter"
150
+ />
151
+ <Combobox.Options mah={300} style={{ overflowY: 'auto' }}>
152
+ {gettersData.length > 0 ? (
153
+ gettersData.map((group) => {
154
+ const option = group as ComboboxItemGroup;
155
+ return (
156
+ <Combobox.Group label={option.group} key={option.group}>
157
+ {option.items.map((opt) => {
158
+ const item = opt as ComboboxItem;
159
+ const active = item.value === selectedItem;
160
+ return (
161
+ <Combobox.Option
162
+ value={item.value}
163
+ disabled={item.disabled}
164
+ active={active}
165
+ key={item.value}
166
+ >
167
+ <Group align="center" gap="xs">
168
+ {active && <IconCheck />}
169
+ {item.label}
170
+ </Group>
171
+ </Combobox.Option>
172
+ );
173
+ })}
174
+ </Combobox.Group>
175
+ );
176
+ })
177
+ ) : (
178
+ <Combobox.Empty>Nothing found</Combobox.Empty>
179
+ )}
180
+ </Combobox.Options>
181
+ </Combobox.Dropdown>
182
+ </Combobox>
183
+
184
+ {children}
185
+ </Group>
186
+ </Fieldset>
187
+ );
188
+ };
@@ -0,0 +1,149 @@
1
+ import {
2
+ JsonInput,
3
+ Button,
4
+ Group,
5
+ Stack,
6
+ ActionIcon,
7
+ ComboboxData,
8
+ } from "@mantine/core";
9
+ import { FC, useEffect, useState } from "react";
10
+ import { TNode } from "southpaw/contexts/dev";
11
+ import { useDebounceCb } from "southpaw/hooks/useDebounceCb";
12
+ import { isRawPropGet, KNOB_DEBOUNCE, refreshProp } from "./helpers";
13
+ import { Linker } from "./linker";
14
+ import { IconPlus, IconTrash } from "@tabler/icons-react";
15
+
16
+ export const KnobListJson: FC<{
17
+ gettersData: ComboboxData;
18
+ node: TNode;
19
+ rawProps: Record<string, string>;
20
+ ready: boolean;
21
+ propName: string;
22
+ label: string;
23
+ }> = ({ gettersData, node, rawProps, ready, propName, label }) => {
24
+ const [value, setValue] = useState<unknown[]>([]);
25
+ const [localValues, setLocalValues] = useState<string[]>([]);
26
+ const [rawGetter, setRawGetter] = useState<{
27
+ type: "get" | "trans";
28
+ storeName: string;
29
+ key: string;
30
+ }>();
31
+
32
+ const props = node.nodeProps || {};
33
+ const rawGetterStr = JSON.stringify(rawGetter);
34
+ const currentPropValue = props[propName];
35
+ const currentRawProp = rawProps[propName];
36
+
37
+ const refreshFile = useDebounceCb(
38
+ async (newValue: unknown) => {
39
+ const arrayValue = Array.isArray(newValue) ? newValue : [];
40
+ const formattedValue =
41
+ arrayValue.length > 0
42
+ ? `{[${arrayValue.map((item) => JSON.stringify(item)).join(", ")}]}`
43
+ : undefined;
44
+ await refreshProp(formattedValue, node, propName);
45
+ },
46
+ KNOB_DEBOUNCE,
47
+ );
48
+
49
+ const addItem = () => {
50
+ const newValue = [...value, {}];
51
+ setValue(newValue);
52
+ setLocalValues([...localValues, "{}"]);
53
+ refreshFile(newValue);
54
+ };
55
+
56
+ const removeItem = (index: number) => {
57
+ const newValue = value.filter((_, i) => i !== index);
58
+ setValue(newValue);
59
+ setLocalValues(localValues.filter((_, i) => i !== index));
60
+ refreshFile(newValue);
61
+ };
62
+
63
+ const updateItem = (index: number, newJsonText: string) => {
64
+ const newLocalValues = [...localValues];
65
+ newLocalValues[index] = newJsonText;
66
+ setLocalValues(newLocalValues);
67
+
68
+ try {
69
+ const jsonValue = JSON.parse(newJsonText);
70
+ const newValue = [...value];
71
+ newValue[index] = jsonValue;
72
+ setValue(newValue);
73
+ refreshFile(newValue);
74
+ } catch {
75
+ // Invalid JSON, don't update parent yet, but store local string
76
+ }
77
+ };
78
+
79
+ useEffect(() => {
80
+ const getter = isRawPropGet(currentRawProp);
81
+ if (getter) {
82
+ setRawGetter(getter);
83
+ } else {
84
+ setRawGetter(undefined);
85
+ }
86
+ }, [currentPropValue, currentRawProp]);
87
+
88
+ useEffect(() => {
89
+ if (!ready) {
90
+ return;
91
+ }
92
+ const propValue = currentPropValue;
93
+ const arrayValue = Array.isArray(propValue) ? propValue : [];
94
+ setValue(arrayValue);
95
+ setLocalValues(arrayValue.map((v) => JSON.stringify(v, null, 2)));
96
+ }, [ready, currentPropValue]);
97
+
98
+ return (
99
+ <Linker
100
+ gettersData={gettersData}
101
+ node={node}
102
+ propName={propName}
103
+ rawGetter={rawGetter}
104
+ setRawGetter={setRawGetter}
105
+ value={value}
106
+ label={label}
107
+ >
108
+ <Stack flex={1} gap="xs">
109
+ {localValues.map((item, index) => (
110
+ <Group key={index} gap="xs" align="flex-start">
111
+ <JsonInput
112
+ flex={1}
113
+ disabled={Boolean(rawGetterStr)}
114
+ value={item}
115
+ placeholder={`Item ${index + 1} (JSON)`}
116
+ validationError="Invalid JSON"
117
+ formatOnBlur
118
+ autosize
119
+ minRows={3}
120
+ onChange={(newText) => {
121
+ updateItem(index, newText);
122
+ }}
123
+ />
124
+ <ActionIcon
125
+ variant="outline"
126
+ color="red"
127
+ disabled={Boolean(rawGetterStr)}
128
+ onClick={() => removeItem(index)}
129
+ size="sm"
130
+ mt={4}
131
+ >
132
+ <IconTrash size={16} />
133
+ </ActionIcon>
134
+ </Group>
135
+ ))}
136
+
137
+ <Button
138
+ variant="outline"
139
+ leftSection={<IconPlus size={16} />}
140
+ disabled={Boolean(rawGetterStr)}
141
+ onClick={addItem}
142
+ size="sm"
143
+ >
144
+ Add Item
145
+ </Button>
146
+ </Stack>
147
+ </Linker>
148
+ );
149
+ };
@@ -0,0 +1,129 @@
1
+ import {
2
+ ComboboxData,
3
+ TextInput,
4
+ Button,
5
+ Group,
6
+ Stack,
7
+ ActionIcon,
8
+ } from "@mantine/core";
9
+ import { FC, useEffect, useState } from "react";
10
+ import { TNode } from "southpaw/contexts/dev";
11
+ import { useDebounceCb } from "southpaw/hooks/useDebounceCb";
12
+ import { isRawPropGet, KNOB_DEBOUNCE, refreshProp } from "./helpers";
13
+ import { Linker } from "./linker";
14
+ import { IconPlus, IconTrash } from "@tabler/icons-react";
15
+
16
+ export const KnobListText: FC<{
17
+ gettersData: ComboboxData;
18
+ node: TNode;
19
+ rawProps: Record<string, string>;
20
+ ready: boolean;
21
+ propName: string;
22
+ label: string;
23
+ }> = ({ gettersData, node, rawProps, ready, propName, label }) => {
24
+ const [value, setValue] = useState<string[]>([]);
25
+ const [rawGetter, setRawGetter] = useState<{
26
+ type: "get" | "trans";
27
+ storeName: string;
28
+ key: string;
29
+ }>();
30
+
31
+ const props = node.nodeProps || {};
32
+ const rawGetterStr = JSON.stringify(rawGetter);
33
+
34
+ const refreshFile = useDebounceCb(
35
+ async (newValue: unknown) => {
36
+ const arrayValue = Array.isArray(newValue) ? newValue : [];
37
+ const formattedValue =
38
+ arrayValue.length > 0
39
+ ? `{[${arrayValue.map((item) => `"${item}"`).join(", ")}]}`
40
+ : undefined;
41
+ await refreshProp(formattedValue, node, propName);
42
+ },
43
+ KNOB_DEBOUNCE,
44
+ );
45
+
46
+ const addItem = () => {
47
+ const newValue = [...value, ""];
48
+ setValue(newValue);
49
+ refreshFile(newValue);
50
+ };
51
+
52
+ const removeItem = (index: number) => {
53
+ const newValue = value.filter((_, i) => i !== index);
54
+ setValue(newValue);
55
+ refreshFile(newValue);
56
+ };
57
+
58
+ const updateItem = (index: number, newText: string) => {
59
+ const newValue = [...value];
60
+ newValue[index] = newText;
61
+ setValue(newValue);
62
+ refreshFile(newValue);
63
+ };
64
+
65
+ useEffect(() => {
66
+ const getter = isRawPropGet(rawProps[propName]);
67
+ if (getter) {
68
+ setRawGetter(getter);
69
+ } else {
70
+ setRawGetter(undefined);
71
+ }
72
+ }, [rawProps[propName]]);
73
+
74
+ useEffect(() => {
75
+ if (!ready) {
76
+ return;
77
+ }
78
+ const propValue = props[propName];
79
+ const arrayValue = Array.isArray(propValue) ? propValue : [];
80
+ setValue(arrayValue);
81
+ }, [ready, props[propName]]);
82
+
83
+ return (
84
+ <Linker
85
+ gettersData={gettersData}
86
+ node={node}
87
+ propName={propName}
88
+ rawGetter={rawGetter}
89
+ setRawGetter={setRawGetter}
90
+ value={value}
91
+ label={label}
92
+ >
93
+ <Stack flex={1} gap="xs">
94
+ {value.map((item, index) => (
95
+ <Group key={index} gap="xs" align="center">
96
+ <TextInput
97
+ flex={1}
98
+ disabled={Boolean(rawGetterStr)}
99
+ value={item}
100
+ placeholder={`Item ${index + 1}`}
101
+ onChange={(event) => {
102
+ updateItem(index, event.target.value);
103
+ }}
104
+ />
105
+ <ActionIcon
106
+ variant="outline"
107
+ color="red"
108
+ disabled={Boolean(rawGetterStr)}
109
+ onClick={() => removeItem(index)}
110
+ size="sm"
111
+ >
112
+ <IconTrash size={16} />
113
+ </ActionIcon>
114
+ </Group>
115
+ ))}
116
+
117
+ <Button
118
+ variant="outline"
119
+ leftSection={<IconPlus size={16} />}
120
+ disabled={Boolean(rawGetterStr)}
121
+ onClick={addItem}
122
+ size="sm"
123
+ >
124
+ Add Item
125
+ </Button>
126
+ </Stack>
127
+ </Linker>
128
+ );
129
+ };
@@ -0,0 +1,84 @@
1
+ import { ComboboxData, NumberInput } 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 KnobNumber: FC<{
9
+ gettersData: ComboboxData;
10
+ node: TNode;
11
+ rawProps: Record<string, string>;
12
+ ready: boolean;
13
+ propName: string;
14
+ label: string;
15
+ min?: number;
16
+ max?: number;
17
+ step?: number;
18
+ }> = ({ gettersData, node, rawProps, ready, propName, label, min, max, step }) => {
19
+ const [value, setValue] = useState<number | string>("");
20
+ const [rawGetter, setRawGetter] = useState<{
21
+ type: "get" | "trans";
22
+ storeName: string;
23
+ key: string;
24
+ }>();
25
+ const props = node.nodeProps || {};
26
+ const rawGetterStr = JSON.stringify(rawGetter);
27
+
28
+ const refreshFile = useDebounceCb(
29
+ async (value: unknown) => {
30
+ await refreshProp(
31
+ value === "" ? undefined : `{${value}}`,
32
+ node,
33
+ propName
34
+ );
35
+ },
36
+ KNOB_DEBOUNCE
37
+ );
38
+
39
+ useEffect(() => {
40
+ const getter = isRawPropGet(rawProps[propName]);
41
+ if (getter) {
42
+ setRawGetter(getter);
43
+ } else {
44
+ setRawGetter(undefined);
45
+ }
46
+ }, [rawProps[propName]]);
47
+
48
+ useEffect(() => {
49
+ if (!ready) {
50
+ return;
51
+ }
52
+ setValue(props[propName]);
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={value}
63
+ label={label}
64
+ >
65
+ <NumberInput
66
+ flex={1}
67
+ disabled={Boolean(rawGetterStr)}
68
+ value={value}
69
+ min={min}
70
+ max={max}
71
+ step={step}
72
+ onChange={(value) => {
73
+ if (!isNaN(Number(value))) {
74
+ setValue(value);
75
+ refreshFile(value);
76
+ } else {
77
+ setValue("");
78
+ refreshFile(undefined);
79
+ }
80
+ }}
81
+ />
82
+ </Linker>
83
+ );
84
+ };
@@ -0,0 +1,81 @@
1
+ import {
2
+ ComboboxData,
3
+ SegmentedControl,
4
+ SegmentedControlItem,
5
+ } from "@mantine/core";
6
+ import { FC, useEffect, useState } from "react";
7
+ import { TNode } from "southpaw/contexts/dev";
8
+ import { useDebounceCb } from "southpaw/hooks/useDebounceCb";
9
+ import { isRawPropGet, KNOB_DEBOUNCE, refreshProp } from "./helpers";
10
+ import { Linker } from "./linker";
11
+
12
+ export const KnobSegmented: FC<{
13
+ gettersData: ComboboxData;
14
+ node: TNode;
15
+ rawProps: Record<string, string>;
16
+ ready: boolean;
17
+ propName: string;
18
+ label: string;
19
+ options: SegmentedControlItem[];
20
+ }> = ({ gettersData, node, rawProps, ready, propName, label, options }) => {
21
+ const [value, setValue] = useState<string>();
22
+ const [rawGetter, setRawGetter] = useState<{
23
+ type: "get" | "trans";
24
+ storeName: string;
25
+ key: string;
26
+ }>();
27
+
28
+ const props = node.nodeProps || {};
29
+ const rawGetterStr = JSON.stringify(rawGetter);
30
+
31
+ const onValueChange = useDebounceCb(
32
+ async (value: unknown, refresh = true) => {
33
+ setValue(value as string);
34
+ if (refresh) {
35
+ await refreshProp(`"${value}"`, node, propName);
36
+ }
37
+ },
38
+ KNOB_DEBOUNCE
39
+ );
40
+
41
+ useEffect(() => {
42
+ const getter = isRawPropGet(rawProps[propName]);
43
+ if (getter) {
44
+ setRawGetter(getter);
45
+ } else {
46
+ setRawGetter(undefined);
47
+ }
48
+ }, [rawProps[propName]]);
49
+
50
+ useEffect(() => {
51
+ if (!ready) {
52
+ return;
53
+ }
54
+ onValueChange(props[propName], false);
55
+ }, [ready, props[propName]]);
56
+
57
+ return (
58
+ <Linker
59
+ gettersData={gettersData}
60
+ node={node}
61
+ propName={propName}
62
+ rawGetter={rawGetter}
63
+ setRawGetter={setRawGetter}
64
+ value={value}
65
+ label={label}
66
+ >
67
+ <SegmentedControl
68
+ flex={1}
69
+ size="xs"
70
+ disabled={Boolean(rawGetterStr)}
71
+ data={options}
72
+ value={value}
73
+ onChange={(value) => {
74
+ if (value) {
75
+ onValueChange(value);
76
+ }
77
+ }}
78
+ />
79
+ </Linker>
80
+ );
81
+ };