@alepha/ui 0.11.6 → 0.11.9

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.
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "mantine"
7
7
  ],
8
8
  "author": "Feunard",
9
- "version": "0.11.6",
9
+ "version": "0.11.9",
10
10
  "type": "module",
11
11
  "engines": {
12
12
  "node": ">=22.0.0"
@@ -20,13 +20,14 @@
20
20
  "src"
21
21
  ],
22
22
  "dependencies": {
23
- "@alepha/core": "0.11.6",
24
- "@alepha/datetime": "0.11.6",
25
- "@alepha/react": "0.11.6",
26
- "@alepha/react-form": "0.11.6",
27
- "@alepha/react-head": "0.11.6",
28
- "@alepha/react-i18n": "0.11.6",
29
- "@alepha/server": "0.11.6",
23
+ "@alepha/core": "0.11.9",
24
+ "@alepha/datetime": "0.11.9",
25
+ "@alepha/postgres": "0.11.9",
26
+ "@alepha/react": "0.11.9",
27
+ "@alepha/react-form": "0.11.9",
28
+ "@alepha/react-head": "0.11.9",
29
+ "@alepha/react-i18n": "0.11.9",
30
+ "@alepha/server": "0.11.9",
30
31
  "@mantine/core": "^8.3.7",
31
32
  "@mantine/dates": "^8.3.7",
32
33
  "@mantine/hooks": "^8.3.7",
@@ -38,12 +39,12 @@
38
39
  "dayjs": "^1.11.19"
39
40
  },
40
41
  "devDependencies": {
41
- "@alepha/cli": "0.11.6",
42
- "@alepha/vite": "0.11.6",
43
- "@biomejs/biome": "^2.3.4",
42
+ "@alepha/cli": "0.11.9",
43
+ "@alepha/vite": "0.11.9",
44
+ "@biomejs/biome": "^2.3.5",
44
45
  "react": "^19.2.0",
45
46
  "react-dom": "^19.2.0",
46
- "tsdown": "^0.16.1",
47
+ "tsdown": "^0.16.4",
47
48
  "typescript": "^5.9.3",
48
49
  "vite": "^7.2.2",
49
50
  "vitest": "^4.0.8"
@@ -0,0 +1,352 @@
1
+ import {
2
+ ActionIcon,
3
+ Box,
4
+ Collapse,
5
+ CopyButton,
6
+ type MantineSize,
7
+ Text,
8
+ Tooltip,
9
+ } from "@mantine/core";
10
+ import {
11
+ IconCheck,
12
+ IconChevronDown,
13
+ IconChevronRight,
14
+ IconCopy,
15
+ } from "@tabler/icons-react";
16
+ import { type ReactNode, useState } from "react";
17
+
18
+ interface JsonViewerProps {
19
+ data: any;
20
+ defaultExpanded?: boolean;
21
+ maxDepth?: number;
22
+ copyable?: boolean;
23
+ size?: MantineSize;
24
+ }
25
+
26
+ interface JsonNodeProps {
27
+ name?: string;
28
+ value: any;
29
+ depth: number;
30
+ maxDepth: number;
31
+ isLast?: boolean;
32
+ isArrayItem?: boolean;
33
+ size?: MantineSize;
34
+ }
35
+
36
+ const getSizeConfig = (size: MantineSize = "sm") => {
37
+ const configs = {
38
+ xs: { text: "xs", icon: 12, indent: 16, gap: 2 },
39
+ sm: { text: "sm", icon: 14, indent: 20, gap: 4 },
40
+ md: { text: "md", icon: 16, indent: 24, gap: 6 },
41
+ lg: { text: "lg", icon: 18, indent: 28, gap: 8 },
42
+ xl: { text: "xl", icon: 20, indent: 32, gap: 10 },
43
+ };
44
+ return configs[size] || configs.sm;
45
+ };
46
+
47
+ const JsonNode = ({
48
+ name,
49
+ value,
50
+ depth,
51
+ maxDepth,
52
+ isLast = false,
53
+ isArrayItem = false,
54
+ size = "sm",
55
+ }: JsonNodeProps) => {
56
+ const [expanded, setExpanded] = useState(depth < 2);
57
+ const sizeConfig = getSizeConfig(size);
58
+
59
+ const getValueType = (val: any): string => {
60
+ if (val === null) return "null";
61
+ if (val === undefined) return "undefined";
62
+ if (Array.isArray(val)) return "array";
63
+ return typeof val;
64
+ };
65
+
66
+ const valueType = getValueType(value);
67
+
68
+ const renderPrimitive = (val: any): ReactNode => {
69
+ const type = getValueType(val);
70
+
71
+ switch (type) {
72
+ case "string":
73
+ return (
74
+ <Text
75
+ component="span"
76
+ c="teal"
77
+ ff="monospace"
78
+ size={sizeConfig.text}
79
+ style={{ whiteSpace: "nowrap" }}
80
+ >
81
+ "{val}"
82
+ </Text>
83
+ );
84
+ case "number":
85
+ return (
86
+ <Text
87
+ component="span"
88
+ c="blue"
89
+ ff="monospace"
90
+ size={sizeConfig.text}
91
+ style={{ whiteSpace: "nowrap" }}
92
+ >
93
+ {val}
94
+ </Text>
95
+ );
96
+ case "boolean":
97
+ return (
98
+ <Text
99
+ component="span"
100
+ c="violet"
101
+ ff="monospace"
102
+ size={sizeConfig.text}
103
+ style={{ whiteSpace: "nowrap" }}
104
+ >
105
+ {String(val)}
106
+ </Text>
107
+ );
108
+ case "null":
109
+ return (
110
+ <Text
111
+ component="span"
112
+ c="dimmed"
113
+ ff="monospace"
114
+ size={sizeConfig.text}
115
+ style={{ whiteSpace: "nowrap" }}
116
+ >
117
+ null
118
+ </Text>
119
+ );
120
+ case "undefined":
121
+ return (
122
+ <Text
123
+ component="span"
124
+ c="dimmed"
125
+ ff="monospace"
126
+ size={sizeConfig.text}
127
+ style={{ whiteSpace: "nowrap" }}
128
+ >
129
+ undefined
130
+ </Text>
131
+ );
132
+ default:
133
+ return (
134
+ <Text
135
+ component="span"
136
+ ff="monospace"
137
+ size={sizeConfig.text}
138
+ style={{ whiteSpace: "nowrap" }}
139
+ >
140
+ {String(val)}
141
+ </Text>
142
+ );
143
+ }
144
+ };
145
+
146
+ const renderKey = () => {
147
+ if (!name) return null;
148
+ return (
149
+ <Text
150
+ component="span"
151
+ c="cyan"
152
+ ff="monospace"
153
+ fw={500}
154
+ size={sizeConfig.text}
155
+ >
156
+ {isArrayItem ? `[${name}]` : `"${name}"`}:
157
+ </Text>
158
+ );
159
+ };
160
+
161
+ if (valueType === "object" || valueType === "array") {
162
+ const isObject = valueType === "object";
163
+ const entries = isObject
164
+ ? Object.entries(value)
165
+ : value.map((v: any, i: number) => [i, v]);
166
+ const isEmpty = entries.length === 0;
167
+ const canExpand = depth < maxDepth && !isEmpty;
168
+
169
+ const preview = isObject ? "{...}" : "[...]";
170
+ const brackets = isObject ? ["{", "}"] : ["[", "]"];
171
+
172
+ return (
173
+ <Box>
174
+ <Box
175
+ style={{
176
+ display: "flex",
177
+ alignItems: "center",
178
+ gap: sizeConfig.gap,
179
+ minWidth: "max-content",
180
+ }}
181
+ >
182
+ {canExpand && (
183
+ <ActionIcon
184
+ size="xs"
185
+ variant="transparent"
186
+ c="dimmed"
187
+ onClick={() => setExpanded(!expanded)}
188
+ style={{ cursor: "pointer", flexShrink: 0 }}
189
+ >
190
+ {expanded ? (
191
+ <IconChevronDown size={sizeConfig.icon} />
192
+ ) : (
193
+ <IconChevronRight size={sizeConfig.icon} />
194
+ )}
195
+ </ActionIcon>
196
+ )}
197
+ {!canExpand && (
198
+ <Box w={sizeConfig.icon + 6} style={{ flexShrink: 0 }} />
199
+ )}
200
+ <Box style={{ flexShrink: 0 }}>{renderKey()}</Box>{" "}
201
+ <Text
202
+ component="span"
203
+ c="dimmed"
204
+ ff="monospace"
205
+ size={sizeConfig.text}
206
+ style={{ flexShrink: 0 }}
207
+ >
208
+ {brackets[0]}
209
+ </Text>
210
+ {!expanded && !isEmpty && (
211
+ <Text
212
+ component="span"
213
+ c="dimmed"
214
+ ff="monospace"
215
+ fs="italic"
216
+ size={sizeConfig.text}
217
+ style={{ flexShrink: 0 }}
218
+ >
219
+ {preview}
220
+ </Text>
221
+ )}
222
+ {(isEmpty || !expanded) && (
223
+ <Text
224
+ component="span"
225
+ c="dimmed"
226
+ ff="monospace"
227
+ size={sizeConfig.text}
228
+ style={{ flexShrink: 0 }}
229
+ >
230
+ {brackets[1]}
231
+ </Text>
232
+ )}
233
+ {!isEmpty && !expanded && (
234
+ <Text
235
+ component="span"
236
+ c="dimmed"
237
+ size={sizeConfig.text}
238
+ style={{ flexShrink: 0 }}
239
+ >
240
+ {entries.length} {entries.length === 1 ? "item" : "items"}
241
+ </Text>
242
+ )}
243
+ </Box>
244
+
245
+ <Collapse in={expanded && canExpand}>
246
+ <Box
247
+ pl={sizeConfig.indent}
248
+ style={{
249
+ borderLeft: "1px solid var(--mantine-color-default-border)",
250
+ marginLeft: Math.floor((sizeConfig.icon + 6) / 2),
251
+ }}
252
+ >
253
+ {entries.map(
254
+ ([key, val]: [string | number, any], index: number) => (
255
+ <JsonNode
256
+ key={String(key)}
257
+ name={String(key)}
258
+ value={val}
259
+ depth={depth + 1}
260
+ maxDepth={maxDepth}
261
+ isLast={index === entries.length - 1}
262
+ isArrayItem={!isObject}
263
+ size={size}
264
+ />
265
+ ),
266
+ )}
267
+ </Box>
268
+ <Box style={{ display: "flex", minWidth: "max-content" }}>
269
+ <Box w={sizeConfig.icon + 6} style={{ flexShrink: 0 }} />
270
+ <Text
271
+ c="dimmed"
272
+ ff="monospace"
273
+ size={sizeConfig.text}
274
+ style={{ flexShrink: 0 }}
275
+ >
276
+ {brackets[1]}
277
+ </Text>
278
+ </Box>
279
+ </Collapse>
280
+ </Box>
281
+ );
282
+ }
283
+
284
+ return (
285
+ <Box
286
+ style={{
287
+ display: "flex",
288
+ alignItems: "center",
289
+ gap: sizeConfig.gap,
290
+ minWidth: "max-content",
291
+ }}
292
+ >
293
+ <Box w={sizeConfig.icon + 6} style={{ flexShrink: 0 }} />
294
+ <Box style={{ flexShrink: 0 }}>{renderKey()}</Box>
295
+ <Box style={{ flexShrink: 0 }}>{renderPrimitive(value)}</Box>
296
+ {!isLast && (
297
+ <Text
298
+ component="span"
299
+ c="dimmed"
300
+ ff="monospace"
301
+ size={sizeConfig.text}
302
+ style={{ flexShrink: 0 }}
303
+ >
304
+ ,
305
+ </Text>
306
+ )}
307
+ </Box>
308
+ );
309
+ };
310
+
311
+ export const JsonViewer = ({
312
+ data,
313
+ defaultExpanded = true,
314
+ maxDepth = 10,
315
+ copyable = true,
316
+ size = "sm",
317
+ }: JsonViewerProps) => {
318
+ const sizeConfig = getSizeConfig(size);
319
+ const copyIconSize = sizeConfig.icon + 2;
320
+
321
+ return (
322
+ <Box pos="relative" w={"100%"}>
323
+ {copyable && (
324
+ <Box pos="absolute" top={0} right={0} style={{ zIndex: 1 }}>
325
+ <CopyButton value={JSON.stringify(data, null, 2)}>
326
+ {({ copied, copy }) => (
327
+ <Tooltip label={copied ? "Copied" : "Copy JSON"}>
328
+ <ActionIcon
329
+ color={copied ? "teal" : "gray"}
330
+ variant="subtle"
331
+ onClick={copy}
332
+ size={size}
333
+ >
334
+ {copied ? (
335
+ <IconCheck size={copyIconSize} />
336
+ ) : (
337
+ <IconCopy size={copyIconSize} />
338
+ )}
339
+ </ActionIcon>
340
+ </Tooltip>
341
+ )}
342
+ </CopyButton>
343
+ </Box>
344
+ )}
345
+ <Box pt={copyable ? 30 : 0} style={{ overflowX: "auto" }}>
346
+ <JsonNode value={data} depth={0} maxDepth={maxDepth} size={size} />
347
+ </Box>
348
+ </Box>
349
+ );
350
+ };
351
+
352
+ export default JsonViewer;
@@ -83,15 +83,15 @@ const Control = (_props: ControlProps) => {
83
83
  //region <QueryBuilder/>
84
84
  if (props.query) {
85
85
  return (
86
- <Input.Wrapper {...inputProps}>
87
- <ControlQueryBuilder
88
- schema={props.query}
89
- value={props.input.props.value}
90
- onChange={(value) => {
91
- props.input.set(value);
92
- }}
93
- />
94
- </Input.Wrapper>
86
+ <ControlQueryBuilder
87
+ {...props.input.props}
88
+ {...inputProps}
89
+ schema={props.query}
90
+ value={props.input.props.value}
91
+ onChange={(value) => {
92
+ props.input.set(value);
93
+ }}
94
+ />
95
95
  );
96
96
  }
97
97
  //endregion