@mrfylke/contact-form 0.1.11 → 0.1.22

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 (160) hide show
  1. package/README.md +393 -2
  2. package/lib/ContactFormRoot.d.ts +6 -1
  3. package/lib/ContactFormRoot.d.ts.map +1 -1
  4. package/lib/ContactFormRoot.js +2 -2
  5. package/lib/commoneEvents.d.ts +1 -1
  6. package/lib/commoneEvents.d.ts.map +1 -1
  7. package/lib/components/form/date-selector.d.ts.map +1 -1
  8. package/lib/components/form/date-selector.js +5 -4
  9. package/lib/components/form/departure-time-field.d.ts +16 -0
  10. package/lib/components/form/departure-time-field.d.ts.map +1 -0
  11. package/lib/components/form/departure-time-field.js +28 -0
  12. package/lib/components/form/description-modal.d.ts.map +1 -1
  13. package/lib/components/form/description-modal.js +2 -1
  14. package/lib/components/form/file-input.d.ts.map +1 -1
  15. package/lib/components/form/file-input.js +2 -1
  16. package/lib/components/form/index.d.ts +1 -0
  17. package/lib/components/form/index.d.ts.map +1 -1
  18. package/lib/components/form/index.js +3 -1
  19. package/lib/components/form/input.d.ts.map +1 -1
  20. package/lib/components/form/input.js +2 -1
  21. package/lib/components/form/searchable-select/utils.js +6 -1
  22. package/lib/components/index.d.ts +1 -1
  23. package/lib/components/index.d.ts.map +1 -1
  24. package/lib/components/index.js +2 -1
  25. package/lib/contact.module.css +9 -0
  26. package/lib/error-content/error-content.d.ts.map +1 -1
  27. package/lib/error-content/error-content.js +2 -1
  28. package/lib/group-travel/index.d.ts.map +1 -1
  29. package/lib/group-travel/index.js +3 -1
  30. package/lib/index.d.ts +16 -4
  31. package/lib/index.d.ts.map +1 -1
  32. package/lib/index.js +41 -1
  33. package/lib/journey-info/index.d.ts.map +1 -1
  34. package/lib/journey-info/index.js +13 -7
  35. package/lib/journey-info/journeyInfoStateMachine.d.ts +8 -8
  36. package/lib/layouts/contact-page-layout.d.ts +2 -2
  37. package/lib/layouts/contact-page-layout.d.ts.map +1 -1
  38. package/lib/layouts/contact-page-layout.js +19 -16
  39. package/lib/layouts/container.module.css +1 -1
  40. package/lib/layouts/layout.module.css +23 -2
  41. package/lib/lines/use-departures.d.ts +12 -0
  42. package/lib/lines/use-departures.d.ts.map +1 -0
  43. package/lib/lines/use-departures.js +35 -0
  44. package/lib/lines/use-lines.d.ts +4 -3
  45. package/lib/lines/use-lines.d.ts.map +1 -1
  46. package/lib/lines/use-lines.js +43 -10
  47. package/lib/lost-property/LostPropertyForm.d.ts +10 -0
  48. package/lib/lost-property/LostPropertyForm.d.ts.map +1 -0
  49. package/lib/lost-property/LostPropertyForm.js +65 -0
  50. package/lib/lost-property/LostPropertyMachine.d.ts +116 -0
  51. package/lib/lost-property/LostPropertyMachine.d.ts.map +1 -0
  52. package/lib/lost-property/LostPropertyMachine.js +187 -0
  53. package/lib/lost-property/index.d.ts.map +1 -1
  54. package/lib/lost-property/index.js +27 -3
  55. package/lib/means-of-transport/forms/delayForm.d.ts.map +1 -1
  56. package/lib/means-of-transport/forms/delayForm.js +26 -18
  57. package/lib/means-of-transport/forms/driverForm.d.ts.map +1 -1
  58. package/lib/means-of-transport/forms/driverForm.js +26 -18
  59. package/lib/means-of-transport/forms/injuryForm.d.ts.map +1 -1
  60. package/lib/means-of-transport/forms/injuryForm.js +26 -18
  61. package/lib/means-of-transport/forms/serviceOfferingForm.d.ts.map +1 -1
  62. package/lib/means-of-transport/forms/serviceOfferingForm.js +10 -9
  63. package/lib/means-of-transport/forms/stopForm.d.ts.map +1 -1
  64. package/lib/means-of-transport/forms/stopForm.js +13 -12
  65. package/lib/means-of-transport/forms/transportationForm.d.ts.map +1 -1
  66. package/lib/means-of-transport/forms/transportationForm.js +26 -18
  67. package/lib/means-of-transport/index.d.ts.map +1 -1
  68. package/lib/means-of-transport/index.js +13 -4
  69. package/lib/means-of-transport/means-of-transport-form-machine.d.ts +10 -8
  70. package/lib/means-of-transport/means-of-transport-form-machine.d.ts.map +1 -1
  71. package/lib/means-of-transport/means-of-transport-form-machine.js +6 -0
  72. package/lib/modules/config/types.d.ts +48 -1
  73. package/lib/modules/config/types.d.ts.map +1 -1
  74. package/lib/refund/events.d.ts +1 -1
  75. package/lib/refund/events.d.ts.map +1 -1
  76. package/lib/refund/forms/refund-and-travel-guarantee/index.d.ts.map +1 -1
  77. package/lib/refund/forms/refund-and-travel-guarantee/index.js +2 -2
  78. package/lib/refund/forms/refund-and-travel-guarantee/refundCarForm.d.ts.map +1 -1
  79. package/lib/refund/forms/refund-and-travel-guarantee/refundCarForm.js +15 -8
  80. package/lib/refund/forms/refund-and-travel-guarantee/refundTaxiForm.d.ts.map +1 -1
  81. package/lib/refund/forms/refund-and-travel-guarantee/refundTaxiForm.js +15 -8
  82. package/lib/refund/forms/refund-ticket/appTicketRefund.d.ts.map +1 -1
  83. package/lib/refund/forms/refund-ticket/appTicketRefund.js +46 -11
  84. package/lib/refund/forms/refund-ticket/index.d.ts.map +1 -1
  85. package/lib/refund/forms/refund-ticket/index.js +8 -7
  86. package/lib/refund/forms/refund-ticket/otherTicketRefund.d.ts.map +1 -1
  87. package/lib/refund/forms/refund-ticket/otherTicketRefund.js +53 -19
  88. package/lib/refund/forms/residualValueOnTravelCard/index.d.ts.map +1 -1
  89. package/lib/refund/forms/residualValueOnTravelCard/index.js +5 -3
  90. package/lib/refund/index.d.ts.map +1 -1
  91. package/lib/refund/index.js +39 -6
  92. package/lib/refund/refundFormMachine.d.ts +37 -13
  93. package/lib/refund/refundFormMachine.d.ts.map +1 -1
  94. package/lib/refund/refundFormMachine.js +96 -3
  95. package/lib/server/journey-planner/index.d.ts.map +1 -1
  96. package/lib/server/journey-planner/index.js +15 -6
  97. package/lib/server/journey-planner/validators.d.ts +2 -0
  98. package/lib/server/journey-planner/validators.d.ts.map +1 -1
  99. package/lib/server/journey-planner/validators.js +1 -0
  100. package/lib/success-content/success-content.d.ts.map +1 -1
  101. package/lib/success-content/success-content.js +2 -1
  102. package/lib/ticket-control/events.d.ts +1 -1
  103. package/lib/ticket-control/events.d.ts.map +1 -1
  104. package/lib/ticket-control/forms/feeComplaintForm.d.ts.map +1 -1
  105. package/lib/ticket-control/forms/feeComplaintForm.js +28 -25
  106. package/lib/ticket-control/forms/feedbackForm.d.ts.map +1 -1
  107. package/lib/ticket-control/forms/feedbackForm.js +22 -14
  108. package/lib/ticket-control/forms/postponePayment.d.ts.map +1 -1
  109. package/lib/ticket-control/forms/postponePayment.js +8 -7
  110. package/lib/ticket-control/index.d.ts.map +1 -1
  111. package/lib/ticket-control/index.js +15 -6
  112. package/lib/ticket-control/ticket-control-form-machine.d.ts +14 -10
  113. package/lib/ticket-control/ticket-control-form-machine.d.ts.map +1 -1
  114. package/lib/ticket-control/ticket-control-form-machine.js +17 -2
  115. package/lib/ticketing/forms/app/appAccountForm.d.ts.map +1 -1
  116. package/lib/ticketing/forms/app/appAccountForm.js +9 -8
  117. package/lib/ticketing/forms/app/appTicketingForm.d.ts.map +1 -1
  118. package/lib/ticketing/forms/app/appTicketingForm.js +13 -12
  119. package/lib/ticketing/forms/app/appTravelSuggestionForm.d.ts.map +1 -1
  120. package/lib/ticketing/forms/app/appTravelSuggestionForm.js +6 -5
  121. package/lib/ticketing/forms/app/index.d.ts.map +1 -1
  122. package/lib/ticketing/forms/app/index.js +2 -1
  123. package/lib/ticketing/forms/priceAndTicketTypesForm.d.ts.map +1 -1
  124. package/lib/ticketing/forms/priceAndTicketTypesForm.js +6 -5
  125. package/lib/ticketing/forms/travel-card/index.d.ts.map +1 -1
  126. package/lib/ticketing/forms/travel-card/index.js +2 -1
  127. package/lib/ticketing/forms/travel-card/orderTravelCard.d.ts.map +1 -1
  128. package/lib/ticketing/forms/travel-card/orderTravelCard.js +2 -2
  129. package/lib/ticketing/forms/travel-card/travelCardQuestionForm.d.ts.map +1 -1
  130. package/lib/ticketing/forms/travel-card/travelCardQuestionForm.js +9 -8
  131. package/lib/ticketing/forms/webshop/index.d.ts.map +1 -1
  132. package/lib/ticketing/forms/webshop/index.js +2 -1
  133. package/lib/ticketing/forms/webshop/webshopAccountForm.d.ts.map +1 -1
  134. package/lib/ticketing/forms/webshop/webshopAccountForm.js +9 -8
  135. package/lib/ticketing/forms/webshop/webshopTicketingForm.d.ts.map +1 -1
  136. package/lib/ticketing/forms/webshop/webshopTicketingForm.js +12 -11
  137. package/lib/ticketing/index.d.ts.map +1 -1
  138. package/lib/ticketing/index.js +21 -6
  139. package/lib/ticketing/ticketingStateMachine.d.ts +12 -12
  140. package/lib/translations/index.d.ts +1 -1
  141. package/lib/translations/index.d.ts.map +1 -1
  142. package/lib/translations/index.js +2 -1
  143. package/lib/translations/pages/contact.d.ts +46 -0
  144. package/lib/translations/pages/contact.d.ts.map +1 -1
  145. package/lib/translations/pages/contact.js +50 -2
  146. package/lib/translations/translations-context.d.ts +2 -0
  147. package/lib/translations/translations-context.d.ts.map +1 -1
  148. package/lib/translations/translations-context.js +39 -0
  149. package/lib/types/submit-payloads.d.ts +19 -0
  150. package/lib/types/submit-payloads.d.ts.map +1 -1
  151. package/lib/types.d.ts +1 -1
  152. package/lib/types.d.ts.map +1 -1
  153. package/lib/url-form-type.d.ts +6 -0
  154. package/lib/url-form-type.d.ts.map +1 -0
  155. package/lib/url-form-type.js +21 -0
  156. package/lib/utils.d.ts.map +1 -1
  157. package/lib/utils.js +10 -0
  158. package/lib/validation/validationRules.d.ts.map +1 -1
  159. package/lib/validation/validationRules.js +7 -0
  160. package/package.json +1 -2
package/README.md CHANGED
@@ -8,9 +8,400 @@ A configurable contact form package for public transportation organizations.
8
8
  npm install @mrfylke/contact-form
9
9
  ```
10
10
 
11
- ## Usage
11
+ ### Peer dependencies
12
12
 
13
- See [INTEGRATION.md](./INTEGRATION.md) for detailed integration instructions.
13
+ ```bash
14
+ npm install react react-dom next @atb-as/theme @internationalized/date react-aria-components
15
+ ```
16
+
17
+ ## Quick start
18
+
19
+ ### 1. Create your configuration
20
+
21
+ ```ts
22
+ import { ContactFormConfig } from '@mrfylke/contact-form';
23
+
24
+ export const contactFormConfig: ContactFormConfig = {
25
+ theme: {
26
+ light: { /* colors, spacer, border */ },
27
+ dark: { /* same structure */ },
28
+ },
29
+ icons: {
30
+ 'transportation/BusFill': MyBusIcon,
31
+ // ... see Icon set section below
32
+ },
33
+ api: {
34
+ baseUrl: '/api',
35
+ endpoints: { submitForm: '/contact/submit' },
36
+ onSubmit,
37
+ },
38
+ org: {
39
+ name: 'Your Company',
40
+ supportEmail: 'support@yourcompany.com',
41
+ },
42
+ };
43
+ ```
44
+
45
+ ### 2. Wrap your app
46
+
47
+ ```tsx
48
+ import { ContactFormRoot } from '@mrfylke/contact-form';
49
+ import { contactFormConfig } from './config/contact-form-config';
50
+
51
+ export function RootLayout({ children }) {
52
+ const acceptLanguage = (await headers()).get('accept-language');
53
+ return (
54
+ <ContactFormRoot config={contactFormConfig} acceptLanguageHeader={acceptLanguage ?? undefined}>
55
+ {children}
56
+ </ContactFormRoot>
57
+ );
58
+ }
59
+ ```
60
+
61
+ ### 3. Render form pages
62
+
63
+ ```tsx
64
+ import { RefundContent, ContactPageLayout } from '@mrfylke/contact-form';
65
+
66
+ export default function ContactPage() {
67
+ return (
68
+ <ContactPageLayout title="Contact Us">
69
+ <RefundContent />
70
+ </ContactPageLayout>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ## API configuration
76
+
77
+ The package is decoupled from your backend. You supply an `onSubmit` callback; the package defines the payload shape.
78
+
79
+ ```ts
80
+ import type { FormSchemaName } from '@mrfylke/contact-form';
81
+
82
+ const onSubmit = async (
83
+ schemaName: FormSchemaName,
84
+ state: Record<string, unknown>,
85
+ ): Promise<Response> => {
86
+ return fetch('/api/contact/submit', {
87
+ method: 'POST',
88
+ headers: { 'Content-Type': 'application/json' },
89
+ body: JSON.stringify({ ...state, formType: schemaName }),
90
+ });
91
+ };
92
+ ```
93
+
94
+ `schemaName` is one of: `'ticketControl'` · `'refund'` · `'meansOfTransport'` · `'ticketing'` · `'journeyInfo'`
95
+
96
+ ### Routing submissions to different endpoints
97
+
98
+ ```ts
99
+ const onSubmit = async (schemaName: FormSchemaName, state) => {
100
+ const endpointMap: Record<FormSchemaName, string> = {
101
+ ticketControl: '/api/contact/ticket-control',
102
+ refund: '/api/contact/refund',
103
+ meansOfTransport: '/api/contact/means-of-transport',
104
+ ticketing: '/api/contact/ticketing',
105
+ journeyInfo: '/api/contact/journey-info',
106
+ };
107
+ return fetch(endpointMap[schemaName], {
108
+ method: 'POST',
109
+ headers: { 'Content-Type': 'application/json' },
110
+ body: JSON.stringify(state),
111
+ });
112
+ };
113
+ ```
114
+
115
+ ### Payload types
116
+
117
+ Import these in your API handlers to type-check incoming data:
118
+
119
+ ```ts
120
+ import type {
121
+ TicketControlSubmitPayload,
122
+ RefundSubmitPayload,
123
+ MeansOfTransportSubmitPayload,
124
+ TicketingSubmitPayload,
125
+ JourneyInfoSubmitPayload,
126
+ } from '@mrfylke/contact-form';
127
+ ```
128
+
129
+ Attachments are base64-encoded strings within the payload.
130
+
131
+ ### Lines endpoint
132
+
133
+ The package includes a ready-made route handler for fetching transit lines from Entur.
134
+
135
+ **Option 1 – ready-made handler (recommended)**
136
+
137
+ ```ts
138
+ // app/api/contact/lines/route.ts
139
+ import { createLinesRouteHandler } from '@mrfylke/contact-form/server';
140
+
141
+ export const GET = createLinesRouteHandler();
142
+ // Or with explicit authority:
143
+ // export const GET = createLinesRouteHandler({ getAuthorityId: () => 'ATB:Authority:2' });
144
+ ```
145
+
146
+ Set `ENTUR_AUTHORITY_ID` in your `.env` and point `api.endpoints.lines` at the mounted path.
147
+
148
+ **Option 2 – call `getLines()` directly**
149
+
150
+ ```ts
151
+ import { getLines } from '@mrfylke/contact-form/server';
152
+
153
+ export async function GET() {
154
+ const lines = await getLines(); // reads ENTUR_AUTHORITY_ID from env
155
+ return Response.json(lines);
156
+ }
157
+ ```
158
+
159
+ **Server exports:** `createLinesRouteHandler` · `getLines` · `getEnturAuthorityId` · `EnturAuthority`
160
+
161
+ ## `ContactFormRoot` props
162
+
163
+ | Prop | Type | Description |
164
+ |---|---|---|
165
+ | `config` | `ContactFormConfig` | Required. Theme, icons, API, org, features. |
166
+ | `acceptLanguageHeader` | `string` | Raw `Accept-Language` header from the server. |
167
+ | `initialLanguage` | `Language` | Client-side initial language when no header is available. |
168
+ | `forceTheme` | `'light' \| 'dark'` | Forces a theme variant; disables the dark-mode toggle. |
169
+ | `translationsOverride` | `ContactFormTranslationsOverride` | Merges additional translation overrides on top of `config.translations`. |
170
+
171
+ ## Container and layout
172
+
173
+ ```tsx
174
+ import { ContactFormRoot, ContactFormContainer, ContactPageLayout } from '@mrfylke/contact-form';
175
+
176
+ <ContactFormRoot config={contactFormConfig}>
177
+ <ContactFormContainer header={<MyHeader />} footer={<MyFooter />}>
178
+ <ContactPageLayout title="Contact" backLink={backLink}>
179
+ {content}
180
+ </ContactPageLayout>
181
+ </ContactFormContainer>
182
+ </ContactFormRoot>
183
+ ```
184
+
185
+ Header and footer are your app's responsibility. Their components can use `useContactFormConfig()`, `useTheme()`, and `useTranslation()` from the package.
186
+
187
+ ### Layout config
188
+
189
+ ```ts
190
+ layout: {
191
+ basePath: '/contact',
192
+ backLinkDefault: { href: '/', label: PageText.Contact.contactPageLayout.homeLink },
193
+ successPath: '/contact/thanks', // default: basePath + '/success'
194
+ errorPath: '/contact/oops', // default: basePath + '/error'
195
+ successContent: <CustomThanksMessage />,
196
+ errorContent: <CustomErrorMessage />,
197
+ footerUrls: {
198
+ privacy: 'https://example.com/privacy',
199
+ accessibility: 'https://example.com/accessibility',
200
+ help: '/contact',
201
+ tickets: 'https://example.com/tickets',
202
+ homePage: { href: 'https://example.com', name: 'example.com' },
203
+ },
204
+ }
205
+ ```
206
+
207
+ Render `SuccessContent`/`ErrorContent` (or your overrides) without `ContactPageLayout` — they are standalone result pages.
208
+
209
+ ## Deep-linking
210
+
211
+ Form type selections are reflected in the URL as a `?type=` query parameter. This allows direct links to specific form states.
212
+
213
+ **Reading on load:** on mount each form section reads `?type=` and selects the matching radio if it is in the enabled set.
214
+
215
+ **Writing on change:** selecting a radio updates `?type=` via `replaceState` (no page reload).
216
+
217
+ **Bypassing agreement checkboxes:** for sections with an initial agreement step (refund), append `&agreed=true` to pre-tick the checkbox and jump straight to the form.
218
+
219
+ **Available values per section:**
220
+
221
+ | Section | `?type=` values |
222
+ |---|---|
223
+ | Means of transport | `driver` · `transportation` · `delay` · `stop` · `serviceOffering` · `injury` |
224
+ | Ticket control | `feeComplaint` · `feedback` · `postponePayment` |
225
+ | Refund | `refundOfTicket` · `refundAndTravelGuarantee` · `residualValueOnTravelCard` |
226
+ | Ticketing | `priceAndTicketTypes` · `app` · `webshop` · `travelCard` |
227
+
228
+ Examples:
229
+ - `/contact/refund?type=refundAndTravelGuarantee&agreed=true` — opens travel guarantee form with agreement pre-accepted
230
+ - `/contact/ticket-control?type=feeComplaint` — opens fee complaint form directly
231
+
232
+ Only values present in `formSchemaOverrides.enabledFormTypes` / `enabledFormCategories` are accepted; invalid values are ignored.
233
+
234
+ ## Translations
235
+
236
+ Translations are required — the package ships no built-in copy. The key structure follows **planner-web** as source of truth.
237
+
238
+ ```tsx
239
+ import {
240
+ PageText,
241
+ ComponentText,
242
+ CommonText,
243
+ ServerText,
244
+ } from '@mrfylke/contact-form/translations';
245
+
246
+ const contactFormConfig: ContactFormConfig = {
247
+ translations: { pages: PageText, components: ComponentText, common: CommonText, server: ServerText },
248
+ };
249
+ ```
250
+
251
+ To override specific keys, merge with the defaults:
252
+
253
+ ```tsx
254
+ const customTranslations = {
255
+ pages: {
256
+ Contact: {
257
+ contactPageLayout: {
258
+ title: { no: 'Kontakt oss', en: 'Contact us', nn: 'Kontakt oss' },
259
+ },
260
+ },
261
+ },
262
+ components: ComponentText,
263
+ common: CommonText,
264
+ server: ServerText,
265
+ };
266
+ ```
267
+
268
+ ## Theme
269
+
270
+ ### Using `@atb-as/theme` (recommended)
271
+
272
+ ```ts
273
+ import { createThemesFor, ThemeVariant } from '@atb-as/theme';
274
+ import { adaptAtbTheme } from '@mrfylke/contact-form/config';
275
+
276
+ const theme = adaptAtbTheme(createThemesFor(ThemeVariant.FRAM));
277
+ ```
278
+
279
+ ### Manual theme
280
+
281
+ ```ts
282
+ type Theme = {
283
+ light: ThemeColors;
284
+ dark: ThemeColors;
285
+ };
286
+
287
+ type ThemeColors = {
288
+ color: {
289
+ transport: TransportColors;
290
+ status: StatusColors;
291
+ background: BackgroundColors;
292
+ interactive: { primary: InteractiveColor; secondary: InteractiveColor; destructive: InteractiveColor };
293
+ };
294
+ spacer: { small: string; medium: string; large: string; xLarge: string };
295
+ border: { radius: { small: string; regular: string; circle: string }; width: { slim: string; medium: string } };
296
+ };
297
+ ```
298
+
299
+ Use `themeOverrides` for small tweaks without rebuilding the full theme:
300
+
301
+ ```ts
302
+ themeOverrides: { light: { spacer: { medium: '1.25rem' } } }
303
+ ```
304
+
305
+ ## Icon set
306
+
307
+ Provide a React component for each key. Each component accepts `size`, `className`, `role`, `alt`, and `style` props.
308
+
309
+ **Transportation:** `transportation/BusFill` · `transportation/TrainFill` · `transportation/TramFill` · `transportation/WalkFill` · `transportation/BicycleFill` · `transportation/PlaneFill` · `transportation/BoatFill` · `transportation/FerryFill` · `transportation/MetroFill` · `transportation/UnknownFill`
310
+
311
+ **Actions:** `actions/Close` · `actions/Clear` · `actions/Add`
312
+
313
+ **Status:** `status/CheckmarkFill` · `status/ErrorFill`
314
+
315
+ ## Overriding standard behavior
316
+
317
+ | Feature | Default | Config key |
318
+ |---|---|---|
319
+ | Pages/tabs | All 7 tabs, all internal | `pagesOverrides` |
320
+ | Ferry transport mode | Included | `features.includeFerry` |
321
+ | Form schemas | All types/categories per page | `formSchemaOverrides` |
322
+ | Header links | Single optional home link | `layout.headerLinksOverride` |
323
+ | Footer links | Standard sections | `layout.footerLinksOverride` |
324
+ | Translations | No defaults — must be provided | `translations` |
325
+ | Theme | Must be provided | `theme` + `themeOverrides` |
326
+
327
+ ### Pages
328
+
329
+ ```ts
330
+ pagesOverrides: {
331
+ hidePageIds: ['lost-property'],
332
+ externalUrlByPageId: { refund: 'https://example.com/refund-form' },
333
+ }
334
+ ```
335
+
336
+ Page IDs: `'ticket-control'` · `'refund'` · `'means-of-transport'` · `'ticketing'` · `'lost-property'` · `'group-travel'` · `'journey-info'`
337
+
338
+ ### Ferry flag
339
+
340
+ ```ts
341
+ features: { includeFerry: false } // removes ferry from transport mode dropdowns and line queries
342
+ ```
343
+
344
+ ### Form schema overrides
345
+
346
+ ```ts
347
+ formSchemaOverrides: {
348
+ ticketControl: { enabledFormTypes: ['feeComplaint', 'feedback'] },
349
+ meansOfTransport: { enabledFormTypes: ['driver', 'delay', 'transportation'] },
350
+ refund: { enabledFormCategories: ['refundOfTicket', 'refundAndTravelGuarantee'] },
351
+ ticketing: { enabledFormCategories: ['app', 'webshop'] },
352
+ }
353
+ ```
354
+
355
+ Can also be loaded from a JSON/YAML file:
356
+
357
+ ```ts
358
+ import schemaOverrides from './config/contact-form-schema.json';
359
+ // ...
360
+ formSchemaOverrides: schemaOverrides,
361
+ ```
362
+
363
+ ### Header links override
364
+
365
+ ```ts
366
+ layout: {
367
+ headerLinksOverride: [
368
+ { href: 'https://example.com', label: { no: 'Hjem', en: 'Home', nn: 'Heim' } },
369
+ ],
370
+ }
371
+ ```
372
+
373
+ Empty array removes all header links.
374
+
375
+ ### Footer links override
376
+
377
+ ```ts
378
+ layout: {
379
+ footerLinksOverride: [
380
+ { href: 'https://example.com/tickets', label: { no: 'Billetter', en: 'Tickets', nn: 'Billettar' }, section: 'general' },
381
+ { href: 'https://example.com/privacy', label: { no: 'Personvern', en: 'Privacy', nn: 'Personvern' }, section: 'bottom' },
382
+ ],
383
+ }
384
+ ```
385
+
386
+ Links with `section: 'bottom'` replace the standard privacy/accessibility row.
387
+
388
+ ## Styling
389
+
390
+ ```ts
391
+ import '@mrfylke/contact-form/lib/styles.css';
392
+ ```
393
+
394
+ ## Examples
395
+
396
+ See the `apps/next-demo` directory for a complete implementation, including commented examples of all override options in `apps/next-demo/src/config/contact-form-config.tsx`.
397
+
398
+ ## Troubleshooting
399
+
400
+ **`useContactFormConfig must be used within ContactFormConfigProvider`** — wrap your app with `<ContactFormRoot>`.
401
+
402
+ **Icons not showing** — verify all required icon keys are provided and check the browser console for warnings.
403
+
404
+ **Theme not applying** — ensure `<ContactFormRoot>` wraps everything and your theme object matches the expected structure.
14
405
 
15
406
  ## License
16
407
 
@@ -30,6 +30,11 @@ export type ContactFormRootProps = {
30
30
  * variant is always used.
31
31
  */
32
32
  forceTheme?: 'light' | 'dark';
33
+ /**
34
+ * Optional explicit initial dark-mode state, e.g. restored from a cookie.
35
+ * Ignored when forceTheme is set.
36
+ */
37
+ defaultDarkMode?: boolean;
33
38
  /**
34
39
  * Optional additional translation overrides that will be merged on top of config.translations.
35
40
  * Config translations are required; this allows additional overrides at the root level if needed.
@@ -43,5 +48,5 @@ export type ContactFormRootProps = {
43
48
  * providers together. Renders children inside the provider stack so
44
49
  * consumers can pass their routes/layout as children.
45
50
  */
46
- export declare function ContactFormRoot({ config, acceptLanguageHeader, initialLanguage, defaultLanguage, onLanguageChange, forceTheme, translationsOverride, children, }: ContactFormRootProps): import("react/jsx-runtime").JSX.Element;
51
+ export declare function ContactFormRoot({ config, acceptLanguageHeader, initialLanguage, defaultLanguage, onLanguageChange, forceTheme, defaultDarkMode, translationsOverride, children, }: ContactFormRootProps): import("react/jsx-runtime").JSX.Element;
47
52
  //# sourceMappingURL=ContactFormRoot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ContactFormRoot.d.ts","sourceRoot":"","sources":["../src/ContactFormRoot.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,EACL,KAAK,iBAAiB,EAEvB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAEL,KAAK,+BAA+B,EACpC,QAAQ,EACT,MAAM,gBAAgB,CAAC;AAQxB,MAAM,MAAM,oBAAoB,GAAG;IACjC,mEAAmE;IACnE,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,eAAe,CAAC,EAAE,QAAQ,CAAC;IAC3B;;;OAGG;IACH,eAAe,CAAC,EAAE,QAAQ,CAAC;IAC3B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC5C;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,+BAA+B,CAAC;IACvD,sDAAsD;IACtD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,oBAAoB,EACpB,QAAQ,GACT,EAAE,oBAAoB,2CAyBtB"}
1
+ {"version":3,"file":"ContactFormRoot.d.ts","sourceRoot":"","sources":["../src/ContactFormRoot.tsx"],"names":[],"mappings":"AAAA,OAAc,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAE9C,OAAO,EACL,KAAK,iBAAiB,EAEvB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAEL,KAAK,+BAA+B,EACpC,QAAQ,EACT,MAAM,gBAAgB,CAAC;AAQxB,MAAM,MAAM,oBAAoB,GAAG;IACjC,mEAAmE;IACnE,MAAM,EAAE,iBAAiB,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;OAGG;IACH,eAAe,CAAC,EAAE,QAAQ,CAAC;IAC3B;;;OAGG;IACH,eAAe,CAAC,EAAE,QAAQ,CAAC;IAC3B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IAC5C;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B;;;OAGG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,+BAA+B,CAAC;IACvD,sDAAsD;IACtD,QAAQ,CAAC,EAAE,SAAS,CAAC;CACtB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,oBAAoB,EACpB,QAAQ,GACT,EAAE,oBAAoB,2CAyBtB"}
@@ -15,13 +15,13 @@ function ThemeSync({ children }) {
15
15
  * providers together. Renders children inside the provider stack so
16
16
  * consumers can pass their routes/layout as children.
17
17
  */
18
- function ContactFormRoot({ config, acceptLanguageHeader, initialLanguage, defaultLanguage, onLanguageChange, forceTheme, translationsOverride, children, }) {
18
+ function ContactFormRoot({ config, acceptLanguageHeader, initialLanguage, defaultLanguage, onLanguageChange, forceTheme, defaultDarkMode, translationsOverride, children, }) {
19
19
  const header = acceptLanguageHeader ?? deriveHeaderFromInitialLanguage(initialLanguage);
20
20
  // Merge config translations (required) with optional root-level override (override takes precedence)
21
21
  const mergedTranslations = translationsOverride
22
22
  ? deepMerge(config.translations, translationsOverride)
23
23
  : config.translations;
24
- return ((0, jsx_runtime_1.jsx)(config_1.ContactFormConfigProvider, { config: config, children: (0, jsx_runtime_1.jsx)(theme_1.ThemeProvider, { forceTheme: forceTheme, children: (0, jsx_runtime_1.jsx)(ThemeSync, { children: (0, jsx_runtime_1.jsx)(translations_1.AppLanguageProvider, { serverAcceptLanguage: header ?? 'no,en;q=0.9', overrides: mergedTranslations, defaultLanguage: defaultLanguage, onLanguageChange: onLanguageChange, children: children }) }) }) }));
24
+ return ((0, jsx_runtime_1.jsx)(config_1.ContactFormConfigProvider, { config: config, children: (0, jsx_runtime_1.jsx)(theme_1.ThemeProvider, { forceTheme: forceTheme, defaultDarkMode: defaultDarkMode, children: (0, jsx_runtime_1.jsx)(ThemeSync, { children: (0, jsx_runtime_1.jsx)(translations_1.AppLanguageProvider, { serverAcceptLanguage: header ?? 'no,en;q=0.9', overrides: mergedTranslations, defaultLanguage: defaultLanguage, onLanguageChange: onLanguageChange, children: children }) }) }) }));
25
25
  }
26
26
  function deriveHeaderFromInitialLanguage(language) {
27
27
  if (!language) {
@@ -4,7 +4,7 @@ export declare const commonEvents: {
4
4
  type: "VALIDATE";
5
5
  } | {
6
6
  type: "ON_INPUT_CHANGE";
7
- inputName: "address" | "attachments" | "bankAccountNumber" | "city" | "date" | "email" | "firstName" | "feedback" | "IBAN" | "lastName" | "phoneNumber" | "postalCode" | "SWIFT" | "toStop" | "fromStop" | "plannedDepartureTime";
7
+ inputName: "address" | "attachments" | "bankAccountNumber" | "city" | "date" | "email" | "firstName" | "feedback" | "IBAN" | "lastName" | "phoneNumber" | "postalCode" | "SWIFT" | "toStop" | "fromStop" | "plannedDepartureTime" | "tripId";
8
8
  value: any;
9
9
  } | {
10
10
  type: "ON_TRANSPORTMODE_CHANGE";
@@ -1 +1 @@
1
- {"version":3,"file":"commoneEvents.d.ts","sourceRoot":"","sources":["../src/commoneEvents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC;AAEzB,eAAO,MAAM,YAAY,EACrB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IACE,IAAI,EAAE,iBAAiB,CAAC;IACxB,SAAS,EACL,SAAS,GACT,aAAa,GACb,mBAAmB,GACnB,MAAM,GACN,MAAM,GACN,OAAO,GACP,WAAW,GACX,UAAU,GACV,MAAM,GACN,UAAU,GACV,aAAa,GACb,YAAY,GACZ,OAAO,GACP,QAAQ,GACR,UAAU,GACV,sBAAsB,CAAC;IAC3B,KAAK,EAAE,GAAG,CAAC;CACZ,GACD;IACE,IAAI,EAAE,yBAAyB,CAAC;IAChC,KAAK,EAAE,iBAAiB,CAAC;CAC1B,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,IAAI,GAAG,SAAS,CAAC;CACzB,CAAC"}
1
+ {"version":3,"file":"commoneEvents.d.ts","sourceRoot":"","sources":["../src/commoneEvents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC;AAEzB,eAAO,MAAM,YAAY,EACrB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GACpB;IACE,IAAI,EAAE,iBAAiB,CAAC;IACxB,SAAS,EACL,SAAS,GACT,aAAa,GACb,mBAAmB,GACnB,MAAM,GACN,MAAM,GACN,OAAO,GACP,WAAW,GACX,UAAU,GACV,MAAM,GACN,UAAU,GACV,aAAa,GACb,YAAY,GACZ,OAAO,GACP,QAAQ,GACR,UAAU,GACV,sBAAsB,GACtB,QAAQ,CAAC;IACb,KAAK,EAAE,GAAG,CAAC;CACZ,GACD;IACE,IAAI,EAAE,yBAAyB,CAAC;IAChC,KAAK,EAAE,iBAAiB,CAAC;CAC1B,GACD;IACE,IAAI,EAAE,gBAAgB,CAAC;IACvB,KAAK,EAAE,IAAI,GAAG,SAAS,CAAC;CACzB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"date-selector.d.ts","sourceRoot":"","sources":["../../../src/components/form/date-selector.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAE,gBAAgB,EAAkB,MAAM,oBAAoB,CAAC;AAItE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,EAAE,EACF,KAAK,EACL,KAAK,EACL,UAAU,EACV,YAAY,EACZ,QAAQ,GACT,EAAE,iBAAiB,2CAuEnB"}
1
+ {"version":3,"file":"date-selector.d.ts","sourceRoot":"","sources":["../../../src/components/form/date-selector.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAE,gBAAgB,EAAkB,MAAM,oBAAoB,CAAC;AAItE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC,CAAC;AAEF,MAAM,CAAC,OAAO,UAAU,YAAY,CAAC,EACnC,EAAE,EACF,KAAK,EACL,KAAK,EACL,UAAU,EACV,YAAY,EACZ,QAAQ,GACT,EAAE,iBAAiB,2CAwEnB"}
@@ -14,8 +14,9 @@ const icon_1 = require("../icon");
14
14
  const error_message_1 = require("../error-message");
15
15
  function DateSelector({ id, label, value, isRequired, errorMessage, onChange, }) {
16
16
  const { t } = (0, translations_1.useTranslation)();
17
- const zonedDateTime = value
18
- ? (0, date_1.fromDate)(new Date(value || ''), 'Europe/Oslo')
19
- : undefined;
20
- return ((0, jsx_runtime_1.jsxs)("div", { className: form_module_css_1.default.dateSelectorContainer, children: [(0, jsx_runtime_1.jsxs)(react_aria_components_1.DatePicker, { id: `date_selector__${id}`, granularity: "day", value: zonedDateTime, onChange: (e) => e && onChange(e.toString().slice(0, 10)), className: form_module_css_1.default.dateSelector, shouldForceLeadingZeros: true, "aria-label": label, "aria-labelledby": `label-${id}`, children: [(0, jsx_runtime_1.jsx)(form_component_label_1.default, { label: label, htmlFor: `date-selector-${id}`, isRequired: isRequired }), (0, jsx_runtime_1.jsxs)(react_aria_components_1.Group, { className: form_module_css_1.default.calendarSelectorGroup, children: [(0, jsx_runtime_1.jsx)(react_aria_components_1.DateInput, { className: form_module_css_1.default.dateSelectorInput, children: (segment) => ((0, jsx_runtime_1.jsx)(react_aria_components_1.DateSegment, { segment: segment, className: form_module_css_1.default.dateSelectorSegment })) }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Button, { className: form_module_css_1.default.calendarButton, children: (0, jsx_runtime_1.jsx)(icon_1.MonoIcon, { icon: "time/Date" }) })] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Popover, { className: form_module_css_1.default.calendarDialog, placement: "bottom right", children: (0, jsx_runtime_1.jsx)(react_aria_components_1.Dialog, { children: (0, jsx_runtime_1.jsxs)(react_aria_components_1.Calendar, { children: [(0, jsx_runtime_1.jsxs)("header", { className: form_module_css_1.default.calendarDialog__header, children: [(0, jsx_runtime_1.jsx)(react_aria_components_1.Button, { slot: "previous", className: form_module_css_1.default.calendarDialog__headerButtons, children: (0, jsx_runtime_1.jsx)(icon_1.MonoIcon, { icon: "navigation/ArrowLeft" }) }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Heading, { className: form_module_css_1.default.calendarDialog__title }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Button, { slot: "next", className: form_module_css_1.default.calendarDialog__headerButtons, children: (0, jsx_runtime_1.jsx)(icon_1.MonoIcon, { icon: "navigation/ArrowRight" }) })] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.CalendarGrid, { className: form_module_css_1.default.calendarGrid, children: (date) => ((0, jsx_runtime_1.jsx)(react_aria_components_1.CalendarCell, { date: date, className: form_module_css_1.default.calendarGrid__cell })) })] }) }) })] }), (0, jsx_runtime_1.jsx)("input", { id: `date_selector__${id}`, type: "hidden", value: value ?? '' }), errorMessage && (0, jsx_runtime_1.jsx)(error_message_1.ErrorMessage, { message: t(errorMessage) })] }));
17
+ const zonedDateTime = value ? (0, date_1.fromDate)(new Date(value), 'Europe/Oslo') : null;
18
+ return ((0, jsx_runtime_1.jsxs)("div", { className: form_module_css_1.default.dateSelectorContainer, children: [(0, jsx_runtime_1.jsxs)(react_aria_components_1.DatePicker, { id: `date_selector__${id}`, granularity: "day", value: zonedDateTime, onChange: (e) => e && onChange(e.toString().slice(0, 10)), onOpenChange: (isOpen) => {
19
+ if (isOpen && !value)
20
+ onChange((0, date_1.today)('Europe/Oslo').toString());
21
+ }, className: form_module_css_1.default.dateSelector, shouldForceLeadingZeros: true, "aria-label": label, "aria-labelledby": `label-${id}`, children: [(0, jsx_runtime_1.jsx)(form_component_label_1.default, { label: label, htmlFor: `date-selector-${id}`, isRequired: isRequired }), (0, jsx_runtime_1.jsxs)(react_aria_components_1.Group, { className: form_module_css_1.default.calendarSelectorGroup, children: [(0, jsx_runtime_1.jsx)(react_aria_components_1.DateInput, { className: form_module_css_1.default.dateSelectorInput, children: (segment) => ((0, jsx_runtime_1.jsx)(react_aria_components_1.DateSegment, { segment: segment, className: form_module_css_1.default.dateSelectorSegment })) }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Button, { className: form_module_css_1.default.calendarButton, children: (0, jsx_runtime_1.jsx)(icon_1.MonoIcon, { icon: "time/Date" }) })] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Popover, { className: form_module_css_1.default.calendarDialog, placement: "bottom right", children: (0, jsx_runtime_1.jsx)(react_aria_components_1.Dialog, { children: (0, jsx_runtime_1.jsxs)(react_aria_components_1.Calendar, { children: [(0, jsx_runtime_1.jsxs)("header", { className: form_module_css_1.default.calendarDialog__header, children: [(0, jsx_runtime_1.jsx)(react_aria_components_1.Button, { slot: "previous", className: form_module_css_1.default.calendarDialog__headerButtons, children: (0, jsx_runtime_1.jsx)(icon_1.MonoIcon, { icon: "navigation/ArrowLeft" }) }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Heading, { className: form_module_css_1.default.calendarDialog__title }), (0, jsx_runtime_1.jsx)(react_aria_components_1.Button, { slot: "next", className: form_module_css_1.default.calendarDialog__headerButtons, children: (0, jsx_runtime_1.jsx)(icon_1.MonoIcon, { icon: "navigation/ArrowRight" }) })] }), (0, jsx_runtime_1.jsx)(react_aria_components_1.CalendarGrid, { className: form_module_css_1.default.calendarGrid, children: (date) => ((0, jsx_runtime_1.jsx)(react_aria_components_1.CalendarCell, { date: date, className: form_module_css_1.default.calendarGrid__cell })) })] }) }) })] }), (0, jsx_runtime_1.jsx)("input", { id: `date_selector__${id}`, type: "hidden", value: value ?? '' }), errorMessage && (0, jsx_runtime_1.jsx)(error_message_1.ErrorMessage, { message: t(errorMessage) })] }));
21
22
  }
@@ -0,0 +1,16 @@
1
+ import { TranslatedString } from '../../translations';
2
+ type DepartureTimeFieldProps = {
3
+ id?: string;
4
+ date?: string;
5
+ lineId?: string;
6
+ fromStopId?: string;
7
+ toStopId?: string;
8
+ selectedTripId?: string;
9
+ plannedDepartureTime?: string;
10
+ onChange: (departureTime?: string, tripId?: string) => void;
11
+ isRequired?: boolean;
12
+ errorMessage?: TranslatedString;
13
+ };
14
+ export declare function DepartureTimeField({ id, date, lineId, fromStopId, toStopId, selectedTripId, plannedDepartureTime, onChange, isRequired, errorMessage, }: DepartureTimeFieldProps): import("react/jsx-runtime").JSX.Element;
15
+ export {};
16
+ //# sourceMappingURL=departure-time-field.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"departure-time-field.d.ts","sourceRoot":"","sources":["../../../src/components/form/departure-time-field.tsx"],"names":[],"mappings":"AAMA,OAAO,EACL,gBAAgB,EAGjB,MAAM,oBAAoB,CAAC;AAI5B,KAAK,uBAAuB,GAAG;IAC7B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,EAAE,CAAC,aAAa,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5D,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,gBAAgB,CAAC;CACjC,CAAC;AAEF,wBAAgB,kBAAkB,CAAC,EACjC,EAA2B,EAC3B,IAAI,EACJ,MAAM,EACN,UAAU,EACV,QAAQ,EACR,cAAc,EACd,oBAAoB,EACpB,QAAQ,EACR,UAAU,EACV,YAAY,GACb,EAAE,uBAAuB,2CAoDzB"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DepartureTimeField = DepartureTimeField;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ const use_departures_1 = require("../../lines/use-departures");
9
+ const translations_1 = require("../../translations");
10
+ const select_1 = __importDefault(require("./select"));
11
+ const time_selector_1 = __importDefault(require("./time-selector"));
12
+ function DepartureTimeField({ id = 'plannedDepartureTime', date, lineId, fromStopId, toStopId, selectedTripId, plannedDepartureTime, onChange, isRequired, errorMessage, }) {
13
+ const PageText = (0, translations_1.usePageText)();
14
+ const { t } = (0, translations_1.useTranslation)();
15
+ const hasDeparturesEndpoint = (0, use_departures_1.useDeparturesEndpointConfigured)();
16
+ const { departures, isLoading } = (0, use_departures_1.useDepartures)(date, lineId, fromStopId, toStopId);
17
+ const label = t(PageText.Contact.input.plannedDepartureTime.label);
18
+ const errorStr = errorMessage ? t(errorMessage) : undefined;
19
+ if (!hasDeparturesEndpoint) {
20
+ return ((0, jsx_runtime_1.jsx)(time_selector_1.default, { id: id, label: label, value: plannedDepartureTime, isRequired: isRequired, errorMessage: errorMessage, onChange: (time) => onChange(time, undefined) }));
21
+ }
22
+ const selectedDeparture = departures.find((d) => selectedTripId
23
+ ? d.tripId === selectedTripId
24
+ : d.departureTime === plannedDepartureTime);
25
+ return ((0, jsx_runtime_1.jsx)(select_1.default, { id: id, label: label, value: selectedDeparture, options: departures, valueToId: (d) => d.tripId, valueToText: (d) => (0, use_departures_1.formatDepartureTime)(d.departureTime), onChange: (d) => onChange(d?.departureTime, d?.tripId), isRequired: isRequired, disabled: !lineId || !fromStopId || !toStopId || !date, placeholder: isLoading
26
+ ? t(PageText.Contact.input.plannedDepartureTime.loading)
27
+ : t(PageText.Contact.input.plannedDepartureTime.optionLabel), error: errorStr }));
28
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"description-modal.d.ts","sourceRoot":"","sources":["../../../src/components/form/description-modal.tsx"],"names":[],"mappings":"AASA,KAAK,qBAAqB,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE;QACZ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC;IACF,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF,QAAA,MAAM,gBAAgB,GAAI,mDAKvB,qBAAqB,mDA0FvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
1
+ {"version":3,"file":"description-modal.d.ts","sourceRoot":"","sources":["../../../src/components/form/description-modal.tsx"],"names":[],"mappings":"AASA,KAAK,qBAAqB,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE;QACZ,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC;IACF,WAAW,EAAE,OAAO,CAAC;IACrB,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB,CAAC;AAEF,QAAA,MAAM,gBAAgB,GAAI,mDAKvB,qBAAqB,mDA2FvB,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
@@ -13,6 +13,7 @@ const typography_1 = require("../typography");
13
13
  const button_1 = require("../button");
14
14
  const icon_1 = require("../icon");
15
15
  const DescriptionModal = ({ title, modalContent, isModalOpen, closeModal, }) => {
16
+ const PageText = (0, translations_1.usePageText)();
16
17
  const { t } = (0, translations_1.useTranslation)();
17
18
  const modalRef = (0, react_1.useRef)(null);
18
19
  const { description, instruction, bulletPoints } = modalContent;
@@ -37,7 +38,7 @@ const DescriptionModal = ({ title, modalContent, isModalOpen, closeModal, }) =>
37
38
  if (!isModalOpen)
38
39
  return null;
39
40
  return ((0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { className: form_module_css_1.default.modal_wrapper, initial: { opacity: 0 }, animate: { opacity: 1 }, exit: { opacity: 0 }, transition: { ease: 'easeInOut', duration: 0.15 }, children: (0, jsx_runtime_1.jsx)(focus_1.FocusScope, { contain: true, restoreFocus: true, autoFocus: true, children: (0, jsx_runtime_1.jsx)(framer_motion_1.motion.div, { ref: modalRef, initial: { opacity: 0, y: -30 }, animate: { opacity: 1, y: 0 }, exit: { opacity: 0, y: 0 }, transition: { ease: 'backOut', delay: 0.1, duration: 0.15 }, children: (0, jsx_runtime_1.jsxs)("dialog", { "aria-modal": "true", "aria-labelledby": "title", tabIndex: 0, className: form_module_css_1.default.modal_content, children: [(0, jsx_runtime_1.jsxs)("div", { className: form_module_css_1.default.modal_header, children: [(0, jsx_runtime_1.jsx)(typography_1.Typo.h2, { id: "title", textType: "body__m", autoFocus: true, children: title }), (0, jsx_runtime_1.jsx)(button_1.Button, { className: form_module_css_1.default.iconButton, onClick: closeModal, icon: { right: (0, jsx_runtime_1.jsx)(icon_1.MonoIcon, { icon: 'actions/Close' }) }, buttonProps: {
40
- 'aria-label': t(translations_1.PageText.Contact.components.modal.close()),
41
+ 'aria-label': t(PageText.Contact.components.modal.close()),
41
42
  } })] }), description && (0, jsx_runtime_1.jsx)(typography_1.Typo.p, { textType: "body__m", children: description }), bulletPoints && ((0, jsx_runtime_1.jsx)("ul", { className: form_module_css_1.default.modal__rules_list, children: bulletPoints.map((desc, index) => ((0, jsx_runtime_1.jsx)("li", { children: (0, jsx_runtime_1.jsx)(typography_1.Typo.p, { textType: "body__m", children: desc }, index) }, index))) })), instruction && ((0, jsx_runtime_1.jsx)(typography_1.Typo.p, { textType: "body__m__strong", children: instruction }))] }) }) }) }));
42
43
  };
43
44
  exports.default = DescriptionModal;
@@ -1 +1 @@
1
- {"version":3,"file":"file-input.d.ts","sourceRoot":"","sources":["../../../src/components/form/file-input.tsx"],"names":[],"mappings":"AAUA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;CACpC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC;AAI3D,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,EAAE,EACF,QAAQ,EACR,SAAS,EACT,KAAK,EACL,UAAkB,EAClB,IAAI,EACJ,YAAY,GACb,EAAE,cAAc,2CAiHhB"}
1
+ {"version":3,"file":"file-input.d.ts","sourceRoot":"","sources":["../../../src/components/form/file-input.tsx"],"names":[],"mappings":"AAUA,MAAM,MAAM,cAAc,GAAG;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;CACpC,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC;AAI3D,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,EAAE,EACF,QAAQ,EACR,SAAS,EACT,KAAK,EACL,UAAkB,EAClB,IAAI,EACJ,YAAY,GACb,EAAE,cAAc,2CAkHhB"}
@@ -16,6 +16,7 @@ const translations_1 = require("../../translations");
16
16
  const theme_1 = require("../../modules/theme");
17
17
  const MAX_ALLOWED_FILE_SIZE = 10 * 1024 * 1024; // 10MB
18
18
  function FileInput({ id, onChange, iconLabel, label, isRequired = false, name, errorMessage, }) {
19
+ const PageText = (0, translations_1.usePageText)();
19
20
  const { t } = (0, translations_1.useTranslation)();
20
21
  const { theme } = (0, theme_1.useTheme)();
21
22
  const { background } = theme.color;
@@ -47,7 +48,7 @@ function FileInput({ id, onChange, iconLabel, label, isRequired = false, name, e
47
48
  const filteredFiles = uploadedFiles.filter((file) => {
48
49
  const isTooLarge = file.size > MAX_ALLOWED_FILE_SIZE;
49
50
  if (isTooLarge) {
50
- alert(t(translations_1.PageText.Contact.components.fileinput.errorMessages.tooLarge(file.name)));
51
+ alert(t(PageText.Contact.components.fileinput.errorMessages.tooLarge(file.name)));
51
52
  }
52
53
  return !isTooLarge;
53
54
  });
@@ -7,5 +7,6 @@ export { default as Select } from './select';
7
7
  export { default as Textarea } from './textarea';
8
8
  export { default as DateSelector } from './date-selector';
9
9
  export { default as TimeSelector } from './time-selector';
10
+ export { DepartureTimeField } from './departure-time-field';
10
11
  export { SearchableSelect, getLineOptions, getStopOptions, } from './searchable-select';
11
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/form/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,cAAc,GACf,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/form/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,IAAI,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAC9E,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE,MAAM,SAAS,CAAC;AAC3C,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,OAAO,IAAI,QAAQ,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,OAAO,EACL,gBAAgB,EAChB,cAAc,EACd,cAAc,GACf,MAAM,qBAAqB,CAAC"}
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.getStopOptions = exports.getLineOptions = exports.SearchableSelect = exports.TimeSelector = exports.DateSelector = exports.Textarea = exports.Select = exports.Radio = exports.Input = exports.Fieldset = exports.FileInput = exports.FieldWrapperWithError = void 0;
6
+ exports.getStopOptions = exports.getLineOptions = exports.SearchableSelect = exports.DepartureTimeField = exports.TimeSelector = exports.DateSelector = exports.Textarea = exports.Select = exports.Radio = exports.Input = exports.Fieldset = exports.FileInput = exports.FieldWrapperWithError = void 0;
7
7
  var field_wrapper_with_error_1 = require("./field-wrapper-with-error");
8
8
  Object.defineProperty(exports, "FieldWrapperWithError", { enumerable: true, get: function () { return __importDefault(field_wrapper_with_error_1).default; } });
9
9
  var file_input_1 = require("./file-input");
@@ -22,6 +22,8 @@ var date_selector_1 = require("./date-selector");
22
22
  Object.defineProperty(exports, "DateSelector", { enumerable: true, get: function () { return __importDefault(date_selector_1).default; } });
23
23
  var time_selector_1 = require("./time-selector");
24
24
  Object.defineProperty(exports, "TimeSelector", { enumerable: true, get: function () { return __importDefault(time_selector_1).default; } });
25
+ var departure_time_field_1 = require("./departure-time-field");
26
+ Object.defineProperty(exports, "DepartureTimeField", { enumerable: true, get: function () { return departure_time_field_1.DepartureTimeField; } });
25
27
  var searchable_select_1 = require("./searchable-select");
26
28
  Object.defineProperty(exports, "SearchableSelect", { enumerable: true, get: function () { return searchable_select_1.SearchableSelect; } });
27
29
  Object.defineProperty(exports, "getLineOptions", { enumerable: true, get: function () { return searchable_select_1.getLineOptions; } });
@@ -1 +1 @@
1
- {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/form/input.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAY,MAAM,OAAO,CAAC;AAGrD,OAAO,EAAY,gBAAgB,EAAkB,MAAM,oBAAoB,CAAC;AAMhF,KAAK,UAAU,GAAG;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC;IACF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;CAC3D,GAAG,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAEzC,eAAO,MAAM,KAAK,GAAI,qHAanB,UAAU,4CAkEZ,CAAC;AAEF,eAAe,KAAK,CAAC"}
1
+ {"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../../src/components/form/input.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,WAAW,EAAY,MAAM,OAAO,CAAC;AAGrD,OAAO,EACL,gBAAgB,EAGjB,MAAM,oBAAoB,CAAC;AAM5B,KAAK,UAAU,GAAG;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE;QACb,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;KACzB,CAAC;IACF,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,CAAC,gBAAgB,CAAC,KAAK,IAAI,CAAC;CAC3D,GAAG,KAAK,CAAC,GAAG,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;AAEzC,eAAO,MAAM,KAAK,GAAI,qHAanB,UAAU,4CAmEZ,CAAC;AAEF,eAAe,KAAK,CAAC"}