@ogygie/ui 0.17.0 → 0.19.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/package.json +2 -2
- package/src/CharacterCount.tsx +1 -1
- package/src/ContractForm.test.tsx +6 -0
- package/src/ContractForm.tsx +6 -2
- package/src/failures.test.ts +5 -0
- package/src/failures.ts +3 -1
- package/src/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ogygie/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"description": "Ogygie front socle: the five prescribed patterns (DataTable, EmptyState, ErrorBanner, ContractForm, VarianceChart), one screen-state component (ScreenState), fetchContract and EditableText. Ships TypeScript sources — Ogygie products run Vite/tsx/vitest by construction (D1); a product imports them, it no longer copies them (D39).",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"./variance-chart": "./src/VarianceChart.tsx"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@ogygie/core": "0.
|
|
22
|
+
"@ogygie/core": "0.19.0"
|
|
23
23
|
},
|
|
24
24
|
"peerDependencies": {
|
|
25
25
|
"@tanstack/react-table": "^9.0.0",
|
package/src/CharacterCount.tsx
CHANGED
|
@@ -11,7 +11,7 @@ export function CharacterCount({ length, max }: { length: number; max: number })
|
|
|
11
11
|
data-pattern="CharacterCount"
|
|
12
12
|
className={`ml-2 text-xs tabular-nums ${length >= max ? "text-warning" : "text-text-muted"}`}
|
|
13
13
|
>
|
|
14
|
-
{length} / {max}
|
|
14
|
+
{length.toLocaleString()} / {max.toLocaleString()}
|
|
15
15
|
</span>
|
|
16
16
|
);
|
|
17
17
|
}
|
|
@@ -110,6 +110,12 @@ describe("ContractForm", () => {
|
|
|
110
110
|
expect(onCancel).toHaveBeenCalledTimes(1);
|
|
111
111
|
});
|
|
112
112
|
|
|
113
|
+
it("ÉTANT DONNÉ controls ALORS un texte sans borne se saisit dans un textarea quand la vue le demande", () => {
|
|
114
|
+
const Import = z.object({ csv: z.string().min(1) });
|
|
115
|
+
render(<ContractForm schema={Import} onSubmit={() => undefined} submitLabel="Import" labels={{ csv: "CSV" }} controls={{ csv: "textarea" }} />);
|
|
116
|
+
expect(screen.getByLabelText(/CSV/).tagName).toBe("TEXTAREA");
|
|
117
|
+
});
|
|
118
|
+
|
|
113
119
|
it("ÉTANT DONNÉ resetOnSubmit ALORS le formulaire se vide après une soumission acceptée", async () => {
|
|
114
120
|
const Simple = z.object({ label: z.string().min(1) });
|
|
115
121
|
render(<ContractForm schema={Simple} onSubmit={() => undefined} submitLabel="Create" resetOnSubmit />);
|
package/src/ContractForm.tsx
CHANGED
|
@@ -34,6 +34,8 @@ export interface ContractFormProps<TSchema extends z.ZodType<Record<string, unkn
|
|
|
34
34
|
optionLabels?: Partial<Record<keyof z.input<TSchema> & string, Record<string, ReactNode>>>;
|
|
35
35
|
/** Which fields to show, in which order. Default: every field of the shape, in its order. A hidden field still validates (its default value is sent). */
|
|
36
36
|
fields?: ReadonlyArray<keyof z.input<TSchema> & string>;
|
|
37
|
+
/** The control of a text field when the schema cannot say it: a long text in a textarea, a bounded one in an input. */
|
|
38
|
+
controls?: Partial<Record<keyof z.input<TSchema> & string, "textarea" | "text">>;
|
|
37
39
|
/** The write in flight outside the form (a mutation): the submit stays inactive. */
|
|
38
40
|
pending?: boolean;
|
|
39
41
|
/** The server's refusal, shown as a `save` banner above the actions. */
|
|
@@ -138,8 +140,10 @@ export function ContractForm<TSchema extends z.ZodType<Record<string, unknown>,
|
|
|
138
140
|
disabled: busy,
|
|
139
141
|
autoFocus: autoFocus && index === 0,
|
|
140
142
|
};
|
|
143
|
+
const forced = props.controls?.[field.name as keyof z.input<TSchema> & string];
|
|
144
|
+
const kind = forced === undefined ? field.kind : forced === "textarea" && (field.kind === "text" || field.kind === "textarea") ? "textarea" : forced === "text" && field.kind === "textarea" ? "text" : field.kind;
|
|
141
145
|
let control: ReactNode;
|
|
142
|
-
switch (
|
|
146
|
+
switch (kind) {
|
|
143
147
|
case "checkbox":
|
|
144
148
|
control = (
|
|
145
149
|
<input type="checkbox" {...shared} {...register(field.name)} className="h-4 w-4 rounded-sm border-border accent-accent" />
|
|
@@ -198,7 +202,7 @@ export function ContractForm<TSchema extends z.ZodType<Record<string, unknown>,
|
|
|
198
202
|
break;
|
|
199
203
|
default: {
|
|
200
204
|
const type =
|
|
201
|
-
|
|
205
|
+
kind === "email" ? "email" : kind === "url" ? "url" : kind === "date" ? "date" : kind === "datetime" ? "datetime-local" : "text";
|
|
202
206
|
control = (
|
|
203
207
|
<input
|
|
204
208
|
type={type}
|
package/src/failures.test.ts
CHANGED
|
@@ -28,4 +28,9 @@ describe("failures", () => {
|
|
|
28
28
|
expect(messageOf(new OgygieFailure(payload, 403))).toBe(payload.message);
|
|
29
29
|
expect(failureOf({ error: { code: "x" } })).toBeNull();
|
|
30
30
|
});
|
|
31
|
+
it("croit sur parole une OgygieFailure par sa classe, même quand son corps ne passerait pas le schéma", () => {
|
|
32
|
+
const failure = new OgygieFailure({ ogygie: true, code: "CHECK_IN_EMPTY", message: "m", file: null, cause: "text=''", suggestedFix: "f", details: {}, timestamp: new Date(0).toISOString() }, 400);
|
|
33
|
+
expect(failureOf(failure)?.code).toBe("CHECK_IN_EMPTY");
|
|
34
|
+
expect(messageOf(failure)).toBe("m");
|
|
35
|
+
});
|
|
31
36
|
});
|
package/src/failures.ts
CHANGED
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
* lose the code and the suggested fix, the only two things a reader can act
|
|
9
9
|
* on. Anything else degrades to its message.
|
|
10
10
|
*/
|
|
11
|
-
import { OgygieErrorSchema, type OgygieError } from "@ogygie/core/errors";
|
|
11
|
+
import { OgygieErrorSchema, OgygieFailure, type OgygieError } from "@ogygie/core/errors";
|
|
12
12
|
|
|
13
13
|
/** The OgygieError a failure carries, or null when the thrown value is not one. */
|
|
14
14
|
export function failureOf(error: unknown): OgygieError | null {
|
|
15
|
+
// A failure our own code built answers for its body: read as it is — a test's short message is not a foreign object (astorg-ai-pdlc, 2026-10-18).
|
|
16
|
+
if (error instanceof OgygieFailure) return error.error;
|
|
15
17
|
if (typeof error !== "object" || error === null || !("error" in error)) return null;
|
|
16
18
|
const parsed = OgygieErrorSchema.safeParse((error as { error: unknown }).error);
|
|
17
19
|
return parsed.success ? parsed.data : null;
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,8 @@ export {
|
|
|
22
22
|
type FetchLike,
|
|
23
23
|
} from "./fetch-contract.js";
|
|
24
24
|
export { failureOf, isAccessDenied, messageOf, statusOf } from "./failures.js";
|
|
25
|
+
// What a generated screen test imports of the core — so a front product declares `@ogygie/ui` alone, never the core (lot 4 M2 validation, 2026-10-18).
|
|
26
|
+
export { OgygieFailure, type OgygieError } from "@ogygie/core/errors";
|
|
25
27
|
export {
|
|
26
28
|
failedQuery,
|
|
27
29
|
idleMutation,
|