@valbuild/ui 0.15.0 → 0.17.0

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 (46) hide show
  1. package/dist/valbuild-ui.cjs.d.ts +13 -18
  2. package/dist/valbuild-ui.cjs.js +7599 -1654
  3. package/dist/valbuild-ui.esm.js +7600 -1655
  4. package/package.json +5 -2
  5. package/src/assets/icons/ImageIcon.tsx +15 -7
  6. package/src/assets/icons/Section.tsx +41 -0
  7. package/src/assets/icons/TextIcon.tsx +20 -0
  8. package/src/components/Button.tsx +18 -7
  9. package/src/components/DraggableList.stories.tsx +20 -0
  10. package/src/components/DraggableList.tsx +95 -0
  11. package/src/components/Dropdown.tsx +2 -0
  12. package/src/components/ExpandLogo.tsx +72 -0
  13. package/src/components/RichTextEditor/Plugins/Toolbar.tsx +1 -16
  14. package/src/components/RichTextEditor/RichTextEditor.tsx +2 -2
  15. package/src/components/User.tsx +17 -0
  16. package/src/components/ValMenu.tsx +40 -0
  17. package/src/components/ValOverlay.tsx +513 -29
  18. package/src/components/ValOverlayContext.tsx +63 -0
  19. package/src/components/ValWindow.stories.tsx +3 -3
  20. package/src/components/ValWindow.tsx +26 -18
  21. package/src/components/dashboard/DashboardButton.tsx +25 -0
  22. package/src/components/dashboard/DashboardDropdown.tsx +59 -0
  23. package/src/components/dashboard/Dropdown.stories.tsx +11 -0
  24. package/src/components/dashboard/Dropdown.tsx +70 -0
  25. package/src/components/dashboard/FormGroup.stories.tsx +37 -0
  26. package/src/components/dashboard/FormGroup.tsx +36 -0
  27. package/src/components/dashboard/Grid.stories.tsx +52 -0
  28. package/src/components/dashboard/Grid.tsx +126 -0
  29. package/src/components/dashboard/Grid2.stories.tsx +56 -0
  30. package/src/components/dashboard/Grid2.tsx +72 -0
  31. package/src/components/dashboard/Tree.stories.tsx +91 -0
  32. package/src/components/dashboard/Tree.tsx +72 -0
  33. package/src/components/dashboard/ValDashboard.tsx +148 -0
  34. package/src/components/dashboard/ValDashboardEditor.tsx +269 -0
  35. package/src/components/dashboard/ValDashboardGrid.tsx +142 -0
  36. package/src/components/dashboard/ValTreeNavigator.tsx +253 -0
  37. package/src/components/forms/Form.tsx +2 -2
  38. package/src/components/forms/{TextForm.tsx → TextArea.tsx} +5 -3
  39. package/src/dto/SerializedSchema.ts +69 -0
  40. package/src/dto/Session.ts +12 -0
  41. package/src/dto/SessionMode.ts +5 -0
  42. package/src/dto/Tree.ts +18 -0
  43. package/src/exports.ts +1 -0
  44. package/src/utils/Remote.ts +15 -0
  45. package/src/utils/resolvePath.ts +33 -0
  46. package/tailwind.config.js +20 -1
@@ -0,0 +1,142 @@
1
+ import { SerializedModule } from "@valbuild/core";
2
+ import { ValApi } from "@valbuild/core";
3
+ import classNames from "classnames";
4
+ import React, { useState, FC, ReactNode, useEffect } from "react";
5
+ import { ValDashboardEditor } from "./ValDashboardEditor";
6
+ import { ValTreeNavigator } from "./ValTreeNavigator";
7
+
8
+ interface PanelProps {
9
+ header?: ReactNode;
10
+ width?: number;
11
+ onResize?: (width: number) => void;
12
+ collapsible?: boolean;
13
+ collapsed?: boolean;
14
+ onCollapse?: () => void;
15
+ children: ReactNode;
16
+ }
17
+
18
+ const Panel: FC<PanelProps> = ({
19
+ header,
20
+ width,
21
+ onResize,
22
+ collapsible,
23
+ collapsed,
24
+ onCollapse,
25
+ children,
26
+ }) => {
27
+ const handleMouseDown = (e: React.MouseEvent) => {
28
+ if (!onResize) return;
29
+
30
+ e.preventDefault();
31
+ const initialX = e.clientX;
32
+ const initialWidth = width || 0;
33
+
34
+ const handleMouseMove = (moveEvent: MouseEvent) => {
35
+ const newWidth = initialWidth + moveEvent.clientX - initialX;
36
+ onResize(newWidth);
37
+ };
38
+
39
+ const handleMouseUp = () => {
40
+ window.removeEventListener("mousemove", handleMouseMove);
41
+ window.removeEventListener("mouseup", handleMouseUp);
42
+ };
43
+
44
+ window.addEventListener("mousemove", handleMouseMove);
45
+ window.addEventListener("mouseup", handleMouseUp);
46
+ };
47
+ return (
48
+ <>
49
+ {!collapsed ? (
50
+ <div
51
+ className={classNames(
52
+ "relative border border-dark-gray min-w-0 h-full overflow-auto",
53
+ {
54
+ "flex-grow": !width,
55
+ }
56
+ )}
57
+ style={width ? { width: `${width}px` } : {}}
58
+ >
59
+ {onResize && (
60
+ <div
61
+ className="absolute inset-y-0 right-0 cursor-col-resize w-[1px] bg-dark-gray hover:w-[2px] hover:bg-light-gray"
62
+ onMouseDown={handleMouseDown}
63
+ />
64
+ )}
65
+ <div className="bg-gray-300 border border-dark-gray flex justify-between items-center h-[75px] w-full font-serif px-4">
66
+ {header}
67
+ {collapsible && (
68
+ <button
69
+ onClick={onCollapse}
70
+ className="px-2 py-1 font-bold text-white bg-red-500 rounded hover:bg-red-700"
71
+ >
72
+ {collapsed ? "Expand" : "Collapse"}
73
+ </button>
74
+ )}
75
+ </div>
76
+ <div>{children}</div>
77
+ </div>
78
+ ) : (
79
+ <button
80
+ onClick={onCollapse}
81
+ className="absolute inset-y-0 right-[16px] w-fit flex items-center justify-end"
82
+ >
83
+ open panel again
84
+ </button>
85
+ )}
86
+ </>
87
+ );
88
+ };
89
+
90
+ interface ValDashboardGridProps {
91
+ valApi: ValApi;
92
+ editMode: boolean;
93
+ }
94
+
95
+ export const ValDashboardGrid: FC<ValDashboardGridProps> = ({
96
+ valApi,
97
+ editMode,
98
+ }) => {
99
+ const [widths, setWidths] = useState([300, (2 * window.innerWidth) / 3]);
100
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
101
+ const [modules, setModules] = useState<SerializedModule[]>([]);
102
+ const [selectedPath, setSelectedPath] = useState<string>("");
103
+ useEffect(() => {
104
+ // valApi.getTree({}).then((modules) => {
105
+ // setModules(modules);
106
+ // });
107
+ }, [editMode]);
108
+
109
+ const handleResize = (index: number) => (width: number) => {
110
+ setWidths((prevWidths) => {
111
+ const newWidths = [...prevWidths];
112
+ newWidths[index] = Math.max(width, 300);
113
+ return newWidths;
114
+ });
115
+ };
116
+
117
+ return (
118
+ <div className="flex justify-start h-screen">
119
+ <Panel width={widths[0]} onResize={handleResize(0)}>
120
+ <ValTreeNavigator
121
+ modules={modules}
122
+ selectedModule={selectedPath}
123
+ setSelectedModule={setSelectedPath}
124
+ valApi={valApi}
125
+ />
126
+ </Panel>
127
+ <Panel
128
+ header={
129
+ selectedPath && (
130
+ <div className="w-full max-w-[1000px] bg-dark-gray px-4 py-2 rounded-lg">
131
+ {selectedPath}
132
+ </div>
133
+ )
134
+ }
135
+ width={widths[1]}
136
+ onResize={handleResize(1)}
137
+ >
138
+ <ValDashboardEditor selectedPath={selectedPath} valApi={valApi} />
139
+ </Panel>
140
+ </div>
141
+ );
142
+ };
@@ -0,0 +1,253 @@
1
+ import { Internal, SerializedModule, SourcePath } from "@valbuild/core";
2
+ import { Json, JsonArray } from "@valbuild/core/src/Json";
3
+ import { ValApi } from "@valbuild/core";
4
+ import classNames from "classnames";
5
+ import { Dispatch, FC, SetStateAction, useState } from "react";
6
+ import Chevron from "../../assets/icons/Chevron";
7
+ import { DraggableList, DraggableResult } from "../DraggableList";
8
+
9
+ const ValTreeArrayModuleItem: FC<{
10
+ submodule: Json;
11
+ selectedModule: string;
12
+ setSelectedModule: Dispatch<SetStateAction<string>>;
13
+ idx: number;
14
+ path: string;
15
+ reOrder: (oldIdx: number, newIdx: number) => void;
16
+ reorderMode: boolean;
17
+ }> = ({
18
+ submodule,
19
+ selectedModule,
20
+ setSelectedModule,
21
+ idx,
22
+ path,
23
+ reOrder,
24
+ reorderMode,
25
+ }) => {
26
+ const title = resolveTitle(submodule);
27
+ return (
28
+ <div className="w-fit" draggable id={idx.toString()}>
29
+ <div className="flex gap-4">
30
+ <button
31
+ onClick={() => setSelectedModule(path + idx.toString())}
32
+ className={classNames(
33
+ "px-4 py-2 text-start hover:bg-light-gray/20 hover:rounded-lg",
34
+ {
35
+ "font-extrabold ":
36
+ module.path + idx.toString() === selectedModule,
37
+ }
38
+ )}
39
+ >
40
+ {title}
41
+ </button>
42
+ {reorderMode && (
43
+ <div className="flex gap-2">
44
+ <button
45
+ onClick={() => reOrder(idx, idx + 1)}
46
+ className="disabled:text-dark-gray"
47
+ >
48
+ <Chevron className="rotate-90" />
49
+ </button>
50
+ <button
51
+ onClick={() => reOrder(idx, idx - 1)}
52
+ className="disabled:text-dark-gray"
53
+ >
54
+ <Chevron className="-rotate-90" />
55
+ </button>
56
+ </div>
57
+ )}
58
+ </div>
59
+ </div>
60
+ );
61
+ };
62
+
63
+ const DEFAULT_TITLE = "Untitled";
64
+ function resolveTitle(submodule: Json): string {
65
+ if (!submodule) {
66
+ return DEFAULT_TITLE;
67
+ }
68
+ if (typeof submodule === "string") {
69
+ return submodule;
70
+ }
71
+ if (typeof submodule === "object") {
72
+ if ("title" in submodule && typeof submodule.title === "string") {
73
+ return submodule.title;
74
+ }
75
+ if ("name" in submodule && typeof submodule.name === "string") {
76
+ return submodule.name;
77
+ }
78
+ const firstStringField = Object.entries(submodule).find(([, value]) => {
79
+ return typeof value === "string";
80
+ })?.[1];
81
+ if (typeof firstStringField === "string") {
82
+ return firstStringField;
83
+ }
84
+ }
85
+ return DEFAULT_TITLE;
86
+ }
87
+
88
+ const ValTreeNavigatorArrayModule: FC<{
89
+ module: SerializedModule;
90
+ selectedModule: string;
91
+ setSelectedModule: Dispatch<SetStateAction<string>>;
92
+ valApi: ValApi;
93
+ }> = ({ module, selectedModule, setSelectedModule }) => {
94
+ const [collapsed, setCollapsed] = useState(true);
95
+ const [items, setItems] = useState<Json[]>(
96
+ (module.source as JsonArray).map((submodule) => submodule as Json)
97
+ );
98
+ const [reOrderMode, setReOrderMode] = useState(false);
99
+
100
+ const reOrder = async (oldIndex: number, newIndex: number) => {
101
+ const sanitizedNewIndex =
102
+ newIndex < 0 ? items.length - 1 : newIndex % items.length;
103
+ const path = module.path + oldIndex.toString();
104
+ const newPath = module.path + sanitizedNewIndex.toString();
105
+ // const [moduleId, modulePath] = Internal.splitModuleIdAndModulePath(
106
+ // path as SourcePath
107
+ // );
108
+
109
+ const [newModuleId, newModulePath] = Internal.splitModuleIdAndModulePath(
110
+ newPath as SourcePath
111
+ );
112
+ // const patch: PatchJSON = [
113
+ // {
114
+ // op: "move",
115
+ // from: `/${modulePath
116
+ // .split(".")
117
+ // .map((p) => {
118
+ // return JSON.parse(p);
119
+ // })
120
+ // .join("/")}`,
121
+ // path: `/${newModulePath
122
+ // .split(".")
123
+ // .map((p) => {
124
+ // return JSON.parse(p);
125
+ // })
126
+ // .join("/")}`,
127
+ // },
128
+ // ];
129
+ // await valApi.patchModuleContent(moduleId, patch);
130
+ if (selectedModule === path) {
131
+ setSelectedModule(`${newModuleId}.${newModulePath}`);
132
+ }
133
+ setItems((items) => {
134
+ const newItems = [...items];
135
+ const item = newItems.splice(oldIndex, 1)[0];
136
+ newItems.splice(sanitizedNewIndex, 0, item);
137
+ return newItems;
138
+ });
139
+ };
140
+
141
+ const toggleReorderMode = () => {
142
+ setReOrderMode(!reOrderMode);
143
+ if (collapsed) setCollapsed(false);
144
+ };
145
+
146
+ return (
147
+ <div className="relative flex flex-col gap-3">
148
+ <div className="flex items-center justify-between ">
149
+ <button
150
+ className="flex items-center justify-between px-4 py-2 hover:bg-light-gray/20 hover:rounded-lg "
151
+ onClick={() => {
152
+ setSelectedModule(`${module.path.toString()}`);
153
+ setCollapsed(!collapsed);
154
+ }}
155
+ >
156
+ <div className="flex items-center gap-2">
157
+ <Chevron className={classNames({ "rotate-90": !collapsed })} />
158
+ <h1 className="text-xl">{module.path}</h1>
159
+ </div>
160
+ </button>
161
+ <div className="flex gap-2">
162
+ <button
163
+ className="relative w-[20px] h-[20px] flex flex-col justify-between items-center"
164
+ onClick={toggleReorderMode}
165
+ >
166
+ <Chevron className="top-0 -rotate-90" />
167
+ <Chevron className="rotate-90 " />
168
+ </button>
169
+ <button className="text-2xl w-[20px] h-[20px] rounded-full flex justify-center items-center">
170
+ +
171
+ </button>
172
+ </div>
173
+ </div>
174
+
175
+ {!collapsed && (
176
+ <DraggableList
177
+ onDragEnd={(res: DraggableResult) => reOrder(res.from, res.to)}
178
+ >
179
+ {items.map((submodule, idx) => (
180
+ <ValTreeArrayModuleItem
181
+ submodule={submodule}
182
+ idx={idx}
183
+ selectedModule={selectedModule}
184
+ setSelectedModule={setSelectedModule}
185
+ path={module.path}
186
+ key={idx}
187
+ reOrder={reOrder}
188
+ reorderMode={reOrderMode}
189
+ />
190
+ ))}
191
+ </DraggableList>
192
+ )}
193
+ </div>
194
+ );
195
+ };
196
+
197
+ const ValTreeNavigatorModule: FC<{
198
+ module: SerializedModule;
199
+ setSelectedModule: Dispatch<SetStateAction<string>>;
200
+ }> = ({ module, setSelectedModule }) => {
201
+ return (
202
+ <div className="relative flex flex-col gap-3 px-4 py-2 hover:bg-light-gray/20 hover:rounded-lg ">
203
+ <button
204
+ className="flex items-center justify-between"
205
+ onClick={() => setSelectedModule(module.path)}
206
+ >
207
+ <div className="flex items-center gap-2">
208
+ <Chevron className={classNames("opacity-0")} />
209
+ <h1 className="text-xl">{module.path}</h1>
210
+ </div>
211
+ </button>
212
+ </div>
213
+ );
214
+ };
215
+
216
+ interface ValTreeNavigator {
217
+ modules: SerializedModule[];
218
+ selectedModule: string;
219
+ setSelectedModule: Dispatch<SetStateAction<string>>;
220
+ valApi: ValApi;
221
+ }
222
+ export const ValTreeNavigator: FC<ValTreeNavigator> = ({
223
+ modules,
224
+ selectedModule,
225
+ setSelectedModule,
226
+ valApi,
227
+ }) => {
228
+ console.log("hello?????????????", modules);
229
+ return (
230
+ <div
231
+ className={classNames("flex flex-col gap-4 font-serif text-lg px-4 py-3")}
232
+ >
233
+ {modules.map((module, idx) => (
234
+ <div key={idx}>
235
+ {module.schema.type === "array" ? (
236
+ <ValTreeNavigatorArrayModule
237
+ module={module}
238
+ key={idx}
239
+ selectedModule={selectedModule}
240
+ setSelectedModule={setSelectedModule}
241
+ valApi={valApi}
242
+ />
243
+ ) : (
244
+ <ValTreeNavigatorModule
245
+ module={module}
246
+ setSelectedModule={setSelectedModule}
247
+ />
248
+ )}
249
+ </div>
250
+ ))}
251
+ </div>
252
+ );
253
+ };
@@ -4,7 +4,7 @@ import { useEffect, useState } from "react";
4
4
  import { RichTextEditor } from "../RichTextEditor/RichTextEditor";
5
5
  import { FormContainer } from "./FormContainer";
6
6
  import { ImageForm, ImageData } from "./ImageForm";
7
- import { TextData, TextForm } from "./TextForm";
7
+ import { TextData, TextArea } from "./TextArea";
8
8
 
9
9
  export type Inputs = {
10
10
  [path: string]:
@@ -89,7 +89,7 @@ export function Form({ onSubmit, inputs }: FormProps): React.ReactElement {
89
89
  />
90
90
  )}
91
91
  {input.status === "completed" && input.type === "text" && (
92
- <TextForm
92
+ <TextArea
93
93
  name={path}
94
94
  text={input.data}
95
95
  onChange={(data) => {
@@ -2,18 +2,20 @@ export type TextData = string;
2
2
  export type TextInputProps = {
3
3
  name: string;
4
4
  text: TextData;
5
+ disabled?: boolean;
5
6
  onChange: (value: string) => void;
6
7
  };
7
8
 
8
- export function TextForm({ name, text, onChange }: TextInputProps) {
9
+ export function TextArea({ name, disabled, text, onChange }: TextInputProps) {
9
10
  return (
10
11
  <div
11
- className="w-full py-2 grow-wrap"
12
+ className="w-full h-full py-2 overflow-y-scroll grow-wrap"
12
13
  data-replicated-value={text} /* see grow-wrap */
13
14
  >
14
15
  <textarea
16
+ disabled={disabled}
15
17
  name={name}
16
- className="w-full p-2 border outline-none bg-fill text-primary border-border focus-visible:border-highlight"
18
+ className="p-2 border outline-none resize-none bg-fill text-primary border-border focus-visible:border-highlight"
17
19
  defaultValue={text}
18
20
  onChange={(e) => onChange(e.target.value)}
19
21
  />
@@ -0,0 +1,69 @@
1
+ export default {};
2
+ // TODO:
3
+ // import type { SerializedSchema as SerializedSchemaT } from "@valbuild/core";
4
+ // import { z } from "zod";
5
+ //
6
+ // export const SerializedSchema: z.ZodType<SerializedSchemaT> = z.lazy(() => {
7
+ // const SerializedStringSchema = z.object({
8
+ // _type: z.literal("string"),
9
+ // opt: z.boolean(),
10
+ // });
11
+ // const SerializedBooleanSchema = z.object({
12
+ // _type: z.literal("boolean"),
13
+ // opt: z.boolean(),
14
+ // });
15
+ // const SerializedNumberSchema = z.object({
16
+ // _type: z.literal("number"),
17
+ // opt: z.boolean(),
18
+ // });
19
+ // const SerializedLiteralSchema = z.object({
20
+ // _type: z.literal("literal"),
21
+ // value: z.string(),
22
+ // opt: z.boolean(),
23
+ // });
24
+ // const SerializedObjectSchema = z.object({
25
+ // _type: z.literal("object"),
26
+ // opt: z.boolean(),
27
+ // items: z.record(SerializedSchema),
28
+ // });
29
+ // const SerializedOneOfSchema = z.object({
30
+ // _type: z.literal("oneOf"),
31
+ // opt: z.boolean(),
32
+ // });
33
+ // const SerializedArraySchema = z.object({
34
+ // _type: z.literal("array"),
35
+ // item: SerializedSchema,
36
+ // opt: z.boolean(),
37
+ // });
38
+ // const SerializedUnionSchema = z.object({
39
+ // _type: z.literal("union"),
40
+ // opt: z.boolean(),
41
+ // });
42
+ // const SerializedRichTextSchema = z.object({
43
+ // _type: z.literal("richtext"),
44
+ // opt: z.boolean(),
45
+ // });
46
+ // const SerializedImageSchema = z.object({
47
+ // _type: z.literal("image"),
48
+ // opt: z.boolean(),
49
+ // });
50
+ // const SerializedI18nSchema = z.object({
51
+ // _type: z.literal("i18n"),
52
+ // locales: z.array(z.string()),
53
+ // item: SerializedSchema,
54
+ // opt: z.boolean(),
55
+ // });
56
+ // return z.union([
57
+ // SerializedStringSchema,
58
+ // SerializedLiteralSchema,
59
+ // SerializedBooleanSchema,
60
+ // SerializedNumberSchema,
61
+ // SerializedObjectSchema,
62
+ // SerializedOneOfSchema,
63
+ // SerializedArraySchema,
64
+ // SerializedUnionSchema,
65
+ // SerializedRichTextSchema,
66
+ // SerializedImageSchema,
67
+ // SerializedI18nSchema,
68
+ // ]);
69
+ // });
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ import { SessionMode } from "./SessionMode";
3
+
4
+ export const Session = z.object({
5
+ id: z.string(),
6
+ mode: SessionMode,
7
+ full_name: z.string().nullable(),
8
+ username: z.string().nullable(),
9
+ avatar_url: z.string().url().nullable(),
10
+ });
11
+
12
+ export type Session = z.infer<typeof Session>;
@@ -0,0 +1,5 @@
1
+ import { z } from "zod";
2
+
3
+ export const SessionMode = z.union([z.literal("local"), z.literal("proxy")]);
4
+
5
+ export type SessionMode = z.infer<typeof SessionMode>;
@@ -0,0 +1,18 @@
1
+ import { z } from "zod";
2
+
3
+ export const Tree = z.object({
4
+ git: z.object({
5
+ commit: z.string(),
6
+ branch: z.string(),
7
+ }),
8
+ modules: z.record(
9
+ z.object({
10
+ // TODO: types!
11
+ schema: z.any().optional(),
12
+ source: z.any().optional(),
13
+ // TODO: patches
14
+ })
15
+ ),
16
+ });
17
+
18
+ export type Tree = z.infer<typeof Tree>;
package/src/exports.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { RichTextEditor } from "./components/RichTextEditor/RichTextEditor";
2
2
  export { ValOverlay } from "./components/ValOverlay";
3
3
  export { type Inputs } from "./components/forms/Form";
4
+ export { ValDashboard } from "./components/dashboard/ValDashboard";
@@ -0,0 +1,15 @@
1
+ export type Remote<T> =
2
+ | {
3
+ status: "not-asked";
4
+ }
5
+ | {
6
+ status: "loading";
7
+ }
8
+ | {
9
+ status: "success";
10
+ data: T;
11
+ }
12
+ | {
13
+ status: "error";
14
+ error: string;
15
+ };
@@ -0,0 +1,33 @@
1
+ import {
2
+ ApiTreeResponse,
3
+ Internal,
4
+ Json,
5
+ SerializedSchema,
6
+ SourcePath,
7
+ } from "@valbuild/core";
8
+ import { result } from "@valbuild/core/fp";
9
+
10
+ export type Modules = ApiTreeResponse["modules"];
11
+
12
+ export function resolvePath(sourcePath: SourcePath, modules: Modules) {
13
+ const [moduleId, modulePath] =
14
+ Internal.splitModuleIdAndModulePath(sourcePath);
15
+ const valModule = modules[moduleId];
16
+ console.log("resolvePath", sourcePath, moduleId, modulePath, modules);
17
+ if (!valModule?.source) {
18
+ return result.err({
19
+ message: `Module "${moduleId}" has no source`,
20
+ });
21
+ }
22
+ if (!valModule?.schema) {
23
+ return result.err({
24
+ message: `Module "${moduleId}" has no schema`,
25
+ });
26
+ }
27
+ return result.ok(
28
+ Internal.resolvePath(modulePath, valModule.source, valModule.schema) as {
29
+ source: Json;
30
+ schema: SerializedSchema;
31
+ }
32
+ );
33
+ }
@@ -3,6 +3,12 @@ module.exports = {
3
3
  content: [__dirname + "/src/**/*.{js,ts,jsx,tsx}"],
4
4
  darkMode: ["class", '[data-mode="dark"]'],
5
5
  theme: {
6
+ zIndex: {
7
+ overlay: 8999, // 1 less than the NextJS error z-index: 9000
8
+ hover: 8996,
9
+ window: 8997,
10
+ full: 8998,
11
+ },
6
12
  colors: {
7
13
  base: "var(--val-theme-base)",
8
14
  highlight: "var(--val-theme-highlight)",
@@ -42,11 +48,24 @@ module.exports = {
42
48
  "0%": { transform: "rotate(0deg)" },
43
49
  "100%": { transform: "rotate(-45deg)" },
44
50
  },
51
+ spin: {
52
+ "0%": { transform: "rotate(0deg)" },
53
+ "100%": { transform: "rotate(360deg)" },
54
+ },
45
55
  },
46
56
  animation: {
47
57
  rotateLeft: "rotateLeft 200ms ease-in-out",
48
58
  rotateRight: "rotateRight 200ms ease-in-out",
59
+ spin: "spin 1s linear infinite",
60
+ },
61
+ transitionProperty: {
62
+ opacity: ["opacity"],
49
63
  },
50
64
  },
51
- plugins: [],
65
+ plugins: [
66
+ function ({ addVariant }) {
67
+ addVariant("all-but-last-child", "& > *:not(:last-child)");
68
+ addVariant("children", "& *");
69
+ },
70
+ ],
52
71
  };