@tpzdsp/next-toolkit 1.12.1 → 1.14.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 +4 -4
- package/package.json +13 -6
- package/src/assets/styles/ol.css +147 -176
- package/src/components/ButtonLink/ButtonLink.stories.tsx +72 -0
- package/src/components/ButtonLink/ButtonLink.test.tsx +154 -0
- package/src/components/ButtonLink/ButtonLink.tsx +33 -0
- package/src/components/InfoBox/InfoBox.stories.tsx +460 -0
- package/src/components/InfoBox/InfoBox.test.tsx +330 -0
- package/src/components/InfoBox/InfoBox.tsx +168 -0
- package/src/components/InfoBox/types.ts +6 -0
- package/src/components/LinkButton/LinkButton.stories.tsx +74 -0
- package/src/components/LinkButton/LinkButton.test.tsx +177 -0
- package/src/components/LinkButton/LinkButton.tsx +80 -0
- package/src/components/index.ts +5 -0
- package/src/components/link/ExternalLink.test.tsx +104 -0
- package/src/components/link/ExternalLink.tsx +1 -0
- package/src/map/FullScreenControl.ts +126 -0
- package/src/map/LayerSwitcherControl.ts +87 -181
- package/src/map/LayerSwitcherPanel.tsx +173 -0
- package/src/map/MapComponent.tsx +12 -46
- package/src/map/createControlButton.ts +72 -0
- package/src/map/geocoder/Geocoder.test.tsx +115 -0
- package/src/map/geocoder/Geocoder.tsx +393 -0
- package/src/map/geocoder/groupResults.ts +12 -0
- package/src/map/geocoder/index.ts +4 -0
- package/src/map/geocoder/types.ts +11 -0
- package/src/map/index.ts +4 -1
- package/src/map/osOpenNamesSearch.ts +112 -57
- package/src/test/renderers.tsx +9 -20
- package/src/map/geocoder.ts +0 -61
- package/src/ol-geocoder.d.ts +0 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ExtendProps } from '../../types/utils';
|
|
2
|
+
import { cn } from '../../utils';
|
|
3
|
+
|
|
4
|
+
type Props = {
|
|
5
|
+
type?: 'submit' | 'reset' | 'button';
|
|
6
|
+
children: React.ReactNode;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type ButtonLinkProps = ExtendProps<'button', Props>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A button that looks like a link.
|
|
13
|
+
* Use when you need button functionality (onClick, form submission)
|
|
14
|
+
* but want the visual appearance of a link.
|
|
15
|
+
*/
|
|
16
|
+
export const ButtonLink = ({ type = 'button', className, children, ...props }: ButtonLinkProps) => {
|
|
17
|
+
return (
|
|
18
|
+
<button
|
|
19
|
+
type={type}
|
|
20
|
+
className={cn(
|
|
21
|
+
`cursor-pointer text-link hover:decoration-[max(3px,_.1875rem,_.12em)] hover:text-link-hover
|
|
22
|
+
visited:text-link-visited active:text-black focus:decoration-[max(3px,_.1875rem,_.12em)]
|
|
23
|
+
decoration-[max(1px,_.0625rem)] underline-offset-[0.1578em] underline outline-none
|
|
24
|
+
focus:text-focus-text focus:bg-focus inline-block bg-transparent border-0 p-0
|
|
25
|
+
disabled:opacity-50 disabled:cursor-not-allowed`,
|
|
26
|
+
className,
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
>
|
|
30
|
+
{children}
|
|
31
|
+
</button>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
|
|
3
|
+
import { InfoBox } from './InfoBox';
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: 'Components/InfoBox',
|
|
7
|
+
component: InfoBox,
|
|
8
|
+
parameters: {
|
|
9
|
+
layout: 'centered',
|
|
10
|
+
docs: {
|
|
11
|
+
description: {
|
|
12
|
+
component:
|
|
13
|
+
'An accessible info box component that displays contextual information in a popover triggered by an info icon button. Uses Floating UI for intelligent auto-positioning that adapts to viewport constraints.',
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
tags: ['autodocs'],
|
|
18
|
+
argTypes: {
|
|
19
|
+
title: {
|
|
20
|
+
control: 'text',
|
|
21
|
+
description: 'Optional title displayed at the top of the info box content',
|
|
22
|
+
},
|
|
23
|
+
defaultOpen: {
|
|
24
|
+
control: 'boolean',
|
|
25
|
+
description: 'Whether the info box starts open',
|
|
26
|
+
defaultValue: false,
|
|
27
|
+
},
|
|
28
|
+
maxWidth: {
|
|
29
|
+
control: 'text',
|
|
30
|
+
description: 'Maximum width of the content box (default: 320px)',
|
|
31
|
+
},
|
|
32
|
+
triggerLabel: {
|
|
33
|
+
control: 'text',
|
|
34
|
+
description: 'Accessible label for the trigger button',
|
|
35
|
+
},
|
|
36
|
+
placement: {
|
|
37
|
+
control: 'select',
|
|
38
|
+
options: [
|
|
39
|
+
'top',
|
|
40
|
+
'top-start',
|
|
41
|
+
'top-end',
|
|
42
|
+
'bottom',
|
|
43
|
+
'bottom-start',
|
|
44
|
+
'bottom-end',
|
|
45
|
+
'left',
|
|
46
|
+
'left-start',
|
|
47
|
+
'left-end',
|
|
48
|
+
'right',
|
|
49
|
+
'right-start',
|
|
50
|
+
'right-end',
|
|
51
|
+
],
|
|
52
|
+
description:
|
|
53
|
+
'Preferred placement (Floating UI will auto-adjust if there is not enough space)',
|
|
54
|
+
},
|
|
55
|
+
children: {
|
|
56
|
+
control: false,
|
|
57
|
+
description: 'Content to display inside the info box',
|
|
58
|
+
},
|
|
59
|
+
onOpenChange: {
|
|
60
|
+
action: 'onOpenChange',
|
|
61
|
+
description: 'Callback when the info box opens or closes',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
} satisfies Meta<typeof InfoBox>;
|
|
65
|
+
|
|
66
|
+
export default meta;
|
|
67
|
+
type Story = StoryObj<typeof meta>;
|
|
68
|
+
|
|
69
|
+
export const Default: Story = {
|
|
70
|
+
args: {
|
|
71
|
+
children: <p>This is helpful information about this feature.</p>,
|
|
72
|
+
},
|
|
73
|
+
parameters: {
|
|
74
|
+
docs: {
|
|
75
|
+
description: {
|
|
76
|
+
story: 'Basic info box with simple text content. Click the info icon to open.',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export const WithTitle: Story = {
|
|
83
|
+
args: {
|
|
84
|
+
title: 'Important Information',
|
|
85
|
+
children: (
|
|
86
|
+
<div className="space-y-2">
|
|
87
|
+
<p>Here are some key points to remember:</p>
|
|
88
|
+
|
|
89
|
+
<ul className="list-disc list-inside text-sm">
|
|
90
|
+
<li>Point one</li>
|
|
91
|
+
|
|
92
|
+
<li>Point two</li>
|
|
93
|
+
|
|
94
|
+
<li>Point three</li>
|
|
95
|
+
</ul>
|
|
96
|
+
</div>
|
|
97
|
+
),
|
|
98
|
+
},
|
|
99
|
+
parameters: {
|
|
100
|
+
docs: {
|
|
101
|
+
description: {
|
|
102
|
+
story: 'Info box with a title header for more structured content.',
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export const DefaultOpen: Story = {
|
|
109
|
+
args: {
|
|
110
|
+
title: 'Getting Started',
|
|
111
|
+
defaultOpen: true,
|
|
112
|
+
children: <p>This info box starts open by default.</p>,
|
|
113
|
+
},
|
|
114
|
+
parameters: {
|
|
115
|
+
docs: {
|
|
116
|
+
description: {
|
|
117
|
+
story: 'Info box that starts in the open state.',
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export const LongContent: Story = {
|
|
124
|
+
args: {
|
|
125
|
+
title: 'Detailed Information',
|
|
126
|
+
maxWidth: '400px',
|
|
127
|
+
children: (
|
|
128
|
+
<div className="space-y-2 max-h-48 overflow-y-auto">
|
|
129
|
+
<p>
|
|
130
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt
|
|
131
|
+
ut labore et dolore magna aliqua.
|
|
132
|
+
</p>
|
|
133
|
+
|
|
134
|
+
<p>
|
|
135
|
+
Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea
|
|
136
|
+
commodo consequat.
|
|
137
|
+
</p>
|
|
138
|
+
|
|
139
|
+
<p>
|
|
140
|
+
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
|
|
141
|
+
nulla pariatur.
|
|
142
|
+
</p>
|
|
143
|
+
|
|
144
|
+
<p>
|
|
145
|
+
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
|
|
146
|
+
anim id est laborum.
|
|
147
|
+
</p>
|
|
148
|
+
</div>
|
|
149
|
+
),
|
|
150
|
+
},
|
|
151
|
+
parameters: {
|
|
152
|
+
docs: {
|
|
153
|
+
description: {
|
|
154
|
+
story: 'Info box with longer scrollable content and custom max width.',
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
export const AllPositions: Story = {
|
|
161
|
+
args: {
|
|
162
|
+
children: null,
|
|
163
|
+
},
|
|
164
|
+
parameters: {
|
|
165
|
+
layout: 'fullscreen',
|
|
166
|
+
docs: {
|
|
167
|
+
description: {
|
|
168
|
+
story:
|
|
169
|
+
'Demonstrates various placement options using Floating UI. The content will automatically adjust if there is not enough space.',
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
render: () => (
|
|
174
|
+
<div className="p-8 grid grid-cols-2 gap-8 h-screen">
|
|
175
|
+
<div className="flex items-start justify-start">
|
|
176
|
+
<div className="flex items-center gap-2">
|
|
177
|
+
<span className="text-sm text-gray-600">bottom-start:</span>
|
|
178
|
+
|
|
179
|
+
<InfoBox title="Position Demo" placement="bottom-start">
|
|
180
|
+
<p>Prefers bottom-start placement.</p>
|
|
181
|
+
</InfoBox>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<div className="flex items-start justify-end">
|
|
186
|
+
<div className="flex items-center gap-2">
|
|
187
|
+
<span className="text-sm text-gray-600">bottom-end:</span>
|
|
188
|
+
|
|
189
|
+
<InfoBox title="Position Demo" placement="bottom-end">
|
|
190
|
+
<p>Prefers bottom-end placement.</p>
|
|
191
|
+
</InfoBox>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<div className="flex items-end justify-start">
|
|
196
|
+
<div className="flex items-center gap-2">
|
|
197
|
+
<span className="text-sm text-gray-600">top-start:</span>
|
|
198
|
+
|
|
199
|
+
<InfoBox title="Position Demo" placement="top-start">
|
|
200
|
+
<p>Prefers top-start placement.</p>
|
|
201
|
+
</InfoBox>
|
|
202
|
+
</div>
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
<div className="flex items-end justify-end">
|
|
206
|
+
<div className="flex items-center gap-2">
|
|
207
|
+
<span className="text-sm text-gray-600">top-end:</span>
|
|
208
|
+
|
|
209
|
+
<InfoBox title="Position Demo" placement="top-end">
|
|
210
|
+
<p>Prefers top-end placement.</p>
|
|
211
|
+
</InfoBox>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
),
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export const InCorners: Story = {
|
|
219
|
+
args: {
|
|
220
|
+
children: null,
|
|
221
|
+
},
|
|
222
|
+
parameters: {
|
|
223
|
+
layout: 'fullscreen',
|
|
224
|
+
docs: {
|
|
225
|
+
description: {
|
|
226
|
+
story:
|
|
227
|
+
'Demonstrates auto-positioning when the info box is placed in different corners of the viewport. The info box automatically positions itself to stay visible.',
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
},
|
|
231
|
+
render: () => (
|
|
232
|
+
<div className="relative h-screen w-full">
|
|
233
|
+
<div className="absolute top-4 left-4 flex items-center gap-2">
|
|
234
|
+
<InfoBox>
|
|
235
|
+
<p>Auto-positioned from top-left corner. Should open to bottom-right.</p>
|
|
236
|
+
</InfoBox>
|
|
237
|
+
|
|
238
|
+
<span className="text-xs text-gray-500">Top-left corner</span>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
<div className="absolute top-4 right-4 flex items-center gap-2">
|
|
242
|
+
<span className="text-xs text-gray-500">Top-right corner</span>
|
|
243
|
+
|
|
244
|
+
<InfoBox>
|
|
245
|
+
<p>Auto-positioned from top-right corner. Should open to bottom-left.</p>
|
|
246
|
+
</InfoBox>
|
|
247
|
+
</div>
|
|
248
|
+
|
|
249
|
+
<div className="absolute bottom-4 left-4 flex items-center gap-2">
|
|
250
|
+
<InfoBox>
|
|
251
|
+
<p>Auto-positioned from bottom-left corner. Should open to top-right.</p>
|
|
252
|
+
</InfoBox>
|
|
253
|
+
|
|
254
|
+
<span className="text-xs text-gray-500">Bottom-left corner</span>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<div className="absolute bottom-4 right-4 flex items-center gap-2">
|
|
258
|
+
<span className="text-xs text-gray-500">Bottom-right corner</span>
|
|
259
|
+
|
|
260
|
+
<InfoBox>
|
|
261
|
+
<p>Auto-positioned from bottom-right corner. Should open to top-left.</p>
|
|
262
|
+
</InfoBox>
|
|
263
|
+
</div>
|
|
264
|
+
|
|
265
|
+
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-center">
|
|
266
|
+
<p className="text-gray-400 text-sm">
|
|
267
|
+
Click the info icons in each corner to see auto-positioning
|
|
268
|
+
</p>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
),
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
export const WithInteractiveContent: Story = {
|
|
275
|
+
args: {
|
|
276
|
+
title: 'Take Action',
|
|
277
|
+
children: (
|
|
278
|
+
<div className="space-y-3">
|
|
279
|
+
<p className="text-sm">You can interact with elements inside:</p>
|
|
280
|
+
|
|
281
|
+
<input
|
|
282
|
+
type="text"
|
|
283
|
+
placeholder="Type something..."
|
|
284
|
+
className="w-full px-3 py-2 text-sm border border-gray-300 rounded focus:outline-none
|
|
285
|
+
focus:ring-2 focus:ring-blue-500"
|
|
286
|
+
/>
|
|
287
|
+
|
|
288
|
+
<button
|
|
289
|
+
type="button"
|
|
290
|
+
className="w-full px-4 py-2 text-sm text-white bg-blue-600 rounded hover:bg-blue-700
|
|
291
|
+
focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
292
|
+
>
|
|
293
|
+
Submit
|
|
294
|
+
</button>
|
|
295
|
+
</div>
|
|
296
|
+
),
|
|
297
|
+
},
|
|
298
|
+
parameters: {
|
|
299
|
+
docs: {
|
|
300
|
+
description: {
|
|
301
|
+
story:
|
|
302
|
+
'Info box with interactive content (form elements). Focus is trapped within the info box when open.',
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
export const InlineWithText: Story = {
|
|
309
|
+
args: {
|
|
310
|
+
children: null,
|
|
311
|
+
},
|
|
312
|
+
parameters: {
|
|
313
|
+
docs: {
|
|
314
|
+
description: {
|
|
315
|
+
story: 'Info box used inline with paragraph text to provide contextual information.',
|
|
316
|
+
},
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
render: () => (
|
|
320
|
+
<div className="max-w-md p-4">
|
|
321
|
+
<p className="text-sm leading-relaxed">
|
|
322
|
+
This is a paragraph with an inline info box{' '}
|
|
323
|
+
<span className="inline-flex align-middle mx-1">
|
|
324
|
+
<InfoBox triggerLabel="Learn more about this term">
|
|
325
|
+
<p>Additional context about the preceding text that helps explain the concept.</p>
|
|
326
|
+
</InfoBox>
|
|
327
|
+
</span>{' '}
|
|
328
|
+
that provides more context about a specific term or concept in the text.
|
|
329
|
+
</p>
|
|
330
|
+
</div>
|
|
331
|
+
),
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
export const CustomTriggerLabel: Story = {
|
|
335
|
+
args: {
|
|
336
|
+
triggerLabel: 'Learn more about data privacy',
|
|
337
|
+
title: 'Data Privacy',
|
|
338
|
+
children: (
|
|
339
|
+
<div className="space-y-2 text-sm">
|
|
340
|
+
<p>Your data is handled securely and in accordance with our privacy policy.</p>
|
|
341
|
+
|
|
342
|
+
<a href="https://example.com" className="text-blue-600 hover:underline">
|
|
343
|
+
Read our full privacy policy
|
|
344
|
+
</a>
|
|
345
|
+
</div>
|
|
346
|
+
),
|
|
347
|
+
},
|
|
348
|
+
parameters: {
|
|
349
|
+
docs: {
|
|
350
|
+
description: {
|
|
351
|
+
story:
|
|
352
|
+
'Info box with a custom trigger label for better accessibility context. Screen readers will announce the custom label.',
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
export const MultipleInfoBoxes: Story = {
|
|
359
|
+
args: {
|
|
360
|
+
children: null,
|
|
361
|
+
},
|
|
362
|
+
parameters: {
|
|
363
|
+
docs: {
|
|
364
|
+
description: {
|
|
365
|
+
story: 'Multiple info boxes can be used independently on the same page.',
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
render: () => (
|
|
370
|
+
<div className="space-y-4 p-4">
|
|
371
|
+
<div className="flex items-center gap-2">
|
|
372
|
+
<span className="text-sm font-medium">Username</span>
|
|
373
|
+
|
|
374
|
+
<InfoBox triggerLabel="Username requirements">
|
|
375
|
+
<p className="text-sm">Username must be 3-20 characters and contain only letters.</p>
|
|
376
|
+
</InfoBox>
|
|
377
|
+
</div>
|
|
378
|
+
|
|
379
|
+
<div className="flex items-center gap-2">
|
|
380
|
+
<span className="text-sm font-medium">Password</span>
|
|
381
|
+
|
|
382
|
+
<InfoBox triggerLabel="Password requirements">
|
|
383
|
+
<div className="text-sm space-y-1">
|
|
384
|
+
<p>Password must contain:</p>
|
|
385
|
+
|
|
386
|
+
<ul className="list-disc list-inside">
|
|
387
|
+
<li>At least 8 characters</li>
|
|
388
|
+
|
|
389
|
+
<li>One uppercase letter</li>
|
|
390
|
+
|
|
391
|
+
<li>One number</li>
|
|
392
|
+
</ul>
|
|
393
|
+
</div>
|
|
394
|
+
</InfoBox>
|
|
395
|
+
</div>
|
|
396
|
+
|
|
397
|
+
<div className="flex items-center gap-2">
|
|
398
|
+
<span className="text-sm font-medium">Email</span>
|
|
399
|
+
|
|
400
|
+
<InfoBox triggerLabel="Why we need your email">
|
|
401
|
+
<p className="text-sm">We use your email for account recovery and important updates.</p>
|
|
402
|
+
</InfoBox>
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
),
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
export const Accessibility: Story = {
|
|
409
|
+
args: {
|
|
410
|
+
children: null,
|
|
411
|
+
},
|
|
412
|
+
parameters: {
|
|
413
|
+
docs: {
|
|
414
|
+
description: {
|
|
415
|
+
story: 'Demonstrates the accessibility features of the InfoBox component.',
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
},
|
|
419
|
+
render: () => (
|
|
420
|
+
<div className="space-y-6 max-w-md p-4">
|
|
421
|
+
<div className="bg-blue-50 border border-blue-200 rounded p-4">
|
|
422
|
+
<h3 className="font-bold text-blue-800 mb-2">Accessibility Features:</h3>
|
|
423
|
+
|
|
424
|
+
<ul className="list-disc list-inside space-y-1 text-blue-700 text-sm">
|
|
425
|
+
<li>Trigger button has aria-expanded and aria-haspopup</li>
|
|
426
|
+
|
|
427
|
+
<li>Content has role="dialog" and aria-modal="true"</li>
|
|
428
|
+
|
|
429
|
+
<li>Focus is trapped within the info box when open</li>
|
|
430
|
+
|
|
431
|
+
<li>Escape key closes the info box</li>
|
|
432
|
+
|
|
433
|
+
<li>Click outside closes the info box</li>
|
|
434
|
+
|
|
435
|
+
<li>Focus returns to trigger on close (via keyboard)</li>
|
|
436
|
+
|
|
437
|
+
<li>Title provides aria-labelledby relationship</li>
|
|
438
|
+
</ul>
|
|
439
|
+
</div>
|
|
440
|
+
|
|
441
|
+
<div className="flex items-center gap-2">
|
|
442
|
+
<span className="text-sm">Try this accessible info box:</span>
|
|
443
|
+
|
|
444
|
+
<InfoBox title="Accessible Info Box" triggerLabel="Show accessibility information">
|
|
445
|
+
<div className="text-sm space-y-2">
|
|
446
|
+
<p>This info box demonstrates proper accessibility:</p>
|
|
447
|
+
|
|
448
|
+
<ul className="list-disc list-inside">
|
|
449
|
+
<li>Use Tab to navigate between elements</li>
|
|
450
|
+
|
|
451
|
+
<li>Press Escape to close</li>
|
|
452
|
+
|
|
453
|
+
<li>Click outside to close</li>
|
|
454
|
+
</ul>
|
|
455
|
+
</div>
|
|
456
|
+
</InfoBox>
|
|
457
|
+
</div>
|
|
458
|
+
</div>
|
|
459
|
+
),
|
|
460
|
+
};
|