@regardio/react 0.4.5 → 0.5.1

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 (202) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +5 -5
  3. package/dist/{components/background-slideshow.js → background-slideshow/index.js} +2 -11
  4. package/dist/{components/blurry-gradient.js → blurry-gradient/index.js} +15 -9
  5. package/dist/{components/carousel.d.ts → carousel/index.d.ts} +17 -9
  6. package/dist/{components/carousel.js → carousel/index.js} +34 -30
  7. package/dist/{components/countdown.js → countdown/index.js} +2 -11
  8. package/dist/{components/generic-error.js → generic-error/index.js} +1 -1
  9. package/dist/grid/index.d.ts +1196 -0
  10. package/dist/grid/index.js +239 -0
  11. package/dist/heading/index.d.ts +24 -0
  12. package/dist/{components/heading.js → heading/index.js} +15 -34
  13. package/dist/highlight/index.d.ts +13 -0
  14. package/dist/{components/highlight.js → highlight/index.js} +9 -17
  15. package/dist/hooks/{use-current-route-data.js → use-current-route-data/index.js} +1 -1
  16. package/dist/hooks/{use-focus-search.js → use-focus-search/index.js} +1 -1
  17. package/dist/hooks/{use-matches-data.js → use-matches-data/index.js} +1 -1
  18. package/dist/hooks/{use-media-query.js → use-media-query/index.js} +1 -1
  19. package/dist/hooks/{use-mobile.js → use-mobile/index.js} +1 -1
  20. package/dist/hooks/use-nonce/index.d.ts +6 -0
  21. package/dist/hooks/use-nonce/index.js +8 -0
  22. package/dist/hooks/{use-orientation.d.ts → use-orientation/index.d.ts} +1 -1
  23. package/dist/hooks/{use-orientation.js → use-orientation/index.js} +1 -1
  24. package/dist/hooks/{use-user.js → use-user/index.js} +1 -1
  25. package/dist/{components/icon-button.js → icon-button/index.js} +1 -1
  26. package/dist/{components/if.js → if/index.js} +1 -1
  27. package/dist/{components/iframe.js → iframe/index.js} +2 -11
  28. package/dist/{components/link.d.ts → link/index.d.ts} +19 -13
  29. package/dist/{components/link.js → link/index.js} +31 -36
  30. package/dist/list/index.d.ts +69 -0
  31. package/dist/list/index.js +65 -0
  32. package/dist/{components/markdown-container.js → markdown-container/index.js} +3 -67
  33. package/dist/{components/password-input.js → password-input/index.js} +2 -11
  34. package/dist/{components/picture.js → picture/index.js} +2 -11
  35. package/dist/{components/protected-email.d.ts → protected-email/index.d.ts} +1 -1
  36. package/dist/{components/protected-email.js → protected-email/index.js} +1 -1
  37. package/dist/text/index.d.ts +20 -0
  38. package/dist/text/index.js +38 -0
  39. package/dist/utils/author/index.d.ts +3 -0
  40. package/dist/utils/author/index.js +33 -0
  41. package/dist/utils/text/index.d.ts +15 -0
  42. package/dist/utils/text/index.js +73 -0
  43. package/package.json +124 -26
  44. package/src/{stories/BackgroundSlideshow.stories.tsx → background-slideshow/background-slideshow.stories.tsx} +1 -1
  45. package/src/{components → background-slideshow}/background-slideshow.tsx +3 -1
  46. package/src/background-slideshow/index.ts +2 -0
  47. package/src/{stories/BlurryGradient.stories.tsx → blurry-gradient/blurry-gradient.stories.tsx} +1 -1
  48. package/src/{components → blurry-gradient}/blurry-gradient.tsx +14 -8
  49. package/src/blurry-gradient/index.ts +2 -0
  50. package/src/carousel/carousel-content.tsx +16 -0
  51. package/src/carousel/carousel-item.tsx +23 -0
  52. package/src/carousel/carousel-next.tsx +22 -0
  53. package/src/carousel/carousel-previous.tsx +22 -0
  54. package/src/{components/carousel.tsx → carousel/carousel-root.tsx} +8 -78
  55. package/src/carousel/carousel.stories.tsx +89 -0
  56. package/src/carousel/index.parts.ts +5 -0
  57. package/src/carousel/index.ts +4 -0
  58. package/src/{stories/Countdown.stories.tsx → countdown/countdown.stories.tsx} +1 -1
  59. package/src/{components → countdown}/countdown.tsx +3 -7
  60. package/src/countdown/index.ts +1 -0
  61. package/src/{stories/GenericError.stories.tsx → generic-error/generic-error.stories.tsx} +1 -1
  62. package/src/{components → generic-error}/generic-error.tsx +2 -0
  63. package/src/generic-error/index.ts +2 -0
  64. package/src/grid/grid-item.tsx +188 -0
  65. package/src/grid/grid-root.tsx +72 -0
  66. package/src/grid/grid.stories.tsx +236 -0
  67. package/src/grid/index.parts.ts +2 -0
  68. package/src/grid/index.ts +5 -0
  69. package/src/{stories/Heading.stories.tsx → heading/heading.stories.tsx} +1 -1
  70. package/src/{components → heading}/heading.tsx +17 -25
  71. package/src/heading/index.ts +2 -0
  72. package/src/{stories/Highlight.stories.tsx → highlight/highlight.stories.tsx} +1 -1
  73. package/src/{components → highlight}/highlight.tsx +13 -9
  74. package/src/highlight/index.ts +2 -0
  75. package/src/hooks/use-current-route-data/index.ts +1 -0
  76. package/src/hooks/use-focus-search/index.ts +1 -0
  77. package/src/hooks/use-matches-data/index.ts +1 -0
  78. package/src/hooks/use-media-query/index.ts +1 -0
  79. package/src/hooks/use-mobile/index.ts +1 -0
  80. package/src/hooks/use-nonce/index.ts +1 -0
  81. package/src/hooks/use-orientation/index.ts +1 -0
  82. package/src/hooks/use-user/index.ts +2 -0
  83. package/src/{stories/IconButton.stories.tsx → icon-button/icon-button.stories.tsx} +1 -1
  84. package/src/icon-button/index.ts +2 -0
  85. package/src/{stories/If.stories.tsx → if/if.stories.tsx} +1 -1
  86. package/src/if/index.ts +1 -0
  87. package/src/{stories/Iframe.stories.tsx → iframe/iframe.stories.tsx} +1 -1
  88. package/src/{components → iframe}/iframe.tsx +1 -1
  89. package/src/iframe/index.ts +2 -0
  90. package/src/link/index.ts +2 -0
  91. package/src/{stories/Link.stories.tsx → link/link.stories.tsx} +1 -1
  92. package/src/{components → link}/link.tsx +39 -28
  93. package/src/list/index.parts.ts +2 -0
  94. package/src/list/index.ts +4 -0
  95. package/src/list/list-item.tsx +63 -0
  96. package/src/list/list-root-context.ts +21 -0
  97. package/src/list/list-root.tsx +81 -0
  98. package/src/list/list.css +32 -0
  99. package/src/list/list.stories.tsx +119 -0
  100. package/src/list/list.test.tsx +168 -0
  101. package/src/markdown-container/index.ts +2 -0
  102. package/src/{stories/MarkdownContainer.stories.tsx → markdown-container/markdown-container.stories.tsx} +1 -1
  103. package/src/{components → markdown-container}/markdown-container.tsx +3 -1
  104. package/src/password-input/index.ts +2 -0
  105. package/src/{stories/PasswordInput.stories.tsx → password-input/password-input.stories.tsx} +1 -1
  106. package/src/{components → password-input}/password-input.tsx +4 -4
  107. package/src/picture/index.ts +2 -0
  108. package/src/{stories/Picture.stories.tsx → picture/picture.stories.tsx} +1 -1
  109. package/src/{components → picture}/picture.tsx +2 -4
  110. package/src/protected-email/index.ts +2 -0
  111. package/src/{stories/ProtectedEmail.stories.tsx → protected-email/protected-email.stories.tsx} +1 -1
  112. package/src/{components → protected-email}/protected-email.tsx +3 -1
  113. package/src/tailwind.css +10 -0
  114. package/src/text/index.ts +2 -0
  115. package/src/{stories/Text.stories.tsx → text/text.stories.tsx} +1 -1
  116. package/src/text/text.tsx +46 -0
  117. package/src/utils/author/author.tsx +36 -0
  118. package/src/utils/author/index.ts +1 -0
  119. package/src/utils/text/index.ts +1 -0
  120. package/src/utils/text/text.tsx +103 -0
  121. package/dist/components/box.d.ts +0 -20
  122. package/dist/components/box.js +0 -50
  123. package/dist/components/definition-list.d.ts +0 -43
  124. package/dist/components/definition-list.js +0 -89
  125. package/dist/components/heading.d.ts +0 -27
  126. package/dist/components/highlight.d.ts +0 -19
  127. package/dist/components/item.d.ts +0 -70
  128. package/dist/components/item.js +0 -512
  129. package/dist/components/leaflet-map.d.ts +0 -34
  130. package/dist/components/leaflet-map.js +0 -201
  131. package/dist/components/list-item.d.ts +0 -19
  132. package/dist/components/list-item.js +0 -37
  133. package/dist/components/maptiler-map.d.ts +0 -27
  134. package/dist/components/maptiler-map.js +0 -129
  135. package/dist/components/text.d.ts +0 -20
  136. package/dist/components/text.js +0 -45
  137. package/dist/components/unordered-list.d.ts +0 -19
  138. package/dist/components/unordered-list.js +0 -39
  139. package/dist/hooks/use-nonce.d.ts +0 -12
  140. package/dist/hooks/use-nonce.js +0 -13
  141. package/dist/utils/author.d.ts +0 -9
  142. package/dist/utils/author.js +0 -55
  143. package/dist/utils/cn.d.ts +0 -9
  144. package/dist/utils/cn.js +0 -14
  145. package/dist/utils/is-route-active.d.ts +0 -19
  146. package/dist/utils/is-route-active.js +0 -56
  147. package/dist/utils/text.d.ts +0 -24
  148. package/dist/utils/text.js +0 -127
  149. package/src/components/box.tsx +0 -45
  150. package/src/components/definition-list.tsx +0 -90
  151. package/src/components/item.tsx +0 -340
  152. package/src/components/leaflet-map.tsx +0 -294
  153. package/src/components/link.test.tsx +0 -387
  154. package/src/components/list-item.tsx +0 -30
  155. package/src/components/maptiler-map.tsx +0 -181
  156. package/src/components/text.tsx +0 -38
  157. package/src/components/unordered-list.tsx +0 -32
  158. package/src/hooks/use-nonce.test.ts +0 -35
  159. package/src/stories/Box.stories.tsx +0 -83
  160. package/src/stories/Carousel.stories.tsx +0 -95
  161. package/src/stories/DefinitionList.stories.tsx +0 -51
  162. package/src/stories/Item.stories.tsx +0 -79
  163. package/src/stories/ListItem.stories.tsx +0 -38
  164. package/src/stories/UnorderedList.stories.tsx +0 -73
  165. package/src/styles/tailwind.css +0 -7
  166. package/src/test-setup.ts +0 -1
  167. package/src/utils/author.test.ts +0 -54
  168. package/src/utils/author.tsx +0 -73
  169. package/src/utils/cn.test.ts +0 -48
  170. package/src/utils/cn.ts +0 -14
  171. package/src/utils/is-route-active.test.ts +0 -80
  172. package/src/utils/is-route-active.ts +0 -100
  173. package/src/utils/text.test.ts +0 -152
  174. package/src/utils/text.tsx +0 -209
  175. package/src/vite-env.d.ts +0 -1
  176. /package/dist/{components/background-slideshow.d.ts → background-slideshow/index.d.ts} +0 -0
  177. /package/dist/{components/blurry-gradient.d.ts → blurry-gradient/index.d.ts} +0 -0
  178. /package/dist/{components/countdown.d.ts → countdown/index.d.ts} +0 -0
  179. /package/dist/{components/generic-error.d.ts → generic-error/index.d.ts} +0 -0
  180. /package/dist/hooks/{use-current-route-data.d.ts → use-current-route-data/index.d.ts} +0 -0
  181. /package/dist/hooks/{use-focus-search.d.ts → use-focus-search/index.d.ts} +0 -0
  182. /package/dist/hooks/{use-matches-data.d.ts → use-matches-data/index.d.ts} +0 -0
  183. /package/dist/hooks/{use-media-query.d.ts → use-media-query/index.d.ts} +0 -0
  184. /package/dist/hooks/{use-mobile.d.ts → use-mobile/index.d.ts} +0 -0
  185. /package/dist/hooks/{use-user.d.ts → use-user/index.d.ts} +0 -0
  186. /package/dist/{components/icon-button.d.ts → icon-button/index.d.ts} +0 -0
  187. /package/dist/{components/if.d.ts → if/index.d.ts} +0 -0
  188. /package/dist/{components/iframe.d.ts → iframe/index.d.ts} +0 -0
  189. /package/dist/{components/markdown-container.d.ts → markdown-container/index.d.ts} +0 -0
  190. /package/dist/{components/password-input.d.ts → password-input/index.d.ts} +0 -0
  191. /package/dist/{components/picture.d.ts → picture/index.d.ts} +0 -0
  192. /package/src/hooks/{use-current-route-data.ts → use-current-route-data/use-current-route-data.ts} +0 -0
  193. /package/src/hooks/{use-focus-search.ts → use-focus-search/use-focus-search.ts} +0 -0
  194. /package/src/hooks/{use-matches-data.ts → use-matches-data/use-matches-data.ts} +0 -0
  195. /package/src/hooks/{use-media-query.ts → use-media-query/use-media-query.ts} +0 -0
  196. /package/src/hooks/{use-mobile.ts → use-mobile/use-mobile.ts} +0 -0
  197. /package/src/hooks/{use-nonce.ts → use-nonce/use-nonce.ts} +0 -0
  198. /package/src/hooks/{use-orientation.ts → use-orientation/use-orientation.ts} +0 -0
  199. /package/src/hooks/{use-user.tsx → use-user/use-user.tsx} +0 -0
  200. /package/src/{components → icon-button}/icon-button.tsx +0 -0
  201. /package/src/{components → if}/if.tsx +0 -0
  202. /package/src/{styles/storybook.css → storybook.css} +0 -0
@@ -1,73 +0,0 @@
1
- import type { Meta, StoryObj } from '@storybook/react-vite';
2
- import { ListItem } from '../components/list-item';
3
- import { UnorderedList } from '../components/unordered-list';
4
-
5
- const meta: Meta<typeof UnorderedList> = {
6
- component: UnorderedList,
7
- parameters: {
8
- layout: 'padded',
9
- },
10
- tags: ['autodocs'],
11
- title: 'Components/UnorderedList',
12
- };
13
-
14
- export default meta;
15
- type Story = StoryObj<typeof UnorderedList>;
16
-
17
- export const Default: Story = {
18
- render: () => (
19
- <UnorderedList>
20
- <ListItem>Item 1</ListItem>
21
- <ListItem>Item 2</ListItem>
22
- <ListItem>Item 3</ListItem>
23
- </UnorderedList>
24
- ),
25
- };
26
-
27
- export const Inline: Story = {
28
- render: () => (
29
- <UnorderedList variant="inline">
30
- <ListItem>Tag 1</ListItem>
31
- <ListItem>Tag 2</ListItem>
32
- <ListItem>Tag 3</ListItem>
33
- </UnorderedList>
34
- ),
35
- };
36
-
37
- export const Unstyled: Story = {
38
- render: () => (
39
- <UnorderedList variant="unstyled">
40
- <ListItem>No bullet 1</ListItem>
41
- <ListItem>No bullet 2</ListItem>
42
- <ListItem>No bullet 3</ListItem>
43
- </UnorderedList>
44
- ),
45
- };
46
-
47
- export const AllVariants: Story = {
48
- render: () => (
49
- <div style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
50
- <div>
51
- <h4>Primary (default)</h4>
52
- <UnorderedList variant="primary">
53
- <ListItem>Item 1</ListItem>
54
- <ListItem>Item 2</ListItem>
55
- </UnorderedList>
56
- </div>
57
- <div>
58
- <h4>Inline</h4>
59
- <UnorderedList variant="inline">
60
- <ListItem>Item 1</ListItem>
61
- <ListItem>Item 2</ListItem>
62
- </UnorderedList>
63
- </div>
64
- <div>
65
- <h4>Unstyled</h4>
66
- <UnorderedList variant="unstyled">
67
- <ListItem>Item 1</ListItem>
68
- <ListItem>Item 2</ListItem>
69
- </UnorderedList>
70
- </div>
71
- </div>
72
- ),
73
- };
@@ -1,7 +0,0 @@
1
- /* Tailwind v4 source declarations for @regardio/react
2
- Consumers should import this file from their global stylesheet:
3
- @import "@regardio/react/tailwind.css";
4
- */
5
-
6
- /* Scan all source files in this package for utility classes */
7
- @source "../";
package/src/test-setup.ts DELETED
@@ -1 +0,0 @@
1
- import '@testing-library/jest-dom/vitest';
@@ -1,54 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { parseAuthorString } from './author';
3
-
4
- describe('parseAuthorString', () => {
5
- test('parses name only', () => {
6
- expect(parseAuthorString('John Doe')).toEqual({ name: 'John Doe' });
7
- });
8
-
9
- test('parses name and email', () => {
10
- expect(parseAuthorString('John Doe <john@example.com>')).toEqual({
11
- email: 'john@example.com',
12
- name: 'John Doe',
13
- });
14
- });
15
-
16
- test('parses name and URL', () => {
17
- expect(parseAuthorString('John Doe (https://example.com)')).toEqual({
18
- name: 'John Doe',
19
- url: 'https://example.com',
20
- });
21
- });
22
-
23
- test('parses name, email, and URL', () => {
24
- expect(parseAuthorString('John Doe <john@example.com> (https://example.com)')).toEqual({
25
- email: 'john@example.com',
26
- name: 'John Doe',
27
- url: 'https://example.com',
28
- });
29
- });
30
-
31
- test('handles email only (no name)', () => {
32
- expect(parseAuthorString('<john@example.com>')).toEqual({
33
- email: 'john@example.com',
34
- });
35
- });
36
-
37
- test('handles whitespace in name', () => {
38
- expect(parseAuthorString(' John Doe <john@example.com>')).toEqual({
39
- email: 'john@example.com',
40
- name: 'John Doe',
41
- });
42
- });
43
-
44
- test('returns empty object for empty string', () => {
45
- expect(parseAuthorString('')).toEqual({});
46
- });
47
-
48
- test('handles relative URL', () => {
49
- expect(parseAuthorString('John Doe (/about/john)')).toEqual({
50
- name: 'John Doe',
51
- url: '/about/john',
52
- });
53
- });
54
- });
@@ -1,73 +0,0 @@
1
- type AuthorInfo = {
2
- name?: string;
3
- email?: string;
4
- url?: string;
5
- };
6
-
7
- const regex = /^(.*?)\s*(?:<([^>]+)>)?\s*(?:\(([^)]+)\))?$/;
8
-
9
- export function parseAuthorString(input: string): AuthorInfo {
10
- const match = input.match(regex);
11
-
12
- if (match) {
13
- const [, name, email, url] = match;
14
-
15
- const result: AuthorInfo = {};
16
-
17
- if (email) {
18
- result.email = email;
19
- }
20
-
21
- const trimmedName = name?.trim();
22
-
23
- if (trimmedName) {
24
- result.name = trimmedName;
25
- }
26
-
27
- if (url) {
28
- result.url = url;
29
- }
30
-
31
- return result;
32
- }
33
-
34
- return {};
35
- }
36
-
37
- export function generateLinkFromAuthorString(input: string): React.ReactNode {
38
- const match = input.match(regex);
39
- if (match) {
40
- const [, name, email, url] = match.map((part) => {
41
- return part?.trim();
42
- });
43
-
44
- // Generate email link if email is present
45
- if (email) {
46
- return (
47
- <a
48
- className={'u-email p-name'}
49
- href={`mailto:${email}`}
50
- >
51
- {name}
52
- </a>
53
- );
54
- }
55
-
56
- // Generate URL link if URL is present (including relative URLs that start with a slash)
57
- if (url && (url.startsWith('/') || url.startsWith('http'))) {
58
- return (
59
- <a
60
- className={'u-url p-name'}
61
- href={url}
62
- >
63
- {name}
64
- </a>
65
- );
66
- }
67
-
68
- // Return plain name with microformat class if only name is present
69
- return <span className={'p-name'}>{name}</span>;
70
- }
71
-
72
- return;
73
- }
@@ -1,48 +0,0 @@
1
- import { expect, test } from 'vitest';
2
-
3
- import { cn } from './cn';
4
-
5
- test('removes conflicting classes using twMerge', () => {
6
- const classes = cn('text-red-500', 'text-blue-500');
7
-
8
- expect(classes).toBe('text-blue-500');
9
- });
10
-
11
- test('merges multiple classes into a single string', () => {
12
- const classes = cn('text-red-500', 'font-bold');
13
-
14
- expect(classes).toBe('text-red-500 font-bold');
15
- });
16
-
17
- test('removes empty classes', () => {
18
- const classes = cn('text-red-500', '', 'font-bold');
19
-
20
- expect(classes).toBe('text-red-500 font-bold');
21
- });
22
-
23
- test('removes undefined classes', () => {
24
- const classes = cn('text-red-500', undefined, 'font-bold');
25
-
26
- expect(classes).toBe('text-red-500 font-bold');
27
- });
28
-
29
- test('removes null classes', () => {
30
- const classes = cn('text-red-500', null, 'font-bold');
31
-
32
- expect(classes).toBe('text-red-500 font-bold');
33
- });
34
-
35
- test('resolves nested arrays', () => {
36
- const classes = cn('text-red-500', ['bg-blue-500', 'font-bold']);
37
-
38
- expect(classes).toBe('text-red-500 bg-blue-500 font-bold');
39
- });
40
-
41
- test('resolves nested objects', () => {
42
- const classes = cn('text-red-500', {
43
- 'bg-blue-500': true,
44
- 'font-bold': true,
45
- });
46
-
47
- expect(classes).toBe('text-red-500 bg-blue-500 font-bold');
48
- });
package/src/utils/cn.ts DELETED
@@ -1,14 +0,0 @@
1
- import { cx, defineConfig } from 'cva';
2
- import { twMerge } from 'fluid-tailwindcss/tailwind-merge';
3
-
4
- export type { VariantProps } from 'cva';
5
-
6
- export const { cva, compose } = defineConfig({
7
- hooks: {
8
- onComplete: (className: string) => {
9
- return twMerge(className);
10
- },
11
- },
12
- });
13
-
14
- export const cn = (...inputs: Parameters<typeof cx>) => twMerge(cx(inputs));
@@ -1,80 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { checkIfRouteIsActive, isRouteActive } from './is-route-active';
3
-
4
- describe('isRouteActive', () => {
5
- test('returns true when path matches currentPath exactly', () => {
6
- expect(isRouteActive('/about', '/about')).toBe(true);
7
- expect(isRouteActive('/contact', '/contact')).toBe(true);
8
- });
9
-
10
- test('returns false when paths are different', () => {
11
- expect(isRouteActive('/about', '/contact')).toBe(false);
12
- });
13
-
14
- test('with end=true (default), only matches exact path', () => {
15
- expect(isRouteActive('/about', '/about/team', true)).toBe(false);
16
- expect(isRouteActive('/about', '/about', true)).toBe(true);
17
- });
18
-
19
- test('with end=false, matches parent paths', () => {
20
- expect(isRouteActive('/about', '/about/team', false)).toBe(true);
21
- expect(isRouteActive('/about', '/about/team/members', false)).toBe(true);
22
- });
23
-
24
- test('with end as function, uses function result', () => {
25
- const endFn = (path: string) => path.includes('team');
26
- expect(isRouteActive('/about', '/about/team', endFn)).toBe(false);
27
- expect(isRouteActive('/about', '/about/contact', endFn)).toBe(true);
28
- });
29
-
30
- test('root path only matches root', () => {
31
- expect(isRouteActive('/', '/')).toBe(true);
32
- expect(isRouteActive('/', '/about', true)).toBe(false);
33
- });
34
- });
35
-
36
- describe('checkIfRouteIsActive', () => {
37
- test('returns true for exact match', () => {
38
- expect(checkIfRouteIsActive('/about', '/about')).toBe(true);
39
- expect(checkIfRouteIsActive('/users/123', '/users/123')).toBe(true);
40
- });
41
-
42
- test('returns false when root is target but current is not root', () => {
43
- expect(checkIfRouteIsActive('/', '/about')).toBe(false);
44
- expect(checkIfRouteIsActive('/', '/users/123')).toBe(false);
45
- });
46
-
47
- test('returns true for root when current is root', () => {
48
- expect(checkIfRouteIsActive('/', '/')).toBe(true);
49
- });
50
-
51
- test('returns false when target is not in current path', () => {
52
- expect(checkIfRouteIsActive('/about', '/contact')).toBe(false);
53
- expect(checkIfRouteIsActive('/users', '/posts')).toBe(false);
54
- });
55
-
56
- test('with depth=1, matches when segments match within depth', () => {
57
- // depth=1 means matchingSegments > segments.length - 0, i.e., matchingSegments > 1
58
- // /about has 1 segment, /about/team has 2 segments, 1 matching
59
- // 1 > 1 - 0 = 1 > 1 = false
60
- expect(checkIfRouteIsActive('/about', '/about/team', 1)).toBe(false);
61
- expect(checkIfRouteIsActive('/about', '/about/team/members', 1)).toBe(false);
62
- });
63
-
64
- test('with depth=2, matches one level deep', () => {
65
- // depth=2 means matchingSegments > segments.length - 1
66
- // /about has 1 segment, 1 > 1 - 1 = 1 > 0 = true
67
- expect(checkIfRouteIsActive('/about', '/about/team', 2)).toBe(true);
68
- expect(checkIfRouteIsActive('/about', '/about/team/members', 2)).toBe(true);
69
- });
70
-
71
- test('with depth=3, matches two levels deep', () => {
72
- expect(checkIfRouteIsActive('/about', '/about/team', 3)).toBe(true);
73
- expect(checkIfRouteIsActive('/about', '/about/team/members', 3)).toBe(true);
74
- expect(checkIfRouteIsActive('/about', '/about/team/members/details', 3)).toBe(true);
75
- });
76
-
77
- test('ignores query parameters in current route', () => {
78
- expect(checkIfRouteIsActive('/about', '/about?foo=bar')).toBe(true);
79
- });
80
- });
@@ -1,100 +0,0 @@
1
- const ROOT_PATH = '/';
2
-
3
- /**
4
- * @name isRouteActive
5
- * @description A function to check if a route is active. This is used to
6
- * @param end
7
- * @param path
8
- * @param currentPath
9
- */
10
- export function isRouteActive(
11
- path: string,
12
- currentPath: string,
13
- end?: boolean | ((path: string) => boolean) | undefined,
14
- ) {
15
- // if the path is the same as the current path, we return true
16
- if (path === currentPath) {
17
- return true;
18
- }
19
-
20
- // if the end prop is a function, we call it with the current path
21
- if (typeof end === 'function') {
22
- return !end(currentPath);
23
- }
24
-
25
- // otherwise - we use the evaluateIsRouteActive function
26
- const defaultEnd = end ?? true;
27
- const oneLevelDeep = 1;
28
- const threeLevelsDeep = 3;
29
-
30
- // how far down should segments be matched?
31
- const depth = defaultEnd ? oneLevelDeep : threeLevelsDeep;
32
-
33
- return checkIfRouteIsActive(path, currentPath, depth);
34
- }
35
-
36
- /**
37
- * @name checkIfRouteIsActive
38
- * @description A function to check if a route is active. This is used to
39
- * highlight the active link in the navigation.
40
- * @param targetLink - The link to check against
41
- * @param currentRoute - the current route
42
- * @param depth - how far down should segments be matched?
43
- */
44
- export function checkIfRouteIsActive(targetLink: string, currentRoute: string, depth = 1) {
45
- // we remove any eventual query param from the route's URL
46
- const currentRoutePath = currentRoute.split('?')[0] ?? '';
47
-
48
- if (!isRoot(currentRoutePath) && isRoot(targetLink)) {
49
- return false;
50
- }
51
-
52
- if (!currentRoutePath.includes(targetLink)) {
53
- return false;
54
- }
55
-
56
- const isSameRoute = targetLink === currentRoutePath;
57
-
58
- if (isSameRoute) {
59
- return true;
60
- }
61
-
62
- return hasMatchingSegments(targetLink, currentRoutePath, depth);
63
- }
64
-
65
- function splitIntoSegments(href: string) {
66
- return href.split('/').filter(Boolean);
67
- }
68
-
69
- function hasMatchingSegments(targetLink: string, currentRoute: string, depth: number) {
70
- const segments = splitIntoSegments(targetLink);
71
- const matchingSegments = numberOfMatchingSegments(currentRoute, segments);
72
-
73
- if (targetLink === currentRoute) {
74
- return true;
75
- }
76
-
77
- // how far down should segments be matched?
78
- // - if depth = 1 => only highlight the links of the immediate parent
79
- // - if depth = 2 => for url = /account match /account/organization/members
80
- return matchingSegments > segments.length - (depth - 1);
81
- }
82
-
83
- function numberOfMatchingSegments(href: string, segments: string[]) {
84
- let count = 0;
85
-
86
- for (const segment of splitIntoSegments(href)) {
87
- // for as long as the segments match, keep counting + 1
88
- if (segments.includes(segment)) {
89
- count += 1;
90
- } else {
91
- return count;
92
- }
93
- }
94
-
95
- return count;
96
- }
97
-
98
- function isRoot(path: string) {
99
- return path === ROOT_PATH;
100
- }
@@ -1,152 +0,0 @@
1
- import { describe, expect, test } from 'vitest';
2
- import { replaceSpecialChars, shy, toBoolean, typographicQuotes } from './text';
3
-
4
- describe('toBoolean', () => {
5
- test('returns true for boolean true', () => {
6
- expect(toBoolean(true)).toBe(true);
7
- });
8
-
9
- test('returns false for boolean false', () => {
10
- expect(toBoolean(false)).toBe(false);
11
- });
12
-
13
- test('returns true for string "true"', () => {
14
- expect(toBoolean('true')).toBe(true);
15
- });
16
-
17
- test('returns true for string "1"', () => {
18
- expect(toBoolean('1')).toBe(true);
19
- });
20
-
21
- test('returns false for string "false"', () => {
22
- expect(toBoolean('false')).toBe(false);
23
- });
24
-
25
- test('returns false for string "0"', () => {
26
- expect(toBoolean('0')).toBe(false);
27
- });
28
-
29
- test('returns false for null', () => {
30
- expect(toBoolean(null)).toBe(false);
31
- });
32
-
33
- test('returns false for undefined', () => {
34
- expect(toBoolean(undefined)).toBe(false);
35
- });
36
-
37
- test('returns false for other strings', () => {
38
- expect(toBoolean('yes')).toBe(false);
39
- expect(toBoolean('no')).toBe(false);
40
- expect(toBoolean('')).toBe(false);
41
- });
42
- });
43
-
44
- describe('shy', () => {
45
- test('returns null for null input', () => {
46
- expect(shy(null)).toBe(null);
47
- });
48
-
49
- test('replaces &shy; with soft hyphen in strings', () => {
50
- const result = shy('hello&shy;world');
51
- expect(result).toBe('hello\u00ADworld');
52
- });
53
-
54
- test('handles strings without &shy;', () => {
55
- expect(shy('hello world')).toBe('hello world');
56
- });
57
-
58
- test('handles multiple &shy; occurrences', () => {
59
- const result = shy('one&shy;two&shy;three');
60
- expect(result).toBe('one\u00ADtwo\u00ADthree');
61
- });
62
- });
63
-
64
- describe('replaceSpecialChars', () => {
65
- test('replaces straight quotes for de locale', () => {
66
- const result = replaceSpecialChars('"Hello"', 'de');
67
- const str = String(result);
68
- // Should not contain straight quotes anymore
69
- expect(str).not.toContain('"Hello"');
70
- expect(str).toContain('Hello');
71
- });
72
-
73
- test('replaces straight quotes for de-DE locale', () => {
74
- const result = replaceSpecialChars('"Hello"', 'de-DE');
75
- const str = String(result);
76
- expect(str).not.toContain('"Hello"');
77
- expect(str).toContain('Hello');
78
- });
79
-
80
- test('replaces straight quotes for en locale', () => {
81
- const result = replaceSpecialChars('"Hello"', 'en');
82
- const str = String(result);
83
- // Should not contain straight quotes anymore
84
- expect(str).not.toContain('"Hello"');
85
- expect(str).toContain('Hello');
86
- });
87
-
88
- test('handles multiple quoted strings', () => {
89
- const result = replaceSpecialChars('"one" and "two"', 'de');
90
- const str = String(result);
91
- expect(str).toContain('one');
92
- expect(str).toContain('two');
93
- expect(str).toContain('and');
94
- });
95
-
96
- test('also applies shy replacement', () => {
97
- const result = replaceSpecialChars('"Hello&shy;World"', 'en');
98
- expect(String(result)).toContain('\u00AD');
99
- });
100
- });
101
-
102
- describe('typographicQuotes', () => {
103
- test('replaces double quotes with English curly quotes', () => {
104
- const result = typographicQuotes('"Hello"', 'en');
105
- expect(result).toBe('\u201CHello\u201D');
106
- });
107
-
108
- test('replaces double quotes with German quotes', () => {
109
- const result = typographicQuotes('"Hello"', 'de');
110
- expect(result).toBe('\u201EHello\u201D');
111
- });
112
-
113
- test('replaces double quotes with French guillemets', () => {
114
- const result = typographicQuotes('"Hello"', 'fr');
115
- expect(result).toBe('\u00AB Hello \u00BB');
116
- });
117
-
118
- test('handles de-DE locale (falls back to de)', () => {
119
- const result = typographicQuotes('"Hello"', 'de-DE');
120
- expect(result).toBe('\u201EHello\u201D');
121
- });
122
-
123
- test('handles de-CH locale (Swiss German with guillemets)', () => {
124
- const result = typographicQuotes('"Hello"', 'de-CH');
125
- expect(result).toBe('\u00ABHello\u00BB');
126
- });
127
-
128
- test('handles unknown locale (falls back to English)', () => {
129
- const result = typographicQuotes('"Hello"', 'xx-YY');
130
- expect(result).toBe('\u201CHello\u201D');
131
- });
132
-
133
- test('replaces single quotes', () => {
134
- const result = typographicQuotes("'Hello'", 'en');
135
- expect(result).toBe('\u2018Hello\u2019');
136
- });
137
-
138
- test('handles multiple quoted strings', () => {
139
- const result = typographicQuotes('"one" and "two"', 'de');
140
- expect(result).toBe('\u201Eone\u201D and \u201Etwo\u201D');
141
- });
142
-
143
- test('handles Japanese quotes', () => {
144
- const result = typographicQuotes('"Hello"', 'ja');
145
- expect(result).toBe('\u300CHello\u300D');
146
- });
147
-
148
- test('preserves text without quotes', () => {
149
- const result = typographicQuotes('Hello World', 'en');
150
- expect(result).toBe('Hello World');
151
- });
152
- });