@stokr/components-library 3.0.22 → 3.0.24

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 (76) hide show
  1. package/README.md +467 -467
  2. package/dist/components/Modal/SuccessModal/SuccessModal.js +7 -1
  3. package/dist/components/StatusDot/StatusDot.js +68 -0
  4. package/dist/components/StatusDot/StatusDot.styles.js +45 -0
  5. package/dist/components/VerifyEmailModal/VerifyEmailModal.js +2 -0
  6. package/dist/components/headerHo/HeaderHo.js +2 -0
  7. package/dist/components/icons/LinkIcon.js +2 -2
  8. package/dist/index.js +2 -0
  9. package/dist/runtime-config.js +1 -1
  10. package/dist/static/animations/upload.lottie +0 -0
  11. package/dist/static/animations/upload.lottie.js +4 -0
  12. package/dist/static/country-list.json +251 -251
  13. package/dist/static/fonts/Ionicons/Ionicons.ttf.js +3 -1
  14. package/dist/static/fonts/Ionicons/Ionicons.woff.js +3 -1
  15. package/dist/static/fonts/Ionicons/Ionicons.woff2.js +3 -1
  16. package/dist/static/fonts/Ionicons/ionicons.min.css +2810 -2810
  17. package/dist/static/fonts/Ionicons/ionicons.min.css.js +1 -1
  18. package/dist/static/fonts/OpenSans/OpenSans-Bold.ttf.js +3 -1
  19. package/dist/static/fonts/OpenSans/OpenSans-Bold.woff.js +3 -1
  20. package/dist/static/fonts/OpenSans/OpenSans-Bold.woff2.js +3 -1
  21. package/dist/static/fonts/OpenSans/OpenSans-ExtraBold.ttf.js +3 -1
  22. package/dist/static/fonts/OpenSans/OpenSans-ExtraBold.woff.js +3 -1
  23. package/dist/static/fonts/OpenSans/OpenSans-ExtraBold.woff2.js +3 -1
  24. package/dist/static/fonts/OpenSans/OpenSans-Light.ttf.js +3 -1
  25. package/dist/static/fonts/OpenSans/OpenSans-Light.woff.js +3 -1
  26. package/dist/static/fonts/OpenSans/OpenSans-Light.woff2.js +3 -1
  27. package/dist/static/fonts/OpenSans/OpenSans-Regular.ttf.js +3 -1
  28. package/dist/static/fonts/OpenSans/OpenSans-Regular.woff.js +3 -1
  29. package/dist/static/fonts/OpenSans/OpenSans-Regular.woff2.js +3 -1
  30. package/dist/static/fonts/OpenSans/OpenSans-SemiBold.ttf.js +3 -1
  31. package/dist/static/fonts/OpenSans/OpenSans-SemiBold.woff.js +3 -1
  32. package/dist/static/fonts/OpenSans/OpenSans-SemiBold.woff2.js +3 -1
  33. package/dist/static/fonts/icomoon/icomoon.eot.js +3 -1
  34. package/dist/static/fonts/icomoon/icomoon.svg.js +3 -1
  35. package/dist/static/fonts/icomoon/icomoon.ttf.js +3 -1
  36. package/dist/static/fonts/icomoon/icomoon.woff.js +3 -1
  37. package/dist/static/fonts/icomoon/selection.json +910 -910
  38. package/dist/static/fonts/icomoon/style.css +139 -139
  39. package/dist/static/images/address-refreshing.gif.js +3 -1
  40. package/dist/static/images/arrow-down-black.svg.js +3 -1
  41. package/dist/static/images/avatar-placeholder.png.js +3 -1
  42. package/dist/static/images/background3.png.js +3 -1
  43. package/dist/static/images/bitcoin-logo.svg.js +3 -1
  44. package/dist/static/images/bmn2-logo.svg.js +3 -1
  45. package/dist/static/images/copy_icon.svg +4 -4
  46. package/dist/static/images/download_icon.svg +3 -3
  47. package/dist/static/images/early-adopter.png.js +3 -1
  48. package/dist/static/images/eth_logo.svg.js +3 -1
  49. package/dist/static/images/external-link-icon.svg.js +3 -1
  50. package/dist/static/images/google_auth.png.js +3 -1
  51. package/dist/static/images/graduation.png.js +3 -1
  52. package/dist/static/images/mangopay.svg.js +3 -1
  53. package/dist/static/images/numbers/number_eight.svg +3 -3
  54. package/dist/static/images/numbers/number_five.svg +4 -4
  55. package/dist/static/images/numbers/number_four.svg +3 -3
  56. package/dist/static/images/numbers/number_nine.svg +4 -4
  57. package/dist/static/images/numbers/number_one.svg +4 -4
  58. package/dist/static/images/numbers/number_seven.svg +4 -4
  59. package/dist/static/images/numbers/number_six.svg +4 -4
  60. package/dist/static/images/numbers/number_three.svg +3 -3
  61. package/dist/static/images/numbers/number_two.svg +4 -4
  62. package/dist/static/images/numbers/number_zero.svg +3 -3
  63. package/dist/static/images/plus-icon.svg +4 -4
  64. package/dist/static/images/process-waiting.gif.js +3 -1
  65. package/dist/static/images/search-icon.svg +3 -3
  66. package/dist/static/images/social/Facebook_Logo.png.js +3 -1
  67. package/dist/static/images/social/LI-In-Bug.png.js +3 -1
  68. package/dist/static/images/social/Telegram-Logo.png.js +3 -1
  69. package/dist/static/images/social/X-logo-black.png.js +3 -1
  70. package/dist/static/images/social/youtube_social_circle_red.png.js +3 -1
  71. package/dist/static/images/transfer-icon.svg +10 -10
  72. package/dist/static/images/upload.svg +5 -1
  73. package/dist/static/images/usdc-logo.svg.js +3 -1
  74. package/dist/static/images/usdq-logo.png.js +3 -1
  75. package/dist/static/images/warning-filled.svg +3 -3
  76. package/package.json +1 -1
package/README.md CHANGED
@@ -1,467 +1,467 @@
1
- # @stokr/components-library
2
-
3
- React component library for STOKR applications. Includes modals, forms, buttons, tables, and shared styles.
4
-
5
- ## Table of contents
6
-
7
- - [Installation](#installation)
8
- - [How to start](#how-to-start)
9
- - [Configuration](#configuration)
10
- - [Runtime config (npm consumers)](#runtime-config)
11
- - [Path-based URLs vs subdomains](#path-based-urls-vs-subdomains)
12
- - [Reading config with `getConfig()`](#reading-config-with-getconfig)
13
- - [Ionicons](#ionicons)
14
- - [React Router](#react-router)
15
- - [Troubleshooting](#troubleshooting)
16
- - [Development & publishing](#development--publishing)
17
-
18
- ---
19
-
20
- ## Installation
21
-
22
- ```bash
23
- npm install @stokr/components-library
24
- ```
25
-
26
- **Peer dependencies** (install in your app if not already present):
27
-
28
- ```bash
29
- npm install react react-dom styled-components react-router-dom
30
- ```
31
-
32
- - **React** 18 or 19
33
- - **styled-components** 6.x
34
- - **react-router-dom** 6.x (required if you use routing-dependent components)
35
-
36
- ---
37
-
38
- ## Implementing in your app
39
-
40
- Minimal path to use the library in a **Vite + React** consumer:
41
-
42
- 1. **Install** the package and [peer dependencies](#installation) (`react`, `react-dom`, `styled-components`, and `react-router-dom` if you use routing-heavy components).
43
- 2. **Router** — Wrap the tree with `BrowserRouter` (or another React Router v6 router) if you use `HeaderHo`, `MainMenu`, `LearnMore`, etc. If the host app may already provide a router (e.g. micro-frontend), use **`RouterWrapper`** from the package once at your root so you do not nest two `BrowserRouter` instances (see [step 2](#2-wrap-your-app-with-a-router-if-you-use-routing)).
44
- 3. **Runtime config** — Wrap your authenticated area with `<AuthProvider config={…}>` and pass **your** `import.meta.env.VITE_*` values (see [Runtime config](#runtime-config)). This is required for correct API URLs, Firebase, cookies, and domain-based links in pre-built npm installs.
45
- 4. **Optional** — `configure()` before `AuthProvider` if something (e.g. analytics) must read config earlier; `IoniconsStyles` or `styles.css` if you use icons or shared fonts (see below).
46
-
47
- Then import components from `@stokr/components-library` as in [step 4 under How to start](#4-import-and-use-components).
48
-
49
- ---
50
-
51
- ## How to start
52
-
53
- ### 1. Install the package and peers
54
-
55
- ```bash
56
- npm install @stokr/components-library react react-dom styled-components react-router-dom
57
- ```
58
-
59
- ### 2. Wrap your app with a Router (if you use routing)
60
-
61
- Components that use navigation (e.g. `MainMenu`, `LearnMore`, `HeaderHo`) must live inside a React Router.
62
-
63
- **Simple app** — wrap with `BrowserRouter`:
64
-
65
- ```jsx
66
- // main.jsx or App.jsx
67
- import { BrowserRouter } from 'react-router-dom'
68
- import App from './App'
69
-
70
- root.render(
71
- <BrowserRouter>
72
- <App />
73
- </BrowserRouter>,
74
- )
75
- ```
76
-
77
- **Host already has a router** (or you want one `BrowserRouter` only when needed) — wrap the library subtree with **`RouterWrapper`** from `@stokr/components-library`. It renders `BrowserRouter` only when `useInRouterContext()` is false, so you avoid duplicate routers and keep SPA navigation for `withRouter` / `useNavigate`:
78
-
79
- ```jsx
80
- import { RouterWrapper } from '@stokr/components-library'
81
- import { BrowserRouter } from 'react-router-dom'
82
- import App from './App'
83
-
84
- // Host owns the router:
85
- root.render(
86
- <BrowserRouter>
87
- <RouterWrapper>
88
- <App />
89
- </RouterWrapper>
90
- </BrowserRouter>,
91
- )
92
-
93
- // Standalone shell (no outer router): RouterWrapper adds BrowserRouter once.
94
- root.render(
95
- <RouterWrapper>
96
- <App />
97
- </RouterWrapper>,
98
- )
99
- ```
100
-
101
- ### 3. (Optional) Add Ionicons for icons
102
-
103
- If you use **Modal**, **ConfirmModal**, **BackButton**, **Select**, **InfoIcon**, etc., add the icon styles once at the root:
104
-
105
- ```jsx
106
- import { IoniconsStyles } from '@stokr/components-library'
107
-
108
- function App() {
109
- return (
110
- <>
111
- <IoniconsStyles />
112
- {/* your app */}
113
- </>
114
- )
115
- }
116
- ```
117
-
118
- You can skip this; the library will inject icon styles when you first use a component that needs them.
119
-
120
- ### 4. Import and use components
121
-
122
- ```jsx
123
- import { ConfirmModal, Button } from '@stokr/components-library'
124
-
125
- function MyPage() {
126
- const [open, setOpen] = useState(false)
127
- return (
128
- <>
129
- <Button onClick={() => setOpen(true)}>Open</Button>
130
- <ConfirmModal isOpen={open} onClose={() => setOpen(false)} onConfirm={() => {}} title="Confirm?" />
131
- </>
132
- )
133
- }
134
- ```
135
-
136
- ---
137
-
138
- ## Configuration
139
-
140
- ### Runtime config (required when consuming as npm package) {#runtime-config}
141
-
142
- Since v3.0.16, the library uses a **runtime config** system. When this package is consumed by an external Vite app, `import.meta.env` values are baked at **library build time** and do not reflect the consuming app's `.env` file. Pass a `config` prop to `<AuthProvider>` so API URLs, Firebase credentials, and cookie domain are resolved from **your** environment:
143
-
144
- ```jsx
145
- import { AuthProvider } from '@stokr/components-library'
146
-
147
- function App() {
148
- return (
149
- <AuthProvider
150
- config={{
151
- apiUrl: import.meta.env.VITE_API_URL,
152
- baseUrlPublic: import.meta.env.VITE_BASE_URL_PUBLIC,
153
- cookieDomain: import.meta.env.VITE_COOKIE_DOMAIN,
154
- websiteDomain: import.meta.env.VITE_WEBSITE_DOMAIN,
155
- photoApiUrl: import.meta.env.VITE_PHOTO_API_URL,
156
- firebase: {
157
- apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
158
- authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
159
- projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
160
- storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
161
- messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
162
- appId: import.meta.env.VITE_FIREBASE_APP_ID,
163
- measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
164
- },
165
- }}>
166
- {/* your app */}
167
- </AuthProvider>
168
- )
169
- }
170
- ```
171
-
172
- | Prop key | Env variable it replaces | Purpose |
173
- | -------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
174
- | `apiUrl` | `VITE_API_URL` | Backend API base URL |
175
- | `baseUrlPublic` | `VITE_BASE_URL_PUBLIC` | Public (no-auth) API base URL |
176
- | `cookieDomain` | `VITE_COOKIE_DOMAIN` | Domain attribute for auth cookies |
177
- | `websiteDomain` | `VITE_WEBSITE_DOMAIN` | Platform domain (redirects, links) |
178
- | `photoApiUrl` | `VITE_PHOTO_API_URL` | Photo upload / avatar API URL |
179
- | `firebase` | `VITE_FIREBASE_*` | Full Firebase config object |
180
- | `routingMode` | `VITE_ROUTING_MODE` | Set to `path` for single-origin app URLs (see below). |
181
- | `appUrlPaths` | — | Optional object overriding first path segments in path mode (e.g. `{ onboarding: '/signup', dashboard: '/app' }`). |
182
- | `surfaceRoutingMode` | — | Optional per-surface `'path'` / `'subdomain'` overrides for incremental URL migration (see [below](#incremental-url-migration-surfaceRoutingMode)). |
183
-
184
- > **Why is this needed?** With the old CRA / `react-scripts` build, Webpack re-processed library code through the consuming app's build pipeline, so the app's `.env` values were injected automatically. Vite treats npm packages as pre-built — `import.meta.env` values in the compiled library are frozen at library build time. The `config` prop passes them at runtime instead.
185
-
186
- If you also need config values **before** `<AuthProvider>` mounts (e.g. for analytics init), you can call `configure()` directly:
187
-
188
- ```js
189
- import { configure } from '@stokr/components-library'
190
-
191
- configure({
192
- apiUrl: import.meta.env.VITE_API_URL,
193
- cookieDomain: import.meta.env.VITE_COOKIE_DOMAIN,
194
- // ...
195
- })
196
- ```
197
-
198
- After `configure()` / `<AuthProvider config>`, the library may log a **one-time `console.warn`** listing any **`VITE_*`** variables that are still missing (no override and no env fallback). Fix your `.env` or extend the `config` object until the list is empty.
199
-
200
- ### Path-based URLs vs subdomains {#path-based-urls-vs-subdomains}
201
-
202
- By default the library builds **subdomain** links (`https://dashboard.{websiteDomain}`, `https://signup.{websiteDomain}/welcome`, `https://admin.{websiteDomain}`, etc.), matching historical STOKR hosting.
203
-
204
- To switch to **one origin + path prefixes** (for example `https://example.com/dashboard`, `https://example.com/signin/welcome`, `https://example.com/admin`), pass **`routingMode: 'path'`** (or `VITE_ROUTING_MODE=path`) and optionally **`appUrlPaths`** overrides. The public origin for path mode is always **`https://{websiteDomain}`** (same as **`getPlatformURL()`**).
205
-
206
- ```jsx
207
- <AuthProvider
208
- config={{
209
- apiUrl: import.meta.env.VITE_API_URL,
210
- websiteDomain: import.meta.env.VITE_WEBSITE_DOMAIN,
211
- routingMode: 'path',
212
- appUrlPaths: {
213
- onboarding: '/signin', // flows that used https://signup.{domain}
214
- registerEntry: '/signup', // CTA from login; default in code is /signup
215
- dashboard: '/dashboard',
216
- admin: '/admin',
217
- },
218
- firebase: { /* … */ },
219
- /* …other keys */
220
- }}>
221
- ```
222
-
223
- **Analytics and backoffice** always use legacy subdomains **`https://analytics.{websiteDomain}`** and **`https://backoffice.{websiteDomain}`**, even in path mode. They are implemented in **`src/utils/app-urls-analytics-backoffice.js`** and re-exported from **`app-urls.js`** (`getAnalyticsIngestUrl`, `getBackofficeAppUrl`); they are not driven by **`appUrlPaths`**.
224
-
225
- #### Incremental URL migration (`surfaceRoutingMode`) {#incremental-url-migration-surfaceRoutingMode}
226
-
227
- You do not have to flip every surface at once.
228
-
229
- - **Global default:** `routingMode: 'path'` means “use path URLs for every surface that supports it”, unless overridden. Leaving `routingMode` unset (or not `'path'`) keeps **subdomain** URLs by default.
230
- - **Per-surface override:** pass **`surfaceRoutingMode`** on `configure()` / `<AuthProvider config>`. Each key is one of **`onboarding`**, **`dashboard`**, **`admin`**, **`registerEntry`**, **`investorRoot`** (see **`SURFACE_ROUTING_PATH_KEYS`** on the package entry). Each value is **`'path'`** or **`'subdomain'`**. Any surface **omitted** from the object follows the global **`routingMode`**.
231
-
232
- Examples:
233
-
234
- 1. **Path everywhere except admin** (admin still `https://admin.{domain}` until infra is ready):
235
-
236
- ```js
237
- configure({
238
- websiteDomain: 'example.com',
239
- routingMode: 'path',
240
- appUrlPaths: { dashboard: '/dashboard', onboarding: '/signin', registerEntry: '/signup', admin: '/admin' },
241
- surfaceRoutingMode: { admin: 'subdomain' },
242
- })
243
- ```
244
-
245
- 2. **Subdomains by default, dashboard and admin on path** (e.g. first rollout):
246
-
247
- ```js
248
- configure({
249
- websiteDomain: 'example.com',
250
- appUrlPaths: { dashboard: '/dashboard', admin: '/admin' },
251
- surfaceRoutingMode: { dashboard: 'path', admin: 'path' },
252
- })
253
- ```
254
-
255
- In host UI or guards, use **`isPathForSurface('dashboard')`** / **`isPathForSurface('admin')`** (exported from **`@stokr/components-library`**) so behaviour matches **`resolveAppHref`**.
256
-
257
- ##### Worked example (no script)
258
-
259
- Assume **`websiteDomain: 'acme.com'`** and default **`appUrlPaths`** (among others: **`dashboard`** → **`/dashboard`**, **`admin`** → **`/admin`**).
260
-
261
- **A — Full path mode** (every surface that supports paths uses **`https://acme.com`** + prefix):
262
-
263
- ```js
264
- import { configure, resolveAppHref, isPathForSurface, AppSurface } from '@stokr/components-library'
265
-
266
- configure({ websiteDomain: 'acme.com', routingMode: 'path' })
267
- ```
268
-
269
- | Call | Result |
270
- | ---------------------------------------------------- | -------------------------------------- |
271
- | `resolveAppHref(AppSurface.DASHBOARD, '')` | `https://acme.com/dashboard` |
272
- | `resolveAppHref(AppSurface.DASHBOARD, '/checklist')` | `https://acme.com/dashboard/checklist` |
273
- | `resolveAppHref(AppSurface.ADMIN, '')` | `https://acme.com/admin` |
274
- | `isPathForSurface('dashboard')` | `true` |
275
- | `isPathForSurface('admin')` | `true` |
276
-
277
- **B — Path mode, but admin stays on subdomain** until infra is ready:
278
-
279
- ```js
280
- configure({
281
- websiteDomain: 'acme.com',
282
- routingMode: 'path',
283
- surfaceRoutingMode: { admin: 'subdomain' },
284
- })
285
- ```
286
-
287
- | Call | Result |
288
- | ---------------------------------------------------- | -------------------------------------------------- |
289
- | `resolveAppHref(AppSurface.DASHBOARD, '/checklist')` | `https://acme.com/dashboard/checklist` (unchanged) |
290
- | `resolveAppHref(AppSurface.ADMIN, '')` | `https://admin.acme.com` |
291
- | `isPathForSurface('admin')` | `false` |
292
-
293
- **C — Global default is still subdomain; dashboard and admin use path** (incremental first step):
294
-
295
- ```js
296
- configure({
297
- websiteDomain: 'acme.com',
298
- surfaceRoutingMode: { dashboard: 'path', admin: 'path' },
299
- })
300
- ```
301
-
302
- (`routingMode` is not **`'path'`**, so any surface **not** listed in **`surfaceRoutingMode`** keeps subdomain URLs.)
303
-
304
- | Call | Result |
305
- | ------------------------------------------ | ---------------------------- |
306
- | `resolveAppHref(AppSurface.DASHBOARD, '')` | `https://acme.com/dashboard` |
307
- | `resolveAppHref(AppSurface.ADMIN, '')` | `https://acme.com/admin` |
308
- | `isPathForSurface('dashboard')` | `true` |
309
- | `isPathForSurface('admin')` | `true` |
310
-
311
- **Rule of thumb:** for each routable surface, the code checks **`surfaceRoutingMode[surfaceKey]`** first (`'path'` or `'subdomain'`). If that key is **omitted**, it falls back to the global flag **`routingMode === 'path'`**. **`resolveAppHref`** and **`isPathForSurface`** use the same logic.
312
-
313
- Readable routing API (recommended):
314
-
315
- - **`AppSurface`** / **`AppRoute`** — named surfaces and path suffixes (`src/routing/app-routes.js`).
316
- - **`resolveAppHref(surface, path)`** — one function that returns the absolute URL for any surface + suffix.
317
- - **`isPathForSurface(key)`** / **`SURFACE_ROUTING_PATH_KEYS`** — align host code with per-surface path vs subdomain resolution when using **`surfaceRoutingMode`**.
318
- - **`navigateApp(navigate, surface, path)`** / **`navigateToHref(navigate, url)`** — use the `navigate` function from `react-router-dom` when the target is **same-origin** (SPA transition); otherwise they fall back to `location.assign` (e.g. `admin.` / `signup.` subdomains).
319
- - **`RouterWrapper`** — optional root helper: mounts `BrowserRouter` only when the tree is not already under a Router (same-origin SPA navigation without nesting two browser routers).
320
-
321
- Legacy helpers (`buildDashboardUrl`, `getAdminAppUrl`, …) remain on **`./utils/app-urls`** for compatibility.
322
-
323
- **Authentication-only HTTP** — backend routes under `auth/*` (e.g. forgot password) are exposed as **`authenticationApi.post(segment, body)`** from `src/api/authenticationApi.js` (also re-exported from the package entry). The default axios instance is the **general API client** for all authenticated backend calls, not auth-only.
324
-
325
- ### Reading config with `getConfig()` {#reading-config-with-getconfig}
326
-
327
- In your app (or in code next to the library), read the same resolved values the package uses:
328
-
329
- ```js
330
- import { getConfig } from '@stokr/components-library'
331
-
332
- const api = getConfig('apiUrl')
333
- const domain = getConfig('websiteDomain')
334
- const firebaseOptions = getConfig('firebase') // override object or env-built fallback
335
- ```
336
-
337
- Supported keys: `apiUrl`, `baseUrlPublic`, `cookieDomain`, `websiteDomain`, `photoApiUrl`, `routingMode`, `appUrlPaths`, `surfaceRoutingMode`, `firebase`. Resolution order is always **explicit `configure()` / `AuthProvider` `config`** first, then **`import.meta.env`** in the consuming Vite app (where a `VITE_*` mapping exists).
338
-
339
- **Marketing site origin** — the old `platformDomain` / `platformURL` exports were removed. Use **`getPlatformURL()`** for `https://{websiteDomain}`, or **`getConfig('websiteDomain')`** for the bare host (e.g. `"example.com"`). Both read the same runtime config after **`configure()`** / **`AuthProvider`**.
340
-
341
- ```js
342
- import { getConfig, getPlatformURL } from '@stokr/components-library'
343
-
344
- const host = getConfig('websiteDomain') // e.g. "example.com"
345
- const origin = getPlatformURL() // e.g. "https://example.com"
346
- ```
347
-
348
- **Footer link groups** — if you previously imported the static `footerGroups` from `FooterLayout`, import **`getFooterGroups`** instead (same package entry as other footer exports). It returns fresh URLs using the current `getPlatformURL()`.
349
-
350
- ### Ionicons
351
-
352
- Components such as **Modal**, **ConfirmModal**, **BackButton**, **InfoIcon**, **Select**, **MainMenu**, and **RegisterLiquidSteps** use [Ionicons](http://ionicons.com/). You can enable them in three ways:
353
-
354
- | Approach | When to use |
355
- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
356
- | **Global injection** | Render `<IoniconsStyles />` once at app root. Full icon set, singleton. |
357
- | **No setup** | Don’t render anything; styles inject on first use of an icon component. |
358
- | **CSS import** | Prefer loading via CSS: `import '@stokr/components-library/styles.css'` or `import '@stokr/components-library/ionicons.css'`. |
359
-
360
- **Global injection example:**
361
-
362
- ```jsx
363
- import { IoniconsStyles } from '@stokr/components-library'
364
-
365
- function App() {
366
- return (
367
- <>
368
- <IoniconsStyles />
369
- {/* your routes, layout, etc. */}
370
- </>
371
- )
372
- }
373
- ```
374
-
375
- If you use **Layout**, **GlobalStyle**, or need **Open Sans**, import the full styles once:
376
-
377
- ```js
378
- import '@stokr/components-library/styles.css'
379
- ```
380
-
381
- ### React Router
382
-
383
- Any component that uses `useNavigate()` or routing must be rendered inside a `Router` from `react-router-dom` (e.g. `BrowserRouter`), or under **`RouterWrapper`** so a router exists when the host does not provide one. Components wrapped with **`withRouter`** receive `navigate` from `useNavigate()` when inside a Router; outside a Router, `navigate` throws with a message to add `BrowserRouter` or `RouterWrapper`. See [How to start – step 2](#2-wrap-your-app-with-a-router-if-you-use-routing).
384
-
385
- ---
386
-
387
- ## Troubleshooting
388
-
389
- ### "useNavigate() may be used only in the context of a \<Router\> component"
390
-
391
- Wrap your app with a Router, or use **`RouterWrapper`** at the root when you are not already inside a host `BrowserRouter`:
392
-
393
- ```jsx
394
- import { BrowserRouter } from 'react-router-dom'
395
-
396
- root.render(
397
- <BrowserRouter>
398
- <App />
399
- </BrowserRouter>,
400
- )
401
- ```
402
-
403
- ```jsx
404
- import { RouterWrapper } from '@stokr/components-library'
405
-
406
- root.render(
407
- <RouterWrapper>
408
- <App />
409
- </RouterWrapper>,
410
- )
411
- ```
412
-
413
- Install the peer dependency: `npm install react-router-dom`
414
-
415
- ### "Invalid hook call" / "Cannot read properties of null (reading 'use')"
416
-
417
- Your app and the library must use the **same** React instance.
418
-
419
- 1. Install peer dependencies:
420
- `npm install react react-dom styled-components`
421
-
422
- 2. **Vite apps** – add dedupe in `vite.config.js` or `vite.config.ts`:
423
-
424
- ```js
425
- export default defineConfig({
426
- resolve: {
427
- dedupe: ['react', 'react-dom', 'styled-components'],
428
- },
429
- // ...rest of config
430
- })
431
- ```
432
-
433
- 3. Reinstall or refresh the library after updating (e.g. `npm install` or clear cache and reinstall).
434
-
435
- ---
436
-
437
- ## Development & publishing
438
-
439
- ### Run Storybook
440
-
441
- ```bash
442
- npm run storybook
443
- ```
444
-
445
- Use **Story Source** for consumption examples and **Viewport** for different screen sizes.
446
-
447
- ### Build for distribution
448
-
449
- ```bash
450
- npm run build:dist
451
- ```
452
-
453
- This runs `vite build` and copies static assets to `dist/`.
454
-
455
- ### Publish a new version
456
-
457
- 1. Commit your changes.
458
- 2. Update [CHANGELOG.md](CHANGELOG.md) – add a new `# vX.Y.Z` section at the top with the list of changes.
459
- 3. Bump the version: `npm version <version>` (e.g. `npm version 3.0.7`).
460
- 4. (ensure you are authenticated) Run `npm login` to log first on NPM package (only needed one time a day)
461
- 5. Run `npm run pub` (will first run `npm run build:dist`).
462
-
463
- Consumers can see what changed in each release in [CHANGELOG.md](CHANGELOG.md).
464
-
465
- ### Reference
466
-
467
- - [How to publish a React component library](https://medium.com/better-programming/how-to-publish-a-react-component-library-c89a07566770)
1
+ # @stokr/components-library
2
+
3
+ React component library for STOKR applications. Includes modals, forms, buttons, tables, and shared styles.
4
+
5
+ ## Table of contents
6
+
7
+ - [Installation](#installation)
8
+ - [How to start](#how-to-start)
9
+ - [Configuration](#configuration)
10
+ - [Runtime config (npm consumers)](#runtime-config)
11
+ - [Path-based URLs vs subdomains](#path-based-urls-vs-subdomains)
12
+ - [Reading config with `getConfig()`](#reading-config-with-getconfig)
13
+ - [Ionicons](#ionicons)
14
+ - [React Router](#react-router)
15
+ - [Troubleshooting](#troubleshooting)
16
+ - [Development & publishing](#development--publishing)
17
+
18
+ ---
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @stokr/components-library
24
+ ```
25
+
26
+ **Peer dependencies** (install in your app if not already present):
27
+
28
+ ```bash
29
+ npm install react react-dom styled-components react-router-dom
30
+ ```
31
+
32
+ - **React** 18 or 19
33
+ - **styled-components** 6.x
34
+ - **react-router-dom** 6.x (required if you use routing-dependent components)
35
+
36
+ ---
37
+
38
+ ## Implementing in your app
39
+
40
+ Minimal path to use the library in a **Vite + React** consumer:
41
+
42
+ 1. **Install** the package and [peer dependencies](#installation) (`react`, `react-dom`, `styled-components`, and `react-router-dom` if you use routing-heavy components).
43
+ 2. **Router** — Wrap the tree with `BrowserRouter` (or another React Router v6 router) if you use `HeaderHo`, `MainMenu`, `LearnMore`, etc. If the host app may already provide a router (e.g. micro-frontend), use **`RouterWrapper`** from the package once at your root so you do not nest two `BrowserRouter` instances (see [step 2](#2-wrap-your-app-with-a-router-if-you-use-routing)).
44
+ 3. **Runtime config** — Wrap your authenticated area with `<AuthProvider config={…}>` and pass **your** `import.meta.env.VITE_*` values (see [Runtime config](#runtime-config)). This is required for correct API URLs, Firebase, cookies, and domain-based links in pre-built npm installs.
45
+ 4. **Optional** — `configure()` before `AuthProvider` if something (e.g. analytics) must read config earlier; `IoniconsStyles` or `styles.css` if you use icons or shared fonts (see below).
46
+
47
+ Then import components from `@stokr/components-library` as in [step 4 under How to start](#4-import-and-use-components).
48
+
49
+ ---
50
+
51
+ ## How to start
52
+
53
+ ### 1. Install the package and peers
54
+
55
+ ```bash
56
+ npm install @stokr/components-library react react-dom styled-components react-router-dom
57
+ ```
58
+
59
+ ### 2. Wrap your app with a Router (if you use routing)
60
+
61
+ Components that use navigation (e.g. `MainMenu`, `LearnMore`, `HeaderHo`) must live inside a React Router.
62
+
63
+ **Simple app** — wrap with `BrowserRouter`:
64
+
65
+ ```jsx
66
+ // main.jsx or App.jsx
67
+ import { BrowserRouter } from 'react-router-dom'
68
+ import App from './App'
69
+
70
+ root.render(
71
+ <BrowserRouter>
72
+ <App />
73
+ </BrowserRouter>,
74
+ )
75
+ ```
76
+
77
+ **Host already has a router** (or you want one `BrowserRouter` only when needed) — wrap the library subtree with **`RouterWrapper`** from `@stokr/components-library`. It renders `BrowserRouter` only when `useInRouterContext()` is false, so you avoid duplicate routers and keep SPA navigation for `withRouter` / `useNavigate`:
78
+
79
+ ```jsx
80
+ import { RouterWrapper } from '@stokr/components-library'
81
+ import { BrowserRouter } from 'react-router-dom'
82
+ import App from './App'
83
+
84
+ // Host owns the router:
85
+ root.render(
86
+ <BrowserRouter>
87
+ <RouterWrapper>
88
+ <App />
89
+ </RouterWrapper>
90
+ </BrowserRouter>,
91
+ )
92
+
93
+ // Standalone shell (no outer router): RouterWrapper adds BrowserRouter once.
94
+ root.render(
95
+ <RouterWrapper>
96
+ <App />
97
+ </RouterWrapper>,
98
+ )
99
+ ```
100
+
101
+ ### 3. (Optional) Add Ionicons for icons
102
+
103
+ If you use **Modal**, **ConfirmModal**, **BackButton**, **Select**, **InfoIcon**, etc., add the icon styles once at the root:
104
+
105
+ ```jsx
106
+ import { IoniconsStyles } from '@stokr/components-library'
107
+
108
+ function App() {
109
+ return (
110
+ <>
111
+ <IoniconsStyles />
112
+ {/* your app */}
113
+ </>
114
+ )
115
+ }
116
+ ```
117
+
118
+ You can skip this; the library will inject icon styles when you first use a component that needs them.
119
+
120
+ ### 4. Import and use components
121
+
122
+ ```jsx
123
+ import { ConfirmModal, Button } from '@stokr/components-library'
124
+
125
+ function MyPage() {
126
+ const [open, setOpen] = useState(false)
127
+ return (
128
+ <>
129
+ <Button onClick={() => setOpen(true)}>Open</Button>
130
+ <ConfirmModal isOpen={open} onClose={() => setOpen(false)} onConfirm={() => {}} title="Confirm?" />
131
+ </>
132
+ )
133
+ }
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Configuration
139
+
140
+ ### Runtime config (required when consuming as npm package) {#runtime-config}
141
+
142
+ Since v3.0.16, the library uses a **runtime config** system. When this package is consumed by an external Vite app, `import.meta.env` values are baked at **library build time** and do not reflect the consuming app's `.env` file. Pass a `config` prop to `<AuthProvider>` so API URLs, Firebase credentials, and cookie domain are resolved from **your** environment:
143
+
144
+ ```jsx
145
+ import { AuthProvider } from '@stokr/components-library'
146
+
147
+ function App() {
148
+ return (
149
+ <AuthProvider
150
+ config={{
151
+ apiUrl: import.meta.env.VITE_API_URL,
152
+ baseUrlPublic: import.meta.env.VITE_BASE_URL_PUBLIC,
153
+ cookieDomain: import.meta.env.VITE_COOKIE_DOMAIN,
154
+ websiteDomain: import.meta.env.VITE_WEBSITE_DOMAIN,
155
+ photoApiUrl: import.meta.env.VITE_PHOTO_API_URL,
156
+ firebase: {
157
+ apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
158
+ authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
159
+ projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
160
+ storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
161
+ messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
162
+ appId: import.meta.env.VITE_FIREBASE_APP_ID,
163
+ measurementId: import.meta.env.VITE_FIREBASE_MEASUREMENT_ID,
164
+ },
165
+ }}>
166
+ {/* your app */}
167
+ </AuthProvider>
168
+ )
169
+ }
170
+ ```
171
+
172
+ | Prop key | Env variable it replaces | Purpose |
173
+ | -------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
174
+ | `apiUrl` | `VITE_API_URL` | Backend API base URL |
175
+ | `baseUrlPublic` | `VITE_BASE_URL_PUBLIC` | Public (no-auth) API base URL |
176
+ | `cookieDomain` | `VITE_COOKIE_DOMAIN` | Domain attribute for auth cookies |
177
+ | `websiteDomain` | `VITE_WEBSITE_DOMAIN` | Platform domain (redirects, links) |
178
+ | `photoApiUrl` | `VITE_PHOTO_API_URL` | Photo upload / avatar API URL |
179
+ | `firebase` | `VITE_FIREBASE_*` | Full Firebase config object |
180
+ | `routingMode` | `VITE_ROUTING_MODE` | Set to `path` for single-origin app URLs (see below). |
181
+ | `appUrlPaths` | — | Optional object overriding first path segments in path mode (e.g. `{ onboarding: '/signup', dashboard: '/app' }`). |
182
+ | `surfaceRoutingMode` | — | Optional per-surface `'path'` / `'subdomain'` overrides for incremental URL migration (see [below](#incremental-url-migration-surfaceRoutingMode)). |
183
+
184
+ > **Why is this needed?** With the old CRA / `react-scripts` build, Webpack re-processed library code through the consuming app's build pipeline, so the app's `.env` values were injected automatically. Vite treats npm packages as pre-built — `import.meta.env` values in the compiled library are frozen at library build time. The `config` prop passes them at runtime instead.
185
+
186
+ If you also need config values **before** `<AuthProvider>` mounts (e.g. for analytics init), you can call `configure()` directly:
187
+
188
+ ```js
189
+ import { configure } from '@stokr/components-library'
190
+
191
+ configure({
192
+ apiUrl: import.meta.env.VITE_API_URL,
193
+ cookieDomain: import.meta.env.VITE_COOKIE_DOMAIN,
194
+ // ...
195
+ })
196
+ ```
197
+
198
+ After `configure()` / `<AuthProvider config>`, the library may log a **one-time `console.warn`** listing any **`VITE_*`** variables that are still missing (no override and no env fallback). Fix your `.env` or extend the `config` object until the list is empty.
199
+
200
+ ### Path-based URLs vs subdomains {#path-based-urls-vs-subdomains}
201
+
202
+ By default the library builds **subdomain** links (`https://dashboard.{websiteDomain}`, `https://signup.{websiteDomain}/welcome`, `https://admin.{websiteDomain}`, etc.), matching historical STOKR hosting.
203
+
204
+ To switch to **one origin + path prefixes** (for example `https://example.com/dashboard`, `https://example.com/signin/welcome`, `https://example.com/admin`), pass **`routingMode: 'path'`** (or `VITE_ROUTING_MODE=path`) and optionally **`appUrlPaths`** overrides. The public origin for path mode is always **`https://{websiteDomain}`** (same as **`getPlatformURL()`**).
205
+
206
+ ```jsx
207
+ <AuthProvider
208
+ config={{
209
+ apiUrl: import.meta.env.VITE_API_URL,
210
+ websiteDomain: import.meta.env.VITE_WEBSITE_DOMAIN,
211
+ routingMode: 'path',
212
+ appUrlPaths: {
213
+ onboarding: '/signin', // flows that used https://signup.{domain}
214
+ registerEntry: '/signup', // CTA from login; default in code is /signup
215
+ dashboard: '/dashboard',
216
+ admin: '/admin',
217
+ },
218
+ firebase: { /* … */ },
219
+ /* …other keys */
220
+ }}>
221
+ ```
222
+
223
+ **Analytics and backoffice** always use legacy subdomains **`https://analytics.{websiteDomain}`** and **`https://backoffice.{websiteDomain}`**, even in path mode. They are implemented in **`src/utils/app-urls-analytics-backoffice.js`** and re-exported from **`app-urls.js`** (`getAnalyticsIngestUrl`, `getBackofficeAppUrl`); they are not driven by **`appUrlPaths`**.
224
+
225
+ #### Incremental URL migration (`surfaceRoutingMode`) {#incremental-url-migration-surfaceRoutingMode}
226
+
227
+ You do not have to flip every surface at once.
228
+
229
+ - **Global default:** `routingMode: 'path'` means “use path URLs for every surface that supports it”, unless overridden. Leaving `routingMode` unset (or not `'path'`) keeps **subdomain** URLs by default.
230
+ - **Per-surface override:** pass **`surfaceRoutingMode`** on `configure()` / `<AuthProvider config>`. Each key is one of **`onboarding`**, **`dashboard`**, **`admin`**, **`registerEntry`**, **`investorRoot`** (see **`SURFACE_ROUTING_PATH_KEYS`** on the package entry). Each value is **`'path'`** or **`'subdomain'`**. Any surface **omitted** from the object follows the global **`routingMode`**.
231
+
232
+ Examples:
233
+
234
+ 1. **Path everywhere except admin** (admin still `https://admin.{domain}` until infra is ready):
235
+
236
+ ```js
237
+ configure({
238
+ websiteDomain: 'example.com',
239
+ routingMode: 'path',
240
+ appUrlPaths: { dashboard: '/dashboard', onboarding: '/signin', registerEntry: '/signup', admin: '/admin' },
241
+ surfaceRoutingMode: { admin: 'subdomain' },
242
+ })
243
+ ```
244
+
245
+ 2. **Subdomains by default, dashboard and admin on path** (e.g. first rollout):
246
+
247
+ ```js
248
+ configure({
249
+ websiteDomain: 'example.com',
250
+ appUrlPaths: { dashboard: '/dashboard', admin: '/admin' },
251
+ surfaceRoutingMode: { dashboard: 'path', admin: 'path' },
252
+ })
253
+ ```
254
+
255
+ In host UI or guards, use **`isPathForSurface('dashboard')`** / **`isPathForSurface('admin')`** (exported from **`@stokr/components-library`**) so behaviour matches **`resolveAppHref`**.
256
+
257
+ ##### Worked example (no script)
258
+
259
+ Assume **`websiteDomain: 'acme.com'`** and default **`appUrlPaths`** (among others: **`dashboard`** → **`/dashboard`**, **`admin`** → **`/admin`**).
260
+
261
+ **A — Full path mode** (every surface that supports paths uses **`https://acme.com`** + prefix):
262
+
263
+ ```js
264
+ import { configure, resolveAppHref, isPathForSurface, AppSurface } from '@stokr/components-library'
265
+
266
+ configure({ websiteDomain: 'acme.com', routingMode: 'path' })
267
+ ```
268
+
269
+ | Call | Result |
270
+ | ---------------------------------------------------- | -------------------------------------- |
271
+ | `resolveAppHref(AppSurface.DASHBOARD, '')` | `https://acme.com/dashboard` |
272
+ | `resolveAppHref(AppSurface.DASHBOARD, '/checklist')` | `https://acme.com/dashboard/checklist` |
273
+ | `resolveAppHref(AppSurface.ADMIN, '')` | `https://acme.com/admin` |
274
+ | `isPathForSurface('dashboard')` | `true` |
275
+ | `isPathForSurface('admin')` | `true` |
276
+
277
+ **B — Path mode, but admin stays on subdomain** until infra is ready:
278
+
279
+ ```js
280
+ configure({
281
+ websiteDomain: 'acme.com',
282
+ routingMode: 'path',
283
+ surfaceRoutingMode: { admin: 'subdomain' },
284
+ })
285
+ ```
286
+
287
+ | Call | Result |
288
+ | ---------------------------------------------------- | -------------------------------------------------- |
289
+ | `resolveAppHref(AppSurface.DASHBOARD, '/checklist')` | `https://acme.com/dashboard/checklist` (unchanged) |
290
+ | `resolveAppHref(AppSurface.ADMIN, '')` | `https://admin.acme.com` |
291
+ | `isPathForSurface('admin')` | `false` |
292
+
293
+ **C — Global default is still subdomain; dashboard and admin use path** (incremental first step):
294
+
295
+ ```js
296
+ configure({
297
+ websiteDomain: 'acme.com',
298
+ surfaceRoutingMode: { dashboard: 'path', admin: 'path' },
299
+ })
300
+ ```
301
+
302
+ (`routingMode` is not **`'path'`**, so any surface **not** listed in **`surfaceRoutingMode`** keeps subdomain URLs.)
303
+
304
+ | Call | Result |
305
+ | ------------------------------------------ | ---------------------------- |
306
+ | `resolveAppHref(AppSurface.DASHBOARD, '')` | `https://acme.com/dashboard` |
307
+ | `resolveAppHref(AppSurface.ADMIN, '')` | `https://acme.com/admin` |
308
+ | `isPathForSurface('dashboard')` | `true` |
309
+ | `isPathForSurface('admin')` | `true` |
310
+
311
+ **Rule of thumb:** for each routable surface, the code checks **`surfaceRoutingMode[surfaceKey]`** first (`'path'` or `'subdomain'`). If that key is **omitted**, it falls back to the global flag **`routingMode === 'path'`**. **`resolveAppHref`** and **`isPathForSurface`** use the same logic.
312
+
313
+ Readable routing API (recommended):
314
+
315
+ - **`AppSurface`** / **`AppRoute`** — named surfaces and path suffixes (`src/routing/app-routes.js`).
316
+ - **`resolveAppHref(surface, path)`** — one function that returns the absolute URL for any surface + suffix.
317
+ - **`isPathForSurface(key)`** / **`SURFACE_ROUTING_PATH_KEYS`** — align host code with per-surface path vs subdomain resolution when using **`surfaceRoutingMode`**.
318
+ - **`navigateApp(navigate, surface, path)`** / **`navigateToHref(navigate, url)`** — use the `navigate` function from `react-router-dom` when the target is **same-origin** (SPA transition); otherwise they fall back to `location.assign` (e.g. `admin.` / `signup.` subdomains).
319
+ - **`RouterWrapper`** — optional root helper: mounts `BrowserRouter` only when the tree is not already under a Router (same-origin SPA navigation without nesting two browser routers).
320
+
321
+ Legacy helpers (`buildDashboardUrl`, `getAdminAppUrl`, …) remain on **`./utils/app-urls`** for compatibility.
322
+
323
+ **Authentication-only HTTP** — backend routes under `auth/*` (e.g. forgot password) are exposed as **`authenticationApi.post(segment, body)`** from `src/api/authenticationApi.js` (also re-exported from the package entry). The default axios instance is the **general API client** for all authenticated backend calls, not auth-only.
324
+
325
+ ### Reading config with `getConfig()` {#reading-config-with-getconfig}
326
+
327
+ In your app (or in code next to the library), read the same resolved values the package uses:
328
+
329
+ ```js
330
+ import { getConfig } from '@stokr/components-library'
331
+
332
+ const api = getConfig('apiUrl')
333
+ const domain = getConfig('websiteDomain')
334
+ const firebaseOptions = getConfig('firebase') // override object or env-built fallback
335
+ ```
336
+
337
+ Supported keys: `apiUrl`, `baseUrlPublic`, `cookieDomain`, `websiteDomain`, `photoApiUrl`, `routingMode`, `appUrlPaths`, `surfaceRoutingMode`, `firebase`. Resolution order is always **explicit `configure()` / `AuthProvider` `config`** first, then **`import.meta.env`** in the consuming Vite app (where a `VITE_*` mapping exists).
338
+
339
+ **Marketing site origin** — the old `platformDomain` / `platformURL` exports were removed. Use **`getPlatformURL()`** for `https://{websiteDomain}`, or **`getConfig('websiteDomain')`** for the bare host (e.g. `"example.com"`). Both read the same runtime config after **`configure()`** / **`AuthProvider`**.
340
+
341
+ ```js
342
+ import { getConfig, getPlatformURL } from '@stokr/components-library'
343
+
344
+ const host = getConfig('websiteDomain') // e.g. "example.com"
345
+ const origin = getPlatformURL() // e.g. "https://example.com"
346
+ ```
347
+
348
+ **Footer link groups** — if you previously imported the static `footerGroups` from `FooterLayout`, import **`getFooterGroups`** instead (same package entry as other footer exports). It returns fresh URLs using the current `getPlatformURL()`.
349
+
350
+ ### Ionicons
351
+
352
+ Components such as **Modal**, **ConfirmModal**, **BackButton**, **InfoIcon**, **Select**, **MainMenu**, and **RegisterLiquidSteps** use [Ionicons](http://ionicons.com/). You can enable them in three ways:
353
+
354
+ | Approach | When to use |
355
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
356
+ | **Global injection** | Render `<IoniconsStyles />` once at app root. Full icon set, singleton. |
357
+ | **No setup** | Don’t render anything; styles inject on first use of an icon component. |
358
+ | **CSS import** | Prefer loading via CSS: `import '@stokr/components-library/styles.css'` or `import '@stokr/components-library/ionicons.css'`. |
359
+
360
+ **Global injection example:**
361
+
362
+ ```jsx
363
+ import { IoniconsStyles } from '@stokr/components-library'
364
+
365
+ function App() {
366
+ return (
367
+ <>
368
+ <IoniconsStyles />
369
+ {/* your routes, layout, etc. */}
370
+ </>
371
+ )
372
+ }
373
+ ```
374
+
375
+ If you use **Layout**, **GlobalStyle**, or need **Open Sans**, import the full styles once:
376
+
377
+ ```js
378
+ import '@stokr/components-library/styles.css'
379
+ ```
380
+
381
+ ### React Router
382
+
383
+ Any component that uses `useNavigate()` or routing must be rendered inside a `Router` from `react-router-dom` (e.g. `BrowserRouter`), or under **`RouterWrapper`** so a router exists when the host does not provide one. Components wrapped with **`withRouter`** receive `navigate` from `useNavigate()` when inside a Router; outside a Router, `navigate` throws with a message to add `BrowserRouter` or `RouterWrapper`. See [How to start – step 2](#2-wrap-your-app-with-a-router-if-you-use-routing).
384
+
385
+ ---
386
+
387
+ ## Troubleshooting
388
+
389
+ ### "useNavigate() may be used only in the context of a \<Router\> component"
390
+
391
+ Wrap your app with a Router, or use **`RouterWrapper`** at the root when you are not already inside a host `BrowserRouter`:
392
+
393
+ ```jsx
394
+ import { BrowserRouter } from 'react-router-dom'
395
+
396
+ root.render(
397
+ <BrowserRouter>
398
+ <App />
399
+ </BrowserRouter>,
400
+ )
401
+ ```
402
+
403
+ ```jsx
404
+ import { RouterWrapper } from '@stokr/components-library'
405
+
406
+ root.render(
407
+ <RouterWrapper>
408
+ <App />
409
+ </RouterWrapper>,
410
+ )
411
+ ```
412
+
413
+ Install the peer dependency: `npm install react-router-dom`
414
+
415
+ ### "Invalid hook call" / "Cannot read properties of null (reading 'use')"
416
+
417
+ Your app and the library must use the **same** React instance.
418
+
419
+ 1. Install peer dependencies:
420
+ `npm install react react-dom styled-components`
421
+
422
+ 2. **Vite apps** – add dedupe in `vite.config.js` or `vite.config.ts`:
423
+
424
+ ```js
425
+ export default defineConfig({
426
+ resolve: {
427
+ dedupe: ['react', 'react-dom', 'styled-components'],
428
+ },
429
+ // ...rest of config
430
+ })
431
+ ```
432
+
433
+ 3. Reinstall or refresh the library after updating (e.g. `npm install` or clear cache and reinstall).
434
+
435
+ ---
436
+
437
+ ## Development & publishing
438
+
439
+ ### Run Storybook
440
+
441
+ ```bash
442
+ npm run storybook
443
+ ```
444
+
445
+ Use **Story Source** for consumption examples and **Viewport** for different screen sizes.
446
+
447
+ ### Build for distribution
448
+
449
+ ```bash
450
+ npm run build:dist
451
+ ```
452
+
453
+ This runs `vite build` and copies static assets to `dist/`.
454
+
455
+ ### Publish a new version
456
+
457
+ 1. Commit your changes.
458
+ 2. Update [CHANGELOG.md](CHANGELOG.md) – add a new `# vX.Y.Z` section at the top with the list of changes.
459
+ 3. Bump the version: `npm version <version>` (e.g. `npm version 3.0.7`).
460
+ 4. (ensure you are authenticated) Run `npm login` to log first on NPM package (only needed one time a day)
461
+ 5. Run `npm run pub` (will first run `npm run build:dist`).
462
+
463
+ Consumers can see what changed in each release in [CHANGELOG.md](CHANGELOG.md).
464
+
465
+ ### Reference
466
+
467
+ - [How to publish a React component library](https://medium.com/better-programming/how-to-publish-a-react-component-library-c89a07566770)