@tpzdsp/next-toolkit 1.2.8 → 1.3.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 (29) hide show
  1. package/package.json +7 -1
  2. package/src/components/Card/Card.stories.tsx +21 -0
  3. package/src/components/Card/Card.test.tsx +46 -7
  4. package/src/components/Card/Card.tsx +1 -1
  5. package/src/components/Paragraph/Paragraph.tsx +9 -5
  6. package/src/components/backToTop/BackToTop.stories.tsx +409 -0
  7. package/src/components/backToTop/BackToTop.test.tsx +57 -0
  8. package/src/components/backToTop/BackToTop.tsx +131 -0
  9. package/src/components/chip/Chip.stories.tsx +40 -0
  10. package/src/components/chip/Chip.test.tsx +31 -0
  11. package/src/components/chip/Chip.tsx +20 -0
  12. package/src/components/cookieBanner/CookieBanner.stories.tsx +258 -0
  13. package/src/components/cookieBanner/CookieBanner.test.tsx +68 -0
  14. package/src/components/cookieBanner/CookieBanner.tsx +73 -0
  15. package/src/components/form/Input.stories.tsx +435 -0
  16. package/src/components/form/Input.test.tsx +214 -0
  17. package/src/components/form/Input.tsx +24 -0
  18. package/src/components/form/TextArea.stories.tsx +465 -0
  19. package/src/components/form/TextArea.test.tsx +236 -0
  20. package/src/components/form/TextArea.tsx +24 -0
  21. package/src/components/googleAnalytics/GlobalVars.tsx +31 -0
  22. package/src/components/googleAnalytics/GoogleAnalytics.tsx +15 -0
  23. package/src/components/index.ts +13 -0
  24. package/src/components/skipLink/SkipLink.stories.tsx +346 -0
  25. package/src/components/skipLink/SkipLink.test.tsx +22 -0
  26. package/src/components/skipLink/SkipLink.tsx +19 -0
  27. package/src/map/MapComponent.tsx +2 -35
  28. package/src/utils/http.ts +1 -0
  29. package/src/utils/index.ts +0 -1
@@ -0,0 +1,24 @@
1
+ 'use client';
2
+
3
+ import { twMerge } from 'tailwind-merge';
4
+
5
+ import type { ExtendProps } from '../../types/utils';
6
+
7
+ type Props = {
8
+ hasError?: boolean;
9
+ };
10
+
11
+ export type TextAreaProps = ExtendProps<'textarea', Props>;
12
+
13
+ export const TextArea = ({ hasError, className, ...props }: TextAreaProps) => {
14
+ return (
15
+ <textarea
16
+ {...props}
17
+ className={twMerge(
18
+ 'rounded-md border p-1 disabled:opacity-60 disabled:bg-gray-100',
19
+ hasError ? 'border-error' : '',
20
+ className,
21
+ )}
22
+ />
23
+ );
24
+ };
@@ -0,0 +1,31 @@
1
+ import { memo } from 'react';
2
+
3
+ import Script from 'next/script';
4
+
5
+ type GlobalVarsProps = {
6
+ analyticsId: string;
7
+ };
8
+
9
+ const GlobalVarsComponent = ({ analyticsId }: GlobalVarsProps) => {
10
+ console.log(`Global Vars: ${analyticsId}`); // Debugging line to check the ID
11
+
12
+ return (
13
+ <>
14
+ {/*
15
+ Set global variables on the window object.
16
+ Any further global variables can be added after these.
17
+ */}
18
+ <Script
19
+ id="global-vars"
20
+ dangerouslySetInnerHTML={{
21
+ __html: `
22
+ window.swirrl = window.swirrl ?? {};
23
+ window.swirrl.gtagUACode = "${analyticsId}";
24
+ `,
25
+ }}
26
+ />
27
+ </>
28
+ );
29
+ };
30
+
31
+ export const GlobalVars = memo(GlobalVarsComponent);
@@ -0,0 +1,15 @@
1
+ import { memo } from 'react';
2
+
3
+ import Script from 'next/script';
4
+
5
+ type GoogleAnalyticsProps = {
6
+ analyticsId: string;
7
+ };
8
+
9
+ const GoogleAnalyticsComponent = ({ analyticsId }: GoogleAnalyticsProps) => {
10
+ console.log(`Google Analytics ID: ${analyticsId}`); // Debugging line to check the ID
11
+
12
+ return <Script src={`https://www.googletagmanager.com/gtag/js?id=${analyticsId}`} />;
13
+ };
14
+
15
+ export const GoogleAnalytics = memo(GoogleAnalyticsComponent);
@@ -1,8 +1,13 @@
1
1
  // Default components - these can be used in server or client-side rendering
2
+ export { BackToTop } from './backToTop/BackToTop';
2
3
  export { Button } from './Button/Button';
3
4
  export { Card } from './Card/Card';
5
+ export { Chip } from './chip/Chip';
6
+ export { CookieBanner } from './cookieBanner/CookieBanner';
4
7
  export { Container } from './container/Container';
5
8
  export { ErrorText } from './ErrorText/ErrorText';
9
+ export { GlobalVars } from './googleAnalytics/GlobalVars';
10
+ export { GoogleAnalytics } from './googleAnalytics/GoogleAnalytics';
6
11
  export { Heading } from './Heading/Heading';
7
12
  export { Hint } from './Hint/Hint';
8
13
  export { DefraLogo } from './images/DefraLogo';
@@ -12,10 +17,15 @@ export { ExternalLink } from './link/ExternalLink';
12
17
  export { Link } from './link/Link';
13
18
  export { Paragraph } from './Paragraph/Paragraph';
14
19
  export { RuleDivider } from './divider/RuleDivider';
20
+ export { SkipLink } from './skipLink/SkipLink';
21
+ export { Input } from './form/Input';
22
+ export { TextArea } from './form/TextArea';
15
23
 
16
24
  // Export default component types
25
+ export type { BackToTopProps } from './backToTop/BackToTop';
17
26
  export type { ButtonProps } from './Button/Button';
18
27
  export type { CardProps } from './Card/Card';
28
+ export type { ChipProps } from './chip/Chip';
19
29
  export type { ContainerProps } from './container/Container';
20
30
  export type { ErrorTextProps } from './ErrorText/ErrorText';
21
31
  export type { HeadingProps } from './Heading/Heading';
@@ -24,6 +34,9 @@ export type { ExternalLinkProps } from './link/ExternalLink';
24
34
  export type { LinkProps } from './link/Link';
25
35
  export type { ParagraphProps } from './Paragraph/Paragraph';
26
36
  export type { SlidingPanelProps } from './SlidingPanel/SlidingPanel';
37
+ export type { SkipLinkProps } from './skipLink/SkipLink';
38
+ export type { InputProps } from './form/Input';
39
+ export type { TextAreaProps } from './form/TextArea';
27
40
 
28
41
  // Client components - these require 'use client' directive
29
42
  export { DropdownMenu } from './dropdown/DropdownMenu';
@@ -0,0 +1,346 @@
1
+ /* eslint-disable jsx-a11y/anchor-is-valid */
2
+ /* eslint-disable storybook/no-renderer-packages */
3
+ import type { Meta, StoryObj } from '@storybook/react';
4
+
5
+ import { SkipLink } from './SkipLink';
6
+
7
+ const meta = {
8
+ title: 'Components/SkipLink',
9
+ component: SkipLink,
10
+ parameters: {
11
+ layout: 'fullscreen',
12
+ docs: {
13
+ description: {
14
+ component:
15
+ 'A skip link component that allows users to jump to the main content, improving accessibility for keyboard and screen reader users.',
16
+ },
17
+ },
18
+ },
19
+ tags: ['autodocs'],
20
+ argTypes: {
21
+ mainContentId: {
22
+ control: 'text',
23
+ description: 'The ID of the main content element to skip to',
24
+ defaultValue: 'main-content',
25
+ },
26
+ },
27
+ } satisfies Meta<typeof SkipLink>;
28
+
29
+ export default meta;
30
+ type Story = StoryObj<typeof meta>;
31
+
32
+ export const Default: Story = {
33
+ parameters: {
34
+ docs: {
35
+ description: {
36
+ story: 'Default skip link with the standard main content ID.',
37
+ },
38
+ },
39
+ },
40
+ };
41
+
42
+ export const CustomTarget: Story = {
43
+ args: {
44
+ mainContentId: 'custom-main',
45
+ },
46
+ parameters: {
47
+ docs: {
48
+ description: {
49
+ story: 'Skip link with a custom target ID.',
50
+ },
51
+ },
52
+ },
53
+ };
54
+
55
+ export const WithMainContent: Story = {
56
+ render: (args) => (
57
+ <div className="min-h-screen">
58
+ <SkipLink {...args} />
59
+
60
+ <header className="bg-blue-600 text-white p-4">
61
+ <nav>
62
+ <ul className="flex space-x-4">
63
+ <li>
64
+ <a href="#" className="hover:underline">
65
+ Home
66
+ </a>
67
+ </li>
68
+
69
+ <li>
70
+ <a href="#" className="hover:underline">
71
+ About
72
+ </a>
73
+ </li>
74
+
75
+ <li>
76
+ <a href="#" className="hover:underline">
77
+ Services
78
+ </a>
79
+ </li>
80
+
81
+ <li>
82
+ <a href="#" className="hover:underline">
83
+ Contact
84
+ </a>
85
+ </li>
86
+ </ul>
87
+ </nav>
88
+ </header>
89
+
90
+ <main id="main-content" className="p-8">
91
+ <h1 className="text-3xl font-bold mb-6">Main Content</h1>
92
+
93
+ <p className="mb-4">
94
+ This is the main content area. Use Tab key to navigate to the skip link at the top, then
95
+ press Enter to jump directly here.
96
+ </p>
97
+
98
+ <p className="mb-4">
99
+ The skip link is invisible by default but becomes visible when focused, providing an
100
+ accessible way for keyboard users to bypass navigation.
101
+ </p>
102
+
103
+ <div className="space-y-4">
104
+ <p>Sample content paragraph 1.</p>
105
+
106
+ <p>Sample content paragraph 2.</p>
107
+
108
+ <p>Sample content paragraph 3.</p>
109
+ </div>
110
+ </main>
111
+
112
+ <footer className="bg-gray-800 text-white p-4 mt-8">
113
+ <p>&copy; 2024 Example Site. All rights reserved.</p>
114
+ </footer>
115
+ </div>
116
+ ),
117
+ parameters: {
118
+ docs: {
119
+ description: {
120
+ story:
121
+ 'Complete page layout showing how the skip link works in context. Use Tab to focus the skip link, then Enter to jump to main content.',
122
+ },
123
+ },
124
+ },
125
+ };
126
+
127
+ export const CustomTargetWithContent: Story = {
128
+ args: {
129
+ mainContentId: 'article-content',
130
+ },
131
+ render: (args) => (
132
+ <div className="min-h-screen">
133
+ <SkipLink {...args} />
134
+
135
+ <header className="bg-green-600 text-white p-4">
136
+ <h1 className="text-xl font-bold">Site Header</h1>
137
+ </header>
138
+
139
+ <nav className="bg-gray-200 p-4">
140
+ <ul className="flex space-x-4">
141
+ <li>
142
+ <a href="#" className="text-blue-600 hover:underline">
143
+ Navigation Link 1
144
+ </a>
145
+ </li>
146
+
147
+ <li>
148
+ <a href="#" className="text-blue-600 hover:underline">
149
+ Navigation Link 2
150
+ </a>
151
+ </li>
152
+
153
+ <li>
154
+ <a href="#" className="text-blue-600 hover:underline">
155
+ Navigation Link 3
156
+ </a>
157
+ </li>
158
+ </ul>
159
+ </nav>
160
+
161
+ <aside className="bg-yellow-100 p-4">
162
+ <h2 className="font-bold mb-2">Sidebar</h2>
163
+
164
+ <p>Some sidebar content that users might want to skip.</p>
165
+ </aside>
166
+
167
+ <article id="article-content" className="p-8 bg-white">
168
+ <h1 className="text-3xl font-bold mb-6">Article Title</h1>
169
+
170
+ <p className="mb-4">
171
+ This skip link targets a custom ID &quot;article-content&quot; instead of the default
172
+ &quot;main-content&quot;.
173
+ </p>
174
+
175
+ <p className="mb-4">
176
+ This demonstrates how the component can be configured for different page layouts and
177
+ content structures.
178
+ </p>
179
+ </article>
180
+ </div>
181
+ ),
182
+ parameters: {
183
+ docs: {
184
+ description: {
185
+ story:
186
+ 'Skip link with custom target ID, demonstrating flexibility for different page structures.',
187
+ },
188
+ },
189
+ },
190
+ };
191
+
192
+ export const FocusedState: Story = {
193
+ args: {
194
+ mainContentId: 'article-content-focused',
195
+ },
196
+ render: (args) => (
197
+ <div className="min-h-screen relative">
198
+ <style>
199
+ {`
200
+ .skip-link {
201
+ position: relative !important;
202
+ top: 0 !important;
203
+ display: block !important;
204
+ }
205
+ `}
206
+ </style>
207
+
208
+ <SkipLink {...args} />
209
+
210
+ <div className="p-8">
211
+ <h1 className="text-2xl font-bold mb-4">Skip Link in Focused State</h1>
212
+
213
+ <p className="mb-4">
214
+ This story shows how the skip link appears when focused. Normally it&apos;s hidden
215
+ off-screen until a user tabs to it.
216
+ </p>
217
+
218
+ <main id="main-content-focused" className="mt-8 p-4 border-2 border-dashed border-gray-400">
219
+ <h2 className="text-xl font-bold mb-2">Main Content Target</h2>
220
+
221
+ <p>This is where users will land when they activate the skip link.</p>
222
+ </main>
223
+ </div>
224
+ </div>
225
+ ),
226
+ parameters: {
227
+ docs: {
228
+ description: {
229
+ story:
230
+ 'Shows the visual appearance of the skip link when focused (normally hidden off-screen).',
231
+ },
232
+ },
233
+ },
234
+ };
235
+
236
+ export const AccessibilityTest: Story = {
237
+ args: {
238
+ mainContentId: 'article-content-accessibility',
239
+ },
240
+ render: (args) => (
241
+ <div className="min-h-screen">
242
+ <SkipLink {...args} />
243
+
244
+ <div className="p-8">
245
+ <h1 className="text-2xl font-bold mb-6">Accessibility Testing</h1>
246
+
247
+ <div className="bg-blue-50 border border-blue-200 rounded p-4 mb-6">
248
+ <h2 className="font-bold text-blue-800 mb-2">Testing Instructions:</h2>
249
+
250
+ <ol className="list-decimal list-inside space-y-1 text-blue-700">
251
+ <li>Use Tab key to navigate to the skip link (it will appear at the top)</li>
252
+
253
+ <li>Press Enter or Space to activate the skip link</li>
254
+
255
+ <li>Verify that focus moves to the main content below</li>
256
+
257
+ <li>Test with screen reader for proper announcement</li>
258
+ </ol>
259
+ </div>
260
+
261
+ <nav className="mb-8">
262
+ <h2 className="text-lg font-semibold mb-3">Navigation Menu</h2>
263
+
264
+ <ul className="space-y-2">
265
+ <li>
266
+ <a
267
+ href="#"
268
+ className="text-blue-600 hover:underline focus:outline-2 focus:outline-blue-500"
269
+ >
270
+ Link 1
271
+ </a>
272
+ </li>
273
+
274
+ <li>
275
+ <a
276
+ href="#"
277
+ className="text-blue-600 hover:underline focus:outline-2 focus:outline-blue-500"
278
+ >
279
+ Link 2
280
+ </a>
281
+ </li>
282
+
283
+ <li>
284
+ <a
285
+ href="#"
286
+ className="text-blue-600 hover:underline focus:outline-2 focus:outline-blue-500"
287
+ >
288
+ Link 3
289
+ </a>
290
+ </li>
291
+
292
+ <li>
293
+ <a
294
+ href="#"
295
+ className="text-blue-600 hover:underline focus:outline-2 focus:outline-blue-500"
296
+ >
297
+ Link 4
298
+ </a>
299
+ </li>
300
+
301
+ <li>
302
+ <a
303
+ href="#"
304
+ className="text-blue-600 hover:underline focus:outline-2 focus:outline-blue-500"
305
+ >
306
+ Link 5
307
+ </a>
308
+ </li>
309
+ </ul>
310
+ </nav>
311
+
312
+ <main
313
+ id="main-content-accessibility"
314
+ className="p-6 bg-green-50 border border-green-200 rounded"
315
+ >
316
+ <h2 className="text-xl font-bold mb-4 text-green-800">Main Content Area</h2>
317
+
318
+ <p className="mb-4">
319
+ If the skip link worked correctly, focus should now be on this main content area,
320
+ bypassing all the navigation links above.
321
+ </p>
322
+
323
+ <p className="mb-4">
324
+ This is particularly important for users who rely on keyboard navigation or screen
325
+ readers, as it saves them from having to tab through all navigation elements on every
326
+ page.
327
+ </p>
328
+
329
+ <button
330
+ className="bg-green-600 text-white px-4 py-2 rounded hover:bg-green-700 focus:outline-2
331
+ focus:outline-green-500"
332
+ >
333
+ Interactive Element
334
+ </button>
335
+ </main>
336
+ </div>
337
+ </div>
338
+ ),
339
+ parameters: {
340
+ docs: {
341
+ description: {
342
+ story: 'Complete accessibility testing scenario with instructions for manual testing.',
343
+ },
344
+ },
345
+ },
346
+ };
@@ -0,0 +1,22 @@
1
+ import { SkipLink } from './SkipLink';
2
+ import { render, screen, userEvent } from '../../test/renderers';
3
+
4
+ describe('SkipLink Component', () => {
5
+ it('should render correctly', () => {
6
+ render(<SkipLink />);
7
+
8
+ expect(screen.getByRole('link', { name: /skip to main content/i })).toBeInTheDocument();
9
+ });
10
+
11
+ it('should be focusable', async () => {
12
+ render(<SkipLink />);
13
+
14
+ const user = userEvent.setup();
15
+
16
+ await user.tab();
17
+
18
+ const link = screen.getByRole('link', { name: /skip to main content/i });
19
+
20
+ expect(link).toHaveFocus();
21
+ });
22
+ });
@@ -0,0 +1,19 @@
1
+ import { Link } from '../link/Link';
2
+
3
+ export type SkipLinkProps = {
4
+ mainContentId?: string;
5
+ };
6
+
7
+ export const SkipLink = ({ mainContentId = 'main-content' }: SkipLinkProps) => {
8
+ return (
9
+ <nav aria-label="Skip navigation">
10
+ <Link
11
+ className="bg-focus focus:relative focus:top-0 w-full absolute -top-full text-black
12
+ visited:text-black hover:text-black p-3 skip-link"
13
+ href={`#${mainContentId}`}
14
+ >
15
+ Skip to main content
16
+ </Link>
17
+ </nav>
18
+ );
19
+ };
@@ -12,7 +12,7 @@ import { LayerSwitcherControl } from './LayerSwitcherControl';
12
12
  import { useMap } from './MapContext';
13
13
  import { Popup } from './Popup';
14
14
  import { getPopupPositionClass } from './utils';
15
- import type { MapConfig, PopupDirection } from '../types/map';
15
+ import type { PopupDirection } from '../types/map';
16
16
 
17
17
  export type MapComponentProps = {
18
18
  osMapsApiKey?: string;
@@ -63,8 +63,6 @@ export const MapComponent = ({
63
63
  mapRef,
64
64
  mapConfig: { center, zoom },
65
65
  setMap,
66
- setMapConfig,
67
- map,
68
66
  isDrawing,
69
67
  } = useMap();
70
68
 
@@ -159,42 +157,11 @@ export const MapComponent = ({
159
157
  });
160
158
  });
161
159
 
162
- if (!map) {
163
- setMap(newMap);
164
- }
160
+ setMap(newMap);
165
161
 
166
- return () => {
167
- newMap?.setTarget();
168
- };
169
162
  // eslint-disable-next-line react-hooks/exhaustive-deps
170
163
  }, []); // This is a rare case where we want to run this only once.
171
164
 
172
- // Update map config when map is moved
173
- useEffect(() => {
174
- if (!map) {
175
- return;
176
- }
177
-
178
- const view = map.getView();
179
- const moveHandler = () => {
180
- const center = view.getCenter();
181
-
182
- if (center) {
183
- setMapConfig((prev: MapConfig) => ({
184
- ...prev,
185
- center,
186
- zoom: view.getZoom()!,
187
- }));
188
- }
189
- };
190
-
191
- map.on('moveend', moveHandler);
192
-
193
- return () => {
194
- map.un('moveend', moveHandler);
195
- };
196
- }, [map, setMapConfig]);
197
-
198
165
  const closePopup = () => {
199
166
  setPopupFeatures([]);
200
167
  setPopupCoordinate(null);
package/src/utils/http.ts CHANGED
@@ -6,6 +6,7 @@ export const MimeTypes = {
6
6
  GeoJson: 'application/geo+json',
7
7
  Png: 'image/png',
8
8
  XJsonLines: 'application/x-jsonlines',
9
+ Csv: 'text/csv',
9
10
  } as const;
10
11
 
11
12
  export const Http = {
@@ -1,4 +1,3 @@
1
1
  export * from './utils';
2
2
  export * from './auth';
3
3
  export * from './constants';
4
- export * from './http';