@regardio/react 0.5.1 → 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.
Files changed (40) hide show
  1. package/README.md +1 -3
  2. package/dist/hooks/{use-current-route-data/index.js → use-current-route-data.js} +1 -1
  3. package/dist/hooks/{use-focus-search/index.js → use-focus-search.js} +1 -1
  4. package/dist/hooks/{use-matches-data/index.js → use-matches-data.js} +1 -1
  5. package/dist/hooks/{use-media-query/index.js → use-media-query.js} +1 -1
  6. package/dist/hooks/{use-mobile/index.js → use-mobile.js} +1 -1
  7. package/dist/hooks/{use-nonce/index.d.ts → use-nonce.d.ts} +2 -1
  8. package/dist/hooks/{use-nonce/index.js → use-nonce.js} +2 -2
  9. package/dist/hooks/{use-orientation/index.d.ts → use-orientation.d.ts} +1 -1
  10. package/dist/hooks/{use-orientation/index.js → use-orientation.js} +1 -1
  11. package/dist/hooks/{use-user/index.js → use-user.js} +1 -1
  12. package/package.json +143 -131
  13. package/src/background-slideshow/background-slideshow.stories.tsx +69 -0
  14. package/src/carousel/carousel.stories.tsx +46 -0
  15. package/src/hooks/{use-nonce/use-nonce.ts → use-nonce.ts} +0 -10
  16. package/src/link/link.stories.tsx +51 -0
  17. package/src/link/link.test.tsx +169 -0
  18. package/src/markdown-container/markdown-container.stories.tsx +55 -0
  19. package/src/utils/text/text.test.tsx +110 -0
  20. package/src/hooks/use-current-route-data/index.ts +0 -1
  21. package/src/hooks/use-focus-search/index.ts +0 -1
  22. package/src/hooks/use-matches-data/index.ts +0 -1
  23. package/src/hooks/use-media-query/index.ts +0 -1
  24. package/src/hooks/use-mobile/index.ts +0 -1
  25. package/src/hooks/use-nonce/index.ts +0 -1
  26. package/src/hooks/use-orientation/index.ts +0 -1
  27. package/src/hooks/use-user/index.ts +0 -2
  28. /package/dist/hooks/{use-current-route-data/index.d.ts → use-current-route-data.d.ts} +0 -0
  29. /package/dist/hooks/{use-focus-search/index.d.ts → use-focus-search.d.ts} +0 -0
  30. /package/dist/hooks/{use-matches-data/index.d.ts → use-matches-data.d.ts} +0 -0
  31. /package/dist/hooks/{use-media-query/index.d.ts → use-media-query.d.ts} +0 -0
  32. /package/dist/hooks/{use-mobile/index.d.ts → use-mobile.d.ts} +0 -0
  33. /package/dist/hooks/{use-user/index.d.ts → use-user.d.ts} +0 -0
  34. /package/src/hooks/{use-current-route-data/use-current-route-data.ts → use-current-route-data.ts} +0 -0
  35. /package/src/hooks/{use-focus-search/use-focus-search.ts → use-focus-search.ts} +0 -0
  36. /package/src/hooks/{use-matches-data/use-matches-data.ts → use-matches-data.ts} +0 -0
  37. /package/src/hooks/{use-media-query/use-media-query.ts → use-media-query.ts} +0 -0
  38. /package/src/hooks/{use-mobile/use-mobile.ts → use-mobile.ts} +0 -0
  39. /package/src/hooks/{use-orientation/use-orientation.ts → use-orientation.ts} +0 -0
  40. /package/src/hooks/{use-user/use-user.tsx → use-user.tsx} +0 -0
package/README.md CHANGED
@@ -65,7 +65,6 @@ import '@regardio/react/tailwind.css';
65
65
  | `Box` | Flexible container with variant-based styling |
66
66
  | `Carousel` | Embla-powered carousel with navigation controls |
67
67
  | `Countdown` | Dynamic countdown timer display |
68
- | `DefinitionList` | Semantic `<dl>` with `Dt` and `Dd` sub-components |
69
68
  | `GenericError` | React Router error boundary with i18n support |
70
69
  | `Heading` | Semantic headings (h1-h6) with consistent styling |
71
70
  | `Highlight` | Text highlighting with customizable styles |
@@ -75,14 +74,13 @@ import '@regardio/react/tailwind.css';
75
74
  | `Item` | Grid item with theme colors and link support |
76
75
  | `LeafletMap` | Leaflet map integration |
77
76
  | `Link` | React Router link with external URL detection |
78
- | `ListItem` | Styled list item component |
77
+ | `List` | Compound list component with Root and Item |
79
78
  | `MaptilerMap` | MapTiler SDK integration |
80
79
  | `MarkdownContainer` | MDX/Markdown renderer with typography processing |
81
80
  | `PasswordInput` | Password field with visibility toggle |
82
81
  | `Picture` | Responsive images with srcset generation |
83
82
  | `ProtectedEmail` | Email obfuscation for spam protection |
84
83
  | `Text` | Typography component with variants |
85
- | `UnorderedList` | Styled unordered list with variants |
86
84
 
87
85
  ### Hooks
88
86
 
@@ -1,7 +1,7 @@
1
1
  import { useMemo } from 'react';
2
2
  import { useLocation, useMatches } from 'react-router';
3
3
 
4
- // src/hooks/use-current-route-data/use-current-route-data.ts
4
+ // src/hooks/use-current-route-data.ts
5
5
  function useCurrentRouteData() {
6
6
  const location = useLocation();
7
7
  const matchingRoutes = useMatches();
@@ -1,6 +1,6 @@
1
1
  import { useEffect } from 'react';
2
2
 
3
- // src/hooks/use-focus-search/use-focus-search.ts
3
+ // src/hooks/use-focus-search.ts
4
4
  function useFocusSearch(ref) {
5
5
  useEffect(() => {
6
6
  function handleKeyDown(event) {
@@ -1,7 +1,7 @@
1
1
  import { useMemo } from 'react';
2
2
  import { useMatches } from 'react-router';
3
3
 
4
- // src/hooks/use-matches-data/use-matches-data.ts
4
+ // src/hooks/use-matches-data.ts
5
5
  function useMatchesData(id) {
6
6
  const matchingRoutes = useMatches();
7
7
  const route = useMemo(() => {
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect } from 'react';
2
2
 
3
- // src/hooks/use-media-query/use-media-query.ts
3
+ // src/hooks/use-media-query.ts
4
4
  function useMediaQuery(query) {
5
5
  const [matches, setMatches] = useState(false);
6
6
  useEffect(() => {
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect } from 'react';
2
2
 
3
- // src/hooks/use-mobile/use-mobile.ts
3
+ // src/hooks/use-mobile.ts
4
4
  var MOBILE_BREAKPOINT = 768;
5
5
  function useIsMobile() {
6
6
  const [isMobile, setIsMobile] = useState(void 0);
@@ -1,6 +1,7 @@
1
1
  import * as react from 'react';
2
2
 
3
+ declare const NonceContext: react.Context<string>;
3
4
  declare const NonceProvider: react.Provider<string>;
4
5
  declare const useNonce: () => string;
5
6
 
6
- export { NonceProvider, useNonce };
7
+ export { NonceContext, NonceProvider, useNonce };
@@ -1,8 +1,8 @@
1
1
  import { createContext, useContext } from 'react';
2
2
 
3
- // src/hooks/use-nonce/use-nonce.ts
3
+ // src/hooks/use-nonce.ts
4
4
  var NonceContext = createContext("");
5
5
  var NonceProvider = NonceContext.Provider;
6
6
  var useNonce = () => useContext(NonceContext);
7
7
 
8
- export { NonceProvider, useNonce };
8
+ export { NonceContext, NonceProvider, useNonce };
@@ -8,4 +8,4 @@ type Orientation = 'portrait' | 'landscape';
8
8
  */
9
9
  declare function useOrientation(): Orientation;
10
10
 
11
- export { useOrientation };
11
+ export { type Orientation, useOrientation };
@@ -1,6 +1,6 @@
1
1
  import { useState, useEffect } from 'react';
2
2
 
3
- // src/hooks/use-orientation/use-orientation.ts
3
+ // src/hooks/use-orientation.ts
4
4
  function useOrientation() {
5
5
  const [orientation, setOrientation] = useState(() => {
6
6
  if (typeof window !== "undefined") {
@@ -1,7 +1,7 @@
1
1
  import { createContext, useContext } from 'react';
2
2
  import { jsx } from 'react/jsx-runtime';
3
3
 
4
- // src/hooks/use-user/use-user.tsx
4
+ // src/hooks/use-user.tsx
5
5
  var defaultContextValue = {
6
6
  isLoading: false,
7
7
  user: null
package/package.json CHANGED
@@ -1,194 +1,149 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
- "author": "Bernd Matzner <bernd.matzner@regard.io>",
3
+ "name": "@regardio/react",
4
+ "version": "0.5.7",
5
+ "private": false,
6
+ "description": "Regardio React UI components",
7
+ "keywords": [
8
+ "react",
9
+ "components",
10
+ "ui",
11
+ "regardio",
12
+ "tailwindcss"
13
+ ],
14
+ "homepage": "https://github.com/regardio/react/blob/main/README.md",
4
15
  "bugs": {
5
16
  "url": "https://github.com/regardio/react/issues"
6
17
  },
7
- "dependencies": {
8
- "@base-ui/react": "1.0.0",
9
- "@maptiler/leaflet-maptilersdk": "4.1.1",
10
- "@maptiler/sdk": "3.9.0",
11
- "@mdx-js/react": "3.1.1",
12
- "@regardio/js": "0.5.0",
13
- "@regardio/tailwind": "0.1.2",
14
- "@supabase/supabase-js": "2.90.0",
15
- "cmdk": "1.1.1",
16
- "embla-carousel": "8.6.0",
17
- "embla-carousel-react": "8.6.0",
18
- "input-otp": "1.4.2",
19
- "intl-parse-accept-language": "1.0.0",
20
- "leaflet": "alpha",
21
- "lucide-react": "0.562.0",
22
- "markdown-to-jsx": "9.5.0",
23
- "react": "19.2.3",
24
- "react-day-picker": "9.13.0",
25
- "react-dom": "19.2.3",
26
- "react-hook-form": "7.70.0",
27
- "react-resizable-panels": "4.3.0",
28
- "react-router": "7.11.0",
29
- "tailwind-variants": "3.2.2",
30
- "vaul": "1.1.2",
31
- "zod": "4.3.5"
32
- },
33
- "description": "Regardio React UI components",
34
- "devDependencies": {
35
- "@regardio/dev": "1.10.3",
36
- "@storybook/addon-a11y": "10.1.11",
37
- "@storybook/addon-docs": "10.1.11",
38
- "@storybook/addon-vitest": "10.1.11",
39
- "@storybook/react-vite": "10.1.11",
40
- "@tailwindcss/vite": "4.1.18",
41
- "@testing-library/jest-dom": "6.9.1",
42
- "@testing-library/react": "16.3.1",
43
- "@total-typescript/ts-reset": "0.6.1",
44
- "@types/leaflet": "1.9.21",
45
- "@types/node": "25.0.3",
46
- "@types/react": "19.2.7",
47
- "@types/react-dom": "19.2.3",
48
- "@vitejs/plugin-react": "5.1.2",
49
- "@vitest/browser-playwright": "4.0.16",
50
- "@vitest/coverage-v8": "4.0.16",
51
- "@vitest/ui": "4.0.16",
52
- "jsdom": "27.4.0",
53
- "playwright": "1.57.0",
54
- "storybook": "10.1.11",
55
- "tailwindcss": "4.1.18",
56
- "tsup": "8.5.1",
57
- "typescript": "5.9.3",
58
- "vite": "7.3.1",
59
- "vitest": "4.0.16"
60
- },
61
- "engines": {
62
- "node": ">=18"
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/regardio/react.git"
63
21
  },
22
+ "license": "MIT",
23
+ "author": "Bernd Matzner <bernd.matzner@regard.io>",
24
+ "sideEffects": [
25
+ "./src/tailwind.css"
26
+ ],
27
+ "type": "module",
64
28
  "exports": {
65
29
  "./background-slideshow": {
66
- "import": "./dist/background-slideshow/index.js",
67
- "types": "./dist/background-slideshow/index.d.ts"
30
+ "types": "./dist/background-slideshow/index.d.ts",
31
+ "import": "./dist/background-slideshow/index.js"
68
32
  },
69
33
  "./blurry-gradient": {
70
- "import": "./dist/blurry-gradient/index.js",
71
- "types": "./dist/blurry-gradient/index.d.ts"
34
+ "types": "./dist/blurry-gradient/index.d.ts",
35
+ "import": "./dist/blurry-gradient/index.js"
72
36
  },
73
37
  "./carousel": {
74
- "import": "./dist/carousel/index.js",
75
- "types": "./dist/carousel/index.d.ts"
38
+ "types": "./dist/carousel/index.d.ts",
39
+ "import": "./dist/carousel/index.js"
76
40
  },
77
41
  "./countdown": {
78
- "import": "./dist/countdown/index.js",
79
- "types": "./dist/countdown/index.d.ts"
42
+ "types": "./dist/countdown/index.d.ts",
43
+ "import": "./dist/countdown/index.js"
80
44
  },
81
45
  "./generic-error": {
82
- "import": "./dist/generic-error/index.js",
83
- "types": "./dist/generic-error/index.d.ts"
46
+ "types": "./dist/generic-error/index.d.ts",
47
+ "import": "./dist/generic-error/index.js"
84
48
  },
85
49
  "./grid": {
86
- "import": "./dist/grid/index.js",
87
- "types": "./dist/grid/index.d.ts"
50
+ "types": "./dist/grid/index.d.ts",
51
+ "import": "./dist/grid/index.js"
88
52
  },
89
53
  "./heading": {
90
- "import": "./dist/heading/index.js",
91
- "types": "./dist/heading/index.d.ts"
54
+ "types": "./dist/heading/index.d.ts",
55
+ "import": "./dist/heading/index.js"
92
56
  },
93
57
  "./highlight": {
94
- "import": "./dist/highlight/index.js",
95
- "types": "./dist/highlight/index.d.ts"
58
+ "types": "./dist/highlight/index.d.ts",
59
+ "import": "./dist/highlight/index.js"
96
60
  },
97
61
  "./hooks/use-current-route-data": {
98
- "import": "./dist/hooks/use-current-route-data/index.js",
99
- "types": "./dist/hooks/use-current-route-data/index.d.ts"
62
+ "types": "./dist/hooks/use-current-route-data.d.ts",
63
+ "import": "./dist/hooks/use-current-route-data.js"
100
64
  },
101
65
  "./hooks/use-focus-search": {
102
- "import": "./dist/hooks/use-focus-search/index.js",
103
- "types": "./dist/hooks/use-focus-search/index.d.ts"
66
+ "types": "./dist/hooks/use-focus-search.d.ts",
67
+ "import": "./dist/hooks/use-focus-search.js"
104
68
  },
105
69
  "./hooks/use-matches-data": {
106
- "import": "./dist/hooks/use-matches-data/index.js",
107
- "types": "./dist/hooks/use-matches-data/index.d.ts"
70
+ "types": "./dist/hooks/use-matches-data.d.ts",
71
+ "import": "./dist/hooks/use-matches-data.js"
108
72
  },
109
73
  "./hooks/use-media-query": {
110
- "import": "./dist/hooks/use-media-query/index.js",
111
- "types": "./dist/hooks/use-media-query/index.d.ts"
74
+ "types": "./dist/hooks/use-media-query.d.ts",
75
+ "import": "./dist/hooks/use-media-query.js"
112
76
  },
113
77
  "./hooks/use-mobile": {
114
- "import": "./dist/hooks/use-mobile/index.js",
115
- "types": "./dist/hooks/use-mobile/index.d.ts"
78
+ "types": "./dist/hooks/use-mobile.d.ts",
79
+ "import": "./dist/hooks/use-mobile.js"
116
80
  },
117
81
  "./hooks/use-nonce": {
118
- "import": "./dist/hooks/use-nonce/index.js",
119
- "types": "./dist/hooks/use-nonce/index.d.ts"
82
+ "types": "./dist/hooks/use-nonce.d.ts",
83
+ "import": "./dist/hooks/use-nonce.js"
120
84
  },
121
85
  "./hooks/use-orientation": {
122
- "import": "./dist/hooks/use-orientation/index.js",
123
- "types": "./dist/hooks/use-orientation/index.d.ts"
86
+ "types": "./dist/hooks/use-orientation.d.ts",
87
+ "import": "./dist/hooks/use-orientation.js"
124
88
  },
125
89
  "./hooks/use-user": {
126
- "import": "./dist/hooks/use-user/index.js",
127
- "types": "./dist/hooks/use-user/index.d.ts"
90
+ "types": "./dist/hooks/use-user.d.ts",
91
+ "import": "./dist/hooks/use-user.js"
128
92
  },
129
93
  "./icon-button": {
130
- "import": "./dist/icon-button/index.js",
131
- "types": "./dist/icon-button/index.d.ts"
94
+ "types": "./dist/icon-button/index.d.ts",
95
+ "import": "./dist/icon-button/index.js"
132
96
  },
133
97
  "./if": {
134
- "import": "./dist/if/index.js",
135
- "types": "./dist/if/index.d.ts"
98
+ "types": "./dist/if/index.d.ts",
99
+ "import": "./dist/if/index.js"
136
100
  },
137
101
  "./iframe": {
138
- "import": "./dist/iframe/index.js",
139
- "types": "./dist/iframe/index.d.ts"
102
+ "types": "./dist/iframe/index.d.ts",
103
+ "import": "./dist/iframe/index.js"
140
104
  },
141
105
  "./link": {
142
- "import": "./dist/link/index.js",
143
- "types": "./dist/link/index.d.ts"
106
+ "types": "./dist/link/index.d.ts",
107
+ "import": "./dist/link/index.js"
144
108
  },
145
109
  "./list": {
146
- "import": "./dist/list/index.js",
147
- "types": "./dist/list/index.d.ts"
110
+ "types": "./dist/list/index.d.ts",
111
+ "import": "./dist/list/index.js"
148
112
  },
149
113
  "./markdown-container": {
150
- "import": "./dist/markdown-container/index.js",
151
- "types": "./dist/markdown-container/index.d.ts"
114
+ "types": "./dist/markdown-container/index.d.ts",
115
+ "import": "./dist/markdown-container/index.js"
152
116
  },
153
117
  "./password-input": {
154
- "import": "./dist/password-input/index.js",
155
- "types": "./dist/password-input/index.d.ts"
118
+ "types": "./dist/password-input/index.d.ts",
119
+ "import": "./dist/password-input/index.js"
156
120
  },
157
121
  "./picture": {
158
- "import": "./dist/picture/index.js",
159
- "types": "./dist/picture/index.d.ts"
122
+ "types": "./dist/picture/index.d.ts",
123
+ "import": "./dist/picture/index.js"
160
124
  },
161
125
  "./protected-email": {
162
- "import": "./dist/protected-email/index.js",
163
- "types": "./dist/protected-email/index.d.ts"
126
+ "types": "./dist/protected-email/index.d.ts",
127
+ "import": "./dist/protected-email/index.js"
164
128
  },
165
129
  "./tailwind.css": "./src/tailwind.css",
166
130
  "./text": {
167
- "import": "./dist/text/index.js",
168
- "types": "./dist/text/index.d.ts"
131
+ "types": "./dist/text/index.d.ts",
132
+ "import": "./dist/text/index.js"
169
133
  },
170
134
  "./utils/author": {
171
- "import": "./dist/utils/author/index.js",
172
- "types": "./dist/utils/author/index.d.ts"
135
+ "types": "./dist/utils/author/index.d.ts",
136
+ "import": "./dist/utils/author/index.js"
173
137
  },
174
138
  "./utils/text": {
175
- "import": "./dist/utils/text/index.js",
176
- "types": "./dist/utils/text/index.d.ts"
139
+ "types": "./dist/utils/text/index.d.ts",
140
+ "import": "./dist/utils/text/index.js"
177
141
  }
178
142
  },
179
- "files": ["dist", "src"],
180
- "homepage": "https://github.com/regardio/react/blob/main/README.md",
181
- "keywords": ["react", "components", "ui", "regardio", "tailwindcss"],
182
- "license": "MIT",
183
- "name": "@regardio/react",
184
- "private": false,
185
- "publishConfig": {
186
- "access": "public"
187
- },
188
- "repository": {
189
- "type": "git",
190
- "url": "git+https://github.com/regardio/react.git"
191
- },
143
+ "files": [
144
+ "dist",
145
+ "src"
146
+ ],
192
147
  "scripts": {
193
148
  "build": "tsup && post-build-exports --preserve \"./tailwind.css\" && pnpm fix",
194
149
  "clean": "exec-clean .turbo dist",
@@ -196,13 +151,14 @@
196
151
  "fix": "exec-p fix:*",
197
152
  "fix:biome": "lint-biome check --write --unsafe .",
198
153
  "fix:md": "lint-md --fix",
154
+ "fix:pkg": "lint-package --fix",
199
155
  "lint": "exec-p lint:*",
200
156
  "lint:biome": "lint-biome check .",
201
157
  "lint:md": "lint-md",
158
+ "lint:pkg": "lint-package",
202
159
  "prepare": "exec-husky",
203
160
  "release": "flow-release",
204
- "report": "exec-s report:*",
205
- "report:unit": "vitest run --reporter html",
161
+ "report": "vitest run --coverage",
206
162
  "storybook": "storybook dev -p 6006",
207
163
  "storybook:build": "storybook build -o storybook-static",
208
164
  "test": "exec-s test:*",
@@ -210,7 +166,63 @@
210
166
  "typecheck": "exec-tsc --noEmit",
211
167
  "version": "flow-changeset version"
212
168
  },
213
- "sideEffects": ["./src/tailwind.css"],
214
- "type": "module",
215
- "version": "0.5.1"
169
+ "dependencies": {
170
+ "@base-ui/react": "1.0.0",
171
+ "@maptiler/leaflet-maptilersdk": "4.1.1",
172
+ "@maptiler/sdk": "3.9.0",
173
+ "@mdx-js/react": "3.1.1",
174
+ "@regardio/js": "0.6.0",
175
+ "@regardio/tailwind": "0.2.0",
176
+ "@supabase/supabase-js": "2.90.1",
177
+ "cmdk": "1.1.1",
178
+ "embla-carousel": "8.6.0",
179
+ "embla-carousel-react": "8.6.0",
180
+ "input-otp": "1.4.2",
181
+ "intl-parse-accept-language": "1.0.0",
182
+ "leaflet": "alpha",
183
+ "lucide-react": "0.562.0",
184
+ "markdown-to-jsx": "9.5.7",
185
+ "react": "19.2.3",
186
+ "react-day-picker": "9.13.0",
187
+ "react-dom": "19.2.3",
188
+ "react-hook-form": "7.71.0",
189
+ "react-resizable-panels": "4.4.0",
190
+ "react-router": "7.12.0",
191
+ "tailwind-variants": "3.2.2",
192
+ "vaul": "1.1.2",
193
+ "zod": "4.3.5"
194
+ },
195
+ "devDependencies": {
196
+ "@regardio/dev": "1.11.4",
197
+ "@storybook/addon-a11y": "10.1.11",
198
+ "@storybook/addon-docs": "10.1.11",
199
+ "@storybook/addon-vitest": "10.1.11",
200
+ "@storybook/react-vite": "10.1.11",
201
+ "@tailwindcss/vite": "4.1.18",
202
+ "@testing-library/jest-dom": "6.9.1",
203
+ "@testing-library/react": "16.3.1",
204
+ "@total-typescript/ts-reset": "0.6.1",
205
+ "@types/leaflet": "1.9.21",
206
+ "@types/node": "25.0.8",
207
+ "@types/react": "19.2.8",
208
+ "@types/react-dom": "19.2.3",
209
+ "@vitejs/plugin-react": "5.1.2",
210
+ "@vitest/browser-playwright": "4.0.17",
211
+ "@vitest/coverage-v8": "4.0.17",
212
+ "@vitest/ui": "4.0.17",
213
+ "jsdom": "27.4.0",
214
+ "playwright": "1.57.0",
215
+ "storybook": "10.1.11",
216
+ "tailwindcss": "4.1.18",
217
+ "tsup": "8.5.1",
218
+ "typescript": "5.9.3",
219
+ "vite": "7.3.1",
220
+ "vitest": "4.0.17"
221
+ },
222
+ "engines": {
223
+ "node": ">=18"
224
+ },
225
+ "publishConfig": {
226
+ "access": "public"
227
+ }
216
228
  }
@@ -66,3 +66,72 @@ export const FastTransition: Story = {
66
66
  transitionDuration: 1000,
67
67
  },
68
68
  };
69
+
70
+ export const SingleImage: Story = {
71
+ args: {
72
+ baseUrl: 'https://via.placeholder.com/{format}?text=Single',
73
+ className: 'h-[400px] w-full',
74
+ images: [sampleImages[0] as ImageData],
75
+ locale: 'en',
76
+ slideshow: true,
77
+ },
78
+ };
79
+
80
+ export const EmptyImages: Story = {
81
+ args: {
82
+ baseUrl: 'https://via.placeholder.com/{format}',
83
+ className: 'h-[400px] w-full bg-gray-200',
84
+ images: [],
85
+ locale: 'en',
86
+ },
87
+ };
88
+
89
+ export const WithFilter: Story = {
90
+ args: {
91
+ baseUrl: 'https://via.placeholder.com/{format}?text=Filtered',
92
+ className: 'h-[400px] w-full',
93
+ filter: (img: ImageData) => img.hu > 100,
94
+ images: sampleImages,
95
+ locale: 'en',
96
+ },
97
+ };
98
+
99
+ export const GermanLocale: Story = {
100
+ args: {
101
+ baseUrl: 'https://via.placeholder.com/{format}?text=German',
102
+ className: 'h-[400px] w-full',
103
+ images: sampleImages,
104
+ locale: 'de',
105
+ },
106
+ };
107
+
108
+ export const ShortTransition: Story = {
109
+ args: {
110
+ baseUrl: 'https://via.placeholder.com/{format}?text=Short',
111
+ className: 'h-[400px] w-full',
112
+ images: sampleImages,
113
+ locale: 'en',
114
+ slideshowInterval: 1000,
115
+ transitionDuration: 2000,
116
+ },
117
+ };
118
+
119
+ export const MediumTransition: Story = {
120
+ args: {
121
+ baseUrl: 'https://via.placeholder.com/{format}?text=Medium',
122
+ className: 'h-[400px] w-full',
123
+ images: sampleImages,
124
+ locale: 'en',
125
+ transitionDuration: 4000,
126
+ },
127
+ };
128
+
129
+ export const LongTransition: Story = {
130
+ args: {
131
+ baseUrl: 'https://via.placeholder.com/{format}?text=Long',
132
+ className: 'h-[400px] w-full',
133
+ images: sampleImages,
134
+ locale: 'en',
135
+ transitionDuration: 5000,
136
+ },
137
+ };
@@ -87,3 +87,49 @@ export const ManySlides: Story = {
87
87
  </Carousel.Root>
88
88
  ),
89
89
  };
90
+
91
+ export const VerticalOrientation: Story = {
92
+ render: () => (
93
+ <Carousel.Root
94
+ className="w-full max-w-md h-[400px]"
95
+ orientation="vertical"
96
+ >
97
+ <Carousel.Content className="flex-col gap-4">
98
+ <Carousel.Item>
99
+ <SlideCard>Vertical Slide 1</SlideCard>
100
+ </Carousel.Item>
101
+ <Carousel.Item>
102
+ <SlideCard>Vertical Slide 2</SlideCard>
103
+ </Carousel.Item>
104
+ <Carousel.Item>
105
+ <SlideCard>Vertical Slide 3</SlideCard>
106
+ </Carousel.Item>
107
+ </Carousel.Content>
108
+ </Carousel.Root>
109
+ ),
110
+ };
111
+
112
+ export const WithOptions: Story = {
113
+ render: () => (
114
+ <Carousel.Root
115
+ className="w-full max-w-md"
116
+ opts={{ align: 'start', loop: true }}
117
+ >
118
+ <Carousel.Content className="gap-4">
119
+ <Carousel.Item>
120
+ <SlideCard>Loop Slide 1</SlideCard>
121
+ </Carousel.Item>
122
+ <Carousel.Item>
123
+ <SlideCard>Loop Slide 2</SlideCard>
124
+ </Carousel.Item>
125
+ <Carousel.Item>
126
+ <SlideCard>Loop Slide 3</SlideCard>
127
+ </Carousel.Item>
128
+ </Carousel.Content>
129
+ <div className="flex justify-center gap-4 mt-4">
130
+ <Carousel.Previous className="px-4 py-2 bg-gray-200 rounded">←</Carousel.Previous>
131
+ <Carousel.Next className="px-4 py-2 bg-gray-200 rounded">→</Carousel.Next>
132
+ </div>
133
+ </Carousel.Root>
134
+ ),
135
+ };
@@ -8,13 +8,3 @@ export const NonceContext = createContext<string>('');
8
8
  export const NonceProvider = NonceContext.Provider;
9
9
 
10
10
  export const useNonce = () => useContext(NonceContext);
11
-
12
- /**
13
- * Generate a cryptographically secure nonce for CSP.
14
- * @returns A base64-encoded random nonce
15
- */
16
- export function generateNonce(): string {
17
- const array = new Uint8Array(16);
18
- crypto.getRandomValues(array);
19
- return btoa(String.fromCharCode(...array));
20
- }
@@ -56,3 +56,54 @@ export const AllVariants: Story = {
56
56
  </div>
57
57
  ),
58
58
  };
59
+
60
+ export const TelephoneLink: Story = {
61
+ args: {
62
+ children: 'Call Us',
63
+ to: 'tel:+1234567890',
64
+ },
65
+ };
66
+
67
+ export const MailtoLink: Story = {
68
+ args: {
69
+ children: 'Email Us',
70
+ to: 'mailto:hello@example.com',
71
+ },
72
+ };
73
+
74
+ export const HashLink: Story = {
75
+ args: {
76
+ children: 'Jump to Section',
77
+ to: '#section-id',
78
+ },
79
+ };
80
+
81
+ export const WithSearchAndHash: Story = {
82
+ args: {
83
+ children: 'Link with Query',
84
+ to: { hash: '#results', pathname: '/search', search: '?q=test' },
85
+ },
86
+ };
87
+
88
+ export const EmptyPath: Story = {
89
+ args: {
90
+ children: 'No destination',
91
+ to: '',
92
+ },
93
+ };
94
+
95
+ export const WithArrow: Story = {
96
+ args: {
97
+ arrow: 'rarr',
98
+ children: 'Link with Arrow',
99
+ to: '/arrow',
100
+ },
101
+ };
102
+
103
+ export const ButtonVariant: Story = {
104
+ args: {
105
+ children: 'Button Style Link',
106
+ to: '/button',
107
+ variant: 'button',
108
+ },
109
+ };
@@ -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
+ });
@@ -74,3 +74,58 @@ export const WithCharacterLimit: Story = {
74
74
  locale: 'en',
75
75
  },
76
76
  };
77
+
78
+ export const WithMDXComponents: Story = {
79
+ args: {
80
+ children: `# Custom Components
81
+
82
+ This text uses custom MDX components.`,
83
+ locale: 'en',
84
+ mdxComponents: {},
85
+ },
86
+ };
87
+
88
+ export const WithMarkdownOverrides: Story = {
89
+ args: {
90
+ children: `# Overridden Heading
91
+
92
+ This paragraph has custom styling applied.`,
93
+ locale: 'en',
94
+ markdownOverrides: {
95
+ h1: {
96
+ props: {
97
+ className: 'text-4xl font-bold text-blue-600',
98
+ },
99
+ },
100
+ },
101
+ },
102
+ };
103
+
104
+ export const WithInternalLink: Story = {
105
+ args: {
106
+ children: 'Check out [our page](https://regardio.com/about) for more info.',
107
+ locale: 'en',
108
+ },
109
+ };
110
+
111
+ export const WithExternalLink: Story = {
112
+ args: {
113
+ children: 'Visit [external site](https://example.com) for details.',
114
+ locale: 'en',
115
+ },
116
+ };
117
+
118
+ export const EmptyContent: Story = {
119
+ args: {
120
+ children: '',
121
+ locale: 'en',
122
+ },
123
+ };
124
+
125
+ export const WithClassName: Story = {
126
+ args: {
127
+ children: '**Bold text** in a styled container.',
128
+ className: 'bg-gray-100 p-4 rounded',
129
+ locale: 'en',
130
+ },
131
+ };
@@ -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
+ });
@@ -1 +0,0 @@
1
- export { useCurrentRouteData } from './use-current-route-data';
@@ -1 +0,0 @@
1
- export { useFocusSearch } from './use-focus-search';
@@ -1 +0,0 @@
1
- export { useMatchesData } from './use-matches-data';
@@ -1 +0,0 @@
1
- export { useMediaQuery } from './use-media-query';
@@ -1 +0,0 @@
1
- export { useIsMobile } from './use-mobile';
@@ -1 +0,0 @@
1
- export { NonceProvider, useNonce } from './use-nonce';
@@ -1 +0,0 @@
1
- export { useOrientation } from './use-orientation';
@@ -1,2 +0,0 @@
1
- export type { UserContextProviderProps, UserContextType } from './use-user';
2
- export { UserContext, UserContextProvider, useUser } from './use-user';