@regardio/react 0.5.5 → 0.5.7

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
3
  "name": "@regardio/react",
4
- "version": "0.5.5",
4
+ "version": "0.5.7",
5
5
  "private": false,
6
6
  "description": "Regardio React UI components",
7
7
  "keywords": [
@@ -172,8 +172,8 @@
172
172
  "@maptiler/sdk": "3.9.0",
173
173
  "@mdx-js/react": "3.1.1",
174
174
  "@regardio/js": "0.6.0",
175
- "@regardio/tailwind": "0.1.3",
176
- "@supabase/supabase-js": "2.90.0",
175
+ "@regardio/tailwind": "0.2.0",
176
+ "@supabase/supabase-js": "2.90.1",
177
177
  "cmdk": "1.1.1",
178
178
  "embla-carousel": "8.6.0",
179
179
  "embla-carousel-react": "8.6.0",
@@ -181,19 +181,19 @@
181
181
  "intl-parse-accept-language": "1.0.0",
182
182
  "leaflet": "alpha",
183
183
  "lucide-react": "0.562.0",
184
- "markdown-to-jsx": "9.5.1",
184
+ "markdown-to-jsx": "9.5.7",
185
185
  "react": "19.2.3",
186
186
  "react-day-picker": "9.13.0",
187
187
  "react-dom": "19.2.3",
188
- "react-hook-form": "7.70.0",
189
- "react-resizable-panels": "4.3.1",
188
+ "react-hook-form": "7.71.0",
189
+ "react-resizable-panels": "4.4.0",
190
190
  "react-router": "7.12.0",
191
191
  "tailwind-variants": "3.2.2",
192
192
  "vaul": "1.1.2",
193
193
  "zod": "4.3.5"
194
194
  },
195
195
  "devDependencies": {
196
- "@regardio/dev": "1.11.1",
196
+ "@regardio/dev": "1.11.4",
197
197
  "@storybook/addon-a11y": "10.1.11",
198
198
  "@storybook/addon-docs": "10.1.11",
199
199
  "@storybook/addon-vitest": "10.1.11",
@@ -203,13 +203,13 @@
203
203
  "@testing-library/react": "16.3.1",
204
204
  "@total-typescript/ts-reset": "0.6.1",
205
205
  "@types/leaflet": "1.9.21",
206
- "@types/node": "25.0.3",
207
- "@types/react": "19.2.7",
206
+ "@types/node": "25.0.8",
207
+ "@types/react": "19.2.8",
208
208
  "@types/react-dom": "19.2.3",
209
209
  "@vitejs/plugin-react": "5.1.2",
210
- "@vitest/browser-playwright": "4.0.16",
211
- "@vitest/coverage-v8": "4.0.16",
212
- "@vitest/ui": "4.0.16",
210
+ "@vitest/browser-playwright": "4.0.17",
211
+ "@vitest/coverage-v8": "4.0.17",
212
+ "@vitest/ui": "4.0.17",
213
213
  "jsdom": "27.4.0",
214
214
  "playwright": "1.57.0",
215
215
  "storybook": "10.1.11",
@@ -217,7 +217,7 @@
217
217
  "tsup": "8.5.1",
218
218
  "typescript": "5.9.3",
219
219
  "vite": "7.3.1",
220
- "vitest": "4.0.16"
220
+ "vitest": "4.0.17"
221
221
  },
222
222
  "engines": {
223
223
  "node": ">=18"
@@ -0,0 +1,169 @@
1
+ import { cleanup, fireEvent, render, screen } from '@testing-library/react';
2
+ import { MemoryRouter } from 'react-router';
3
+ import { afterEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import { Link, LinkBase, MarkdownLink, PathResolverProvider } from './link';
6
+
7
+ afterEach(() => {
8
+ cleanup();
9
+ });
10
+
11
+ const renderWithRouter = (ui: React.ReactNode) => {
12
+ return render(<MemoryRouter>{ui}</MemoryRouter>);
13
+ };
14
+
15
+ describe('LinkBase', () => {
16
+ it('renders internal link with NavLink', () => {
17
+ renderWithRouter(<LinkBase to="/about">About</LinkBase>);
18
+ expect(screen.getByText('About')).toBeDefined();
19
+ });
20
+
21
+ it('renders external http link as anchor', () => {
22
+ renderWithRouter(<LinkBase to="https://example.com">External</LinkBase>);
23
+ const link = screen.getByText('External');
24
+ expect(link.tagName).toBe('A');
25
+ expect(link.getAttribute('href')).toBe('https://example.com');
26
+ });
27
+
28
+ it('renders mailto link as anchor', () => {
29
+ renderWithRouter(<LinkBase to="mailto:test@example.com">Email</LinkBase>);
30
+ const link = screen.getByText('Email');
31
+ expect(link.tagName).toBe('A');
32
+ expect(link.getAttribute('href')).toBe('mailto:test@example.com');
33
+ });
34
+
35
+ it('renders tel link as anchor', () => {
36
+ renderWithRouter(<LinkBase to="tel:+1234567890">Call</LinkBase>);
37
+ const link = screen.getByText('Call');
38
+ expect(link.tagName).toBe('A');
39
+ expect(link.getAttribute('href')).toBe('tel:+1234567890');
40
+ });
41
+
42
+ it('uses pathResolver when routeKey is provided', () => {
43
+ const resolver = vi.fn().mockReturnValue('/resolved-path');
44
+ renderWithRouter(
45
+ <PathResolverProvider value={resolver}>
46
+ <LinkBase routeKey="home">Home</LinkBase>
47
+ </PathResolverProvider>,
48
+ );
49
+ expect(resolver).toHaveBeenCalledWith('home');
50
+ expect(screen.getByText('Home')).toBeDefined();
51
+ });
52
+
53
+ it('handles object to prop with pathname, search, and hash', () => {
54
+ renderWithRouter(
55
+ <LinkBase to={{ hash: '#section', pathname: '/page', search: '?q=test' }}>Complex</LinkBase>,
56
+ );
57
+ expect(screen.getByText('Complex')).toBeDefined();
58
+ });
59
+
60
+ it('renders children only when path is empty', () => {
61
+ renderWithRouter(<LinkBase>No Link</LinkBase>);
62
+ expect(screen.getByText('No Link')).toBeDefined();
63
+ });
64
+
65
+ it('calls onClick handler', () => {
66
+ const handleClick = vi.fn();
67
+ renderWithRouter(
68
+ <LinkBase
69
+ onClick={handleClick}
70
+ to="/test"
71
+ >
72
+ Click Me
73
+ </LinkBase>,
74
+ );
75
+ fireEvent.click(screen.getByText('Click Me'));
76
+ expect(handleClick).toHaveBeenCalled();
77
+ });
78
+
79
+ it('opens external http link in new window', () => {
80
+ const windowOpen = vi.spyOn(window, 'open').mockImplementation(() => null);
81
+ renderWithRouter(<LinkBase to="https://example.com">External</LinkBase>);
82
+ fireEvent.click(screen.getByText('External'));
83
+ expect(windowOpen).toHaveBeenCalledWith('https://example.com', '_blank', 'noopener,noreferrer');
84
+ windowOpen.mockRestore();
85
+ });
86
+
87
+ it('scrolls to element for hash links', () => {
88
+ const scrollIntoView = vi.fn();
89
+ const element = document.createElement('div');
90
+ element.id = 'section';
91
+ element.scrollIntoView = scrollIntoView;
92
+ document.body.appendChild(element);
93
+
94
+ renderWithRouter(<LinkBase to="#section">Jump</LinkBase>);
95
+ fireEvent.click(screen.getByText('Jump'));
96
+ expect(scrollIntoView).toHaveBeenCalledWith({ behavior: 'smooth' });
97
+
98
+ document.body.removeChild(element);
99
+ });
100
+
101
+ it('handles hash link when element not found', () => {
102
+ renderWithRouter(<LinkBase to="#nonexistent">Jump</LinkBase>);
103
+ fireEvent.click(screen.getByText('Jump'));
104
+ });
105
+
106
+ it('does not prevent default for tel links', () => {
107
+ renderWithRouter(<LinkBase to="tel:+1234567890">Call</LinkBase>);
108
+ const link = screen.getByText('Call');
109
+ const event = fireEvent.click(link);
110
+ expect(event).toBe(true);
111
+ });
112
+
113
+ it('respects defaultPrevented in onClick', () => {
114
+ const handleClick = vi.fn((e: React.MouseEvent) => e.preventDefault());
115
+ const windowOpen = vi.spyOn(window, 'open').mockImplementation(() => null);
116
+
117
+ renderWithRouter(
118
+ <LinkBase
119
+ onClick={handleClick}
120
+ to="https://example.com"
121
+ >
122
+ External
123
+ </LinkBase>,
124
+ );
125
+ fireEvent.click(screen.getByText('External'));
126
+ expect(handleClick).toHaveBeenCalled();
127
+ expect(windowOpen).not.toHaveBeenCalled();
128
+
129
+ windowOpen.mockRestore();
130
+ });
131
+ });
132
+
133
+ describe('Link', () => {
134
+ it('renders with variant', () => {
135
+ renderWithRouter(
136
+ <Link
137
+ to="/test"
138
+ variant="button"
139
+ >
140
+ Button Link
141
+ </Link>,
142
+ );
143
+ expect(screen.getByText('Button Link')).toBeDefined();
144
+ });
145
+
146
+ it('renders with arrow', () => {
147
+ renderWithRouter(
148
+ <Link
149
+ arrow="rarr"
150
+ to="/test"
151
+ >
152
+ Arrow Link
153
+ </Link>,
154
+ );
155
+ expect(screen.getByText('Arrow Link')).toBeDefined();
156
+ });
157
+ });
158
+
159
+ describe('MarkdownLink', () => {
160
+ it('renders Link when href is provided', () => {
161
+ renderWithRouter(<MarkdownLink href="/page">Markdown</MarkdownLink>);
162
+ expect(screen.getByText('Markdown')).toBeDefined();
163
+ });
164
+
165
+ it('returns null when href is not provided', () => {
166
+ const { container } = renderWithRouter(<MarkdownLink>No Link</MarkdownLink>);
167
+ expect(container.innerHTML).toBe('');
168
+ });
169
+ });
@@ -0,0 +1,110 @@
1
+ import { isValidElement, type ReactNode } from 'react';
2
+ import { describe, expect, it } from 'vitest';
3
+
4
+ import { lowerCaseSzett, replaceSpecialChars, shy, wrapSentences } from './text';
5
+
6
+ describe('lowerCaseSzett', () => {
7
+ it('wraps ß in lowercase span for strings', () => {
8
+ const result = lowerCaseSzett('Straße') as ReactNode[];
9
+ expect(Array.isArray(result)).toBe(true);
10
+ expect(result).toHaveLength(3);
11
+ expect(result[0]).toBe('Stra');
12
+ expect(isValidElement(result[1])).toBe(true);
13
+ expect((result[1] as { props: { className: string } }).props.className).toBe('lowercase');
14
+ expect((result[1] as { props: { children: string } }).props.children).toBe('ß');
15
+ expect(result[2]).toBe('e');
16
+ });
17
+
18
+ it('handles strings without ß', () => {
19
+ const result = lowerCaseSzett('Hello') as ReactNode[];
20
+ expect(Array.isArray(result)).toBe(true);
21
+ expect(result).toEqual(['Hello']);
22
+ });
23
+
24
+ it('handles React elements with children containing ß', () => {
25
+ const element = <div>Straße</div>;
26
+ const result = lowerCaseSzett(element);
27
+ expect(isValidElement(result)).toBe(true);
28
+ const children = (result as { props: { children: ReactNode[] } }).props.children;
29
+ expect(Array.isArray(children)).toBe(true);
30
+ });
31
+
32
+ it('handles nested React elements', () => {
33
+ const element = (
34
+ <div>
35
+ <span>Große Straße</span>
36
+ </div>
37
+ );
38
+ const result = lowerCaseSzett(element);
39
+ expect(isValidElement(result)).toBe(true);
40
+ });
41
+
42
+ it('handles arrays of children', () => {
43
+ const result = lowerCaseSzett(['Straße', ' und ', 'Größe']);
44
+ expect(Array.isArray(result)).toBe(true);
45
+ });
46
+
47
+ it('returns non-string/element values as-is', () => {
48
+ expect(lowerCaseSzett(null)).toBeNull();
49
+ expect(lowerCaseSzett(undefined)).toBeUndefined();
50
+ expect(lowerCaseSzett(123 as unknown as string)).toBe(123);
51
+ });
52
+ });
53
+
54
+ describe('shy', () => {
55
+ it('returns null for null input', () => {
56
+ expect(shy(null)).toBeNull();
57
+ });
58
+
59
+ it('replaces soft hyphens in strings', () => {
60
+ const input = 'Weihnachts\u00ADspende';
61
+ const result = shy(input);
62
+ expect(typeof result).toBe('string');
63
+ expect(result).toContain('\u00AD');
64
+ });
65
+
66
+ it('handles React elements with soft hyphens', () => {
67
+ const element = <div>Weihnachts­spende</div>;
68
+ const result = shy(element);
69
+ expect(isValidElement(result)).toBe(true);
70
+ const children = (result as { props: { children: string } }).props.children;
71
+ expect(children).toBe('Weihnachtsspende');
72
+ });
73
+
74
+ it('handles nested React elements with soft hyphens', () => {
75
+ const element = (
76
+ <div>
77
+ <span>Weihnachts­spende</span>
78
+ </div>
79
+ );
80
+ const result = shy(element);
81
+ expect(isValidElement(result)).toBe(true);
82
+ });
83
+
84
+ it('handles arrays in React nodes', () => {
85
+ const element = <div>{['Weihnachts­spende', ' ', 'Test­wort']}</div>;
86
+ const result = shy(element);
87
+ expect(isValidElement(result)).toBe(true);
88
+ });
89
+ });
90
+
91
+ describe('replaceSpecialChars', () => {
92
+ it('applies typographic quotes and shy', () => {
93
+ const result = replaceSpecialChars('"Hello"', 'de');
94
+ expect(typeof result).toBe('string');
95
+ expect(result).toBe('\u201EHello\u201D');
96
+ });
97
+ });
98
+
99
+ describe('wrapSentences', () => {
100
+ it('wraps each sentence in a span', () => {
101
+ const result = wrapSentences('First sentence. Second sentence.');
102
+ expect(Array.isArray(result)).toBe(true);
103
+ expect((result as ReactNode[]).length).toBeGreaterThanOrEqual(2);
104
+ });
105
+
106
+ it('handles single sentence', () => {
107
+ const result = wrapSentences('Just one sentence.');
108
+ expect(Array.isArray(result)).toBe(true);
109
+ });
110
+ });