@duskmoon-dev/components 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -41,7 +41,7 @@ import { Button, DmTable, theme } from "@duskmoon-dev/components";
41
41
 
42
42
  ## Public Surface
43
43
 
44
- - 78 standard components such as `Button`, `Chat`, `OtpInput`, `Swap`, `Fab`,
44
+ - 79 standard components such as `Button`, `Chat`, `OtpInput`, `Swap`, `Fab`,
45
45
  `Megamenu`, `ConsolePage`, `Table`, `Modal`, `Select`, and `Typography`.
46
46
  - 21 DuskMoon workflow components such as `DmLayout`, `DmSearch`, `DmTable`,
47
47
  `DmProTable`, and `DmToolbar`.
@@ -90,10 +90,14 @@ export function Conversation() {
90
90
  <Chat.Bubble>The build passed.</Chat.Bubble>
91
91
  <Chat.Status>
92
92
  <Chat.StatusItem>Completed</Chat.StatusItem>
93
- <Chat.StatusItem><Chat.StatusValue>42</Chat.StatusValue> token/s</Chat.StatusItem>
93
+ <Chat.StatusItem>
94
+ <Chat.StatusValue>42</Chat.StatusValue> token/s
95
+ </Chat.StatusItem>
94
96
  </Chat.Status>
95
97
  <Chat.Actions hover>
96
- <button type="button" onClick={copyReply}>Copy reply</button>
98
+ <button type="button" onClick={copyReply}>
99
+ Copy reply
100
+ </button>
97
101
  </Chat.Actions>
98
102
  </Chat>
99
103
  </Chat.ScrollBody>
@@ -124,3 +128,80 @@ bun run typecheck
124
128
  # Check parity manifest coverage
125
129
  bun run parity:components
126
130
  ```
131
+
132
+ ## JSON Schema forms
133
+
134
+ `@duskmoon-dev/components/json-schema-form` exports `JsonSchemaForm`,
135
+ `compileForm`, `JsonSchemaWidget`, and the `RenderableSchema`, `CompiledForm`,
136
+ `FormValue`, `JsonValue`, `FormErrors`, `FormValidationResult`,
137
+ `JsonSchemaFormProps`, `JsonSchemaWidgetProps`, and widget contract types.
138
+ Ajv and ajv-formats are installed as package runtime dependencies. The package
139
+ stylesheet includes the renderer layout. Import these APIs from the explicit
140
+ subpath; schema compilation is kept separate from the root component bundle.
141
+
142
+ ```tsx
143
+ import { useState } from "react";
144
+ import "@duskmoon-dev/components/styles.css";
145
+ import {
146
+ JsonSchemaForm,
147
+ type FormErrors,
148
+ type FormValue,
149
+ type RenderableSchema,
150
+ } from "@duskmoon-dev/components/json-schema-form";
151
+
152
+ const schema: RenderableSchema = {
153
+ type: "object",
154
+ required: ["name"],
155
+ properties: {
156
+ name: { type: "string", title: "Name", minLength: 1 },
157
+ age: { type: "integer", title: "Age", minimum: 0 },
158
+ },
159
+ };
160
+
161
+ export function ProfileIsland({ send }: { send: (value: FormValue) => void }) {
162
+ const [value, setValue] = useState<FormValue>({ name: "Ada" });
163
+ const [errors, setErrors] = useState<FormErrors>({});
164
+ return (
165
+ <JsonSchemaForm
166
+ schema={schema}
167
+ value={value}
168
+ onChange={setValue}
169
+ errors={errors}
170
+ onErrorsChange={setErrors}
171
+ onSubmit={send}
172
+ />
173
+ );
174
+ }
175
+ ```
176
+
177
+ Mount the React root inside a LiveView element with a stable `id` and
178
+ `phx-update="ignore"`. Parse the backend's schema JSON and call `compileForm`
179
+ when accepting untrusted input; catch compilation errors before rendering.
180
+ Pass the returned object as `compiled` instead of `schema` to compile once.
181
+ Send the validated object from `onSubmit` through the island's LiveView hook;
182
+ backend validation is still required. Use a new React `key` when replacing a
183
+ schema and starting a fresh form.
184
+
185
+ `value` and `onChange` control values. Omit `value` for internal state, optionally
186
+ seeded by `defaultValue`. Reset restores `defaultValue` or schema defaults,
187
+ notifies `onChange`, clears validation errors, and calls `onReset`. Backend
188
+ `errors` merge with local errors and use escaped JSON Pointer keys such as
189
+ `/address/email` (the empty key denotes a form error). Clear backend errors in
190
+ `onErrorsChange`, which receives an empty object on edits/reset and validation
191
+ errors on submission. `onSubmit` runs only for valid values and receives a
192
+ copied JSON object with numbers and booleans preserved.
193
+
194
+ The supported schema is a strict JSON Schema draft 2020-12 subset: object roots,
195
+ nested objects, homogeneous arrays, primitive fields/enums/defaults, required
196
+ fields, `additionalProperties: false`, string constraints/formats, numeric
197
+ constraints, and array/object size constraints. References, combinators,
198
+ nullable types, and arbitrary keywords are rejected. Validation does not coerce,
199
+ remove, or default submitted values.
200
+
201
+ Use `x-widget` for explicit component selection, `x-options` for typed option
202
+ labels/hierarchies, and `x-widget-options` for orientation, length, count,
203
+ allowHalf, min, max, and step where supported. `widgetNames`, `WidgetName`,
204
+ `WidgetOption`, `WidgetConfig`, and `OrientationWidgetConfig` expose this
205
+ contract; `compileForm` checks widget/type/config compatibility. Upload widgets
206
+ submit JSON file metadata (`name`, `size`, `type`, `lastModified`), with file
207
+ transfer owned by the application.
@@ -1,4 +1,5 @@
1
- import type { ComponentProps, ForwardRefExoticComponent, RefAttributes } from "react";
1
+ import type { ComponentProps, ForwardRefExoticComponent, RefAttributes, ReactNode } from "react";
2
+ import type { TextAreaProps } from "../input";
2
3
  export type ChatPlacement = "start" | "end";
3
4
  export type ChatTimeline = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24;
4
5
  export type ChatBubbleColor = "primary" | "secondary" | "tertiary" | "accent" | "neutral" | "base" | "info" | "success" | "warning" | "error";
@@ -48,8 +49,81 @@ export type ChatToolStatusProps = ComponentProps<"span">;
48
49
  export type ChatToolCallProps = ComponentProps<"div">;
49
50
  export type ChatToolResultProps = ComponentProps<"div">;
50
51
  export type ChatTypingProps = Omit<ComponentProps<"span">, "children">;
52
+ /** A snapshot of the controlled draft at submission time. */
53
+ export interface ChatComposerSubmission {
54
+ value: string;
55
+ files: File[];
56
+ }
57
+ export interface ChatComposerCompletion {
58
+ id: string;
59
+ label: string;
60
+ description?: ReactNode;
61
+ disabled?: boolean;
62
+ }
63
+ export interface ChatComposerCompletionContext {
64
+ value: string;
65
+ selectionStart: number;
66
+ selectionEnd: number;
67
+ }
68
+ /** The application supplies candidates and owns inserting the selected result. */
69
+ export interface ChatComposerCompletionConfig {
70
+ items: readonly ChatComposerCompletion[];
71
+ onSelect: (item: ChatComposerCompletion, context: ChatComposerCompletionContext) => void;
72
+ label?: string;
73
+ }
74
+ export interface ChatComposerActionContext {
75
+ submit: () => void;
76
+ submitting: boolean;
77
+ canSubmit: boolean;
78
+ disabled: boolean;
79
+ generating: boolean;
80
+ openAttachments: () => void;
81
+ }
82
+ export interface ChatComposerAttachmentContext {
83
+ remove: () => void;
84
+ disabled: boolean;
85
+ }
86
+ export interface ChatComposerProps extends Omit<ComponentProps<"div">, "children" | "onSubmit" | "onChange"> {
87
+ value: string;
88
+ onValueChange: (value: string) => void;
89
+ /** Return false to reject admission and retain the draft. */
90
+ onSubmit: (snapshot: ChatComposerSubmission) => boolean | void | Promise<boolean | void>;
91
+ attachments?: File[];
92
+ /** Enables the native file picker and attachment removal controls. */
93
+ onAttachmentsChange?: (files: File[]) => void;
94
+ accept?: string;
95
+ /** @default true */
96
+ multiple?: boolean;
97
+ /** Permit a draft containing only files. @default false */
98
+ allowAttachmentOnly?: boolean;
99
+ disabled?: boolean;
100
+ /** Gates submission while keeping the draft editable. */
101
+ busy?: boolean;
102
+ /** Shows Stop when onStop is supplied; does not prevent submission. */
103
+ generating?: boolean;
104
+ /** Application-controlled admission, including follow-ups. @default true */
105
+ canSubmit?: boolean;
106
+ onStop?: () => void;
107
+ error?: ReactNode;
108
+ onSubmitError?: (error: unknown) => void;
109
+ /** Clear unchanged admitted text and only admitted file entries. @default true */
110
+ clearOnSubmit?: boolean;
111
+ completion?: ChatComposerCompletionConfig;
112
+ /** @default 1 */
113
+ minRows?: number;
114
+ /** @default 6 */
115
+ maxRows?: number;
116
+ textareaProps?: Omit<TextAreaProps, "value" | "defaultValue" | "onChange" | "disabled" | "rows" | "autoSize" | "ref">;
117
+ attachLabel?: string;
118
+ sendLabel?: string;
119
+ stopLabel?: string;
120
+ renderAttachment?: (file: File, context: ChatComposerAttachmentContext) => ReactNode;
121
+ /** Extra application actions rendered alongside Attach, Stop, and Send. */
122
+ children?: ReactNode | ((context: ChatComposerActionContext) => ReactNode);
123
+ }
51
124
  type ChatPartComponent<T extends HTMLElement, P> = ForwardRefExoticComponent<P & RefAttributes<T>>;
52
125
  export type ChatComponent = ChatPartComponent<HTMLDivElement, ChatProps> & {
126
+ Composer: ChatPartComponent<HTMLDivElement, ChatComposerProps>;
53
127
  Avatar: ChatPartComponent<HTMLDivElement, ChatAvatarProps>;
54
128
  Header: ChatPartComponent<HTMLDivElement, ChatHeaderProps>;
55
129
  Bubble: ChatPartComponent<HTMLDivElement, ChatBubbleProps>;
@@ -0,0 +1,4 @@
1
+ import React from "react";
2
+ import type { ChatComposerProps } from "./Chat.types";
3
+ /** Controlled prompts, attachments, and application-supplied completions. */
4
+ export declare const ChatComposer: React.ForwardRefExoticComponent<Omit<ChatComposerProps, "ref"> & React.RefAttributes<HTMLDivElement>>;
@@ -1,2 +1,3 @@
1
1
  export * from "./Chat";
2
+ export * from "./ChatComposer";
2
3
  export type * from "./Chat.types";