@tpzdsp/next-toolkit 1.1.0 → 1.2.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 +21 -2
- package/src/assets/styles/globals.css +2 -0
- package/src/assets/styles/ol.css +122 -0
- package/src/components/Modal/Modal.stories.tsx +252 -0
- package/src/components/Modal/Modal.test.tsx +248 -0
- package/src/components/Modal/Modal.tsx +61 -0
- package/src/components/accordion/Accordion.stories.tsx +235 -0
- package/src/components/accordion/Accordion.test.tsx +199 -0
- package/src/components/accordion/Accordion.tsx +47 -0
- package/src/components/divider/RuleDivider.stories.tsx +255 -0
- package/src/components/divider/RuleDivider.test.tsx +164 -0
- package/src/components/divider/RuleDivider.tsx +18 -0
- package/src/components/index.ts +9 -2
- package/src/components/layout/header/HeaderAuthClient.tsx +16 -8
- package/src/components/layout/header/HeaderNavClient.tsx +2 -2
- package/src/components/map/LayerSwitcherControl.ts +147 -0
- package/src/components/map/Map.tsx +230 -0
- package/src/components/map/MapContext.tsx +211 -0
- package/src/components/map/Popup.tsx +74 -0
- package/src/components/map/basemaps.ts +79 -0
- package/src/components/map/geocoder.ts +61 -0
- package/src/components/map/geometries.ts +60 -0
- package/src/components/map/images/basemaps/OS.png +0 -0
- package/src/components/map/images/basemaps/dark.png +0 -0
- package/src/components/map/images/basemaps/sat-map-tiler.png +0 -0
- package/src/components/map/images/basemaps/satellite-map-tiler.png +0 -0
- package/src/components/map/images/basemaps/satellite.png +0 -0
- package/src/components/map/images/basemaps/streets.png +0 -0
- package/src/components/map/images/openlayers-logo.png +0 -0
- package/src/components/map/index.ts +10 -0
- package/src/components/map/map.ts +40 -0
- package/src/components/map/osOpenNamesSearch.ts +54 -0
- package/src/components/map/projections.ts +14 -0
- package/src/components/select/Select.stories.tsx +336 -0
- package/src/components/select/Select.test.tsx +473 -0
- package/src/components/select/Select.tsx +132 -0
- package/src/components/select/SelectSkeleton.stories.tsx +195 -0
- package/src/components/select/SelectSkeleton.test.tsx +105 -0
- package/src/components/select/SelectSkeleton.tsx +16 -0
- package/src/components/select/common.ts +4 -0
- package/src/contexts/index.ts +0 -5
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useClickOutside.test.ts +290 -0
- package/src/hooks/useClickOutside.ts +26 -0
- package/src/types.ts +51 -1
- package/src/utils/http.ts +143 -0
- package/src/utils/index.ts +1 -0
- package/src/components/link/NextLinkWrapper.tsx +0 -66
- package/src/contexts/ThemeContext.tsx +0 -72
package/src/types.ts
CHANGED
|
@@ -94,6 +94,56 @@ export type Credentials = {
|
|
|
94
94
|
export type NavLink = {
|
|
95
95
|
label: string;
|
|
96
96
|
url: string;
|
|
97
|
-
isExternal
|
|
97
|
+
isExternal?: boolean;
|
|
98
98
|
icon?: ReactNode;
|
|
99
99
|
};
|
|
100
|
+
|
|
101
|
+
type GenericResponse<Ok, Error> =
|
|
102
|
+
| ({
|
|
103
|
+
readonly ok: true;
|
|
104
|
+
json(): Promise<Ok | null>;
|
|
105
|
+
} & globalThis.Response)
|
|
106
|
+
| ({
|
|
107
|
+
readonly ok: false;
|
|
108
|
+
json(): Promise<Error>;
|
|
109
|
+
} & globalThis.Response);
|
|
110
|
+
|
|
111
|
+
export type Response<Ok, Error> = GenericResponse<Ok, Error>;
|
|
112
|
+
|
|
113
|
+
// Since we only ever fetch a single API we don't need multiple failure types,
|
|
114
|
+
// so defining a global one as the default is fine. This can be changed per-app.
|
|
115
|
+
export type ApiFailure = { message: string };
|
|
116
|
+
|
|
117
|
+
/*
|
|
118
|
+
Override the global Response type to be able to take a success/fail type.
|
|
119
|
+
It defines it as a tagged union so that we can take advantage of type
|
|
120
|
+
narrowing on the `json()` return type when checking `Response.ok`.
|
|
121
|
+
|
|
122
|
+
Generally overriding global types is bad, but in this case we are making
|
|
123
|
+
a normally non-typesafe feature more typesafe so I think it's acceptable.
|
|
124
|
+
*/
|
|
125
|
+
/**
|
|
126
|
+
* Typesafe verion of the `Response` interface.
|
|
127
|
+
*
|
|
128
|
+
* It takes 2 generics:
|
|
129
|
+
* - `Ok` which defines type type of a successful request
|
|
130
|
+
* - `Error` which defines the type of a failed request
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Typesafe version of the normal `fetch` function. It takes 2 generics:
|
|
135
|
+
* - `Ok` which defines type type of a successful request
|
|
136
|
+
* - `Error` which defines the type of a failed request (defaults to the `ApiFailure` type)
|
|
137
|
+
*
|
|
138
|
+
* @param input The URL to fetch
|
|
139
|
+
* @param init Any fetch options, like cache, headers, body, etc.
|
|
140
|
+
*/
|
|
141
|
+
export declare function fetch<Ok = never, Error = ApiFailure>(
|
|
142
|
+
input: RequestInfo | URL,
|
|
143
|
+
init?: RequestInit,
|
|
144
|
+
): Promise<Response<Ok, Error>>;
|
|
145
|
+
|
|
146
|
+
export type WrappedApiResponse<T> = Promise<
|
|
147
|
+
| { success: true; data: T }
|
|
148
|
+
| { success: false; message: string; status?: number; details?: string[] }
|
|
149
|
+
>;
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { Response, ApiFailure } from '../types';
|
|
2
|
+
|
|
3
|
+
export const MimeTypes = {
|
|
4
|
+
Json: 'application/json',
|
|
5
|
+
GeoJson: 'application/geo+json',
|
|
6
|
+
Png: 'image/png',
|
|
7
|
+
XJsonLines: 'application/x-jsonlines',
|
|
8
|
+
} as const;
|
|
9
|
+
|
|
10
|
+
export const Http = {
|
|
11
|
+
Ok: 200,
|
|
12
|
+
Created: 201,
|
|
13
|
+
NoContent: 204,
|
|
14
|
+
BadRequest: 400,
|
|
15
|
+
NotFound: 404,
|
|
16
|
+
NotAllowed: 405,
|
|
17
|
+
InternalServerError: 500,
|
|
18
|
+
} as const;
|
|
19
|
+
|
|
20
|
+
export const HttpMethod = {
|
|
21
|
+
Get: 'GET',
|
|
22
|
+
Post: 'POST',
|
|
23
|
+
Put: 'PUT',
|
|
24
|
+
Patch: 'PATCH',
|
|
25
|
+
Delete: 'DELETE',
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
type NodeGlobal = {
|
|
29
|
+
process?: {
|
|
30
|
+
env?: Record<string, string | undefined>;
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type BrowserGlobal = {
|
|
35
|
+
btoa?: (input: string) => string;
|
|
36
|
+
Buffer?: {
|
|
37
|
+
from: (input: string) => {
|
|
38
|
+
toString: (encoding: string) => string;
|
|
39
|
+
};
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const isNodeEnvironment = (): boolean => {
|
|
44
|
+
const global = globalThis as NodeGlobal;
|
|
45
|
+
|
|
46
|
+
return typeof global.process?.env !== 'undefined';
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getNodeEnvVar = (key: string): string | undefined => {
|
|
50
|
+
if (!isNodeEnvironment()) {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const global = globalThis as NodeGlobal;
|
|
55
|
+
|
|
56
|
+
return global.process?.env?.[key];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const encodeBase64 = (input: string): string => {
|
|
60
|
+
const global = globalThis as BrowserGlobal;
|
|
61
|
+
|
|
62
|
+
// Check for Buffer (Node.js)
|
|
63
|
+
if (global.Buffer) {
|
|
64
|
+
return global.Buffer.from(input).toString('base64');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check for btoa (Browser)
|
|
68
|
+
if (global.btoa) {
|
|
69
|
+
return global.btoa(input);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
throw new Error('Base64 encoding not available in this environment');
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const getAuthorizationHeader = (): { Authorization: string } | Record<string, never> => {
|
|
76
|
+
const apiHasAuth = getNodeEnvVar('API_HAS_AUTH');
|
|
77
|
+
const apiUsername = getNodeEnvVar('API_USERNAME');
|
|
78
|
+
const apiPassword = getNodeEnvVar('API_PASSWORD');
|
|
79
|
+
|
|
80
|
+
if (apiHasAuth && apiUsername && apiPassword) {
|
|
81
|
+
const credentials = `${apiUsername}:${apiPassword}`;
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
Authorization: `Basic ${encodeBase64(credentials)}`,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// common fetch wrapper to provide auth headers if necessary
|
|
92
|
+
declare const fetch: <Ok = unknown, Error = ApiFailure>(
|
|
93
|
+
input: RequestInfo | URL,
|
|
94
|
+
init?: RequestInit,
|
|
95
|
+
) => Promise<Response<Ok, Error>>;
|
|
96
|
+
|
|
97
|
+
export const fetchWithAuth = <Ok, Error = ApiFailure>(
|
|
98
|
+
url: string,
|
|
99
|
+
options?: RequestInit,
|
|
100
|
+
): Promise<Response<Ok, Error>> => {
|
|
101
|
+
const requestOptions = {
|
|
102
|
+
...options,
|
|
103
|
+
headers: { ...options?.headers, ...getAuthorizationHeader() },
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Use your custom fetch declaration directly
|
|
107
|
+
return fetch<Ok, Error>(url, requestOptions);
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const get = <Ok, Error = ApiFailure>(
|
|
111
|
+
url: string,
|
|
112
|
+
options?: RequestInit,
|
|
113
|
+
): Promise<Response<Ok, Error>> => {
|
|
114
|
+
const requestOptions = {
|
|
115
|
+
...options,
|
|
116
|
+
method: HttpMethod.Get,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return fetchWithAuth<Ok, Error>(url, requestOptions);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const post = <Ok, Error = ApiFailure>(
|
|
123
|
+
url: string,
|
|
124
|
+
body: unknown,
|
|
125
|
+
options?: RequestInit,
|
|
126
|
+
): Promise<Response<Ok, Error>> => {
|
|
127
|
+
const requestOptions = {
|
|
128
|
+
...options,
|
|
129
|
+
method: HttpMethod.Post,
|
|
130
|
+
body: JSON.stringify(body),
|
|
131
|
+
headers: {
|
|
132
|
+
'Content-Type': MimeTypes.Json,
|
|
133
|
+
...options?.headers,
|
|
134
|
+
},
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return fetchWithAuth<Ok, Error>(url, requestOptions);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export const FetchWrapper = {
|
|
141
|
+
get,
|
|
142
|
+
post,
|
|
143
|
+
};
|
package/src/utils/index.ts
CHANGED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
|
|
3
|
-
import type { ReactNode } from 'react';
|
|
4
|
-
|
|
5
|
-
import Link from 'next/link';
|
|
6
|
-
|
|
7
|
-
import { cn } from '../../utils';
|
|
8
|
-
|
|
9
|
-
export type NextLinkWrapperProps = {
|
|
10
|
-
href: string;
|
|
11
|
-
children: ReactNode;
|
|
12
|
-
className?: string;
|
|
13
|
-
target?: '_blank' | '_self' | '_parent' | '_top';
|
|
14
|
-
rel?: string;
|
|
15
|
-
prefetch?: boolean;
|
|
16
|
-
replace?: boolean;
|
|
17
|
-
scroll?: boolean;
|
|
18
|
-
shallow?: boolean;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const NextLinkWrapper = ({
|
|
22
|
-
href,
|
|
23
|
-
children,
|
|
24
|
-
className,
|
|
25
|
-
target,
|
|
26
|
-
rel,
|
|
27
|
-
prefetch,
|
|
28
|
-
replace,
|
|
29
|
-
scroll,
|
|
30
|
-
shallow,
|
|
31
|
-
...props
|
|
32
|
-
}: NextLinkWrapperProps) => {
|
|
33
|
-
// Handle external links
|
|
34
|
-
const isExternal =
|
|
35
|
-
href.startsWith('http') || href.startsWith('mailto:') || href.startsWith('tel:');
|
|
36
|
-
|
|
37
|
-
if (isExternal) {
|
|
38
|
-
return (
|
|
39
|
-
<a
|
|
40
|
-
href={href}
|
|
41
|
-
className={cn('text-blue-600 hover:text-blue-800 transition-colors', className)}
|
|
42
|
-
target={target ?? '_blank'}
|
|
43
|
-
rel={rel ?? 'noopener noreferrer'}
|
|
44
|
-
{...props}
|
|
45
|
-
>
|
|
46
|
-
{children}
|
|
47
|
-
</a>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return (
|
|
52
|
-
<Link
|
|
53
|
-
href={href}
|
|
54
|
-
className={cn('text-blue-600 hover:text-blue-800 transition-colors', className)}
|
|
55
|
-
prefetch={prefetch}
|
|
56
|
-
replace={replace}
|
|
57
|
-
scroll={scroll}
|
|
58
|
-
shallow={shallow}
|
|
59
|
-
target={target}
|
|
60
|
-
rel={rel}
|
|
61
|
-
{...props}
|
|
62
|
-
>
|
|
63
|
-
{children}
|
|
64
|
-
</Link>
|
|
65
|
-
);
|
|
66
|
-
};
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
'use client'; // This is necessary for Next.js to treat this file as a client-side component
|
|
2
|
-
|
|
3
|
-
import { createContext, useContext, useState, useEffect } from 'react';
|
|
4
|
-
import type { ReactNode } from 'react';
|
|
5
|
-
|
|
6
|
-
import type { ThemeContextValue } from '../types';
|
|
7
|
-
|
|
8
|
-
export const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
9
|
-
|
|
10
|
-
type ThemeProviderProps = {
|
|
11
|
-
children: ReactNode;
|
|
12
|
-
defaultTheme?: 'light' | 'dark';
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const ThemeProvider = ({ children, defaultTheme = 'light' }: ThemeProviderProps) => {
|
|
16
|
-
const [theme, setTheme] = useState<'light' | 'dark'>(() => {
|
|
17
|
-
if (typeof window === 'undefined') {
|
|
18
|
-
return defaultTheme;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// Check localStorage first
|
|
22
|
-
const savedTheme = localStorage.getItem('theme') as 'light' | 'dark';
|
|
23
|
-
|
|
24
|
-
if (savedTheme) {
|
|
25
|
-
return savedTheme;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// Check system preference
|
|
29
|
-
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
|
30
|
-
return 'dark';
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return defaultTheme;
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
const toggleTheme = () => {
|
|
37
|
-
const newTheme = theme === 'light' ? 'dark' : 'light';
|
|
38
|
-
|
|
39
|
-
setTheme(newTheme);
|
|
40
|
-
|
|
41
|
-
if (typeof window !== 'undefined') {
|
|
42
|
-
localStorage.setItem('theme', newTheme);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
// Add theme class to document element
|
|
48
|
-
if (typeof window !== 'undefined') {
|
|
49
|
-
const root = window.document.documentElement;
|
|
50
|
-
|
|
51
|
-
root.classList.remove('light', 'dark');
|
|
52
|
-
root.classList.add(theme);
|
|
53
|
-
}
|
|
54
|
-
}, [theme]);
|
|
55
|
-
|
|
56
|
-
const value: ThemeContextValue = {
|
|
57
|
-
theme,
|
|
58
|
-
toggleTheme,
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
export const useTheme = (): ThemeContextValue => {
|
|
65
|
-
const context = useContext(ThemeContext);
|
|
66
|
-
|
|
67
|
-
if (context === undefined) {
|
|
68
|
-
throw new Error('useTheme must be used within a ThemeProvider');
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return context;
|
|
72
|
-
};
|