From eb8a08e33341c34787cd2d5c11fdb48d8a94ed2f Mon Sep 17 00:00:00 2001 From: Aleix Suau Date: Wed, 29 Jul 2026 15:05:35 +0200 Subject: [PATCH 1/7] IS-11008 Document the HAAPI React SDK exports (TSDoc). Co-Authored-By: Claude Fable 5 --- src/haapi-react-sdk/haapi-stepper/README.md | 372 ------------------ .../data-access/useHaapiFetch.ts | 16 + .../HaapiStepperClientOperationUI.tsx | 4 +- ...pperWebAuthnRegistrationAttachmentCard.tsx | 2 + .../actions/form/HaapiStepperFormContext.ts | 2 + .../actions/form/HaapiStepperFormHook.ts | 8 + .../form/HaapiStepperFormSubmitButton.tsx | 23 ++ .../actions/form/HaapiStepperFormUI.tsx | 34 +- ...StepperFormValidationErrorInputWrapper.tsx | 17 + .../HaapiStepperCheckboxFormFieldUI.tsx | 28 ++ .../form/fields/HaapiStepperFormFieldUI.tsx | 26 ++ .../HaapiStepperPasswordFormFieldUI.tsx | 29 ++ .../fields/HaapiStepperSelectFormFieldUI.tsx | 28 ++ .../fields/HaapiStepperTextFormFieldUI.tsx | 28 ++ .../selector/HaapiStepperSelectorUI.tsx | 4 +- .../feature/stepper/HaapiStepper.tsx | 192 ++++++--- .../feature/stepper/HaapiStepperContext.tsx | 1 + .../stepper/HaapiStepperErrorNotifier.tsx | 17 + .../feature/stepper/HaapiStepperHook.ts | 3 +- .../feature/steps/HaapiStepperStepUI.tsx | 100 +++-- .../HaapiStepperBankIdPollingProgressUI.tsx | 2 + ...epperBankIdQrCodeAccessibilityMessages.tsx | 2 + .../ui/actions/HaapiStepperActionsUI.tsx | 29 ++ .../ui/links/HaapiStepperLinkUI.tsx | 24 ++ .../ui/links/HaapiStepperLinksUI.tsx | 6 +- .../ui/links/HaapiStepperQrCodeLinkDialog.tsx | 1 + .../ui/messages/HaapiStepperMessageUI.tsx | 24 ++ .../ui/messages/HaapiStepperMessagesUI.tsx | 4 +- 28 files changed, 536 insertions(+), 490 deletions(-) delete mode 100644 src/haapi-react-sdk/haapi-stepper/README.md diff --git a/src/haapi-react-sdk/haapi-stepper/README.md b/src/haapi-react-sdk/haapi-stepper/README.md deleted file mode 100644 index 54faf3ed..00000000 --- a/src/haapi-react-sdk/haapi-stepper/README.md +++ /dev/null @@ -1,372 +0,0 @@ -# HAAPI Frontend Library - -## Conceptual Glossary - -- **Flow**: sequence of steps that results in either a successful authentication (`HAAPI_STEPS.COMPLETED_WITH_SUCCESS`) or an error/failure (`HAAPI_PROBLEM_STEPS.COMPLETED_WITH_ERROR`). -- **Step**: A single stage in the authentication flow, often represented as a screen (e.g., a login page). A step can be composed of actions, links, and messages. - - [Step types](./util/types/haapi-step.types.ts) -- **Action**: instructions about how to progress to the next step in the authentication flow. Actions often require specific user input and change the state of the authentication (e.g., submitting a form). - - [Action types](./util/types/haapi-action.types.ts) -- **Link**: instructions about how to navigate to an alternative but related path (e.g. starting a password reset flow from the main authentication step) - - [Link](./util/types/haapi-step.types.ts#L300) -- **Message**: Text that provides context to the user about the state of the authentication flow and possible interaction options (e.g., validation errors, warnings, or instructions). - - [Message](./util/types/haapi-step.types.ts#L290) - -Check out the following HAAPI documentation for in-depth technical details: - -* [Browserless Login Solution](https://curity.io/product/user-journey-orchestration/browserless-login/) -* [What is Hypermedia Authentication API](https://curity.io/resources/learn/what-is-hypermedia-authentication-api/) -* [HAAPI Data Model](https://curity.io/docs/haapi-data-model/latest/). - - -## Purpose - -The HAAPI Frontend Library is a set of React components that provides: - - A built-in, full management of HAAPI flows in the frontend with minimal setup: - ```tsx - - - - ``` - - A simple toolbox to fully customize HAAPI flows in the frontend, composed of the [HAAPI Stepper](#haapi-stepper), [HAAPI UI Step](#haapi-ui-step), and [HAAPI UI Components](#haapi-stepper-ui-components). - -## HAAPI Stepper - -### Purpose - -The HAAPI Stepper is a React UI-less component designed to handle complex, multi-step authentication HAAPI workflows. It provides a declarative way to manage HAAPI (HTTP Authentication API) flows, abstracting away the complexity of step-by-step user interactions, HTTP requests, and state transitions. - -### Key Features - -- **Step Management**: Automatically handles navigation between authentication steps - - **Automatic Redirections**: Seamlessly handles server-driven redirections without exposing them to consumers - - **Automatic Polling**: Polling steps are exposed to allow custom UI, but polling requests are handled automatically based on the configured interval - - **Automatic Continue Same**: Continue Same responses are automatically merged with the current step without exposing them to consumers -- **State Management**: Centralizes loading, error, and current step state. -- **Action Processing**: Supports multiple action types (forms, links, client operations). -- **Error Handling**: Provides comprehensive error state management with user feedback. -- **Type Safety**: Offers full TypeScript support with strict typing. - - -### Public API - -#### HAAPI Stepper Provider Component - -The `HaapiStepper` sets up the HAAPI API and makes it available to child components: - -```tsx - - {children} - -``` - -#### HAAPI Stepper Hook: `useHaapiStepper()` - -The `useHaapiStepper` hook gives access to the HAAPI API to consumer components: - -```tsx -const { currentStep, loading, error, nextStep } = useHaapiStepper(); -``` - -**State Properties:** -- `currentStep: HaapiProviderStep | null` - The current step in the flow. -- `loading: boolean` - The loading state during transitions. -- `error: HaapiStepperError | null` - Error information if something goes wrong. - -**Actions:** -- `nextStep(action, payload?)` - Advances to the next step with optional form data. - -### Basic Setup - -#### Bootstrap Configuration - -The `HaapiStepper` needs a **bootstrap configuration** — at minimum an `initialUrl` (where the flow starts) and a `haapi` driver config (the HAAPI web-driver settings). - -> Only one HAAPI configuration is supported per page load — the underlying driver is a process-global singleton; switching `bootstrap.haapi` mid-page throws (see [`useHaapiFetch.ts`](./data-access/useHaapiFetch.ts)). - -The bootstrap configuration supports two delivery modes, designed for two different deployment shapes: - -##### Served mode (default) - -When the `HaapiStepper` runs inside a server-rendered shell — like the Curity HAAPI React App — the shell injects the bootstrap configuration onto `window.__CONFIG__` *before* the SPA boots. In that case, no configuration prop is needed: - -```tsx -// The shell has already injected window.__CONFIG__ — just mount the stepper. - - - -``` - -This is the default behavior and covers the vast majority of deployments (the HAAPI React App and any other Curity-served frontend). - -##### Standalone (library) mode - -When the `HaapiStepper` is consumed as a library — e.g. embedded in a third-party app or any context that doesn't set `window.__CONFIG__` — the consumer supplies the bootstrap configuration explicitly via the `config.bootstrap` prop: - -```tsx -import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature'; -import type { HaapiStepperBootstrapConfig } from '@curity/haapi-react-sdk/haapi-stepper/feature'; - -const bootstrapConfig: HaapiStepperBootstrapConfig = { - initialUrl: 'https://idsvr.example.com/oauth/v2/oauth-authorize/...', - haapi: { /* HAAPI web-driver config */ }, -}; - - - - -``` - -Both modes can be mixed with `config` overrides for other tunables (e.g. `pollingInterval`, `bankIdAutostart`); see the [`HaapiStepperConfig` type](./feature/stepper/haapi-stepper.types.ts) for the full set. - -### Usage - -Because `HaapiStepper` does not have a UI, it can be used to build custom flow user interfaces from scratch, or it can be used in combination with the [HaapiStepperStepUI](#haapi-ui-step) component, which provides a ready-to-use, highly customizable, HAAPI UI solution. - -Finally, the `HaapiStepper` can be used in combination with the built-in [HAAPI UI Components](#haapi-stepper-ui-components), which help create highly customized UIs while relying on some defaults. - -Check out [the HaapiStepper documentation and usage examples](./feature/stepper/HaapiStepper.tsx) - -## HAAPI UI Step - -The `HaapiStepperStepUI` component provides a seamless way to implement complete HAAPI authentication flow UIs in your application, allowing extensive customization with minimal setup. - -### Basic Setup - - ```tsx - - - -``` - -### Usage - -Because the `HaapiStepperStepUI` handles all possible HAAPI authentication flows with proper user interfaces (UI), it is the fastest and easiest way to get HAAPI up and running in your application. It is also highly customizable and granular, allowing you to customize some aspects while keeping the defaults for the rest. - -Check out [the HaapiStepperStepUI documentation and usage examples](./feature/steps/HaapiStepperStepUI.tsx). - -### ViewName built-in UIs - -The HaapiStepperStepUI ships built-in UIs for specific HAAPI `viewName`s (`step.metadata.viewName`) that need a more tailored UI than the generic step shell can provide (e.g. the BankID requires the QR link to be lifted above the actions). - -ViewName built-in UIs operate at the **step** level: they reshape the whole step for a given `viewName`. They are displayed by default and can be customized like any other step by using render interceptors. - -Check out documentation and usage examples in [`HaapiStepperStepUI`](./feature/steps/HaapiStepperStepUI.tsx), and the test use cases in [`HaapiStepperStepUI.spec.tsx`](./feature/steps/HaapiStepperStepUI.spec.tsx) (`describe('ViewName built-in UIs Rendering')`) for more details. - - - -## HAAPI Stepper UI Components - -The HAAPI Frontend Library provides some common HAAPI Stepper UI elements that help create highly customized UIs while relying on some defaults. - -### Naming convention - -The HAAPI Stepper UI components are the UI representation of the main HAAPI entities, named with a `UI` suffix: `HaapiStepperStepUI` displays/interacts with `HaapiStepperStep`, `HaapiStepperLinkUI` with `HaapiStepperLink`, and so on. Collection components use the plural form (`HaapiStepperActionsUI`, `HaapiStepperLinksUI`, `HaapiStepperMessagesUI`). - -### Usage - -Check out documentation and usage examples in the links below: - -* [HaapiStepperStepUI](./feature/steps/HaapiStepperStepUI.tsx) - * [HaapiStepperActionsUI](./ui/actions/HaapiStepperActionsUI.tsx) - * [HaapiStepperFormUI](./feature/actions/form/HaapiStepperFormUI.tsx) - * [HaapiStepperFormFieldUI](./feature/actions/form/fields/HaapiStepperFormFieldUI.tsx) - * [HaapiStepperTextFormFieldUI](./feature/actions/form/fields/HaapiStepperTextFormFieldUI.tsx) - * [HaapiStepperPasswordFormFieldUI](./feature/actions/form/fields/HaapiStepperPasswordFormFieldUI.tsx) - * [HaapiStepperCheckboxFormFieldUI](./feature/actions/form/fields/HaapiStepperCheckboxFormFieldUI.tsx) - * [HaapiStepperSelectFormFieldUI](./feature/actions/form/fields/HaapiStepperSelectFormFieldUI.tsx) - * [HaapiStepperSelectorUI](./feature/actions/selector/HaapiStepperSelectorUI.tsx) - * [HaapiStepperClientOperationUI](./feature/actions/client-operation/HaapiStepperClientOperationUI.tsx) - * [HaapiStepperMessagesUI](./ui/messages/HaapiStepperMessagesUI.tsx) - * [HaapiStepperMessageUI](./ui/messages/HaapiStepperMessageUI.tsx) - * [HaapiStepperLinksUI](./ui/links/HaapiStepperLinksUI.tsx) - * [HaapiStepperLinkUI](./ui/links/HaapiStepperLinkUI.tsx) - -### CSS Customization - -The HAAPI UI components are styled via plain CSS class names — no CSS-in-JS, no inline styles. The components only emit class names; the actual rules live in a stylesheet shipped alongside the host application's global stylesheet. For example, in the case of the `haapi-react-app`, in `haapi-react-app/src/shared/util/css/styles.css`. - -#### Importing CSS styles - -Import the stylesheet once from the consuming application's entry point (e.g. `main.tsx`): - -```ts -import './shared/util/css/styles.css'; -``` - -By default, the rules in `styles.css` compose utility classes from [Curity CSS Library](https://github.com/curityio/ui-kit/tree/main/src/common/css) (imported at the top of the file) using PostCSS `@extend` — e.g. `.haapi-stepper-button { @extend .button, .button-medium, .button-primary, .w100, .mt2; }`. The components themselves only know about the `.haapi-stepper-*` class names, so consumers are free to back those classes with anything they like. - -#### Overriding or extending the defaults - -Because the components emit static class names, consumers can: - -- **Override / Extend**: define rules for the same class names — or append additional CSS — in a separate stylesheet imported after `styles.css`. -- **Replace**: skip the default import entirely and provide your own definitions for the classes listed below — written in plain CSS, or composed from any third-party library, for example Tailwind CSS. - -The Curity utility composition shown above is just how *this* project chose to implement the defaults; it is not a contract. Nothing in the components requires `@curity/ui-kit-css`, PostCSS, or `@extend`. - -**Available CSS classes:** - -| Class | Used by | Purpose | -|-------|---------|---------| -| `.haapi-stepper-selector` | `HaapiStepperSelectorUI` | Selector action container | -| `.haapi-stepper-authenticator-button` | `HaapiStepperFormSubmitButton` | Authenticator-selector option button (applied automatically when the action carries `authenticatorType`); combine with `.button-` (e.g. `.button-google`) to get the per-authenticator icon color | -| `.haapi-stepper-messages` | `HaapiStepperMessagesUI` | Messages container | -| `.haapi-stepper-form-field-text-input` | `HaapiStepperTextFormFieldUI` | Text input fields | -| `.haapi-stepper-form-field-text-label` | `HaapiStepperTextFormFieldUI` | Form field labels | -| `.haapi-stepper-form-field-checkbox-input` | `HaapiStepperCheckboxFormFieldUI` | Checkbox inputs | -| `.haapi-stepper-form-field-checkbox-label` | `HaapiStepperCheckboxFormFieldUI` | Checkbox-specific labels | -| `.haapi-stepper-form-field-select-input` | `HaapiStepperSelectFormFieldUI` | Select inputs | -| `.haapi-stepper-form-field-select-label` | `HaapiStepperSelectFormFieldUI` | Select-specific labels | -| `.haapi-stepper-form-field-password-wrapper` | `HaapiStepperPasswordFormFieldUI` | Password input container | -| `.haapi-stepper-form-field-password-label` | `HaapiStepperPasswordFormFieldUI` | Password label | -| `.haapi-stepper-form-field-password-input` | `HaapiStepperPasswordFormFieldUI` | Password input | -| `.haapi-stepper-form-field-password-visibility-toggle` | `HaapiStepperPasswordFormFieldUI` | Password visibility toggle button | -| `.haapi-stepper-button` | `HaapiStepperFormUI` | Primary submit buttons | -| `.haapi-stepper-button-outline` | `HaapiStepperFormUI` | Outline/cancel buttons | -| `.haapi-stepper-well` | `Well` | Styled content container | -| `.haapi-stepper-links` | `HaapiStepperLinksUI` | Links container | -| `.haapi-stepper-link` | `HaapiStepperLinkUI` | Link element | -| `.haapi-stepper-link-qr-code` | `HaapiStepperLinkUI` | QR code link figure wrapper | -| `.haapi-stepper-link-qr-code-title` | `HaapiStepperLinkUI` | QR code link figcaption | -| `.haapi-stepper-link-qr-code-button` | `HaapiStepperLinkUI` | QR code link expand button | -| `.haapi-stepper-link-qr-code-dialog` | `HaapiStepperQrCodeLinkDialog` | Fullscreen QR code dialog | -| `.haapi-stepper-link-qr-code-dialog-close-button` | `HaapiStepperQrCodeLinkDialog` | Button wrapping the expanded QR code image; closes the dialog when clicked | -| `.haapi-stepper-link-qr-code-dialog-image` | `HaapiStepperQrCodeLinkDialog` | Fullscreen QR code dialog image | -| `.haapi-stepper-bankid-qr-code-accessibility` | `HaapiStepperBankIdQrCodeAccessibilityMessages` | Container for the BankID QR-code accessibility messages (the collapsible "help" and "screen reader" `
` sections) | -| `.haapi-stepper-actions` | `HaapiStepperActionsUI` | Actions container | -| `.haapi-stepper-heading` | `HaapiStepperMessagesUI` | Heading messages | -| `.haapi-stepper-userName` | `HaapiStepperMessagesUI` | User name display | -| `.haapi-stepper-userCode` | `HaapiStepperMessagesUI` | User code display (e.g. recovery codes) | -| `.haapi-stepper-polling-progress-bar` | `HaapiStepperBankIdPollingProgressUI` | Remaining polling time indicator (the "authentication time" bar, e.g. BankID) | -| `.haapi-stepper-polling-progress-duration` | `HaapiStepperBankIdPollingProgressUI` | Numeric time-left readout shown below the bar (e.g. "24 seconds left") | -| `.haapi-stepper-webauthn-registration-attachment` | `HaapiStepperWebAuthnRegistrationAttachmentCard` | WebAuthn registration attachment-selection option card (icon + title + description) | -| `.haapi-stepper-webauthn-registration-attachment-icon` | `HaapiStepperWebAuthnRegistrationAttachmentCard` | Attachment card icon | -| `.haapi-stepper-webauthn-registration-attachment-title` | `HaapiStepperWebAuthnRegistrationAttachmentCard` | Attachment card option label | -| `.haapi-stepper-webauthn-registration-attachment-description` | `HaapiStepperWebAuthnRegistrationAttachmentCard` | Attachment card option description | -| `.haapi-stepper-consent-logos` | `UserConsentViewNameBuiltInUI` | Container for user consent logos | -| `.haapi-stepper-error-boundary-fallback` | `DefaultErrorFallback` | Error boundary fallback container | -| `.haapi-validation-errors-container` | `HaapiStepperFormValidationErrorInputWrapper` | Wrapper around a form field that has validation errors. Receives the `.has-errors` modifier class while errors are visible | -| `.haapi-validation-errors` | `HaapiStepperFormValidationErrorInputWrapper` | Inner container that holds the list of validation error messages | -| `.haapi-validation-error` | `HaapiStepperFormValidationErrorInputWrapper` | A single validation error entry (also gets the utility classes `.red .py1`) | -| `.haapi-validation-error-description` | `HaapiStepperFormValidationErrorInputWrapper` | Validation error message text | - - - -## Error Handling -The `HaapiStepper` implements a comprehensive error-handling strategy with multiple layers to ensure robust error management and an optimal user experience. - -### Error State Management -The HAAPI stepper manages errors according to two categories: HAAPI errors and non-HAAPI errors. - -#### HAAPI Errors -HAAPI errors are HAAPI `ProblemStep`s (HAAPI flow steps of type [`HAAPI_PROBLEM_STEPS`](./util/types/haapi-step.types.ts)). - - -HAAPI errors are classified into two groups: - -**`AppError` (Unrecoverable)** - - **Description**: Errors that cannot be resolved in the step (action form) where they originated, so they need to be handled at the application level (e.g., show a dedicated error page) and/or require restarting the stepper flow. - * Like any other problem, they might include `UserMessages` and `Links` that need to be displayed to the user. - - **Types**: `UnrecoverableProblemStep`, `UnexpectedProblemStep`, `CompletedWithErrorStep`. [More details here](./util/types/haapi-step.types.ts). - - **Examples**: Authentication failed, too many attempts, session mismatches. - - **Handling**: Displayed as toast notifications and/or a problem step UI. - -**`InputErrors` (Recoverable)** - - **Description**: Errors that can be resolved in the step (form) where they originated. - * They should be handled while keeping the step's UI, providing the problem's `UserMessages` and `Links`, and allowing the user to correct the input and resubmit. - - **Types**: `ValidationProblemStep`, `IncorrectCredentialsProblemStep`. [More details here](./util/types/haapi-step.types.ts). - - **Examples**: Invalid form fields, incorrect credentials. - - **Handling**: Displayed below relevant input fields for immediate correction. - -**`HaapiStepperError` interface**: - -```tsx -interface HaapiStepperError { - app?: AppError | null; - input?: InputError | null; -} -``` - -HAAPI errors are provided by the `useHaapiStepper` hook: - -```tsx -const { error } = useHaapiStepper(); -const { app, input } = error || {}; -``` - -[`More details here`](./util/types/haapi-step.types.ts). - -##### HAAPI Error Utils - -###### HaapiErrorNotifier -**Purpose**: Toast-based notification system for HAAPI `AppError`s, and optionally, `InputErrors`s: - -**Example Usage**: -```tsx - - - -``` - -**Features:** -- Automatically shows notifications for `AppError` and, optionally, `InputError`. -- Auto-dismisses and manually dismisses with a close button. - -###### HaapiValidationErrorInputWrapper (Input-Level Errors) -**Purpose**: A field-specific error display for HAAPI validation `InputError`s: - -**Example Usage**: -```tsx - - - -``` - -**Features:** -- Shows `InputValidationProblemStep` errors below the corresponding input fields. -- Applies the `haapi-validation-error` CSS class for styling. - - - -#### Non-HAAPI Errors -Non-HAAPI errors are network, backend, and frontend errors that are not handled at lower levels. - -The `HaapiStepper` throws them as JavaScript errors so they can be caught by the nearest React error boundary. - - -##### Non-HAAPI Error Utils - -###### useThrowErrorToAppErrorBoundary -**Purpose**: A React hook that provides a function to rethrow async errors so they can be caught by React error boundaries. - -> **Why is this needed?** React error boundaries only catch errors during rendering, in lifecycle methods, and in constructors. They **do not** catch errors in async operations like event handlers, promises, or `setTimeout` callbacks. This hook bridges that gap by allowing you to manually throw errors that will be caught by the nearest `ErrorBoundary`. - -**Example Usage**: -```tsx -function MyComponent() { - const throwErrorToAppErrorBoundary = useThrowErrorToAppErrorBoundary(); - - // Example 1: Async operation error rethrowing - const handleAsyncAction = async () => { - try { - const response = await fetch('/api/critical-data'); - } catch (error) { - // Rethrow async error so the ErrorBoundary can catch it and display the fallback UI - throwErrorToAppErrorBoundary('Failed to load critical data. Please refresh the page.'); - } - }; - - // Example 2: Promise rejection rethrowing - const handlePromiseAction = () => { - someAsyncOperation() - .catch(error => { - // Rethrow async error so the ErrorBoundary can catch it and display the fallback UI - throwErrorToAppErrorBoundary('Operation failed unexpectedly'); - }); - }; -} -``` - -**Integration with HAAPI Stepper**: -The HAAPI Stepper Provider uses this hook to handle non-HAAPI errors (network failures, unexpected server responses) by throwing them to the application-level error boundary rather than trying to handle them locally. diff --git a/src/haapi-react-sdk/haapi-stepper/data-access/useHaapiFetch.ts b/src/haapi-react-sdk/haapi-stepper/data-access/useHaapiFetch.ts index a0780b2a..08d29586 100644 --- a/src/haapi-react-sdk/haapi-stepper/data-access/useHaapiFetch.ts +++ b/src/haapi-react-sdk/haapi-stepper/data-access/useHaapiFetch.ts @@ -27,6 +27,22 @@ import type { HaapiFetchAction } from './types/haapi-fetch.types'; let cachedHaapiFetch: FetchLike | undefined; let cachedConfig: HaapiConfiguration | undefined; +/** + * Low-level access to the attested HAAPI transport, for driving a flow without `HaapiStepper`. + * + * Wraps the HAAPI web driver's fetch in a `sendHaapiFetchRequest(action)` function that submits a HAAPI + * action and returns the raw response. Most applications never need this — `HaapiStepper` (and + * `useHaapiStepper`) manage the flow for you; reach for this hook only to build your own flow handling + * on top of the same attested transport. + * + * ```tsx + * const { sendHaapiFetchRequest } = useHaapiFetch(haapiConfiguration); + * const response = await sendHaapiFetchRequest(action); + * ``` + * + * The underlying driver supports a single configuration per page load: every caller shares one driver + * instance, and passing a different `HaapiConfiguration` later throws — reload the page to switch. + */ export function useHaapiFetch(haapi: HaapiConfiguration) { const haapiFetch = useMemo(() => getHaapiFetch(haapi), [haapi]); return useMemo( diff --git a/src/haapi-react-sdk/haapi-stepper/feature/actions/client-operation/HaapiStepperClientOperationUI.tsx b/src/haapi-react-sdk/haapi-stepper/feature/actions/client-operation/HaapiStepperClientOperationUI.tsx index 8a6302ba..0130484c 100644 --- a/src/haapi-react-sdk/haapi-stepper/feature/actions/client-operation/HaapiStepperClientOperationUI.tsx +++ b/src/haapi-react-sdk/haapi-stepper/feature/actions/client-operation/HaapiStepperClientOperationUI.tsx @@ -21,14 +21,13 @@ interface HaapiStepperClientOperationUIProps { /** * @description - * # CLIENT OPERATION ACTION COMPONENT + * # Client operation action component * * Renders the default UI for a HAAPI client-operation action and forwards the click to * `onAction`. The button is disabled when the action's runtime capability requirements are * not met (e.g. WebAuthn API missing, or platform authenticator unavailable for platform-only * WebAuthn registration). * - * @example * ```tsx * function HaapiComponentExample() { * const { currentStep, nextStep } = useHaapiStepper(); @@ -43,6 +42,7 @@ interface HaapiStepperClientOperationUIProps { * * * ``` + * {@see_example ./docs/sections/01-api-reference/01-ui-components/ClientOperationUiUsageHaapiReactSDKPlaygroundExample.tsx} */ export function HaapiStepperClientOperationUI({ action, onAction }: HaapiStepperClientOperationUIProps) { const isAvailable = useIsClientOperationAvailable(action); diff --git a/src/haapi-react-sdk/haapi-stepper/feature/actions/client-operation/operations/webauthn/HaapiStepperWebAuthnRegistrationAttachmentCard.tsx b/src/haapi-react-sdk/haapi-stepper/feature/actions/client-operation/operations/webauthn/HaapiStepperWebAuthnRegistrationAttachmentCard.tsx index 888bc549..c46f001d 100644 --- a/src/haapi-react-sdk/haapi-stepper/feature/actions/client-operation/operations/webauthn/HaapiStepperWebAuthnRegistrationAttachmentCard.tsx +++ b/src/haapi-react-sdk/haapi-stepper/feature/actions/client-operation/operations/webauthn/HaapiStepperWebAuthnRegistrationAttachmentCard.tsx @@ -27,6 +27,8 @@ export interface HaapiStepperWebAuthnRegistrationAttachmentCardProps { * (icon + bold title + description). * * Must be rendered within `HaapiStepper`, for the current active step's action. + * + * @docsIgnore Not published in the Curity docs. */ export const HaapiStepperWebAuthnRegistrationAttachmentCard = ({ action, diff --git a/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormContext.ts b/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormContext.ts index 14f3f665..9b054986 100644 --- a/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormContext.ts +++ b/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormContext.ts @@ -19,8 +19,10 @@ export interface HaapiStepperFormContextValue { submit: () => void; } +/** Not published in the Curity docs. @docsIgnore */ export const HaapiStepperFormContext = createContext(null); +/** Not published in the Curity docs. @docsIgnore */ export function useHaapiStepperForm(): HaapiStepperFormContextValue { const context = use(HaapiStepperFormContext); if (!context) { diff --git a/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormHook.ts b/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormHook.ts index 29642c1f..14525f7c 100644 --- a/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormHook.ts +++ b/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormHook.ts @@ -17,6 +17,14 @@ import { HaapiStepperFormField, HaapiStepperFormState } from '../../stepper/haap * Hook to manage form state. Returns an array with two values: * 1. a convenience {@link HaapiStepperFormState} object that can be used to get and set field values * 2. a map of the current form values that can be used to submit the form + * + * ```tsx + * const formState = useHaapiStepperFormState(action.model.fields ?? []); + * + * formState.set(field, event.target.value)} />; + * // Submit the action with the current values: nextStep(action, formState.values) + * ``` + * {@see_example ./docs/sections/01-api-reference/01-ui-components/01-form-ui/FormStateHookUsageHaapiReactSDKPlaygroundExample.tsx} */ export function useHaapiStepperFormState(fields: HaapiStepperFormField[]): HaapiStepperFormState { // State to hold values of form fields. Initial value is calculated once, lazily. diff --git a/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormSubmitButton.tsx b/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormSubmitButton.tsx index 50f9dcff..b7da6c4f 100644 --- a/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormSubmitButton.tsx +++ b/src/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormSubmitButton.tsx @@ -20,6 +20,29 @@ interface HaapiStepperFormSubmitButtonProps extends ComponentPropsWithRef<'butto icon?: ReactNode; } +/** + * Renders the form's submit button with the SDK defaults: the label comes from the HAAPI action, the + * icon and styling from the action's authenticator type (cancel actions get the outline style). + * + * Must be rendered inside a `HaapiStepperFormUI` (it reads the action from the form context, so it + * throws outside one). Use it in a `children` render interceptor to keep the default submit button + * while composing your own form layout, and customize it via `label`, `icon`, `children` or any native + * ` + + )} + + ); +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormFieldRenderInterceptorHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormFieldRenderInterceptorHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..a5f258b3 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormFieldRenderInterceptorHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperFormUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormUI'; +import type { HaapiStepperFormFieldRenderInterceptor } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/haapi-stepper.types'; +import { HAAPI_FORM_FIELDS } from '@curity/haapi-react-sdk/haapi-stepper/data-access/types/haapi-form.types'; +import { ExamplePreviewer } from '../../../../_harness/ExamplePreviewer'; + +/** + * UI Customization with render interceptor: per-field customization with `formFieldRenderInterceptor` — + * relabel the username field, replace the password field with a custom input wired to the built-in + * `formState`, and delegate every other field to the default rendering. + */ +function LoginForm() { + const { currentStep, nextStep } = useHaapiStepper(); + const formAction = currentStep?.dataHelpers.actions?.form?.[0]; + + if (!formAction) { + return null; + } + + const formFieldRenderInterceptor: HaapiStepperFormFieldRenderInterceptor = (field, formState) => { + if (field.type === HAAPI_FORM_FIELDS.USERNAME) { + return { ...field, label: 'Account', placeholder: 'user@example.com' }; + } + + if (field.type === HAAPI_FORM_FIELDS.PASSWORD) { + return ( + + ); + } + + // Delegate to default field rendering + return field; + }; + + return ( + + ); +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormStateHookUsageHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormStateHookUsageHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..6f8b44d0 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormStateHookUsageHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperStepUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/steps/HaapiStepperStepUI'; +import { useHaapiStepperFormState } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormHook'; +import { HAAPI_FORM_FIELDS } from '@curity/haapi-react-sdk/haapi-stepper/data-access/types/haapi-form.types'; +import type { + HaapiStepperFormAction, + HaapiStepperNextStep, +} from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/haapi-stepper.types'; +import { ExamplePreviewer } from '../../../../_harness/ExamplePreviewer'; + +/** + * A fully custom form built on `useHaapiStepperFormState`: the hook seeds the state from the action's + * fields and returns `get`/`set` helpers for the inputs plus the `values` map the action expects as its + * submission payload — so you own the markup while the SDK owns the form state. + */ +function CustomLoginForm({ + action, + onSubmit, +}: { + action: HaapiStepperFormAction; + onSubmit: HaapiStepperNextStep; +}) { + const fields = action.model.fields ?? []; + const formState = useHaapiStepperFormState(fields); + const username = fields.find(field => field.type === HAAPI_FORM_FIELDS.USERNAME)!; + const password = fields.find(field => field.type === HAAPI_FORM_FIELDS.PASSWORD)!; + + return ( +
{ + event.preventDefault(); + onSubmit(action, formState.values); + }} + > + + + +
+ ); +} + +function LoginForm() { + const { currentStep, nextStep } = useHaapiStepper(); + const formAction = currentStep?.dataHelpers.actions?.form?.[0]; + + if (!formAction) { + // Not a form step — delegate to the default step UI. + return ; + } + + return ; +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormSubmitBehaviorHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormSubmitBehaviorHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..ce82ca13 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormSubmitBehaviorHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperFormUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormUI'; +import type { + HaapiStepperFormAction, + HaapiStepperNextStep, +} from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/haapi-stepper.types'; +import { ExamplePreviewer } from '../../../../_harness/ExamplePreviewer'; + +/** + * Behaviour override around submission: wrap `onSubmit` to run your own logic (here a confirmation prompt; + * could be analytics or pre-submit validation) before delegating to `nextStep`. The default form UI and + * state management are untouched. + */ +function LoginForm() { + const { currentStep, nextStep } = useHaapiStepper(); + const formAction = currentStep?.dataHelpers.actions?.form?.[0]; + + if (!formAction) { + return null; + } + + const handleSubmit: HaapiStepperNextStep = (action, payload) => { + if (confirm('Submit the login form?')) { + nextStep(action, payload); + } + }; + + return ; +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormUICompositionHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormUICompositionHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..896654da --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/FormUICompositionHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperStepUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/steps/HaapiStepperStepUI'; +import { HaapiStepperFormUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormUI'; +import { HaapiStepperFormFieldUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/fields/HaapiStepperFormFieldUI'; +import { ExamplePreviewer } from '../../../../_harness/ExamplePreviewer'; + +/** + * UI Customization with UI composition: form customization with the `HaapiStepperFormUI` children render + * prop — lay the fields out in your own markup (here a `
`) while the built-in + * `HaapiStepperFormFieldUI` still renders each field and `HaapiStepperFormUI` manages the form state and + * submission. Steps without a form action fall back to the default `HaapiStepperStepUI`. + */ +function LoginForm() { + const { currentStep, nextStep } = useHaapiStepper(); + const formAction = currentStep?.dataHelpers.actions?.form?.[0]; + + if (!formAction) { + // Not a form step — delegate to the default step UI. + return ; + } + + return ( + + {({ fields }) => ( +
+ Sign in + {fields.map(field => ( + + ))} + +
+ )} +
+ ); +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/PasswordFieldRenderingHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/PasswordFieldRenderingHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..b70661c8 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/PasswordFieldRenderingHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperFormUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormUI'; +import { HaapiStepperFormFieldUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/fields/HaapiStepperFormFieldUI'; +import { HaapiStepperPasswordFormFieldUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/fields/HaapiStepperPasswordFormFieldUI'; +import { HaapiStepperFormSubmitButton } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormSubmitButton'; +import { HAAPI_FORM_FIELDS } from '@curity/haapi-react-sdk/haapi-stepper/data-access/types/haapi-form.types'; +import { ExamplePreviewer } from '../../../../_harness/ExamplePreviewer'; + +/** Add a "forgot password?" link under the password field with `HaapiStepperPasswordFormFieldUI`; keep the default for the rest. */ +function LoginForm() { + const { currentStep, nextStep } = useHaapiStepper(); + const formAction = currentStep?.dataHelpers.actions?.form?.[0]; + + if (!formAction) { + return null; + } + + return ( + + {({ fields }) => ( + <> + {fields.map(field => + field.type === HAAPI_FORM_FIELDS.PASSWORD ? ( +
+ + Forgot your password? +
+ ) : ( + + ) + )} + + + )} +
+ ); +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/README.md b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/README.md new file mode 100644 index 00000000..b1a17b6f --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/README.md @@ -0,0 +1,7 @@ +1. HaapiStepperFormFieldUI +2. HaapiStepperTextFormFieldUI +3. HaapiStepperPasswordFormFieldUI +4. HaapiStepperSelectFormFieldUI +5. HaapiStepperCheckboxFormFieldUI +6. HaapiStepperFormSubmitButton +7. useHaapiStepperFormState diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/SelectFieldRenderingHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/SelectFieldRenderingHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..ed899f1e --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/SelectFieldRenderingHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperFormUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormUI'; +import { HaapiStepperFormFieldUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/fields/HaapiStepperFormFieldUI'; +import { HaapiStepperSelectFormFieldUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/fields/HaapiStepperSelectFormFieldUI'; +import { HaapiStepperFormSubmitButton } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormSubmitButton'; +import { HAAPI_FORM_FIELDS } from '@curity/haapi-react-sdk/haapi-stepper/data-access/types/haapi-form.types'; +import { ExamplePreviewer } from '../../../../_harness/ExamplePreviewer'; +import { HAAPI_EXAMPLE } from '../../../../_harness/catalog'; + +/** Pair the select field with a hint using `HaapiStepperSelectFormFieldUI`; keep the default for the rest. */ +function LoginForm() { + const { currentStep, nextStep } = useHaapiStepper(); + const formAction = currentStep?.dataHelpers.actions?.form?.[0]; + + if (!formAction) { + return null; + } + + return ( + + {({ fields }) => ( + <> + {fields.map(field => + field.type === HAAPI_FORM_FIELDS.SELECT ? ( +
+ +

You can change this later in your settings.

+
+ ) : ( + + ) + )} + + + )} +
+ ); +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/SubmitButtonCustomizationHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/SubmitButtonCustomizationHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..af1783e3 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/01-form-ui/SubmitButtonCustomizationHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperStepUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/steps/HaapiStepperStepUI'; +import { HaapiStepperFormUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormUI'; +import { HaapiStepperFormSubmitButton } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormSubmitButton'; +import { HaapiStepperFormFieldUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/fields/HaapiStepperFormFieldUI'; +import { ExamplePreviewer } from '../../../../_harness/ExamplePreviewer'; + +/** + * Compose your own form layout with the `children` render interceptor while keeping the SDK's submit + * button: `HaapiStepperFormSubmitButton` reads the action from the form context, so it keeps the default + * label, icon and styling — customize them via `label`, `icon`, `children` or any native ` + + )} + + ); +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/ActionsUiUsageHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/ActionsUiUsageHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..5094db0a --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/ActionsUiUsageHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperActionsUI } from '@curity/haapi-react-sdk/haapi-stepper/ui/actions/HaapiStepperActionsUI'; +import { ExamplePreviewer } from '../../../_harness/ExamplePreviewer'; +import { HAAPI_EXAMPLE } from '../../../_harness/catalog'; + +/** + * Render a step's actions (form, selector, and client-operation) with the `HaapiStepperActionsUI` + * building block: read `actions.all` from `dataHelpers` and pass it, plus `nextStep`, to the component — + * each action renders with the default UI for its subtype. + */ +function StepActions() { + const { currentStep, nextStep } = useHaapiStepper(); + const actions = currentStep?.dataHelpers.actions?.all; + + return ; +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/ClientOperationUiUsageHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/ClientOperationUiUsageHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..5b031cc7 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/ClientOperationUiUsageHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperClientOperationUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/client-operation/HaapiStepperClientOperationUI'; +import { ExamplePreviewer } from '../../../_harness/ExamplePreviewer'; +import { HAAPI_EXAMPLE } from '../../../_harness/catalog'; + +/** + * Render a step's client-operation action (e.g. WebAuthn, BankID) with the `HaapiStepperClientOperationUI` + * building block: read the action from `dataHelpers` and pass it, plus `nextStep`, to the component. + */ +function ClientOperationStep() { + const { currentStep, nextStep } = useHaapiStepper(); + const clientOperationAction = currentStep?.dataHelpers.actions?.clientOperation?.[0]; + + if (!clientOperationAction) { + return null; + } + + return ; +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/ErrorNotifierHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/ErrorNotifierHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..5cdedf07 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/ErrorNotifierHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { HaapiStepperStepUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/steps/HaapiStepperStepUI'; +import { HaapiStepperErrorNotifier } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperErrorNotifier'; +import { ExamplePreviewer } from '../../../_harness/ExamplePreviewer'; +import { HAAPI_EXAMPLE } from '../../../_harness/catalog'; + +/** + * `HaapiStepperErrorNotifier` wraps your app and surfaces HAAPI errors as a dismissible toast. Submit + * the login form below — the mock rejects the credentials, and the resulting error pops up as a + * notification while the step UI stays in place. + */ +export default function App() { + return ( + + + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/FormValidationErrorWrapperHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/FormValidationErrorWrapperHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..d87548e7 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/FormValidationErrorWrapperHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperFormValidationErrorInputWrapper } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/form/HaapiStepperFormValidationErrorInputWrapper'; +import { ExamplePreviewer } from '../../../_harness/ExamplePreviewer'; +import { HAAPI_EXAMPLE } from '../../../_harness/catalog'; + +/** + * `HaapiStepperFormValidationErrorInputWrapper` renders server-side field validation errors beneath the + * matching input. Submit the form — the mock returns a validation error for the `user` field, which the + * wrapper shows under the username input (other fields are unaffected). + */ +function LoginForm() { + const { currentStep, nextStep } = useHaapiStepper(); + const formAction = currentStep?.dataHelpers.actions?.form[0]; + + if (!formAction) { + return null; + } + + return ( +
{ + event.preventDefault(); + nextStep(formAction); + }} + > + + + + + +
+ ); +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/LinkUiUsageHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/LinkUiUsageHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..673f50f1 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/LinkUiUsageHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperLinksUI } from '@curity/haapi-react-sdk/haapi-stepper/ui/links/HaapiStepperLinksUI'; +import { ExamplePreviewer } from '../../../_harness/ExamplePreviewer'; +import { HAAPI_EXAMPLE } from '../../../_harness/catalog'; + +/** + * Render a step's links (e.g. "Forgot password?", "Create account") with the `HaapiStepperLinksUI` + * building block: read `links` from `dataHelpers` and pass them, plus `nextStep`, to the component. + */ +function StepLinks() { + const { currentStep, nextStep } = useHaapiStepper(); + const links = currentStep?.dataHelpers.links; + + if (!links?.length) { + return null; + } + + return ; +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/MessagesRenderingHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/MessagesRenderingHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..72948597 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/MessagesRenderingHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperMessagesUI } from '@curity/haapi-react-sdk/haapi-stepper/ui/messages/HaapiStepperMessagesUI'; +import { ExamplePreviewer } from '../../../_harness/ExamplePreviewer'; +import { HAAPI_EXAMPLE } from '../../../_harness/catalog'; + +/** Render the current step's messages with the `HaapiStepperMessagesUI` building block. */ +function Messages() { + const { currentStep } = useHaapiStepper(); + const messages = currentStep?.dataHelpers.messages; + + return ; +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/README.md b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/README.md new file mode 100644 index 00000000..c9eccbb3 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/README.md @@ -0,0 +1,67 @@ +# HAAPI stepper UI components + +The HAAPI React SDK provides a set of common HAAPI Stepper UI components that let you build highly +customized UIs while still relying on the SDK's defaults. They are the building blocks +`HaapiStepperStepUI` is made of, and what you compose your own UI from with `useHaapiStepper`. + +## Naming convention + +The HAAPI Stepper UI components are the UI representation of the main HAAPI entities, named with a +`UI` suffix: `HaapiStepperStepUI` displays/interacts with a `HaapiStep`, `HaapiStepperLinkUI` with a +`HaapiLink`, and so on. Collection components use the plural form (`HaapiStepperActionsUI`, +`HaapiStepperLinksUI`, `HaapiStepperMessagesUI`). + +## Usage + +Compose the building blocks to customize a step. For example, pass a `children` render function to +`HaapiStepperFormUI` to swap in your own username field and an Ant Design submit button, while the +built-in `HaapiStepperFormFieldUI` still manages the values and submission: + +```tsx +import { Button } from 'antd'; + +function LoginForm() { + const { currentStep, nextStep } = useHaapiStepper(); + const formAction = currentStep?.dataHelpers.actions.form[0]; + if (!formAction) return null; + + return ( + + {({ fields, formState }) => { + const username = fields.find(field => field.type === HAAPI_FORM_FIELDS.USERNAME); + const otherFields = fields.filter(field => field !== username); + + return ( + <> + {// Custom username field, wired to the built-in form state } + {username && } + + {// Remaining fields keep the default rendering } + {otherFields.map(field => ( + + ))} + + {// Ant Design submit button (submits the form via type="submit") } + + + ); + }} + + ); +} +``` +{@see_example ./docs/sections/01-api-reference/01-ui-components/UiComponentsCompositionUsageHaapiReactSDKPlaygroundExample.tsx} + +For styling, see the CSS Customization section in the Overview. + +1. HaapiStepperActionsUI +2. HaapiStepperSelectorUI +3. HaapiStepperClientOperationUI +4. HaapiStepperLinksUI +5. HaapiStepperLinkUI +6. HaapiStepperMessagesUI +7. HaapiStepperMessageUI +8. HaapiStepperErrorNotifier +9. HaapiStepperFormValidationErrorInputWrapper diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/SelectorUiUsageHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/SelectorUiUsageHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..f06fdc2a --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/SelectorUiUsageHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperSelectorUI } from '@curity/haapi-react-sdk/haapi-stepper/feature/actions/selector/HaapiStepperSelectorUI'; +import { ExamplePreviewer } from '../../../_harness/ExamplePreviewer'; +import { HAAPI_EXAMPLE } from '../../../_harness/catalog'; + +/** + * Render a step's authenticator selector with the `HaapiStepperSelectorUI` building block: read the + * selector action from `dataHelpers` and pass it, plus `nextStep`, to the component. Picking an option + * advances the flow. + */ +function SelectorStep() { + const { currentStep, nextStep } = useHaapiStepper(); + const selectorAction = currentStep?.dataHelpers.actions?.selector?.[0]; + + if (!selectorAction) { + return null; + } + + return ; +} + +export default function App() { + return ( + + + + + + ); +} diff --git a/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/SingleLinkUiUsageHaapiReactSDKPlaygroundExample.tsx b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/SingleLinkUiUsageHaapiReactSDKPlaygroundExample.tsx new file mode 100644 index 00000000..032993b8 --- /dev/null +++ b/src/haapi-react-sdk/docs/sections/01-api-reference/01-ui-components/SingleLinkUiUsageHaapiReactSDKPlaygroundExample.tsx @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2026 Curity AB. All rights reserved. + * + * The contents of this file are the property of Curity AB. + * You may not copy or use this file, in either source code + * or executable form, except in compliance with terms + * set by Curity AB. + * + * For further information, please contact Curity AB. + */ + +import { HaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepper'; +import { useHaapiStepper } from '@curity/haapi-react-sdk/haapi-stepper/feature/stepper/HaapiStepperHook'; +import { HaapiStepperLinkUI } from '@curity/haapi-react-sdk/haapi-stepper/ui/links/HaapiStepperLinkUI'; +import { ExamplePreviewer } from '../../../_harness/ExamplePreviewer'; +import { HAAPI_EXAMPLE } from '../../../_harness/catalog'; + +/** + * Render each of a step's links yourself with the `HaapiStepperLinkUI` building block — here inside a + * custom `