@uktrade/react-component-library 0.11.0 → 0.13.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/dist/components/ApiBoundary/ApiBoundary.d.ts.map +1 -1
- package/dist/components/ApiBoundary/ApiBoundary.js +1 -1
- package/dist/components/ApiQuery/ApiCreateHooks.d.ts +42 -2
- package/dist/components/ApiQuery/ApiCreateHooks.d.ts.map +1 -1
- package/dist/components/ApiQuery/ApiCreateHooks.js +44 -37
- package/dist/components/ApiQuery/ApiProvider.d.ts +5 -5
- package/dist/components/ApiQuery/ApiProvider.d.ts.map +1 -1
- package/dist/components/ApiQuery/ApiProvider.js +4 -2
- package/dist/components/Inputs/CheckBoxesInput/CheckBoxGroupInput.d.ts +21 -0
- package/dist/components/Inputs/CheckBoxesInput/CheckBoxGroupInput.d.ts.map +1 -0
- package/dist/components/Inputs/CheckBoxesInput/CheckBoxGroupInput.js +14 -0
- package/dist/components/Inputs/SelectInput/SelectInput.d.ts +21 -0
- package/dist/components/Inputs/SelectInput/SelectInput.d.ts.map +1 -0
- package/dist/components/Inputs/SelectInput/SelectInput.js +18 -0
- package/dist/components/Inputs/TextInput/TextInput.d.ts +17 -0
- package/dist/components/Inputs/TextInput/TextInput.d.ts.map +1 -0
- package/dist/components/Inputs/TextInput/TextInput.js +19 -0
- package/dist/components/Inputs/index.d.ts +4 -0
- package/dist/components/Inputs/index.d.ts.map +1 -0
- package/dist/components/Inputs/index.js +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/package.json +1 -1
- package/src/components/ApiBoundary/ApiBoundary.tsx +5 -4
- package/src/components/ApiQuery/ApiCreateHooks.tsx +40 -36
- package/src/components/ApiQuery/ApiProvider.tsx +8 -3
- package/src/components/Inputs/CheckBoxesInput/CheckBoxGroupInput.tsx +93 -0
- package/src/components/Inputs/DateInput/.gitkeep +0 -0
- package/src/components/Inputs/RadioInput/.gitkeep +0 -0
- package/src/components/Inputs/SelectInput/SelectInput.tsx +102 -0
- package/src/components/Inputs/TextInput/TextInput.tsx +105 -0
- package/src/components/Inputs/index.ts +3 -0
- package/src/index.ts +1 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiBoundary.d.ts","sourceRoot":"","sources":["../../../src/components/ApiBoundary/ApiBoundary.tsx"],"names":[],"mappings":"AAKA,UAAU,gBAAgB;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAGD,eAAO,MAAM,WAAW,GAAI,mDAMzB,gBAAgB,
|
|
1
|
+
{"version":3,"file":"ApiBoundary.d.ts","sourceRoot":"","sources":["../../../src/components/ApiBoundary/ApiBoundary.tsx"],"names":[],"mappings":"AAKA,UAAU,gBAAgB;IACxB,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;CAC3B;AAGD,eAAO,MAAM,WAAW,GAAI,mDAMzB,gBAAgB,4CAwBlB,CAAC"}
|
|
@@ -4,4 +4,4 @@ import { LoadingSpinner } from "../LoadingSpinner/LoadingSpinner";
|
|
|
4
4
|
import styles from "./ApiBoundary.module.css";
|
|
5
5
|
import { Button } from "../Button";
|
|
6
6
|
/* Boundary for API-related states: loading, success, and error */
|
|
7
|
-
export const ApiBoundary = ({ isLoading, isError, error, onRetry, children, }) => (_jsxs("div", { className: styles.root, "aria-live": "polite", children: [isLoading && _jsx(LoadingSpinner, {}), !isLoading && !isError && children, isError && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.errorBackdrop }), _jsxs("section", { className: styles.error, children: [_jsx("h2", { className: "govuk-heading-m", children: "Error!" }), _jsx("pre", { className: styles.description, children: typeof error === "string" ? error : JSON.stringify(error, null, 2) }), _jsx(Button, { variant: "warning", onClick: onRetry, children: "Retry" })] })] }))] }));
|
|
7
|
+
export const ApiBoundary = ({ isLoading, isError, error, onRetry, children, }) => (_jsxs("div", { className: styles.root, "aria-live": "polite", children: [isLoading && _jsx(LoadingSpinner, {}), !isLoading && !isError && children, isError && (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.errorBackdrop }), _jsxs("section", { className: styles.error, children: [_jsx("h2", { className: "govuk-heading-m", children: "Error!" }), _jsx("pre", { className: styles.description, children: typeof error === "string" ? error : JSON.stringify(error, null, 2) }), onRetry && (_jsx(Button, { variant: "warning", onClick: onRetry, children: "Retry" }))] })] }))] }));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PathsWithMethod } from "openapi-typescript-helpers";
|
|
2
2
|
import { createApiClient } from "./ApiCreateClient";
|
|
3
3
|
export declare function createApiHooks<Paths extends {}>(client: ReturnType<typeof createApiClient<Paths>>): {
|
|
4
|
-
get: <P extends PathsWithMethod<Paths, "get">>(path: P,
|
|
4
|
+
get: <P extends PathsWithMethod<Paths, "get">>(path: P, input?: any) => {
|
|
5
5
|
query: import("@tanstack/react-query").UseQueryResult<any, Error>;
|
|
6
6
|
state: {
|
|
7
7
|
data: any;
|
|
@@ -11,7 +11,7 @@ export declare function createApiHooks<Paths extends {}>(client: ReturnType<type
|
|
|
11
11
|
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<any, Error>>;
|
|
12
12
|
};
|
|
13
13
|
};
|
|
14
|
-
post: <P extends PathsWithMethod<Paths, "post">>(path: P,
|
|
14
|
+
post: <P extends PathsWithMethod<Paths, "post">>(path: P, input?: any) => {
|
|
15
15
|
query: import("@tanstack/react-query").UseQueryResult<any, Error>;
|
|
16
16
|
state: {
|
|
17
17
|
data: any;
|
|
@@ -21,5 +21,45 @@ export declare function createApiHooks<Paths extends {}>(client: ReturnType<type
|
|
|
21
21
|
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<any, Error>>;
|
|
22
22
|
};
|
|
23
23
|
};
|
|
24
|
+
postMutation: <P extends PathsWithMethod<Paths, "post">>(path: P) => {
|
|
25
|
+
mutation: import("@tanstack/react-query").UseMutationResult<any, unknown, any, unknown>;
|
|
26
|
+
state: {
|
|
27
|
+
data: any;
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
isError: boolean;
|
|
30
|
+
error: unknown;
|
|
31
|
+
mutate: import("@tanstack/react-query").UseMutateFunction<any, unknown, any, unknown>;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
putMutation: <P extends PathsWithMethod<Paths, "put">>(path: P) => {
|
|
35
|
+
mutation: import("@tanstack/react-query").UseMutationResult<any, unknown, any, unknown>;
|
|
36
|
+
state: {
|
|
37
|
+
data: any;
|
|
38
|
+
isLoading: boolean;
|
|
39
|
+
isError: boolean;
|
|
40
|
+
error: unknown;
|
|
41
|
+
mutate: import("@tanstack/react-query").UseMutateFunction<any, unknown, any, unknown>;
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
patchMutation: <P extends PathsWithMethod<Paths, "patch">>(path: P) => {
|
|
45
|
+
mutation: import("@tanstack/react-query").UseMutationResult<any, unknown, any, unknown>;
|
|
46
|
+
state: {
|
|
47
|
+
data: any;
|
|
48
|
+
isLoading: boolean;
|
|
49
|
+
isError: boolean;
|
|
50
|
+
error: unknown;
|
|
51
|
+
mutate: import("@tanstack/react-query").UseMutateFunction<any, unknown, any, unknown>;
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
deleteMutation: <P extends PathsWithMethod<Paths, "delete">>(path: P) => {
|
|
55
|
+
mutation: import("@tanstack/react-query").UseMutationResult<any, unknown, any, unknown>;
|
|
56
|
+
state: {
|
|
57
|
+
data: any;
|
|
58
|
+
isLoading: boolean;
|
|
59
|
+
isError: boolean;
|
|
60
|
+
error: unknown;
|
|
61
|
+
mutate: import("@tanstack/react-query").UseMutateFunction<any, unknown, any, unknown>;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
24
64
|
};
|
|
25
65
|
//# sourceMappingURL=ApiCreateHooks.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiCreateHooks.d.ts","sourceRoot":"","sources":["../../../src/components/ApiQuery/ApiCreateHooks.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"ApiCreateHooks.d.ts","sourceRoot":"","sources":["../../../src/components/ApiQuery/ApiCreateHooks.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKpD,wBAAgB,cAAc,CAAC,KAAK,SAAS,EAAE,EAC7C,MAAM,EAAE,UAAU,CAAC,OAAO,eAAe,CAAC,KAAK,CAAC,CAAC;UAQzC,CAAC,SAAS,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,QACrC,CAAC,UACC,GAAG;;;;;;;;;;WAYN,CAAC,SAAS,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,QACvC,CAAC,UACC,GAAG;;;;;;;;;;mBAaE,CAAC,SAAS,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC;;;;;;;;;;kBAQlD,CAAC,SAAS,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,QAAQ,CAAC;;;;;;;;;;oBAQ9C,CAAC,SAAS,eAAe,CAAC,KAAK,EAAE,OAAO,CAAC,QAAQ,CAAC;;;;;;;;;;qBAQjD,CAAC,SAAS,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC;;;;;;;;;;EAQvE"}
|
|
@@ -1,55 +1,62 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { createApiClient } from "./ApiCreateClient";
|
|
3
|
-
import { useApi } from "./ApiProvider";
|
|
3
|
+
import { useApi, useApiMutation } from "./ApiProvider";
|
|
4
|
+
// API hook factory that converts a typed OpenAPI client into React hooks (`useApi` and `useApiMutation`) that the UI can use.
|
|
5
|
+
// Provides a simple interface for making API calls and handling loading and error states in your React components.
|
|
4
6
|
export function createApiHooks(client) {
|
|
7
|
+
// ToDo: Generalize this (params?: any) and fight openapi-fetch’s internal generics to get correct types from PathsWithMethod. Same for the mutation methods.
|
|
5
8
|
// type GetParams<P extends PathsWithMethod<Paths, "get">> =
|
|
6
9
|
// Parameters<typeof client.GET<P>>[1];
|
|
10
|
+
// Using useQuery
|
|
7
11
|
return {
|
|
8
|
-
get: (path,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const res = await client.GET(path, params);
|
|
12
|
+
get: (path, input) => {
|
|
13
|
+
return useApi(["get", path, JSON.stringify(input ?? {})], async () => {
|
|
14
|
+
const res = await client.GET(path, input);
|
|
12
15
|
if (res.error)
|
|
13
16
|
throw res.error;
|
|
14
17
|
return res.data;
|
|
15
18
|
});
|
|
16
19
|
},
|
|
17
|
-
post: (path,
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
post: (path, input) => {
|
|
21
|
+
return useApi(["post", path, JSON.stringify(input ?? {})], async () => {
|
|
22
|
+
const res = await client.POST(path, input);
|
|
23
|
+
if (res.error)
|
|
24
|
+
throw res.error;
|
|
25
|
+
return res.data;
|
|
26
|
+
});
|
|
27
|
+
},
|
|
28
|
+
// Using useMutation
|
|
29
|
+
postMutation: (path) => {
|
|
30
|
+
return useApiMutation(async (input) => {
|
|
31
|
+
const res = await client.POST(path, input);
|
|
32
|
+
if (res.error)
|
|
33
|
+
throw res.error;
|
|
34
|
+
return res.data;
|
|
35
|
+
});
|
|
36
|
+
},
|
|
37
|
+
putMutation: (path) => {
|
|
38
|
+
return useApiMutation(async (input) => {
|
|
39
|
+
const res = await client.PUT(path, input);
|
|
40
|
+
if (res.error)
|
|
41
|
+
throw res.error;
|
|
42
|
+
return res.data;
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
patchMutation: (path) => {
|
|
46
|
+
return useApiMutation(async (input) => {
|
|
47
|
+
const res = await client.PATCH(path, input);
|
|
48
|
+
if (res.error)
|
|
49
|
+
throw res.error;
|
|
50
|
+
return res.data;
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
deleteMutation: (path) => {
|
|
54
|
+
return useApiMutation(async (input) => {
|
|
55
|
+
const res = await client.DELETE(path, input);
|
|
21
56
|
if (res.error)
|
|
22
57
|
throw res.error;
|
|
23
58
|
return res.data;
|
|
24
59
|
});
|
|
25
60
|
},
|
|
26
|
-
// TODO: implement other methods (put, delete), furthermore generalize the mutation method
|
|
27
|
-
// for all methods (No GET) to avoid React Query’s useQuery from running automatically on mount
|
|
28
|
-
// Same here (params?: any)
|
|
29
|
-
// postMutation: <P extends PathsWithMethod<Paths, "post">>(path: P, params?: any) => {
|
|
30
|
-
// return useApiMutation(async () => {
|
|
31
|
-
// const res = await client.POST(path, params);
|
|
32
|
-
// if (res.error) throw res.error;
|
|
33
|
-
// return res.data;
|
|
34
|
-
// });
|
|
35
|
-
// },
|
|
36
|
-
// mutation: <
|
|
37
|
-
// M extends "post" | "put" | "patch" | "delete",
|
|
38
|
-
// P extends PathsWithMethod<Paths, M>
|
|
39
|
-
// >(
|
|
40
|
-
// method: M,
|
|
41
|
-
// path: P,
|
|
42
|
-
// params?: any
|
|
43
|
-
// ) => {
|
|
44
|
-
// return useApiMutation(async () => {
|
|
45
|
-
// const res = await client[method.toUpperCase() as "POST" | "PUT" | "PATCH" | "DELETE"](path, params);
|
|
46
|
-
// if (res.error) throw res.error;
|
|
47
|
-
// return res.data;
|
|
48
|
-
// });
|
|
49
|
-
// }
|
|
50
|
-
// ToDo: Generalize this:
|
|
51
|
-
// const res = await client.POST(path, params);
|
|
52
|
-
// if (res.error) throw res.error;
|
|
53
|
-
// return res.data;
|
|
54
61
|
};
|
|
55
62
|
}
|
|
@@ -21,14 +21,14 @@ export declare function useApi<T>(key: QueryKey, queryFn: () => Promise<T>): {
|
|
|
21
21
|
refetch: (options?: import("@tanstack/query-core").RefetchOptions) => Promise<import("@tanstack/query-core").QueryObserverResult<import("@tanstack/query-core").NoInfer<T>, Error>>;
|
|
22
22
|
};
|
|
23
23
|
};
|
|
24
|
-
export declare function useApiMutation<
|
|
25
|
-
mutation: import("@tanstack/react-query").UseMutationResult<
|
|
24
|
+
export declare function useApiMutation<TData, TVariables = void>(mutationFn: (variables: TVariables) => Promise<TData>): {
|
|
25
|
+
mutation: import("@tanstack/react-query").UseMutationResult<TData, unknown, TVariables, unknown>;
|
|
26
26
|
state: {
|
|
27
|
-
data:
|
|
27
|
+
data: TData | undefined;
|
|
28
28
|
isLoading: boolean;
|
|
29
29
|
isError: boolean;
|
|
30
|
-
error:
|
|
31
|
-
mutate: import("@tanstack/react-query").UseMutateFunction<
|
|
30
|
+
error: unknown;
|
|
31
|
+
mutate: import("@tanstack/react-query").UseMutateFunction<TData, unknown, TVariables, unknown>;
|
|
32
32
|
};
|
|
33
33
|
};
|
|
34
34
|
//# sourceMappingURL=ApiProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ApiProvider.d.ts","sourceRoot":"","sources":["../../../src/components/ApiQuery/ApiProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,EAIX,KAAK,QAAQ,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AASvC,wBAAgB,cAAc,gBAa7B;AAMD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAMhE;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAYF,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;;;;;;;;;EAgBjE;AAYD,wBAAgB,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"ApiProvider.d.ts","sourceRoot":"","sources":["../../../src/components/ApiQuery/ApiProvider.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,WAAW,EAIX,KAAK,QAAQ,EACd,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AASvC,wBAAgB,cAAc,gBAa7B;AAMD,wBAAgB,WAAW,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAMhE;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,IAAI;IAC7B,IAAI,CAAC,EAAE,CAAC,CAAC;IACT,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC;AAYF,wBAAgB,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC;;;;;;;;;EAgBjE;AAYD,wBAAgB,cAAc,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,EACrD,UAAU,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAAO,CAAC,KAAK,CAAC;;;;;;;;;EAgBtD"}
|
|
@@ -65,9 +65,11 @@ It accepts `mutationFn` as function that performs the mutation request, and It r
|
|
|
65
65
|
- `state` — a simplified structure with commonly used properties like `data`, `isLoading`, `isError`
|
|
66
66
|
*/
|
|
67
67
|
export function useApiMutation(mutationFn) {
|
|
68
|
-
const mutation = useMutation({
|
|
68
|
+
const mutation = useMutation({
|
|
69
|
+
mutationFn,
|
|
70
|
+
});
|
|
69
71
|
return {
|
|
70
|
-
mutation,
|
|
72
|
+
mutation,
|
|
71
73
|
state: {
|
|
72
74
|
data: mutation.data,
|
|
73
75
|
isLoading: mutation.isPending,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
type LegendAs = "h1" | "h2" | "h3" | "h4";
|
|
3
|
+
type LegendSize = "s" | "m" | "l" | "xl";
|
|
4
|
+
interface CheckboxOption {
|
|
5
|
+
id: string;
|
|
6
|
+
value: string;
|
|
7
|
+
label: ReactNode;
|
|
8
|
+
checked?: boolean;
|
|
9
|
+
}
|
|
10
|
+
interface CheckboxGroupProps {
|
|
11
|
+
legend: ReactNode;
|
|
12
|
+
hint?: ReactNode;
|
|
13
|
+
error?: string;
|
|
14
|
+
options: CheckboxOption[];
|
|
15
|
+
name: string;
|
|
16
|
+
legendAs?: LegendAs;
|
|
17
|
+
legendSize?: LegendSize;
|
|
18
|
+
}
|
|
19
|
+
export declare const CheckboxGroup: ({ legend, hint, error, options, name, legendAs, legendSize, }: CheckboxGroupProps) => import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=CheckBoxGroupInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CheckBoxGroupInput.d.ts","sourceRoot":"","sources":["../../../../src/components/Inputs/CheckBoxesInput/CheckBoxGroupInput.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,KAAK,QAAQ,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AAC1C,KAAK,UAAU,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC;AAEzC,UAAU,cAAc;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,UAAU,kBAAkB;IACxB,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,UAAU,CAAC,EAAE,UAAU,CAAC;CAC3B;AAED,eAAO,MAAM,aAAa,GAAI,+DAQ3B,kBAAkB,4CA0DpB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { createElement } from "react";
|
|
5
|
+
export const CheckboxGroup = ({ legend, hint, error, options, name, legendAs = "h4", legendSize = "m", }) => {
|
|
6
|
+
const hasError = Boolean(error);
|
|
7
|
+
const errorId = hasError ? `${name}-error` : undefined;
|
|
8
|
+
const legendHeading = createElement(legendAs, { className: "govuk-fieldset__heading" }, legend);
|
|
9
|
+
return (_jsx("div", { className: clsx("govuk-form-group", {
|
|
10
|
+
"govuk-form-group--error": hasError,
|
|
11
|
+
}), children: _jsxs("fieldset", { className: "govuk-fieldset", "aria-describedby": errorId, children: [_jsx("legend", { className: clsx("govuk-fieldset__legend", {
|
|
12
|
+
[`govuk-fieldset__legend--${legendSize}`]: legendSize !== "m",
|
|
13
|
+
}), children: legendHeading }), hint && _jsx("div", { className: "govuk-hint", children: hint }), hasError && (_jsxs("p", { id: errorId, className: "govuk-error-message", children: [_jsx("span", { className: "govuk-visually-hidden", children: "Error:" }), " ", error] })), _jsx("div", { className: "govuk-checkboxes", "data-module": "govuk-checkboxes", children: options.map((opt) => (_jsxs("div", { className: "govuk-checkboxes__item", children: [_jsx("input", { className: "govuk-checkboxes__input", id: opt.id, name: name, type: "checkbox", value: opt.value, defaultChecked: opt.checked }), _jsx("label", { className: "govuk-label govuk-checkboxes__label", htmlFor: opt.id, children: opt.label })] }, opt.id))) })] }) }));
|
|
14
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { JSX, ReactNode } from "react";
|
|
2
|
+
type LabelAs = "h1" | "h2" | "h3" | "h4";
|
|
3
|
+
type LabelSize = "s" | "m" | "l" | "xl";
|
|
4
|
+
type SelectProps = Omit<JSX.IntrinsicElements["select"], "id">;
|
|
5
|
+
interface SelectInputProps extends SelectProps {
|
|
6
|
+
id: string;
|
|
7
|
+
label: ReactNode;
|
|
8
|
+
hint?: ReactNode;
|
|
9
|
+
error?: string;
|
|
10
|
+
options: {
|
|
11
|
+
value: string;
|
|
12
|
+
label: string;
|
|
13
|
+
}[];
|
|
14
|
+
placeholder?: string;
|
|
15
|
+
labelAs?: LabelAs;
|
|
16
|
+
labelSize?: LabelSize;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const SelectInput: ({ id, label, hint, error, options, placeholder, labelAs, labelSize, className, ...props }: SelectInputProps) => import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=SelectInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectInput.d.ts","sourceRoot":"","sources":["../../../../src/components/Inputs/SelectInput/SelectInput.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAG5C,KAAK,OAAO,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACzC,KAAK,SAAS,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC;AACxC,KAAK,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAC;AAE/D,UAAU,gBAAiB,SAAQ,WAAW;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,WAAW,GAAI,2FAWzB,gBAAgB,4CAoElB,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { createElement } from "react";
|
|
5
|
+
export const SelectInput = ({ id, label, hint, error, options, placeholder, labelAs, labelSize = "m", className, ...props }) => {
|
|
6
|
+
const hasError = Boolean(error);
|
|
7
|
+
const hintId = hint ? `${id}-hint` : undefined;
|
|
8
|
+
const errorId = hasError ? `${id}-error` : undefined;
|
|
9
|
+
const ariaDescribedBy = [hintId, errorId].filter(Boolean).join(" ") || undefined;
|
|
10
|
+
const labelElement = (_jsx("label", { htmlFor: id, className: clsx("govuk-label", {
|
|
11
|
+
[`govuk-label--${labelSize}`]: labelSize !== "m",
|
|
12
|
+
}), children: label }));
|
|
13
|
+
return (_jsxs("div", { className: clsx("govuk-form-group", {
|
|
14
|
+
"govuk-form-group--error": hasError,
|
|
15
|
+
}), children: [labelAs
|
|
16
|
+
? createElement(labelAs, { className: "govuk-label-wrapper" }, labelElement)
|
|
17
|
+
: labelElement, hint && (_jsx("div", { id: hintId, className: "govuk-hint", children: hint })), hasError && (_jsxs("p", { id: errorId, className: "govuk-error-message", children: [_jsx("span", { className: "govuk-visually-hidden", children: "Error:" }), " ", error] })), _jsxs("select", { id: id, className: clsx("govuk-select", { "govuk-select--error": hasError }, className), "aria-describedby": ariaDescribedBy, ...props, children: [placeholder && (_jsx("option", { value: "", disabled: true, hidden: true, children: placeholder })), options.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value)))] })] }));
|
|
18
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { JSX, ReactNode } from "react";
|
|
2
|
+
type LabelAs = "h1" | "h2" | "h3" | "h4";
|
|
3
|
+
type InputProps = Omit<JSX.IntrinsicElements["input"], "id">;
|
|
4
|
+
interface TextInputProps extends InputProps {
|
|
5
|
+
id: string;
|
|
6
|
+
label: ReactNode;
|
|
7
|
+
hint?: ReactNode;
|
|
8
|
+
error?: string;
|
|
9
|
+
width?: "10" | "20" | "30";
|
|
10
|
+
labelSize?: "s" | "m" | "l" | "xl";
|
|
11
|
+
labelAs?: LabelAs;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
suffix?: ReactNode;
|
|
14
|
+
}
|
|
15
|
+
export declare const TextInput: ({ id, label, hint, error, width, labelSize, labelAs, className, placeholder, suffix, ...props }: TextInputProps) => import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=TextInput.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TextInput.d.ts","sourceRoot":"","sources":["../../../../src/components/Inputs/TextInput/TextInput.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAG5C,KAAK,OAAO,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;AACzC,KAAK,UAAU,GAAG,IAAI,CAAC,GAAG,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,CAAC;AAE7D,UAAU,cAAe,SAAQ,UAAU;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,SAAS,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC3B,SAAS,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,IAAI,CAAC;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,CAAC;CACtB;AAED,eAAO,MAAM,SAAS,GAAI,iGAYvB,cAAc,4CAuEhB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import { createElement } from "react";
|
|
5
|
+
export const TextInput = ({ id, label, hint, error, width = "20", labelSize = "m", labelAs, className, placeholder, suffix, ...props }) => {
|
|
6
|
+
const hasError = Boolean(error);
|
|
7
|
+
const hintId = hint ? `${id}-hint` : undefined;
|
|
8
|
+
const errorId = hasError ? `${id}-error` : undefined;
|
|
9
|
+
const describedBy = [hintId, errorId].filter(Boolean).join(" ") || undefined;
|
|
10
|
+
const labelElement = (_jsx("label", { htmlFor: id, className: clsx("govuk-label", {
|
|
11
|
+
[`govuk-label--${labelSize}`]: labelSize !== "m",
|
|
12
|
+
}), children: label }));
|
|
13
|
+
const input = (_jsx("input", { id: id, className: clsx("govuk-input", `govuk-input--width-${width}`, className), "aria-invalid": hasError, "aria-describedby": describedBy, placeholder: placeholder, ...props }));
|
|
14
|
+
return (_jsxs("div", { className: clsx("govuk-form-group", {
|
|
15
|
+
"govuk-form-group--error": hasError,
|
|
16
|
+
}), children: [labelAs
|
|
17
|
+
? createElement(labelAs, { className: "govuk-label-wrapper" }, labelElement)
|
|
18
|
+
: labelElement, hint && (_jsx("div", { id: hintId, className: "govuk-hint", children: hint })), hasError && (_jsxs("p", { id: errorId, className: "govuk-error-message", children: [_jsx("span", { className: "govuk-visually-hidden", children: "Error:" }), " ", error] })), suffix ? (_jsxs("div", { className: "govuk-input__wrapper", children: [input, _jsx("div", { className: "govuk-input__suffix", "aria-hidden": "true", children: suffix })] })) : (input)] }));
|
|
19
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/Inputs/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,sCAAsC,CAAC;AACrD,cAAc,2BAA2B,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,4 +11,5 @@ export * from "./components/BackLink";
|
|
|
11
11
|
export * from "./components/LoadingSpinner/LoadingSpinner";
|
|
12
12
|
export * from "./components/ApiBoundary/ApiBoundary";
|
|
13
13
|
export * from "./components/ApiQuery";
|
|
14
|
+
export * from "./components/Inputs";
|
|
14
15
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AACrD,cAAc,wCAAwC,CAAC;AACvD,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,4CAA4C,CAAC;AAC3D,cAAc,sCAAsC,CAAC;AACrD,cAAc,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sCAAsC,CAAC;AACrD,cAAc,sCAAsC,CAAC;AACrD,cAAc,wCAAwC,CAAC;AACvD,cAAc,0BAA0B,CAAC;AACzC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,wBAAwB,CAAC;AACvC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,0BAA0B,CAAC;AACzC,cAAc,uBAAuB,CAAC;AACtC,cAAc,4CAA4C,CAAC;AAC3D,cAAc,sCAAsC,CAAC;AACrD,cAAc,uBAAuB,CAAC;AACtC,cAAc,qBAAqB,CAAC"}
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -33,10 +33,11 @@ export const ApiBoundary = ({
|
|
|
33
33
|
{typeof error === "string" ? error : JSON.stringify(error, null, 2)}
|
|
34
34
|
</pre>
|
|
35
35
|
|
|
36
|
-
{
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
{onRetry && (
|
|
37
|
+
<Button variant="warning" onClick={onRetry}>
|
|
38
|
+
Retry
|
|
39
|
+
</Button>
|
|
40
|
+
)}
|
|
40
41
|
</section>
|
|
41
42
|
</>
|
|
42
43
|
)}
|
|
@@ -3,23 +3,27 @@
|
|
|
3
3
|
import type { PathsWithMethod } from "openapi-typescript-helpers";
|
|
4
4
|
|
|
5
5
|
import { createApiClient } from "./ApiCreateClient";
|
|
6
|
-
import { useApi } from "./ApiProvider";
|
|
6
|
+
import { useApi, useApiMutation } from "./ApiProvider";
|
|
7
7
|
|
|
8
|
+
// API hook factory that converts a typed OpenAPI client into React hooks (`useApi` and `useApiMutation`) that the UI can use.
|
|
9
|
+
// Provides a simple interface for making API calls and handling loading and error states in your React components.
|
|
8
10
|
export function createApiHooks<Paths extends {}>(
|
|
9
11
|
client: ReturnType<typeof createApiClient<Paths>>
|
|
10
12
|
) {
|
|
13
|
+
// ToDo: Generalize this (params?: any) and fight openapi-fetch’s internal generics to get correct types from PathsWithMethod. Same for the mutation methods.
|
|
11
14
|
// type GetParams<P extends PathsWithMethod<Paths, "get">> =
|
|
12
15
|
// Parameters<typeof client.GET<P>>[1];
|
|
13
16
|
|
|
17
|
+
// Using useQuery
|
|
14
18
|
return {
|
|
15
19
|
get: <P extends PathsWithMethod<Paths, "get">>(
|
|
16
20
|
path: P,
|
|
17
|
-
|
|
21
|
+
input?: any
|
|
18
22
|
) => {
|
|
19
23
|
return useApi(
|
|
20
|
-
["get", path, JSON.stringify(
|
|
24
|
+
["get", path, JSON.stringify(input ?? {})],
|
|
21
25
|
async () => {
|
|
22
|
-
const res = await client.GET(path,
|
|
26
|
+
const res = await client.GET(path, input);
|
|
23
27
|
if (res.error) throw res.error;
|
|
24
28
|
return res.data;
|
|
25
29
|
}
|
|
@@ -28,49 +32,49 @@ export function createApiHooks<Paths extends {}>(
|
|
|
28
32
|
|
|
29
33
|
post: <P extends PathsWithMethod<Paths, "post">>(
|
|
30
34
|
path: P,
|
|
31
|
-
|
|
35
|
+
input?: any
|
|
32
36
|
) => {
|
|
33
37
|
return useApi(
|
|
34
|
-
["post", path, JSON.stringify(
|
|
38
|
+
["post", path, JSON.stringify(input ?? {})],
|
|
35
39
|
async () => {
|
|
36
|
-
const res = await client.POST(path,
|
|
40
|
+
const res = await client.POST(path, input);
|
|
37
41
|
if (res.error) throw res.error;
|
|
38
42
|
return res.data;
|
|
39
43
|
}
|
|
40
44
|
);
|
|
41
45
|
},
|
|
42
46
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// return res.data;
|
|
52
|
-
// });
|
|
53
|
-
// },
|
|
47
|
+
// Using useMutation
|
|
48
|
+
postMutation: <P extends PathsWithMethod<Paths, "post">>(path: P) => {
|
|
49
|
+
return useApiMutation(async (input: any) => {
|
|
50
|
+
const res = await client.POST(path, input);
|
|
51
|
+
if (res.error) throw res.error;
|
|
52
|
+
return res.data;
|
|
53
|
+
});
|
|
54
|
+
},
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// ) => {
|
|
63
|
-
// return useApiMutation(async () => {
|
|
64
|
-
// const res = await client[method.toUpperCase() as "POST" | "PUT" | "PATCH" | "DELETE"](path, params);
|
|
65
|
-
// if (res.error) throw res.error;
|
|
66
|
-
// return res.data;
|
|
67
|
-
// });
|
|
68
|
-
// }
|
|
56
|
+
putMutation: <P extends PathsWithMethod<Paths, "put">>(path: P) => {
|
|
57
|
+
return useApiMutation(async (input: any) => {
|
|
58
|
+
const res = await client.PUT(path, input);
|
|
59
|
+
if (res.error) throw res.error;
|
|
60
|
+
return res.data;
|
|
61
|
+
});
|
|
62
|
+
},
|
|
69
63
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
64
|
+
patchMutation: <P extends PathsWithMethod<Paths, "patch">>(path: P) => {
|
|
65
|
+
return useApiMutation(async (input: any) => {
|
|
66
|
+
const res = await client.PATCH(path, input);
|
|
67
|
+
if (res.error) throw res.error;
|
|
68
|
+
return res.data;
|
|
69
|
+
});
|
|
70
|
+
},
|
|
74
71
|
|
|
72
|
+
deleteMutation: <P extends PathsWithMethod<Paths, "delete">>(path: P) => {
|
|
73
|
+
return useApiMutation(async (input: any) => {
|
|
74
|
+
const res = await client.DELETE(path, input);
|
|
75
|
+
if (res.error) throw res.error;
|
|
76
|
+
return res.data;
|
|
77
|
+
});
|
|
78
|
+
},
|
|
75
79
|
};
|
|
76
80
|
}
|
|
@@ -89,11 +89,15 @@ It accepts `mutationFn` as function that performs the mutation request, and It r
|
|
|
89
89
|
- `mutation` — the full React Query mutation object
|
|
90
90
|
- `state` — a simplified structure with commonly used properties like `data`, `isLoading`, `isError`
|
|
91
91
|
*/
|
|
92
|
-
export function useApiMutation<
|
|
93
|
-
|
|
92
|
+
export function useApiMutation<TData, TVariables = void>(
|
|
93
|
+
mutationFn: (variables: TVariables) => Promise<TData>
|
|
94
|
+
) {
|
|
95
|
+
const mutation = useMutation<TData, unknown, TVariables>({
|
|
96
|
+
mutationFn,
|
|
97
|
+
});
|
|
94
98
|
|
|
95
99
|
return {
|
|
96
|
-
mutation,
|
|
100
|
+
mutation,
|
|
97
101
|
state: {
|
|
98
102
|
data: mutation.data,
|
|
99
103
|
isLoading: mutation.isPending,
|
|
@@ -103,3 +107,4 @@ export function useApiMutation<T>(mutationFn: () => Promise<T>) {
|
|
|
103
107
|
},
|
|
104
108
|
};
|
|
105
109
|
}
|
|
110
|
+
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type { ReactNode } from "react";
|
|
5
|
+
import { createElement } from "react";
|
|
6
|
+
|
|
7
|
+
type LegendAs = "h1" | "h2" | "h3" | "h4";
|
|
8
|
+
type LegendSize = "s" | "m" | "l" | "xl";
|
|
9
|
+
|
|
10
|
+
interface CheckboxOption {
|
|
11
|
+
id: string;
|
|
12
|
+
value: string;
|
|
13
|
+
label: ReactNode;
|
|
14
|
+
checked?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface CheckboxGroupProps {
|
|
18
|
+
legend: ReactNode;
|
|
19
|
+
hint?: ReactNode;
|
|
20
|
+
error?: string;
|
|
21
|
+
options: CheckboxOption[];
|
|
22
|
+
name: string;
|
|
23
|
+
legendAs?: LegendAs;
|
|
24
|
+
legendSize?: LegendSize;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const CheckboxGroup = ({
|
|
28
|
+
legend,
|
|
29
|
+
hint,
|
|
30
|
+
error,
|
|
31
|
+
options,
|
|
32
|
+
name,
|
|
33
|
+
legendAs = "h4",
|
|
34
|
+
legendSize = "m",
|
|
35
|
+
}: CheckboxGroupProps) => {
|
|
36
|
+
const hasError = Boolean(error);
|
|
37
|
+
const errorId = hasError ? `${name}-error` : undefined;
|
|
38
|
+
|
|
39
|
+
const legendHeading = createElement(
|
|
40
|
+
legendAs,
|
|
41
|
+
{ className: "govuk-fieldset__heading" },
|
|
42
|
+
legend
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
className={clsx("govuk-form-group", {
|
|
48
|
+
"govuk-form-group--error": hasError,
|
|
49
|
+
})}
|
|
50
|
+
>
|
|
51
|
+
<fieldset className="govuk-fieldset" aria-describedby={errorId}>
|
|
52
|
+
<legend
|
|
53
|
+
className={clsx("govuk-fieldset__legend", {
|
|
54
|
+
[`govuk-fieldset__legend--${legendSize}`]:
|
|
55
|
+
legendSize !== "m",
|
|
56
|
+
})}
|
|
57
|
+
>
|
|
58
|
+
{legendHeading}
|
|
59
|
+
</legend>
|
|
60
|
+
|
|
61
|
+
{hint && <div className="govuk-hint">{hint}</div>}
|
|
62
|
+
|
|
63
|
+
{hasError && (
|
|
64
|
+
<p id={errorId} className="govuk-error-message">
|
|
65
|
+
<span className="govuk-visually-hidden">Error:</span>{" "}
|
|
66
|
+
{error}
|
|
67
|
+
</p>
|
|
68
|
+
)}
|
|
69
|
+
|
|
70
|
+
<div className="govuk-checkboxes" data-module="govuk-checkboxes">
|
|
71
|
+
{options.map((opt) => (
|
|
72
|
+
<div key={opt.id} className="govuk-checkboxes__item">
|
|
73
|
+
<input
|
|
74
|
+
className="govuk-checkboxes__input"
|
|
75
|
+
id={opt.id}
|
|
76
|
+
name={name}
|
|
77
|
+
type="checkbox"
|
|
78
|
+
value={opt.value}
|
|
79
|
+
defaultChecked={opt.checked}
|
|
80
|
+
/>
|
|
81
|
+
<label
|
|
82
|
+
className="govuk-label govuk-checkboxes__label"
|
|
83
|
+
htmlFor={opt.id}
|
|
84
|
+
>
|
|
85
|
+
{opt.label}
|
|
86
|
+
</label>
|
|
87
|
+
</div>
|
|
88
|
+
))}
|
|
89
|
+
</div>
|
|
90
|
+
</fieldset>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
};
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type { JSX, ReactNode } from "react";
|
|
5
|
+
import { createElement } from "react";
|
|
6
|
+
|
|
7
|
+
type LabelAs = "h1" | "h2" | "h3" | "h4";
|
|
8
|
+
type LabelSize = "s" | "m" | "l" | "xl";
|
|
9
|
+
type SelectProps = Omit<JSX.IntrinsicElements["select"], "id">;
|
|
10
|
+
|
|
11
|
+
interface SelectInputProps extends SelectProps {
|
|
12
|
+
id: string;
|
|
13
|
+
label: ReactNode;
|
|
14
|
+
hint?: ReactNode;
|
|
15
|
+
error?: string;
|
|
16
|
+
options: { value: string; label: string }[];
|
|
17
|
+
placeholder?: string;
|
|
18
|
+
labelAs?: LabelAs;
|
|
19
|
+
labelSize?: LabelSize;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const SelectInput = ({
|
|
24
|
+
id,
|
|
25
|
+
label,
|
|
26
|
+
hint,
|
|
27
|
+
error,
|
|
28
|
+
options,
|
|
29
|
+
placeholder,
|
|
30
|
+
labelAs,
|
|
31
|
+
labelSize = "m",
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: SelectInputProps) => {
|
|
35
|
+
const hasError = Boolean(error);
|
|
36
|
+
const hintId = hint ? `${id}-hint` : undefined;
|
|
37
|
+
const errorId = hasError ? `${id}-error` : undefined;
|
|
38
|
+
const ariaDescribedBy =
|
|
39
|
+
[hintId, errorId].filter(Boolean).join(" ") || undefined;
|
|
40
|
+
|
|
41
|
+
const labelElement = (
|
|
42
|
+
<label
|
|
43
|
+
htmlFor={id}
|
|
44
|
+
className={clsx("govuk-label", {
|
|
45
|
+
[`govuk-label--${labelSize}`]: labelSize !== "m",
|
|
46
|
+
})}
|
|
47
|
+
>
|
|
48
|
+
{label}
|
|
49
|
+
</label>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div
|
|
54
|
+
className={clsx("govuk-form-group", {
|
|
55
|
+
"govuk-form-group--error": hasError,
|
|
56
|
+
})}
|
|
57
|
+
>
|
|
58
|
+
{labelAs
|
|
59
|
+
? createElement(
|
|
60
|
+
labelAs,
|
|
61
|
+
{ className: "govuk-label-wrapper" },
|
|
62
|
+
labelElement
|
|
63
|
+
)
|
|
64
|
+
: labelElement}
|
|
65
|
+
|
|
66
|
+
{hint && (
|
|
67
|
+
<div id={hintId} className="govuk-hint">
|
|
68
|
+
{hint}
|
|
69
|
+
</div>
|
|
70
|
+
)}
|
|
71
|
+
|
|
72
|
+
{hasError && (
|
|
73
|
+
<p id={errorId} className="govuk-error-message">
|
|
74
|
+
<span className="govuk-visually-hidden">Error:</span> {error}
|
|
75
|
+
</p>
|
|
76
|
+
)}
|
|
77
|
+
|
|
78
|
+
<select
|
|
79
|
+
id={id}
|
|
80
|
+
className={clsx(
|
|
81
|
+
"govuk-select",
|
|
82
|
+
{ "govuk-select--error": hasError },
|
|
83
|
+
className
|
|
84
|
+
)}
|
|
85
|
+
aria-describedby={ariaDescribedBy}
|
|
86
|
+
{...props}
|
|
87
|
+
>
|
|
88
|
+
{placeholder && (
|
|
89
|
+
<option value="" disabled hidden>
|
|
90
|
+
{placeholder}
|
|
91
|
+
</option>
|
|
92
|
+
)}
|
|
93
|
+
|
|
94
|
+
{options.map((opt) => (
|
|
95
|
+
<option key={opt.value} value={opt.value}>
|
|
96
|
+
{opt.label}
|
|
97
|
+
</option>
|
|
98
|
+
))}
|
|
99
|
+
</select>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import type { JSX, ReactNode } from "react";
|
|
5
|
+
import { createElement } from "react";
|
|
6
|
+
|
|
7
|
+
type LabelAs = "h1" | "h2" | "h3" | "h4";
|
|
8
|
+
type InputProps = Omit<JSX.IntrinsicElements["input"], "id">;
|
|
9
|
+
|
|
10
|
+
interface TextInputProps extends InputProps {
|
|
11
|
+
id: string;
|
|
12
|
+
label: ReactNode;
|
|
13
|
+
hint?: ReactNode;
|
|
14
|
+
error?: string;
|
|
15
|
+
width?: "10" | "20" | "30";
|
|
16
|
+
labelSize?: "s" | "m" | "l" | "xl";
|
|
17
|
+
labelAs?: LabelAs;
|
|
18
|
+
placeholder?: string;
|
|
19
|
+
suffix?: ReactNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const TextInput = ({
|
|
23
|
+
id,
|
|
24
|
+
label,
|
|
25
|
+
hint,
|
|
26
|
+
error,
|
|
27
|
+
width = "20",
|
|
28
|
+
labelSize = "m",
|
|
29
|
+
labelAs,
|
|
30
|
+
className,
|
|
31
|
+
placeholder,
|
|
32
|
+
suffix,
|
|
33
|
+
...props
|
|
34
|
+
}: TextInputProps) => {
|
|
35
|
+
const hasError = Boolean(error);
|
|
36
|
+
|
|
37
|
+
const hintId = hint ? `${id}-hint` : undefined;
|
|
38
|
+
const errorId = hasError ? `${id}-error` : undefined;
|
|
39
|
+
const describedBy = [hintId, errorId].filter(Boolean).join(" ") || undefined;
|
|
40
|
+
|
|
41
|
+
const labelElement = (
|
|
42
|
+
<label
|
|
43
|
+
htmlFor={id}
|
|
44
|
+
className={clsx("govuk-label", {
|
|
45
|
+
[`govuk-label--${labelSize}`]: labelSize !== "m",
|
|
46
|
+
})}
|
|
47
|
+
>
|
|
48
|
+
{label}
|
|
49
|
+
</label>
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const input = (
|
|
53
|
+
<input
|
|
54
|
+
id={id}
|
|
55
|
+
className={clsx(
|
|
56
|
+
"govuk-input",
|
|
57
|
+
`govuk-input--width-${width}`,
|
|
58
|
+
className
|
|
59
|
+
)}
|
|
60
|
+
aria-invalid={hasError}
|
|
61
|
+
aria-describedby={describedBy}
|
|
62
|
+
placeholder={placeholder}
|
|
63
|
+
{...props}
|
|
64
|
+
/>
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
className={clsx("govuk-form-group", {
|
|
70
|
+
"govuk-form-group--error": hasError,
|
|
71
|
+
})}
|
|
72
|
+
>
|
|
73
|
+
{labelAs
|
|
74
|
+
? createElement(
|
|
75
|
+
labelAs,
|
|
76
|
+
{ className: "govuk-label-wrapper" },
|
|
77
|
+
labelElement
|
|
78
|
+
)
|
|
79
|
+
: labelElement}
|
|
80
|
+
|
|
81
|
+
{hint && (
|
|
82
|
+
<div id={hintId} className="govuk-hint">
|
|
83
|
+
{hint}
|
|
84
|
+
</div>
|
|
85
|
+
)}
|
|
86
|
+
|
|
87
|
+
{hasError && (
|
|
88
|
+
<p id={errorId} className="govuk-error-message">
|
|
89
|
+
<span className="govuk-visually-hidden">Error:</span> {error}
|
|
90
|
+
</p>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
{suffix ? (
|
|
94
|
+
<div className="govuk-input__wrapper">
|
|
95
|
+
{input}
|
|
96
|
+
<div className="govuk-input__suffix" aria-hidden="true">
|
|
97
|
+
{suffix}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
) : (
|
|
101
|
+
input
|
|
102
|
+
)}
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
};
|
package/src/index.ts
CHANGED