@thunderid/react 1.3.3 → 1.4.0

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 (159) hide show
  1. package/dist/cjs/components/presentation/ChangeCredential/BaseChangeCredential.cjs +1 -1
  2. package/dist/cjs/components/presentation/auth/AcceptInvite/BaseAcceptInvite.cjs +1 -1
  3. package/dist/cjs/components/presentation/auth/AcceptInvite/BaseAcceptInvite.cjs.map +1 -1
  4. package/dist/cjs/components/presentation/auth/AuthOptionFactory.cjs +56 -17
  5. package/dist/cjs/components/presentation/auth/AuthOptionFactory.cjs.map +1 -1
  6. package/dist/cjs/components/presentation/auth/InviteUser/BaseInviteUser.cjs +21 -5
  7. package/dist/cjs/components/presentation/auth/InviteUser/BaseInviteUser.cjs.map +1 -1
  8. package/dist/cjs/components/presentation/auth/InviteUser/InviteUser.cjs +6 -0
  9. package/dist/cjs/components/presentation/auth/InviteUser/InviteUser.cjs.map +1 -1
  10. package/dist/cjs/components/primitives/AuthFlowSelect/AuthFlowSelect.cjs +21 -0
  11. package/dist/cjs/components/primitives/AuthFlowSelect/AuthFlowSelect.cjs.map +1 -0
  12. package/dist/cjs/components/primitives/AuthFlowSelect/BaseAuthFlowSelect.cjs +32 -0
  13. package/dist/cjs/components/primitives/AuthFlowSelect/BaseAuthFlowSelect.cjs.map +1 -0
  14. package/dist/cjs/components/primitives/CopyableText/CopyableText.cjs +41 -21
  15. package/dist/cjs/components/primitives/CopyableText/CopyableText.cjs.map +1 -1
  16. package/dist/cjs/components/primitives/CopyableText/CopyableText.styles.cjs +5 -2
  17. package/dist/cjs/components/primitives/CopyableText/CopyableText.styles.cjs.map +1 -1
  18. package/dist/cjs/components/primitives/Icons/Copy.cjs +33 -0
  19. package/dist/cjs/components/primitives/Icons/Copy.cjs.map +1 -0
  20. package/dist/cjs/components/primitives/PasswordField/PasswordField.cjs +2 -1
  21. package/dist/cjs/components/primitives/PasswordField/PasswordField.cjs.map +1 -1
  22. package/dist/cjs/components/primitives/Select/ResourceSelect.cjs +104 -0
  23. package/dist/cjs/components/primitives/Select/ResourceSelect.cjs.map +1 -0
  24. package/dist/cjs/components/primitives/Select/Select.cjs +72 -21
  25. package/dist/cjs/components/primitives/Select/Select.cjs.map +1 -1
  26. package/dist/cjs/components/primitives/Select/Select.styles.cjs +19 -3
  27. package/dist/cjs/components/primitives/Select/Select.styles.cjs.map +1 -1
  28. package/dist/cjs/components/primitives/Select/useSelectMessages.cjs +21 -0
  29. package/dist/cjs/components/primitives/Select/useSelectMessages.cjs.map +1 -0
  30. package/dist/cjs/components/primitives/TextField/TextField.cjs +19 -3
  31. package/dist/cjs/components/primitives/TextField/TextField.cjs.map +1 -1
  32. package/dist/cjs/components/primitives/TextField/TextField.styles.cjs +18 -5
  33. package/dist/cjs/components/primitives/TextField/TextField.styles.cjs.map +1 -1
  34. package/dist/cjs/components/primitives/UserSelect/BaseUserSelect.cjs +33 -0
  35. package/dist/cjs/components/primitives/UserSelect/BaseUserSelect.cjs.map +1 -0
  36. package/dist/cjs/components/primitives/UserSelect/UserSelect.cjs +21 -0
  37. package/dist/cjs/components/primitives/UserSelect/UserSelect.cjs.map +1 -0
  38. package/dist/cjs/components/primitives/UserTypeSelect/BaseUserTypeSelect.cjs +31 -0
  39. package/dist/cjs/components/primitives/UserTypeSelect/BaseUserTypeSelect.cjs.map +1 -0
  40. package/dist/cjs/components/primitives/UserTypeSelect/UserTypeSelect.cjs +21 -0
  41. package/dist/cjs/components/primitives/UserTypeSelect/UserTypeSelect.cjs.map +1 -0
  42. package/dist/cjs/hooks/useFetchAuthFlows.cjs +36 -0
  43. package/dist/cjs/hooks/useFetchAuthFlows.cjs.map +1 -0
  44. package/dist/cjs/hooks/useFetchUserTypes.cjs +34 -0
  45. package/dist/cjs/hooks/useFetchUserTypes.cjs.map +1 -0
  46. package/dist/cjs/hooks/useFetchUsers.cjs +35 -0
  47. package/dist/cjs/hooks/useFetchUsers.cjs.map +1 -0
  48. package/dist/cjs/hooks/useInfiniteOptions.cjs +129 -0
  49. package/dist/cjs/hooks/useInfiniteOptions.cjs.map +1 -0
  50. package/dist/cjs/hooks/useManagementEndpoint.cjs.map +1 -1
  51. package/dist/cjs/index.cjs +56 -0
  52. package/dist/cjs/utils/createAuthenticatedFetcher.cjs +1 -0
  53. package/dist/cjs/utils/createAuthenticatedFetcher.cjs.map +1 -1
  54. package/dist/components/presentation/ChangeCredential/BaseChangeCredential.js +1 -1
  55. package/dist/components/presentation/auth/AcceptInvite/BaseAcceptInvite.d.ts.map +1 -1
  56. package/dist/components/presentation/auth/AcceptInvite/BaseAcceptInvite.js +1 -1
  57. package/dist/components/presentation/auth/AcceptInvite/BaseAcceptInvite.js.map +1 -1
  58. package/dist/components/presentation/auth/AuthOptionFactory.d.ts +9 -1
  59. package/dist/components/presentation/auth/AuthOptionFactory.d.ts.map +1 -1
  60. package/dist/components/presentation/auth/AuthOptionFactory.js +56 -17
  61. package/dist/components/presentation/auth/AuthOptionFactory.js.map +1 -1
  62. package/dist/components/presentation/auth/InviteUser/BaseInviteUser.d.ts +18 -2
  63. package/dist/components/presentation/auth/InviteUser/BaseInviteUser.d.ts.map +1 -1
  64. package/dist/components/presentation/auth/InviteUser/BaseInviteUser.js +22 -6
  65. package/dist/components/presentation/auth/InviteUser/BaseInviteUser.js.map +1 -1
  66. package/dist/components/presentation/auth/InviteUser/InviteUser.d.ts +2 -1
  67. package/dist/components/presentation/auth/InviteUser/InviteUser.d.ts.map +1 -1
  68. package/dist/components/presentation/auth/InviteUser/InviteUser.js +6 -0
  69. package/dist/components/presentation/auth/InviteUser/InviteUser.js.map +1 -1
  70. package/dist/components/primitives/AuthFlowSelect/AuthFlowSelect.d.ts +11 -0
  71. package/dist/components/primitives/AuthFlowSelect/AuthFlowSelect.d.ts.map +1 -0
  72. package/dist/components/primitives/AuthFlowSelect/AuthFlowSelect.js +19 -0
  73. package/dist/components/primitives/AuthFlowSelect/AuthFlowSelect.js.map +1 -0
  74. package/dist/components/primitives/AuthFlowSelect/BaseAuthFlowSelect.d.ts +14 -0
  75. package/dist/components/primitives/AuthFlowSelect/BaseAuthFlowSelect.d.ts.map +1 -0
  76. package/dist/components/primitives/AuthFlowSelect/BaseAuthFlowSelect.js +29 -0
  77. package/dist/components/primitives/AuthFlowSelect/BaseAuthFlowSelect.js.map +1 -0
  78. package/dist/components/primitives/CopyableText/CopyableText.d.ts +9 -0
  79. package/dist/components/primitives/CopyableText/CopyableText.d.ts.map +1 -1
  80. package/dist/components/primitives/CopyableText/CopyableText.js +40 -21
  81. package/dist/components/primitives/CopyableText/CopyableText.js.map +1 -1
  82. package/dist/components/primitives/CopyableText/CopyableText.styles.d.ts.map +1 -1
  83. package/dist/components/primitives/CopyableText/CopyableText.styles.js +5 -2
  84. package/dist/components/primitives/CopyableText/CopyableText.styles.js.map +1 -1
  85. package/dist/components/primitives/Icons/Copy.d.ts +7 -0
  86. package/dist/components/primitives/Icons/Copy.d.ts.map +1 -0
  87. package/dist/components/primitives/Icons/Copy.js +31 -0
  88. package/dist/components/primitives/Icons/Copy.js.map +1 -0
  89. package/dist/components/primitives/PasswordField/PasswordField.d.ts +9 -1
  90. package/dist/components/primitives/PasswordField/PasswordField.d.ts.map +1 -1
  91. package/dist/components/primitives/PasswordField/PasswordField.js +2 -1
  92. package/dist/components/primitives/PasswordField/PasswordField.js.map +1 -1
  93. package/dist/components/primitives/Select/ResourceSelect.d.ts +36 -0
  94. package/dist/components/primitives/Select/ResourceSelect.d.ts.map +1 -0
  95. package/dist/components/primitives/Select/ResourceSelect.js +100 -0
  96. package/dist/components/primitives/Select/ResourceSelect.js.map +1 -0
  97. package/dist/components/primitives/Select/Select.d.ts +22 -13
  98. package/dist/components/primitives/Select/Select.d.ts.map +1 -1
  99. package/dist/components/primitives/Select/Select.js +72 -22
  100. package/dist/components/primitives/Select/Select.js.map +1 -1
  101. package/dist/components/primitives/Select/Select.styles.d.ts.map +1 -1
  102. package/dist/components/primitives/Select/Select.styles.js +19 -3
  103. package/dist/components/primitives/Select/Select.styles.js.map +1 -1
  104. package/dist/components/primitives/Select/__tests__/selectTestUtils.d.ts +6 -0
  105. package/dist/components/primitives/Select/__tests__/selectTestUtils.d.ts.map +1 -0
  106. package/dist/components/primitives/Select/useSelectMessages.d.ts +9 -0
  107. package/dist/components/primitives/Select/useSelectMessages.d.ts.map +1 -0
  108. package/dist/components/primitives/Select/useSelectMessages.js +18 -0
  109. package/dist/components/primitives/Select/useSelectMessages.js.map +1 -0
  110. package/dist/components/primitives/TextField/TextField.d.ts +8 -0
  111. package/dist/components/primitives/TextField/TextField.d.ts.map +1 -1
  112. package/dist/components/primitives/TextField/TextField.js +19 -3
  113. package/dist/components/primitives/TextField/TextField.js.map +1 -1
  114. package/dist/components/primitives/TextField/TextField.styles.d.ts +2 -1
  115. package/dist/components/primitives/TextField/TextField.styles.d.ts.map +1 -1
  116. package/dist/components/primitives/TextField/TextField.styles.js +18 -5
  117. package/dist/components/primitives/TextField/TextField.styles.js.map +1 -1
  118. package/dist/components/primitives/UserSelect/BaseUserSelect.d.ts +15 -0
  119. package/dist/components/primitives/UserSelect/BaseUserSelect.d.ts.map +1 -0
  120. package/dist/components/primitives/UserSelect/BaseUserSelect.js +30 -0
  121. package/dist/components/primitives/UserSelect/BaseUserSelect.js.map +1 -0
  122. package/dist/components/primitives/UserSelect/UserSelect.d.ts +11 -0
  123. package/dist/components/primitives/UserSelect/UserSelect.d.ts.map +1 -0
  124. package/dist/components/primitives/UserSelect/UserSelect.js +19 -0
  125. package/dist/components/primitives/UserSelect/UserSelect.js.map +1 -0
  126. package/dist/components/primitives/UserTypeSelect/BaseUserTypeSelect.d.ts +14 -0
  127. package/dist/components/primitives/UserTypeSelect/BaseUserTypeSelect.d.ts.map +1 -0
  128. package/dist/components/primitives/UserTypeSelect/BaseUserTypeSelect.js +28 -0
  129. package/dist/components/primitives/UserTypeSelect/BaseUserTypeSelect.js.map +1 -0
  130. package/dist/components/primitives/UserTypeSelect/UserTypeSelect.d.ts +11 -0
  131. package/dist/components/primitives/UserTypeSelect/UserTypeSelect.d.ts.map +1 -0
  132. package/dist/components/primitives/UserTypeSelect/UserTypeSelect.js +19 -0
  133. package/dist/components/primitives/UserTypeSelect/UserTypeSelect.js.map +1 -0
  134. package/dist/hooks/useFetchAuthFlows.d.ts +9 -0
  135. package/dist/hooks/useFetchAuthFlows.d.ts.map +1 -0
  136. package/dist/hooks/useFetchAuthFlows.js +33 -0
  137. package/dist/hooks/useFetchAuthFlows.js.map +1 -0
  138. package/dist/hooks/useFetchUserTypes.d.ts +8 -0
  139. package/dist/hooks/useFetchUserTypes.d.ts.map +1 -0
  140. package/dist/hooks/useFetchUserTypes.js +31 -0
  141. package/dist/hooks/useFetchUserTypes.js.map +1 -0
  142. package/dist/hooks/useFetchUsers.d.ts +8 -0
  143. package/dist/hooks/useFetchUsers.d.ts.map +1 -0
  144. package/dist/hooks/useFetchUsers.js +32 -0
  145. package/dist/hooks/useFetchUsers.js.map +1 -0
  146. package/dist/hooks/useInfiniteOptions.d.ts +31 -0
  147. package/dist/hooks/useInfiniteOptions.d.ts.map +1 -0
  148. package/dist/hooks/useInfiniteOptions.js +126 -0
  149. package/dist/hooks/useInfiniteOptions.js.map +1 -0
  150. package/dist/hooks/useManagementEndpoint.d.ts +1 -1
  151. package/dist/hooks/useManagementEndpoint.d.ts.map +1 -1
  152. package/dist/hooks/useManagementEndpoint.js.map +1 -1
  153. package/dist/index.d.ts +18 -1
  154. package/dist/index.d.ts.map +1 -1
  155. package/dist/index.js +12 -2
  156. package/dist/utils/createAuthenticatedFetcher.d.ts.map +1 -1
  157. package/dist/utils/createAuthenticatedFetcher.js +1 -0
  158. package/dist/utils/createAuthenticatedFetcher.js.map +1 -1
  159. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"InviteUser.cjs","names":["InviteUser: FC<InviteUserProps>","useThunderID","flowExecuteUrl: string","EmbeddedFlowType","BaseInviteUser"],"sources":["../../../../../../src/components/presentation/auth/InviteUser/InviteUser.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n EmbeddedFlowType,\n getOrganizationUnitChildren,\n OrganizationUnitListResponse,\n resolveResourceEndpoint,\n} from '@thunderid/browser';\nimport {FC, ReactElement, ReactNode, useCallback} from 'react';\n// eslint-disable-next-line import/no-named-as-default\nimport BaseInviteUser, {BaseInviteUserRenderProps, InviteUserFlowResponse} from './BaseInviteUser';\nimport useThunderID from '../../../../contexts/ThunderID/useThunderID';\n\n/**\n * Render props for InviteUser (re-exported for convenience).\n */\nexport type InviteUserRenderProps = BaseInviteUserRenderProps;\n\n/**\n * Props for the InviteUser component.\n */\nexport interface InviteUserProps {\n /**\n * Render props function for custom UI.\n * If not provided, default UI will be rendered by the SDK.\n */\n children?: (props: InviteUserRenderProps) => ReactNode;\n\n /**\n * Custom CSS class name.\n */\n className?: string;\n\n /**\n * Callback when an error occurs.\n */\n onError?: (error: Error) => void;\n\n /**\n * Callback when the flow state changes.\n */\n onFlowChange?: (response: InviteUserFlowResponse) => void;\n\n /**\n * Whether to show the subtitle.\n */\n showSubtitle?: boolean;\n\n /**\n * Whether to show the title.\n */\n showTitle?: boolean;\n\n /**\n * Size variant for the component.\n */\n size?: 'small' | 'medium' | 'large';\n\n /**\n * Theme variant for the component card.\n */\n variant?: 'outlined' | 'elevated';\n}\n\n/**\n * InviteUser component for initiating invite user flow.\n *\n * This component is designed for admin users in the thunder-develop app to:\n * 1. Select a user type (if multiple available)\n * 2. Enter user details (username, email)\n * 3. Generate an invite link for the end user\n *\n * The component uses the authenticated ThunderID SDK context to make API calls\n * with the admin's access token (requires 'system' scope).\n *\n * @example\n * ```tsx\n * import { InviteUser } from '@thunderid/react';\n *\n * const InviteUserPage = () => {\n * const [inviteLink, setInviteLink] = useState<string>();\n *\n * return (\n * <InviteUser\n * onInviteLinkGenerated={(link, executionId) => setInviteLink(link)}\n * onError={(error) => console.error(error)}\n * >\n * {({ values, components, isLoading, handleInputChange, handleSubmit, inviteLink, isInviteGenerated }) => (\n * <div>\n * {isInviteGenerated ? (\n * <div>\n * <h2>Invite Link Generated!</h2>\n * <p>{inviteLink}</p>\n * </div>\n * ) : (\n * // Render form based on components\n * )}\n * </div>\n * )}\n * </InviteUser>\n * );\n * };\n * ```\n */\nconst InviteUser: FC<InviteUserProps> = ({\n onError,\n onFlowChange,\n className,\n children,\n size = 'medium',\n variant = 'outlined',\n showTitle = true,\n showSubtitle = true,\n}: InviteUserProps): ReactElement => {\n const {http, baseUrl, endpoints, getAccessToken, isInitialized} = useThunderID();\n\n // The user-onboarding flow runs on the resource server, which may differ from `baseUrl` (the\n // authorization server) in a trusted-issuer setup. Honor the `flowExecute` endpoint override.\n const flowExecuteUrl: string = resolveResourceEndpoint('flowExecute', {endpoints}) ?? `${baseUrl}/flow/execute`;\n\n /**\n * Initialize the invite user flow.\n * Makes an authenticated request to /flow/execute with flowType: USER_ONBOARDING.\n */\n const handleInitialize = async (payload: Record<string, any>): Promise<InviteUserFlowResponse> => {\n const response: any = await http.request({\n data: {\n ...payload,\n flowType: EmbeddedFlowType.UserOnboarding,\n verbose: true,\n },\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n url: flowExecuteUrl,\n } as any);\n\n return response.data as InviteUserFlowResponse;\n };\n\n /**\n * Submit flow step data.\n * Makes an authenticated request to /flow/execute with the step data.\n */\n const handleSubmit = async (payload: Record<string, any>): Promise<InviteUserFlowResponse> => {\n const response: any = await http.request({\n data: {\n ...payload,\n verbose: true,\n },\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n url: flowExecuteUrl,\n } as any);\n\n return response.data as InviteUserFlowResponse;\n };\n\n const fetchOrganizationUnitChildren: (\n parentId: string,\n limit: number,\n offset: number,\n ) => Promise<OrganizationUnitListResponse> = useCallback(\n async (parentId: string, limit: number, offset: number): Promise<OrganizationUnitListResponse> => {\n const accessToken: string = await getAccessToken();\n\n return getOrganizationUnitChildren({\n baseUrl,\n headers: {Authorization: `Bearer ${accessToken}`},\n limit,\n offset,\n organizationUnitId: parentId,\n });\n },\n [baseUrl, getAccessToken],\n );\n\n return (\n <BaseInviteUser\n onInitialize={handleInitialize}\n onSubmit={handleSubmit}\n onError={onError}\n onFlowChange={onFlowChange}\n className={className}\n fetchOrganizationUnitChildren={fetchOrganizationUnitChildren}\n isInitialized={isInitialized}\n size={size}\n variant={variant}\n showTitle={showTitle}\n showSubtitle={showSubtitle}\n >\n {children}\n </BaseInviteUser>\n );\n};\n\nexport default InviteUser;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyGA,MAAMA,cAAmC,EACvC,SACA,cACA,WACA,UACA,OAAO,UACP,UAAU,YACV,YAAY,MACZ,eAAe,WACoB;CACnC,MAAM,EAAC,MAAM,SAAS,WAAW,gBAAgB,kBAAiBC,8BAAc;CAIhF,MAAMC,kEAAiD,eAAe,EAAC,WAAU,CAAC,IAAI,GAAG,QAAQ;;;;;CAMjG,MAAM,mBAAmB,OAAO,YAAkE;AAehG,UAdsB,MAAM,KAAK,QAAQ;GACvC,MAAM;IACJ,GAAG;IACH,UAAUC,qCAAiB;IAC3B,SAAS;IACV;GACD,SAAS;IACP,QAAQ;IACR,gBAAgB;IACjB;GACD,QAAQ;GACR,KAAK;GACN,CAAQ,EAEO;;;;;;CAOlB,MAAM,eAAe,OAAO,YAAkE;AAc5F,UAbsB,MAAM,KAAK,QAAQ;GACvC,MAAM;IACJ,GAAG;IACH,SAAS;IACV;GACD,SAAS;IACP,QAAQ;IACR,gBAAgB;IACjB;GACD,QAAQ;GACR,KAAK;GACN,CAAQ,EAEO;;AAsBlB,QACE,2CAACC;EACC,cAAc;EACd,UAAU;EACD;EACK;EACH;EACX,sDArBF,OAAO,UAAkB,OAAe,WAA0D;AAGhG,+DAAmC;IACjC;IACA,SAAS,EAAC,eAAe,UAJC,MAAM,gBAAgB,IAIC;IACjD;IACA;IACA,oBAAoB;IACrB,CAAC;KAEJ,CAAC,SAAS,eAAe,CAC1B;EAUkB;EACT;EACG;EACE;EACG;EAEb;GACc;;AAIrB,yBAAe"}
1
+ {"version":3,"file":"InviteUser.cjs","names":["InviteUser: FC<InviteUserProps>","useThunderID","flowExecuteUrl: string","EmbeddedFlowType","BaseInviteUser","useFetchAuthFlows","useFetchUsers","useFetchUserTypes"],"sources":["../../../../../../src/components/presentation/auth/InviteUser/InviteUser.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n EmbeddedFlowType,\n FetchAuthFlows,\n FetchUsers,\n FetchUserTypes,\n getOrganizationUnitChildren,\n OrganizationUnitListResponse,\n resolveResourceEndpoint,\n} from '@thunderid/browser';\nimport {FC, ReactElement, ReactNode, useCallback} from 'react';\n// eslint-disable-next-line import/no-named-as-default\nimport BaseInviteUser, {BaseInviteUserRenderProps, InviteUserFlowResponse} from './BaseInviteUser';\nimport useThunderID from '../../../../contexts/ThunderID/useThunderID';\nimport useFetchAuthFlows from '../../../../hooks/useFetchAuthFlows';\nimport useFetchUsers from '../../../../hooks/useFetchUsers';\nimport useFetchUserTypes from '../../../../hooks/useFetchUserTypes';\n\n/**\n * Render props for InviteUser (re-exported for convenience).\n */\nexport type InviteUserRenderProps = BaseInviteUserRenderProps;\n\n/**\n * Props for the InviteUser component.\n */\nexport interface InviteUserProps {\n /**\n * Render props function for custom UI.\n * If not provided, default UI will be rendered by the SDK.\n */\n children?: (props: InviteUserRenderProps) => ReactNode;\n\n /**\n * Custom CSS class name.\n */\n className?: string;\n\n /**\n * Callback when an error occurs. It also receives the errors of failed user, flow and user type picker loads,\n * which the form itself does not show.\n */\n onError?: (error: Error) => void;\n\n /**\n * Callback when the flow state changes.\n */\n onFlowChange?: (response: InviteUserFlowResponse) => void;\n\n /**\n * Whether to show the subtitle.\n */\n showSubtitle?: boolean;\n\n /**\n * Whether to show the title.\n */\n showTitle?: boolean;\n\n /**\n * Size variant for the component.\n */\n size?: 'small' | 'medium' | 'large';\n\n /**\n * Theme variant for the component card.\n */\n variant?: 'outlined' | 'elevated';\n}\n\n/**\n * InviteUser component for initiating invite user flow.\n *\n * This component is designed for admin users in the thunder-develop app to:\n * 1. Select a user type (if multiple available)\n * 2. Enter user details (username, email)\n * 3. Generate an invite link for the end user\n *\n * The component uses the authenticated ThunderID SDK context to make API calls\n * with the admin's access token (requires 'system' scope).\n *\n * @example\n * ```tsx\n * import { InviteUser } from '@thunderid/react';\n *\n * const InviteUserPage = () => {\n * const [inviteLink, setInviteLink] = useState<string>();\n *\n * return (\n * <InviteUser\n * onInviteLinkGenerated={(link, executionId) => setInviteLink(link)}\n * onError={(error) => console.error(error)}\n * >\n * {({ values, components, isLoading, handleInputChange, handleSubmit, inviteLink, isInviteGenerated }) => (\n * <div>\n * {isInviteGenerated ? (\n * <div>\n * <h2>Invite Link Generated!</h2>\n * <p>{inviteLink}</p>\n * </div>\n * ) : (\n * // Render form based on components\n * )}\n * </div>\n * )}\n * </InviteUser>\n * );\n * };\n * ```\n */\nconst InviteUser: FC<InviteUserProps> = ({\n onError,\n onFlowChange,\n className,\n children,\n size = 'medium',\n variant = 'outlined',\n showTitle = true,\n showSubtitle = true,\n}: InviteUserProps): ReactElement => {\n const {http, baseUrl, endpoints, getAccessToken, isInitialized} = useThunderID();\n\n // The user-onboarding flow runs on the resource server, which may differ from `baseUrl` (the\n // authorization server) in a trusted-issuer setup. Honor the `flowExecute` endpoint override.\n const flowExecuteUrl: string = resolveResourceEndpoint('flowExecute', {endpoints}) ?? `${baseUrl}/flow/execute`;\n\n /**\n * Initialize the invite user flow.\n * Makes an authenticated request to /flow/execute with flowType: USER_ONBOARDING.\n */\n const handleInitialize = async (payload: Record<string, any>): Promise<InviteUserFlowResponse> => {\n const response: any = await http.request({\n data: {\n ...payload,\n flowType: EmbeddedFlowType.UserOnboarding,\n verbose: true,\n },\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n url: flowExecuteUrl,\n } as any);\n\n return response.data as InviteUserFlowResponse;\n };\n\n /**\n * Submit flow step data.\n * Makes an authenticated request to /flow/execute with the step data.\n */\n const handleSubmit = async (payload: Record<string, any>): Promise<InviteUserFlowResponse> => {\n const response: any = await http.request({\n data: {\n ...payload,\n verbose: true,\n },\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n method: 'POST',\n url: flowExecuteUrl,\n } as any);\n\n return response.data as InviteUserFlowResponse;\n };\n\n const fetchOrganizationUnitChildren: (\n parentId: string,\n limit: number,\n offset: number,\n ) => Promise<OrganizationUnitListResponse> = useCallback(\n async (parentId: string, limit: number, offset: number): Promise<OrganizationUnitListResponse> => {\n const accessToken: string = await getAccessToken();\n\n return getOrganizationUnitChildren({\n baseUrl,\n headers: {Authorization: `Bearer ${accessToken}`},\n limit,\n offset,\n organizationUnitId: parentId,\n });\n },\n [baseUrl, getAccessToken],\n );\n\n const fetchAuthFlows: FetchAuthFlows = useFetchAuthFlows();\n const fetchUsers: FetchUsers = useFetchUsers();\n const fetchUserTypes: FetchUserTypes = useFetchUserTypes();\n\n return (\n <BaseInviteUser\n onInitialize={handleInitialize}\n onSubmit={handleSubmit}\n onError={onError}\n onFlowChange={onFlowChange}\n className={className}\n fetchOrganizationUnitChildren={fetchOrganizationUnitChildren}\n fetchAuthFlows={fetchAuthFlows}\n fetchUsers={fetchUsers}\n fetchUserTypes={fetchUserTypes}\n isInitialized={isInitialized}\n size={size}\n variant={variant}\n showTitle={showTitle}\n showSubtitle={showSubtitle}\n >\n {children}\n </BaseInviteUser>\n );\n};\n\nexport default InviteUser;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgHA,MAAMA,cAAmC,EACvC,SACA,cACA,WACA,UACA,OAAO,UACP,UAAU,YACV,YAAY,MACZ,eAAe,WACoB;CACnC,MAAM,EAAC,MAAM,SAAS,WAAW,gBAAgB,kBAAiBC,8BAAc;CAIhF,MAAMC,kEAAiD,eAAe,EAAC,WAAU,CAAC,IAAI,GAAG,QAAQ;;;;;CAMjG,MAAM,mBAAmB,OAAO,YAAkE;AAehG,UAdsB,MAAM,KAAK,QAAQ;GACvC,MAAM;IACJ,GAAG;IACH,UAAUC,qCAAiB;IAC3B,SAAS;IACV;GACD,SAAS;IACP,QAAQ;IACR,gBAAgB;IACjB;GACD,QAAQ;GACR,KAAK;GACN,CAAQ,EAEO;;;;;;CAOlB,MAAM,eAAe,OAAO,YAAkE;AAc5F,UAbsB,MAAM,KAAK,QAAQ;GACvC,MAAM;IACJ,GAAG;IACH,SAAS;IACV;GACD,SAAS;IACP,QAAQ;IACR,gBAAgB;IACjB;GACD,QAAQ;GACR,KAAK;GACN,CAAQ,EAEO;;AA0BlB,QACE,2CAACC;EACC,cAAc;EACd,UAAU;EACD;EACK;EACH;EACX,sDAzBF,OAAO,UAAkB,OAAe,WAA0D;AAGhG,+DAAmC;IACjC;IACA,SAAS,EAAC,eAAe,UAJC,MAAM,gBAAgB,IAIC;IACjD;IACA;IACA,oBAAoB;IACrB,CAAC;KAEJ,CAAC,SAAS,eAAe,CAC1B;EAcG,gBAZmCC,mCAAmB;EAatD,YAZ2BC,+BAAe;EAa1C,gBAZmCC,mCAAmB;EAavC;EACT;EACG;EACE;EACG;EAEb;GACc;;AAIrB,yBAAe"}
@@ -0,0 +1,21 @@
1
+ const require_rolldown_runtime = require('../../../_virtual/rolldown_runtime.cjs');
2
+ const require_useFetchAuthFlows = require('../../../hooks/useFetchAuthFlows.cjs');
3
+ const require_BaseAuthFlowSelect = require('./BaseAuthFlowSelect.cjs');
4
+ let react_jsx_runtime = require("react/jsx-runtime");
5
+ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
6
+
7
+ //#region src/components/primitives/AuthFlowSelect/AuthFlowSelect.tsx
8
+ /**
9
+ * Authentication-flow picker for signed-in screens. It loads the authentication flows itself with the
10
+ * signed-in user's access token and submits the chosen flow's ID, so it takes no data source. For any
11
+ * other data source use `Select` with `useInfiniteOptions`.
12
+ */
13
+ const AuthFlowSelect = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_BaseAuthFlowSelect.default, {
14
+ ...props,
15
+ fetchAuthFlows: require_useFetchAuthFlows.default()
16
+ });
17
+ var AuthFlowSelect_default = AuthFlowSelect;
18
+
19
+ //#endregion
20
+ exports.default = AuthFlowSelect_default;
21
+ //# sourceMappingURL=AuthFlowSelect.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AuthFlowSelect.cjs","names":["AuthFlowSelect: FC<AuthFlowSelectProps>","BaseAuthFlowSelect","useFetchAuthFlows"],"sources":["../../../../../src/components/primitives/AuthFlowSelect/AuthFlowSelect.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {FC} from 'react';\nimport BaseAuthFlowSelect, {BaseAuthFlowSelectProps} from './BaseAuthFlowSelect';\nimport useFetchAuthFlows from '../../../hooks/useFetchAuthFlows';\n\nexport type AuthFlowSelectProps = Omit<BaseAuthFlowSelectProps, 'fetchAuthFlows'>;\n\n/**\n * Authentication-flow picker for signed-in screens. It loads the authentication flows itself with the\n * signed-in user's access token and submits the chosen flow's ID, so it takes no data source. For any\n * other data source use `Select` with `useInfiniteOptions`.\n */\nconst AuthFlowSelect: FC<AuthFlowSelectProps> = (props: AuthFlowSelectProps) => (\n <BaseAuthFlowSelect {...props} fetchAuthFlows={useFetchAuthFlows()} />\n);\n\nexport default AuthFlowSelect;\n"],"mappings":";;;;;;;;;;;;AAcA,MAAMA,kBAA2C,UAC/C,2CAACC;CAAmB,GAAI;CAAO,gBAAgBC,mCAAmB;EAAI;AAGxE,6BAAe"}
@@ -0,0 +1,32 @@
1
+ const require_rolldown_runtime = require('../../../_virtual/rolldown_runtime.cjs');
2
+ const require_ResourceSelect = require('../Select/ResourceSelect.cjs');
3
+ let __thunderid_browser = require("@thunderid/browser");
4
+ __thunderid_browser = require_rolldown_runtime.__toESM(__thunderid_browser);
5
+ let react_jsx_runtime = require("react/jsx-runtime");
6
+ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
7
+
8
+ //#region src/components/primitives/AuthFlowSelect/BaseAuthFlowSelect.tsx
9
+ const AUTH_FLOW_DEFAULTS = { labelAttributes: __thunderid_browser.AuthFlowSelectConstants.LABEL_ATTRIBUTES };
10
+ /**
11
+ * Authentication-flow picker: a `Select` that loads from the given `fetchAuthFlows`, showing each
12
+ * flow by its name and submitting its ID. Building block for {@link AuthFlowSelect}; for a custom
13
+ * data source use `Select` with `useInfiniteOptions`.
14
+ */
15
+ const BaseAuthFlowSelect = ({ fetchAuthFlows,...rest }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ResourceSelect.default, {
16
+ ...rest,
17
+ bemBlock: "auth-flow-select",
18
+ defaults: AUTH_FLOW_DEFAULTS,
19
+ fallbackMessages: {
20
+ ariaLabel: "Select a login flow",
21
+ empty: "No login flows found."
22
+ },
23
+ fetchPage: fetchAuthFlows,
24
+ messageKey: "auth_flow_select",
25
+ resource: "auth flows",
26
+ type: __thunderid_browser.EmbeddedFlowComponentType.AuthFlowSelect
27
+ });
28
+ var BaseAuthFlowSelect_default = BaseAuthFlowSelect;
29
+
30
+ //#endregion
31
+ exports.default = BaseAuthFlowSelect_default;
32
+ //# sourceMappingURL=BaseAuthFlowSelect.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BaseAuthFlowSelect.cjs","names":["AUTH_FLOW_DEFAULTS: SelectOptionDefaults","AuthFlowSelectConstants","BaseAuthFlowSelect: FC<BaseAuthFlowSelectProps>","ResourceSelect","EmbeddedFlowComponentType"],"sources":["../../../../../src/components/primitives/AuthFlowSelect/BaseAuthFlowSelect.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {AuthFlowSelectConstants, EmbeddedFlowComponentType} from '@thunderid/browser';\nimport type {FetchAuthFlows, SelectOptionDefaults} from '@thunderid/browser';\nimport {FC} from 'react';\nimport ResourceSelect, {ResourceSelectProps} from '../Select/ResourceSelect';\n\nexport interface BaseAuthFlowSelectProps\n extends Omit<\n ResourceSelectProps,\n 'bemBlock' | 'defaults' | 'fallbackMessages' | 'fetchPage' | 'mapping' | 'messageKey' | 'resource' | 'type'\n > {\n fetchAuthFlows?: FetchAuthFlows;\n}\n\nconst AUTH_FLOW_DEFAULTS: SelectOptionDefaults = {labelAttributes: AuthFlowSelectConstants.LABEL_ATTRIBUTES};\n\n/**\n * Authentication-flow picker: a `Select` that loads from the given `fetchAuthFlows`, showing each\n * flow by its name and submitting its ID. Building block for {@link AuthFlowSelect}; for a custom\n * data source use `Select` with `useInfiniteOptions`.\n */\nconst BaseAuthFlowSelect: FC<BaseAuthFlowSelectProps> = ({fetchAuthFlows, ...rest}: BaseAuthFlowSelectProps) => (\n <ResourceSelect\n {...rest}\n bemBlock=\"auth-flow-select\"\n defaults={AUTH_FLOW_DEFAULTS}\n fallbackMessages={{\n ariaLabel: 'Select a login flow',\n empty: 'No login flows found.',\n }}\n fetchPage={fetchAuthFlows}\n messageKey=\"auth_flow_select\"\n resource=\"auth flows\"\n type={EmbeddedFlowComponentType.AuthFlowSelect}\n />\n);\n\nexport default BaseAuthFlowSelect;\n"],"mappings":";;;;;;;;AAgBA,MAAMA,qBAA2C,EAAC,iBAAiBC,4CAAwB,kBAAiB;;;;;;AAO5G,MAAMC,sBAAmD,EAAC,eAAgB,GAAG,WAC3E,2CAACC;CACC,GAAI;CACJ,UAAS;CACT,UAAU;CACV,kBAAkB;EAChB,WAAW;EACX,OAAO;EACR;CACD,WAAW;CACX,YAAW;CACX,UAAS;CACT,MAAMC,8CAA0B;EAChC;AAGJ,iCAAe"}
@@ -2,57 +2,77 @@ const require_rolldown_runtime = require('../../../_virtual/rolldown_runtime.cjs
2
2
  const require_useTheme = require('../../../contexts/Theme/useTheme.cjs');
3
3
  const require_useTranslation = require('../../../hooks/useTranslation.cjs');
4
4
  const require_Button = require('../Button/Button.cjs');
5
+ const require_PasswordField = require('../PasswordField/PasswordField.cjs');
5
6
  const require_CopyableText_styles = require('./CopyableText.styles.cjs');
7
+ const require_Check = require('../Icons/Check.cjs');
8
+ const require_Copy = require('../Icons/Copy.cjs');
9
+ let __thunderid_browser = require("@thunderid/browser");
10
+ __thunderid_browser = require_rolldown_runtime.__toESM(__thunderid_browser);
6
11
  let react = require("react");
7
12
  react = require_rolldown_runtime.__toESM(react);
8
13
  let react_jsx_runtime = require("react/jsx-runtime");
9
14
  react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
10
15
 
11
16
  //#region src/components/primitives/CopyableText/CopyableText.tsx
17
+ const ignoreChange = () => void 0;
12
18
  /**
13
19
  * A React component that displays a text value with an optional label and a button to copy the value to
14
20
  * the clipboard. When the button is clicked, it attempts to copy the value using the Clipboard API, and
15
21
  * falls back to a textarea method if the API is not supported.
16
22
  * After copying, it shows a "Copied!" message for 3 seconds before resetting.
23
+ *
24
+ * The copy control is an icon button inside the box in both layouts. With `masked`, the value sits in a read-only
25
+ * password field with the same show and hide control as `PasswordField`. A browser does not copy out of a password
26
+ * field, which is why copying is a button of its own.
17
27
  */
18
- const CopyableText = ({ label, value }) => {
28
+ const CopyableText = ({ label, masked = false, value }) => {
19
29
  const { theme } = require_useTheme.default();
20
30
  const styles = require_CopyableText_styles.default(theme);
21
31
  const { t } = require_useTranslation.default();
22
32
  const [copied, setCopied] = (0, react.useState)(false);
23
33
  const handleCopy = (0, react.useCallback)(async () => {
24
- try {
25
- await navigator.clipboard.writeText(value);
26
- } catch {
27
- const textArea = document.createElement("textarea");
28
- textArea.value = value;
29
- document.body.appendChild(textArea);
30
- textArea.select();
31
- document.execCommand("copy");
32
- document.body.removeChild(textArea);
33
- }
34
+ await (0, __thunderid_browser.copyTextToClipboard)(value);
34
35
  setCopied(true);
35
36
  setTimeout(() => setCopied(false), 3e3);
36
37
  }, [value]);
38
+ const copyLabel = copied ? t("elements.display.copyable_text.copied") : t("elements.display.copyable_text.copy");
39
+ const copyButton = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Button.default, {
40
+ variant: "icon",
41
+ size: "small",
42
+ className: styles["copyButton"],
43
+ "aria-label": copyLabel,
44
+ title: copyLabel,
45
+ onClick: () => {
46
+ handleCopy().catch(() => void 0);
47
+ },
48
+ children: copied ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Check.default, {
49
+ width: 16,
50
+ height: 16
51
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Copy.default, {
52
+ width: 16,
53
+ height: 16
54
+ })
55
+ });
37
56
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
38
57
  className: styles["container"],
39
58
  children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
40
59
  className: styles["label"],
41
60
  children: label
42
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
61
+ }), masked ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_PasswordField.default, {
62
+ "aria-label": label,
63
+ autoComplete: "off",
64
+ endAdornment: copyButton,
65
+ hideLabel: t("elements.display.copyable_text.hide"),
66
+ onChange: ignoreChange,
67
+ readOnly: true,
68
+ showLabel: t("elements.display.copyable_text.show"),
69
+ value
70
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
43
71
  className: styles["valueBox"],
44
72
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
45
73
  className: styles["valueText"],
46
74
  children: value
47
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Button.default, {
48
- variant: "outline",
49
- size: "small",
50
- className: styles["copyButton"],
51
- onClick: () => {
52
- handleCopy().catch(() => void 0);
53
- },
54
- children: copied ? t("elements.display.copyable_text.copied") : t("elements.display.copyable_text.copy")
55
- })]
75
+ }), copyButton]
56
76
  })]
57
77
  });
58
78
  };
@@ -1 +1 @@
1
- {"version":3,"file":"CopyableText.cjs","names":["CopyableText: FC<CopyableTextProps>","useTheme","styles: Record<string, string>","useStyles","useTranslation","handleCopy: ReturnType<typeof useCallback>","textArea: HTMLTextAreaElement","Button"],"sources":["../../../../../src/components/primitives/CopyableText/CopyableText.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {FC, ReactElement, useCallback, useState} from 'react';\nimport useStyles from './CopyableText.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport useTranslation from '../../../hooks/useTranslation';\nimport Button from '../Button/Button';\n\nexport interface CopyableTextProps {\n /**\n * Optional label displayed above the value box.\n */\n label?: string;\n /**\n * The text value to display and copy.\n */\n value: string;\n}\n\n/**\n * A React component that displays a text value with an optional label and a button to copy the value to\n * the clipboard. When the button is clicked, it attempts to copy the value using the Clipboard API, and\n * falls back to a textarea method if the API is not supported.\n * After copying, it shows a \"Copied!\" message for 3 seconds before resetting.\n */\nconst CopyableText: FC<CopyableTextProps> = ({label, value}: CopyableTextProps): ReactElement => {\n const {theme} = useTheme();\n const styles: Record<string, string> = useStyles(theme);\n const {t} = useTranslation();\n const [copied, setCopied] = useState(false);\n\n const handleCopy: ReturnType<typeof useCallback> = useCallback(async (): Promise<void> => {\n try {\n await navigator.clipboard.writeText(value);\n } catch {\n const textArea: HTMLTextAreaElement = document.createElement('textarea');\n textArea.value = value;\n document.body.appendChild(textArea);\n textArea.select();\n document.execCommand('copy');\n document.body.removeChild(textArea);\n }\n setCopied(true);\n setTimeout(() => setCopied(false), 3000);\n }, [value]);\n\n return (\n <div className={styles['container']}>\n {label && <span className={styles['label']}>{label}</span>}\n <div className={styles['valueBox']}>\n <span className={styles['valueText']}>{value}</span>\n <Button\n variant=\"outline\"\n size=\"small\"\n className={styles['copyButton']}\n onClick={() => {\n handleCopy().catch(() => undefined);\n }}\n >\n {copied ? t('elements.display.copyable_text.copied') : t('elements.display.copyable_text.copy')}\n </Button>\n </div>\n </div>\n );\n};\n\nexport default CopyableText;\n"],"mappings":";;;;;;;;;;;;;;;;;AA0BA,MAAMA,gBAAuC,EAAC,OAAO,YAA4C;CAC/F,MAAM,EAAC,UAASC,0BAAU;CAC1B,MAAMC,SAAiCC,oCAAU,MAAM;CACvD,MAAM,EAAC,MAAKC,gCAAgB;CAC5B,MAAM,CAAC,QAAQ,iCAAsB,MAAM;CAE3C,MAAMC,oCAAyD,YAA2B;AACxF,MAAI;AACF,SAAM,UAAU,UAAU,UAAU,MAAM;UACpC;GACN,MAAMC,WAAgC,SAAS,cAAc,WAAW;AACxE,YAAS,QAAQ;AACjB,YAAS,KAAK,YAAY,SAAS;AACnC,YAAS,QAAQ;AACjB,YAAS,YAAY,OAAO;AAC5B,YAAS,KAAK,YAAY,SAAS;;AAErC,YAAU,KAAK;AACf,mBAAiB,UAAU,MAAM,EAAE,IAAK;IACvC,CAAC,MAAM,CAAC;AAEX,QACE,4CAAC;EAAI,WAAW,OAAO;aACpB,SAAS,2CAAC;GAAK,WAAW,OAAO;aAAW;IAAa,EAC1D,4CAAC;GAAI,WAAW,OAAO;cACrB,2CAAC;IAAK,WAAW,OAAO;cAAe;KAAa,EACpD,2CAACC;IACC,SAAQ;IACR,MAAK;IACL,WAAW,OAAO;IAClB,eAAe;AACb,iBAAY,CAAC,YAAY,OAAU;;cAGpC,SAAS,EAAE,wCAAwC,GAAG,EAAE,sCAAsC;KACxF;IACL;GACF;;AAIV,2BAAe"}
1
+ {"version":3,"file":"CopyableText.cjs","names":["CopyableText: FC<CopyableTextProps>","useTheme","styles: Record<string, string>","useStyles","useTranslation","handleCopy: ReturnType<typeof useCallback>","copyLabel: string","copyButton: ReactElement","Button","Check","Copy","PasswordField"],"sources":["../../../../../src/components/primitives/CopyableText/CopyableText.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {copyTextToClipboard} from '@thunderid/browser';\nimport {FC, ReactElement, useCallback, useState} from 'react';\nimport useStyles from './CopyableText.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport useTranslation from '../../../hooks/useTranslation';\nimport Button from '../Button/Button';\nimport Check from '../Icons/Check';\nimport Copy from '../Icons/Copy';\nimport PasswordField from '../PasswordField/PasswordField';\n\nexport interface CopyableTextProps {\n /**\n * Optional label displayed above the value box.\n */\n label?: string;\n /**\n * Hides the value in a read-only password field until the user chooses to show it, as for a secret. Copying always\n * copies the real value.\n */\n masked?: boolean;\n /**\n * The text value to display and copy.\n */\n value: string;\n}\n\nconst ignoreChange = (): void => undefined;\n\n/**\n * A React component that displays a text value with an optional label and a button to copy the value to\n * the clipboard. When the button is clicked, it attempts to copy the value using the Clipboard API, and\n * falls back to a textarea method if the API is not supported.\n * After copying, it shows a \"Copied!\" message for 3 seconds before resetting.\n *\n * The copy control is an icon button inside the box in both layouts. With `masked`, the value sits in a read-only\n * password field with the same show and hide control as `PasswordField`. A browser does not copy out of a password\n * field, which is why copying is a button of its own.\n */\nconst CopyableText: FC<CopyableTextProps> = ({label, masked = false, value}: CopyableTextProps): ReactElement => {\n const {theme} = useTheme();\n const styles: Record<string, string> = useStyles(theme);\n const {t} = useTranslation();\n const [copied, setCopied] = useState(false);\n\n const handleCopy: ReturnType<typeof useCallback> = useCallback(async (): Promise<void> => {\n await copyTextToClipboard(value);\n setCopied(true);\n setTimeout(() => setCopied(false), 3000);\n }, [value]);\n\n const copyLabel: string = copied\n ? t('elements.display.copyable_text.copied')\n : t('elements.display.copyable_text.copy');\n\n const copyButton: ReactElement = (\n <Button\n variant=\"icon\"\n size=\"small\"\n className={styles['copyButton']}\n aria-label={copyLabel}\n title={copyLabel}\n onClick={() => {\n handleCopy().catch(() => undefined);\n }}\n >\n {copied ? <Check width={16} height={16} /> : <Copy width={16} height={16} />}\n </Button>\n );\n\n return (\n <div className={styles['container']}>\n {label && <span className={styles['label']}>{label}</span>}\n {masked ? (\n <PasswordField\n aria-label={label}\n autoComplete=\"off\"\n endAdornment={copyButton}\n hideLabel={t('elements.display.copyable_text.hide')}\n onChange={ignoreChange}\n readOnly\n showLabel={t('elements.display.copyable_text.show')}\n value={value}\n />\n ) : (\n <div className={styles['valueBox']}>\n <span className={styles['valueText']}>{value}</span>\n {copyButton}\n </div>\n )}\n </div>\n );\n};\n\nexport default CopyableText;\n"],"mappings":";;;;;;;;;;;;;;;;AA6BA,MAAM,qBAA2B;;;;;;;;;;;AAYjC,MAAMA,gBAAuC,EAAC,OAAO,SAAS,OAAO,YAA4C;CAC/G,MAAM,EAAC,UAASC,0BAAU;CAC1B,MAAMC,SAAiCC,oCAAU,MAAM;CACvD,MAAM,EAAC,MAAKC,gCAAgB;CAC5B,MAAM,CAAC,QAAQ,iCAAsB,MAAM;CAE3C,MAAMC,oCAAyD,YAA2B;AACxF,qDAA0B,MAAM;AAChC,YAAU,KAAK;AACf,mBAAiB,UAAU,MAAM,EAAE,IAAK;IACvC,CAAC,MAAM,CAAC;CAEX,MAAMC,YAAoB,SACtB,EAAE,wCAAwC,GAC1C,EAAE,sCAAsC;CAE5C,MAAMC,aACJ,2CAACC;EACC,SAAQ;EACR,MAAK;EACL,WAAW,OAAO;EAClB,cAAY;EACZ,OAAO;EACP,eAAe;AACb,eAAY,CAAC,YAAY,OAAU;;YAGpC,SAAS,2CAACC;GAAM,OAAO;GAAI,QAAQ;IAAM,GAAG,2CAACC;GAAK,OAAO;GAAI,QAAQ;IAAM;GACrE;AAGX,QACE,4CAAC;EAAI,WAAW,OAAO;aACpB,SAAS,2CAAC;GAAK,WAAW,OAAO;aAAW;IAAa,EACzD,SACC,2CAACC;GACC,cAAY;GACZ,cAAa;GACb,cAAc;GACd,WAAW,EAAE,sCAAsC;GACnD,UAAU;GACV;GACA,WAAW,EAAE,sCAAsC;GAC5C;IACP,GAEF,4CAAC;GAAI,WAAW,OAAO;cACrB,2CAAC;IAAK,WAAW,OAAO;cAAe;KAAa,EACnD;IACG;GAEJ;;AAIV,2BAAe"}
@@ -12,8 +12,11 @@ const useStyles = (theme) => (0, react.useMemo)(() => ({
12
12
  width: 100%;
13
13
  `,
14
14
  copyButton: require_emotion.css`
15
- flex-shrink: 0;
16
- white-space: nowrap;
15
+ color: ${theme.vars.colors.text.secondary};
16
+
17
+ &:hover:not(:disabled) {
18
+ color: ${theme.vars.colors.text.primary};
19
+ }
17
20
  `,
18
21
  label: require_emotion.css`
19
22
  color: ${theme.vars.colors.text.secondary};
@@ -1 +1 @@
1
- {"version":3,"file":"CopyableText.styles.cjs","names":[],"sources":["../../../../../src/components/primitives/CopyableText/CopyableText.styles.ts"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {Theme} from '@thunderid/browser';\nimport {useMemo} from 'react';\nimport {css} from '../../../styles/emotion';\n\nconst useStyles = (theme: Theme): Record<string, string> =>\n useMemo(\n () => ({\n container: css`\n display: flex;\n flex-direction: column;\n gap: calc(${theme.vars.spacing.unit} * 0.5);\n width: 100%;\n `,\n copyButton: css`\n flex-shrink: 0;\n white-space: nowrap;\n `,\n label: css`\n color: ${theme.vars.colors.text.secondary};\n font-size: 0.875rem;\n font-weight: 500;\n `,\n valueBox: css`\n align-items: center;\n background-color: ${theme.vars.colors.background.surface};\n border: 1px solid ${theme.vars.colors.border};\n border-radius: ${theme.vars.borderRadius.small};\n display: flex;\n gap: calc(${theme.vars.spacing.unit} * 1);\n padding: calc(${theme.vars.spacing.unit} * 0.75) calc(${theme.vars.spacing.unit} * 1);\n `,\n valueText: css`\n color: ${theme.vars.colors.text.primary};\n flex: 1;\n font-family: monospace;\n font-size: 0.85rem;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n word-break: break-all;\n `,\n }),\n [theme],\n );\n\nexport default useStyles;\n"],"mappings":";;;;;;AAOA,MAAM,aAAa,oCAER;CACL,WAAW,mBAAG;;;oBAGA,MAAM,KAAK,QAAQ,KAAK;;;CAGtC,YAAY,mBAAG;;;;CAIf,OAAO,mBAAG;iBACC,MAAM,KAAK,OAAO,KAAK,UAAU;;;;CAI5C,UAAU,mBAAG;;4BAES,MAAM,KAAK,OAAO,WAAW,QAAQ;4BACrC,MAAM,KAAK,OAAO,OAAO;yBAC5B,MAAM,KAAK,aAAa,MAAM;;oBAEnC,MAAM,KAAK,QAAQ,KAAK;wBACpB,MAAM,KAAK,QAAQ,KAAK,gBAAgB,MAAM,KAAK,QAAQ,KAAK;;CAElF,WAAW,mBAAG;iBACH,MAAM,KAAK,OAAO,KAAK,QAAQ;;;;;;;;;CAS3C,GACD,CAAC,MAAM,CACR;AAEH,kCAAe"}
1
+ {"version":3,"file":"CopyableText.styles.cjs","names":[],"sources":["../../../../../src/components/primitives/CopyableText/CopyableText.styles.ts"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {Theme} from '@thunderid/browser';\nimport {useMemo} from 'react';\nimport {css} from '../../../styles/emotion';\n\nconst useStyles = (theme: Theme): Record<string, string> =>\n useMemo(\n () => ({\n container: css`\n display: flex;\n flex-direction: column;\n gap: calc(${theme.vars.spacing.unit} * 0.5);\n width: 100%;\n `,\n copyButton: css`\n color: ${theme.vars.colors.text.secondary};\n\n &:hover:not(:disabled) {\n color: ${theme.vars.colors.text.primary};\n }\n `,\n label: css`\n color: ${theme.vars.colors.text.secondary};\n font-size: 0.875rem;\n font-weight: 500;\n `,\n valueBox: css`\n align-items: center;\n background-color: ${theme.vars.colors.background.surface};\n border: 1px solid ${theme.vars.colors.border};\n border-radius: ${theme.vars.borderRadius.small};\n display: flex;\n gap: calc(${theme.vars.spacing.unit} * 1);\n padding: calc(${theme.vars.spacing.unit} * 0.75) calc(${theme.vars.spacing.unit} * 1);\n `,\n valueText: css`\n color: ${theme.vars.colors.text.primary};\n flex: 1;\n font-family: monospace;\n font-size: 0.85rem;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n word-break: break-all;\n `,\n }),\n [theme],\n );\n\nexport default useStyles;\n"],"mappings":";;;;;;AAOA,MAAM,aAAa,oCAER;CACL,WAAW,mBAAG;;;oBAGA,MAAM,KAAK,QAAQ,KAAK;;;CAGtC,YAAY,mBAAG;iBACJ,MAAM,KAAK,OAAO,KAAK,UAAU;;;mBAG/B,MAAM,KAAK,OAAO,KAAK,QAAQ;;;CAG5C,OAAO,mBAAG;iBACC,MAAM,KAAK,OAAO,KAAK,UAAU;;;;CAI5C,UAAU,mBAAG;;4BAES,MAAM,KAAK,OAAO,WAAW,QAAQ;4BACrC,MAAM,KAAK,OAAO,OAAO;yBAC5B,MAAM,KAAK,aAAa,MAAM;;oBAEnC,MAAM,KAAK,QAAQ,KAAK;wBACpB,MAAM,KAAK,QAAQ,KAAK,gBAAgB,MAAM,KAAK,QAAQ,KAAK;;CAElF,WAAW,mBAAG;iBACH,MAAM,KAAK,OAAO,KAAK,QAAQ;;;;;;;;;CAS3C,GACD,CAAC,MAAM,CACR;AAEH,kCAAe"}
@@ -0,0 +1,33 @@
1
+ const require_rolldown_runtime = require('../../../_virtual/rolldown_runtime.cjs');
2
+ let react_jsx_runtime = require("react/jsx-runtime");
3
+ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
4
+
5
+ //#region src/components/primitives/Icons/Copy.tsx
6
+ /**
7
+ * Copy icon component.
8
+ */
9
+ const Copy = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
10
+ xmlns: "http://www.w3.org/2000/svg",
11
+ width: "24",
12
+ height: "24",
13
+ viewBox: "0 0 24 24",
14
+ fill: "none",
15
+ stroke: "currentColor",
16
+ strokeWidth: "2",
17
+ strokeLinecap: "round",
18
+ strokeLinejoin: "round",
19
+ ...props,
20
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("rect", {
21
+ width: "14",
22
+ height: "14",
23
+ x: "8",
24
+ y: "8",
25
+ rx: "2",
26
+ ry: "2"
27
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" })]
28
+ });
29
+ var Copy_default = Copy;
30
+
31
+ //#endregion
32
+ exports.default = Copy_default;
33
+ //# sourceMappingURL=Copy.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Copy.cjs","names":["Copy: FC<SVGProps<SVGSVGElement>>"],"sources":["../../../../../src/components/primitives/Icons/Copy.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {FC, SVGProps} from 'react';\n\n/**\n * Copy icon component.\n */\nconst Copy: FC<SVGProps<SVGSVGElement>> = (props: SVGProps<SVGSVGElement>) => (\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"24\"\n height=\"24\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth=\"2\"\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n {...props}\n >\n <rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\" />\n <path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\" />\n </svg>\n);\n\nexport default Copy;\n"],"mappings":";;;;;;;;AAQA,MAAMA,QAAqC,UACzC,4CAAC;CACC,OAAM;CACN,OAAM;CACN,QAAO;CACP,SAAQ;CACR,MAAK;CACL,QAAO;CACP,aAAY;CACZ,eAAc;CACd,gBAAe;CACf,GAAI;YAEJ,2CAAC;EAAK,OAAM;EAAK,QAAO;EAAK,GAAE;EAAI,GAAE;EAAI,IAAG;EAAI,IAAG;GAAM,EACzD,2CAAC,UAAK,GAAE,4DAA4D;EAChE;AAGR,mBAAe"}
@@ -17,7 +17,7 @@ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
17
17
  * Password field component with show/hide toggle functionality.
18
18
  * This component extends TextField and adds password visibility toggle functionality.
19
19
  */
20
- const PasswordField = ({ autoComplete = "current-password", onChange, className, disabled, error,...textFieldProps }) => {
20
+ const PasswordField = ({ autoComplete = "current-password", hideLabel = "Hide password", onChange, showLabel = "Show password", className, disabled, error,...textFieldProps }) => {
21
21
  const { theme, colorScheme } = require_useTheme.default();
22
22
  const [showPassword, setShowPassword] = (0, react.useState)(false);
23
23
  const styles = require_PasswordField_styles.default(theme, colorScheme, showPassword, !!disabled, !!error);
@@ -33,6 +33,7 @@ const PasswordField = ({ autoComplete = "current-password", onChange, className,
33
33
  autoComplete,
34
34
  disabled,
35
35
  error,
36
+ endIconLabel: showPassword ? hideLabel : showLabel,
36
37
  endIcon: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(IconComponent, {
37
38
  width: 16,
38
39
  height: 16,
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordField.cjs","names":["PasswordField: FC<PasswordFieldProps>","useTheme","styles: Record<string, string>","useStyles","IconComponent: FC<SVGProps<SVGSVGElement>>","EyeOff","Eye","TextField","cx"],"sources":["../../../../../src/components/primitives/PasswordField/PasswordField.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {ChangeEvent, FC, SVGProps, useState} from 'react';\nimport useStyles from './PasswordField.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport Eye from '../Icons/Eye';\nimport EyeOff from '../Icons/EyeOff';\nimport TextField, {TextFieldProps} from '../TextField/TextField';\n\nexport interface PasswordFieldProps extends Omit<TextFieldProps, 'type' | 'endIcon' | 'onEndIconClick' | 'onChange'> {\n /**\n * The browser autofill hint. Defaults to `current-password`; set `new-password` on the\n * fields of a change-password or sign-up form so password managers offer to generate and\n * store a new credential instead of filling the existing one.\n */\n autoComplete?: string;\n /**\n * Callback function when the field value changes\n */\n onChange: (value: string) => void;\n}\n\n/**\n * Password field component with show/hide toggle functionality.\n * This component extends TextField and adds password visibility toggle functionality.\n */\nconst PasswordField: FC<PasswordFieldProps> = ({\n autoComplete = 'current-password',\n onChange,\n className,\n disabled,\n error,\n ...textFieldProps\n}: PasswordFieldProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const [showPassword, setShowPassword] = useState(false);\n const styles: Record<string, string> = useStyles(theme, colorScheme, showPassword, !!disabled, !!error);\n\n const togglePasswordVisibility = (): void => {\n if (!disabled) {\n setShowPassword(!showPassword);\n }\n };\n\n const IconComponent: FC<SVGProps<SVGSVGElement>> = showPassword ? EyeOff : Eye;\n\n return (\n <TextField\n {...textFieldProps}\n className={cx(withVendorCSSClassPrefix(bem('password-field')), className)}\n type={showPassword ? 'text' : 'password'}\n onChange={(e: ChangeEvent<HTMLInputElement>): void => onChange(e.target.value)}\n autoComplete={autoComplete}\n disabled={disabled}\n error={error}\n endIcon={\n <IconComponent\n width={16}\n height={16}\n className={cx(\n withVendorCSSClassPrefix(bem('password-field', 'toggle-icon')),\n styles['toggleIcon'],\n showPassword ? styles['visibleIcon'] : styles['hiddenIcon'],\n )}\n />\n }\n onEndIconClick={togglePasswordVisibility}\n />\n );\n};\n\nexport default PasswordField;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6BA,MAAMA,iBAAyC,EAC7C,eAAe,oBACf,UACA,WACA,UACA,MACA,GAAG,qBACqB;CACxB,MAAM,EAAC,OAAO,gBAA4CC,0BAAU;CACpE,MAAM,CAAC,cAAc,uCAA4B,MAAM;CACvD,MAAMC,SAAiCC,qCAAU,OAAO,aAAa,cAAc,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM;CAEvG,MAAM,iCAAuC;AAC3C,MAAI,CAAC,SACH,iBAAgB,CAAC,aAAa;;CAIlC,MAAMC,gBAA6C,eAAeC,yBAASC;AAE3E,QACE,2CAACC;EACC,GAAI;EACJ,WAAWC,kGAAgC,iBAAiB,CAAC,EAAE,UAAU;EACzE,MAAM,eAAe,SAAS;EAC9B,WAAW,MAA2C,SAAS,EAAE,OAAO,MAAM;EAChE;EACJ;EACH;EACP,SACE,2CAAC;GACC,OAAO;GACP,QAAQ;GACR,WAAWA,kGACoB,kBAAkB,cAAc,CAAC,EAC9D,OAAO,eACP,eAAe,OAAO,iBAAiB,OAAO,cAC/C;IACD;EAEJ,gBAAgB;GAChB;;AAIN,4BAAe"}
1
+ {"version":3,"file":"PasswordField.cjs","names":["PasswordField: FC<PasswordFieldProps>","useTheme","styles: Record<string, string>","useStyles","IconComponent: FC<SVGProps<SVGSVGElement>>","EyeOff","Eye","TextField","cx"],"sources":["../../../../../src/components/primitives/PasswordField/PasswordField.tsx"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {ChangeEvent, FC, SVGProps, useState} from 'react';\nimport useStyles from './PasswordField.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport Eye from '../Icons/Eye';\nimport EyeOff from '../Icons/EyeOff';\nimport TextField, {TextFieldProps} from '../TextField/TextField';\n\nexport interface PasswordFieldProps\n extends Omit<TextFieldProps, 'type' | 'endIcon' | 'endIconLabel' | 'onEndIconClick' | 'onChange'> {\n /**\n * The browser autofill hint. Defaults to `current-password`; set `new-password` on the\n * fields of a change-password or sign-up form so password managers offer to generate and\n * store a new credential instead of filling the existing one.\n */\n autoComplete?: string;\n /**\n * Accessible name of the toggle button while the password is visible\n */\n hideLabel?: string;\n /**\n * Callback function when the field value changes\n */\n onChange: (value: string) => void;\n /**\n * Accessible name of the toggle button while the password is hidden\n */\n showLabel?: string;\n}\n\n/**\n * Password field component with show/hide toggle functionality.\n * This component extends TextField and adds password visibility toggle functionality.\n */\nconst PasswordField: FC<PasswordFieldProps> = ({\n autoComplete = 'current-password',\n hideLabel = 'Hide password',\n onChange,\n showLabel = 'Show password',\n className,\n disabled,\n error,\n ...textFieldProps\n}: PasswordFieldProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const [showPassword, setShowPassword] = useState(false);\n const styles: Record<string, string> = useStyles(theme, colorScheme, showPassword, !!disabled, !!error);\n\n const togglePasswordVisibility = (): void => {\n if (!disabled) {\n setShowPassword(!showPassword);\n }\n };\n\n const IconComponent: FC<SVGProps<SVGSVGElement>> = showPassword ? EyeOff : Eye;\n\n return (\n <TextField\n {...textFieldProps}\n className={cx(withVendorCSSClassPrefix(bem('password-field')), className)}\n type={showPassword ? 'text' : 'password'}\n onChange={(e: ChangeEvent<HTMLInputElement>): void => onChange(e.target.value)}\n autoComplete={autoComplete}\n disabled={disabled}\n error={error}\n endIconLabel={showPassword ? hideLabel : showLabel}\n endIcon={\n <IconComponent\n width={16}\n height={16}\n className={cx(\n withVendorCSSClassPrefix(bem('password-field', 'toggle-icon')),\n styles['toggleIcon'],\n showPassword ? styles['visibleIcon'] : styles['hiddenIcon'],\n )}\n />\n }\n onEndIconClick={togglePasswordVisibility}\n />\n );\n};\n\nexport default PasswordField;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsCA,MAAMA,iBAAyC,EAC7C,eAAe,oBACf,YAAY,iBACZ,UACA,YAAY,iBACZ,WACA,UACA,MACA,GAAG,qBACqB;CACxB,MAAM,EAAC,OAAO,gBAA4CC,0BAAU;CACpE,MAAM,CAAC,cAAc,uCAA4B,MAAM;CACvD,MAAMC,SAAiCC,qCAAU,OAAO,aAAa,cAAc,CAAC,CAAC,UAAU,CAAC,CAAC,MAAM;CAEvG,MAAM,iCAAuC;AAC3C,MAAI,CAAC,SACH,iBAAgB,CAAC,aAAa;;CAIlC,MAAMC,gBAA6C,eAAeC,yBAASC;AAE3E,QACE,2CAACC;EACC,GAAI;EACJ,WAAWC,kGAAgC,iBAAiB,CAAC,EAAE,UAAU;EACzE,MAAM,eAAe,SAAS;EAC9B,WAAW,MAA2C,SAAS,EAAE,OAAO,MAAM;EAChE;EACJ;EACH;EACP,cAAc,eAAe,YAAY;EACzC,SACE,2CAAC;GACC,OAAO;GACP,QAAQ;GACR,WAAWA,kGACoB,kBAAkB,cAAc,CAAC,EAC9D,OAAO,eACP,eAAe,OAAO,iBAAiB,OAAO,cAC/C;IACD;EAEJ,gBAAgB;GAChB;;AAIN,4BAAe"}
@@ -0,0 +1,104 @@
1
+ const require_rolldown_runtime = require('../../../_virtual/rolldown_runtime.cjs');
2
+ const require_emotion = require('../../../styles/emotion.cjs');
3
+ const require_useTranslation = require('../../../hooks/useTranslation.cjs');
4
+ const require_useInfiniteOptions = require('../../../hooks/useInfiniteOptions.cjs');
5
+ const require_Select = require('./Select.cjs');
6
+ let __thunderid_browser = require("@thunderid/browser");
7
+ __thunderid_browser = require_rolldown_runtime.__toESM(__thunderid_browser);
8
+ let react = require("react");
9
+ react = require_rolldown_runtime.__toESM(react);
10
+ let react_jsx_runtime = require("react/jsx-runtime");
11
+ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
12
+
13
+ //#region src/components/primitives/Select/ResourceSelect.tsx
14
+ const NO_PAGES = () => Promise.resolve({
15
+ nextOffset: null,
16
+ options: []
17
+ });
18
+ /**
19
+ * The part the `USER_SELECT`, `AUTH_FLOW_SELECT` and `USER_TYPE_SELECT` pickers share: a {@link Select} that loads
20
+ * one resource page by page. The list restarts when the page function, filter or page size changes.
21
+ */
22
+ const ResourceSelect = ({ "aria-label": ariaLabel, bemBlock, className, component, defaults, emptyText, fallbackMessages, fetchPage, filter, id, label, mapping, messageKey, name, onError, pageSize = __thunderid_browser.SelectConstants.DEFAULT_PAGE_SIZE, placeholder, resource, type,...rest }) => {
23
+ const { t } = require_useTranslation.default();
24
+ const effectivePageSize = (0, __thunderid_browser.resolvePageSize)(pageSize);
25
+ const live = (0, react.useRef)({
26
+ component,
27
+ defaults,
28
+ mapping
29
+ });
30
+ (0, react.useEffect)(() => {
31
+ live.current = {
32
+ component,
33
+ defaults,
34
+ mapping
35
+ };
36
+ });
37
+ const load = (0, react.useMemo)(() => (0, __thunderid_browser.createResourceOptionsLoader)({
38
+ get component() {
39
+ return live.current.component ?? {
40
+ id: id ?? name ?? bemBlock,
41
+ type
42
+ };
43
+ },
44
+ get defaults() {
45
+ return live.current.defaults;
46
+ },
47
+ fetchPage: fetchPage ?? NO_PAGES,
48
+ filter,
49
+ get mapping() {
50
+ return live.current.mapping;
51
+ },
52
+ pageSize: effectivePageSize,
53
+ resource
54
+ }), [
55
+ bemBlock,
56
+ effectivePageSize,
57
+ fetchPage,
58
+ filter,
59
+ id,
60
+ name,
61
+ resource,
62
+ type
63
+ ]);
64
+ const { hasMore, loading, onLoadMore, options, reset } = require_useInfiniteOptions.default({
65
+ load,
66
+ onError
67
+ });
68
+ const previousLoadRef = (0, react.useRef)(null);
69
+ (0, react.useEffect)(() => {
70
+ const previousLoad = previousLoadRef.current;
71
+ previousLoadRef.current = load;
72
+ if (previousLoad !== null && previousLoad !== load) reset();
73
+ if (fetchPage) onLoadMore();
74
+ }, [
75
+ fetchPage,
76
+ load,
77
+ onLoadMore,
78
+ reset
79
+ ]);
80
+ const accessibleName = [
81
+ ariaLabel,
82
+ label,
83
+ placeholder,
84
+ t(`elements.fields.${messageKey}.aria_label`)
85
+ ].find((text) => Boolean(text)) ?? fallbackMessages.ariaLabel;
86
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_Select.default, {
87
+ ...rest,
88
+ "aria-label": accessibleName,
89
+ className: require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)(bemBlock)), className),
90
+ emptyText: emptyText ?? (t(`elements.fields.${messageKey}.empty`) || fallbackMessages.empty),
91
+ id,
92
+ label,
93
+ loading,
94
+ name,
95
+ onLoadMore: fetchPage && hasMore ? onLoadMore : void 0,
96
+ options,
97
+ placeholder
98
+ });
99
+ };
100
+ var ResourceSelect_default = ResourceSelect;
101
+
102
+ //#endregion
103
+ exports.default = ResourceSelect_default;
104
+ //# sourceMappingURL=ResourceSelect.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResourceSelect.cjs","names":["NO_PAGES: FetchSelectPage","ResourceSelect: FC<ResourceSelectProps>","SelectConstants","useTranslation","effectivePageSize: number","load: ResourceOptionsLoader","useInfiniteOptions","previousLoad: ResourceOptionsLoader | null","accessibleName: string","Select","cx"],"sources":["../../../../../src/components/primitives/Select/ResourceSelect.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n bem,\n createResourceOptionsLoader,\n SelectConstants,\n resolvePageSize,\n withVendorCSSClassPrefix,\n} from '@thunderid/browser';\nimport type {\n EmbeddedFlowComponent,\n EmbeddedFlowComponentType,\n FetchSelectPage,\n ResourceOptionsLoader,\n SelectOptionDefaults,\n SelectOptionMapping,\n} from '@thunderid/browser';\nimport {FC, useEffect, useMemo, useRef} from 'react';\nimport Select, {SelectProps} from './Select';\nimport useInfiniteOptions from '../../../hooks/useInfiniteOptions';\nimport useTranslation from '../../../hooks/useTranslation';\nimport {cx} from '../../../styles/emotion';\n\nexport interface ResourceSelectProps extends Omit<SelectProps, 'loading' | 'onError' | 'onLoadMore' | 'options'> {\n /** Block name for the picker's CSS class, e.g. `user-select`. */\n bemBlock: string;\n /** The flow component the page function is called for. Defaults to one built from `type`. */\n component?: EmbeddedFlowComponent;\n defaults?: SelectOptionDefaults;\n /** Wording used when the translations for the keys under `messageKey` are empty. */\n fallbackMessages: {ariaLabel: string; empty: string};\n /** Loads one page of the resource. Nothing is requested while this is unset. */\n fetchPage?: FetchSelectPage;\n filter?: string;\n mapping?: SelectOptionMapping;\n /** Translation key group under `elements.fields`, e.g. `user_select`. */\n messageKey: string;\n /** Called with the error of every failed load. The list itself shows no error. */\n onError?: (error: Error) => void;\n /** Options per request. Defaults to 30 and is capped at 100. */\n pageSize?: number;\n /** What is loaded, in the words of the failure log, e.g. `users`. */\n resource: string;\n /** The flow component type this picker stands for. */\n type: EmbeddedFlowComponentType;\n}\n\nconst NO_PAGES: FetchSelectPage = () => Promise.resolve({nextOffset: null, options: []});\n\n/**\n * The part the `USER_SELECT`, `AUTH_FLOW_SELECT` and `USER_TYPE_SELECT` pickers share: a {@link Select} that loads\n * one resource page by page. The list restarts when the page function, filter or page size changes.\n */\nconst ResourceSelect: FC<ResourceSelectProps> = ({\n 'aria-label': ariaLabel,\n bemBlock,\n className,\n component,\n defaults,\n emptyText,\n fallbackMessages,\n fetchPage,\n filter,\n id,\n label,\n mapping,\n messageKey,\n name,\n onError,\n pageSize = SelectConstants.DEFAULT_PAGE_SIZE,\n placeholder,\n resource,\n type,\n ...rest\n}: ResourceSelectProps) => {\n const {t} = useTranslation();\n const effectivePageSize: number = resolvePageSize(pageSize);\n\n const live = useRef({component, defaults, mapping});\n\n useEffect(() => {\n live.current = {component, defaults, mapping};\n });\n\n // Only the page function, filter and page size restart the list. Mapping, defaults and component are read when\n // a page loads, so a parent that passes a new object on every render does not reload it.\n const load: ResourceOptionsLoader = useMemo(\n () =>\n createResourceOptionsLoader({\n get component(): EmbeddedFlowComponent {\n return live.current.component ?? ({id: id ?? name ?? bemBlock, type} as EmbeddedFlowComponent);\n },\n get defaults(): SelectOptionDefaults | undefined {\n return live.current.defaults;\n },\n fetchPage: fetchPage ?? NO_PAGES,\n filter,\n get mapping(): SelectOptionMapping | undefined {\n return live.current.mapping;\n },\n pageSize: effectivePageSize,\n resource,\n }),\n [bemBlock, effectivePageSize, fetchPage, filter, id, name, resource, type],\n );\n const {hasMore, loading, onLoadMore, options, reset} = useInfiniteOptions({load, onError});\n\n const previousLoadRef = useRef<ResourceOptionsLoader | null>(null);\n\n // The first batch is requested when the picker renders. A different page function, filter or page size starts\n // the list again from the beginning.\n useEffect(() => {\n const previousLoad: ResourceOptionsLoader | null = previousLoadRef.current;\n\n previousLoadRef.current = load;\n\n if (previousLoad !== null && previousLoad !== load) {\n reset();\n }\n\n if (fetchPage) {\n onLoadMore();\n }\n }, [fetchPage, load, onLoadMore, reset]);\n\n // The first of these that is not empty names the select. An empty string is not a name, which is why this is not a\n // chain of `??`.\n const accessibleName: string =\n [ariaLabel, label, placeholder, t(`elements.fields.${messageKey}.aria_label`)].find((text) => Boolean(text)) ??\n fallbackMessages.ariaLabel;\n\n return (\n <Select\n {...rest}\n aria-label={accessibleName}\n className={cx(withVendorCSSClassPrefix(bem(bemBlock)), className)}\n emptyText={emptyText ?? (t(`elements.fields.${messageKey}.empty`) || fallbackMessages.empty)}\n id={id}\n label={label}\n loading={loading}\n name={name}\n onLoadMore={fetchPage && hasMore ? onLoadMore : undefined}\n options={options}\n placeholder={placeholder}\n />\n );\n};\n\nexport default ResourceSelect;\n"],"mappings":";;;;;;;;;;;;;AAgDA,MAAMA,iBAAkC,QAAQ,QAAQ;CAAC,YAAY;CAAM,SAAS,EAAE;CAAC,CAAC;;;;;AAMxF,MAAMC,kBAA2C,EAC/C,cAAc,WACd,UACA,WACA,WACA,UACA,WACA,kBACA,WACA,QACA,IACA,OACA,SACA,YACA,MACA,SACA,WAAWC,oCAAgB,mBAC3B,aACA,UACA,KACA,GAAG,WACsB;CACzB,MAAM,EAAC,MAAKC,gCAAgB;CAC5B,MAAMC,6DAA4C,SAAS;CAE3D,MAAM,yBAAc;EAAC;EAAW;EAAU;EAAQ,CAAC;AAEnD,4BAAgB;AACd,OAAK,UAAU;GAAC;GAAW;GAAU;GAAQ;GAC7C;CAIF,MAAMC,qFAE0B;EAC1B,IAAI,YAAmC;AACrC,UAAO,KAAK,QAAQ,aAAc;IAAC,IAAI,MAAM,QAAQ;IAAU;IAAK;;EAEtE,IAAI,WAA6C;AAC/C,UAAO,KAAK,QAAQ;;EAEtB,WAAW,aAAa;EACxB;EACA,IAAI,UAA2C;AAC7C,UAAO,KAAK,QAAQ;;EAEtB,UAAU;EACV;EACD,CAAC,EACJ;EAAC;EAAU;EAAmB;EAAW;EAAQ;EAAI;EAAM;EAAU;EAAK,CAC3E;CACD,MAAM,EAAC,SAAS,SAAS,YAAY,SAAS,UAASC,mCAAmB;EAAC;EAAM;EAAQ,CAAC;CAE1F,MAAM,oCAAuD,KAAK;AAIlE,4BAAgB;EACd,MAAMC,eAA6C,gBAAgB;AAEnE,kBAAgB,UAAU;AAE1B,MAAI,iBAAiB,QAAQ,iBAAiB,KAC5C,QAAO;AAGT,MAAI,UACF,aAAY;IAEb;EAAC;EAAW;EAAM;EAAY;EAAM,CAAC;CAIxC,MAAMC,iBACJ;EAAC;EAAW;EAAO;EAAa,EAAE,mBAAmB,WAAW,aAAa;EAAC,CAAC,MAAM,SAAS,QAAQ,KAAK,CAAC,IAC5G,iBAAiB;AAEnB,QACE,2CAACC;EACC,GAAI;EACJ,cAAY;EACZ,WAAWC,kGAAgC,SAAS,CAAC,EAAE,UAAU;EACjE,WAAW,cAAc,EAAE,mBAAmB,WAAW,QAAQ,IAAI,iBAAiB;EAClF;EACG;EACE;EACH;EACN,YAAY,aAAa,UAAU,aAAa;EACvC;EACI;GACb;;AAIN,6BAAe"}
@@ -4,42 +4,93 @@ const require_useTheme = require('../../../contexts/Theme/useTheme.cjs');
4
4
  const require_FormControl = require('../FormControl/FormControl.cjs');
5
5
  const require_InputLabel = require('../InputLabel/InputLabel.cjs');
6
6
  const require_Select_styles = require('./Select.styles.cjs');
7
+ const require_useSelectMessages = require('./useSelectMessages.cjs');
7
8
  let __thunderid_browser = require("@thunderid/browser");
8
9
  __thunderid_browser = require_rolldown_runtime.__toESM(__thunderid_browser);
10
+ let react = require("react");
11
+ react = require_rolldown_runtime.__toESM(react);
9
12
  let react_jsx_runtime = require("react/jsx-runtime");
10
13
  react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
11
14
 
12
15
  //#region src/components/primitives/Select/Select.tsx
13
- const Select = ({ label, error, className, required, disabled, helperText, placeholder, options, style = {},...rest }) => {
16
+ const Select = ({ label, error, className, required, disabled, helperText, placeholder, options = void 0, emptyText = void 0, loading = false, onLoadMore = void 0, onChange = void 0, selectedOption = void 0, style = {},...rest }) => {
14
17
  const { theme, colorScheme } = require_useTheme.default();
18
+ const messages = require_useSelectMessages.default();
15
19
  const hasError = !!error;
16
20
  const styles = require_Select_styles.default(theme, colorScheme, disabled ?? false, hasError);
21
+ const items = (0, react.useMemo)(() => (0, __thunderid_browser.normalizeSelectOptions)(options), [options]);
22
+ const selectRef = (0, react.useRef)(null);
23
+ const chosenValueRef = (0, react.useRef)("");
24
+ (0, react.useEffect)(() => {
25
+ const current = selectRef.current?.value;
26
+ if (current !== void 0 && current !== __thunderid_browser.SelectConstants.LOAD_MORE_OPTION_VALUE) chosenValueRef.current = current;
27
+ });
28
+ const handleChange = (event) => {
29
+ if (event.target.value !== __thunderid_browser.SelectConstants.LOAD_MORE_OPTION_VALUE) {
30
+ chosenValueRef.current = event.target.value;
31
+ onChange?.(event);
32
+ return;
33
+ }
34
+ event.target.value = chosenValueRef.current;
35
+ onLoadMore?.();
36
+ };
17
37
  const selectClassName = require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("select", "input")), styles["select"], hasError && styles["selectError"], disabled && styles["selectDisabled"]);
38
+ const showsSelectedOption = selectedOption !== void 0 && rest.value !== void 0 && String(rest.value) === selectedOption.value && !items.some((item) => item.value === selectedOption.value);
39
+ let statusText;
40
+ if (loading) statusText = items.length === 0 ? messages.loading : messages.loadingMore;
41
+ else if (items.length === 0 && !onLoadMore) statusText = emptyText;
42
+ const hasStatus = onLoadMore !== void 0 || emptyText !== void 0 || loading;
43
+ const showsLoadMore = onLoadMore !== void 0 && (items.length > 0 || !loading);
18
44
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_FormControl.default, {
19
45
  error,
20
46
  helperText,
21
47
  className: require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("select")), className),
22
48
  style,
23
- children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_InputLabel.default, {
24
- required,
25
- error: hasError,
26
- children: label
27
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
28
- className: selectClassName,
29
- disabled,
30
- "aria-invalid": hasError,
31
- "aria-required": required,
32
- ...rest,
33
- children: [placeholder && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
34
- value: "",
35
- disabled: true,
36
- children: placeholder
37
- }), options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
38
- value: option.value,
39
- className: styles["option"],
40
- children: option.label
41
- }, option.value))]
42
- })]
49
+ children: [
50
+ label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_InputLabel.default, {
51
+ required,
52
+ error: hasError,
53
+ children: label
54
+ }),
55
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
56
+ className: selectClassName,
57
+ disabled,
58
+ "aria-invalid": hasError,
59
+ "aria-required": required,
60
+ "aria-busy": loading || void 0,
61
+ ...rest,
62
+ ref: selectRef,
63
+ onChange: onLoadMore ? handleChange : onChange,
64
+ children: [
65
+ placeholder && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
66
+ value: "",
67
+ disabled: Boolean(required),
68
+ children: placeholder
69
+ }),
70
+ showsSelectedOption && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
71
+ value: selectedOption?.value,
72
+ children: selectedOption?.label
73
+ }),
74
+ items.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
75
+ value: option.value,
76
+ disabled: option.disabled,
77
+ className: styles["option"],
78
+ children: option.label
79
+ }, option.value)),
80
+ showsLoadMore && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
81
+ value: __thunderid_browser.SelectConstants.LOAD_MORE_OPTION_VALUE,
82
+ disabled: loading,
83
+ className: require_emotion.cx((0, __thunderid_browser.withVendorCSSClassPrefix)((0, __thunderid_browser.bem)("select", "load-more")), styles["option"], styles["loadMore"]),
84
+ children: loading ? messages.loadingMore : messages.loadMore
85
+ })
86
+ ]
87
+ }),
88
+ hasStatus && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
89
+ role: "status",
90
+ className: styles["status"],
91
+ children: statusText
92
+ })
93
+ ]
43
94
  });
44
95
  };
45
96
  var Select_default = Select;
@@ -1 +1 @@
1
- {"version":3,"file":"Select.cjs","names":["Select: FC<SelectProps>","useTheme","styles: Record<string, string>","useStyles","selectClassName: string","cx","FormControl","InputLabel"],"sources":["../../../../../src/components/primitives/Select/Select.tsx"],"sourcesContent":["// Copyright 2024 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {withVendorCSSClassPrefix, bem} from '@thunderid/browser';\nimport {FC, SelectHTMLAttributes} from 'react';\nimport useStyles from './Select.styles';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\n\nexport interface SelectOption {\n /**\n * The text that will be displayed in the select\n */\n label: string;\n /**\n * The value that will be submitted with the form\n */\n value: string;\n}\n\nexport interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'className'> {\n /**\n * Additional CSS class names\n */\n className?: string;\n /**\n * Whether the field is disabled\n */\n disabled?: boolean;\n /**\n * Error message to display below the select\n */\n error?: string;\n /**\n * Helper text to display below the select\n */\n helperText?: string;\n /**\n * Label text to display above the select\n */\n label?: string;\n /**\n * The options to display in the select\n */\n options: SelectOption[];\n /**\n * Placeholder text for the default/empty option\n */\n placeholder?: string;\n /**\n * Whether the field is required\n */\n required?: boolean;\n}\n\nconst Select: FC<SelectProps> = ({\n label,\n error,\n className,\n required,\n disabled,\n helperText,\n placeholder,\n options,\n style = {},\n ...rest\n}: SelectProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const hasError = !!error;\n const styles: Record<string, string> = useStyles(theme, colorScheme, disabled ?? false, hasError);\n\n const selectClassName: string = cx(\n withVendorCSSClassPrefix(bem('select', 'input')),\n styles['select'],\n hasError && styles['selectError'],\n disabled && styles['selectDisabled'],\n );\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n className={cx(withVendorCSSClassPrefix(bem('select')), className)}\n style={style}\n >\n {label && (\n <InputLabel required={required} error={hasError}>\n {label}\n </InputLabel>\n )}\n <select\n className={selectClassName}\n disabled={disabled}\n aria-invalid={hasError}\n aria-required={required}\n {...rest}\n >\n {placeholder && (\n <option value=\"\" disabled>\n {placeholder}\n </option>\n )}\n {options.map((option: SelectOption) => (\n <option key={option.value} value={option.value} className={styles['option']}>\n {option.label}\n </option>\n ))}\n </select>\n </FormControl>\n );\n};\n\nexport default Select;\n"],"mappings":";;;;;;;;;;;;AAyDA,MAAMA,UAA2B,EAC/B,OACA,OACA,WACA,UACA,UACA,YACA,aACA,SACA,QAAQ,EAAE,CACV,GAAG,WACc;CACjB,MAAM,EAAC,OAAO,gBAA4CC,0BAAU;CACpE,MAAM,WAAW,CAAC,CAAC;CACnB,MAAMC,SAAiCC,8BAAU,OAAO,aAAa,YAAY,OAAO,SAAS;CAEjG,MAAMC,kBAA0BC,kGACD,UAAU,QAAQ,CAAC,EAChD,OAAO,WACP,YAAY,OAAO,gBACnB,YAAY,OAAO,kBACpB;AAED,QACE,4CAACC;EACQ;EACK;EACZ,WAAWD,kGAAgC,SAAS,CAAC,EAAE,UAAU;EAC1D;aAEN,SACC,2CAACE;GAAqB;GAAU,OAAO;aACpC;IACU,EAEf,4CAAC;GACC,WAAW;GACD;GACV,gBAAc;GACd,iBAAe;GACf,GAAI;cAEH,eACC,2CAAC;IAAO,OAAM;IAAG;cACd;KACM,EAEV,QAAQ,KAAK,WACZ,2CAAC;IAA0B,OAAO,OAAO;IAAO,WAAW,OAAO;cAC/D,OAAO;MADG,OAAO,MAEX,CACT;IACK;GACG;;AAIlB,qBAAe"}
1
+ {"version":3,"file":"Select.cjs","names":["Select: FC<SelectProps>","useTheme","messages: SelectMessages","useSelectMessages","styles: Record<string, string>","useStyles","items: SelectOption[]","selectRef: RefObject<HTMLSelectElement | null>","chosenValueRef: RefObject<string>","current: string | undefined","SelectConstants","selectClassName: string","cx","showsSelectedOption: boolean","statusText: string | undefined","hasStatus: boolean","showsLoadMore: boolean","FormControl","InputLabel"],"sources":["../../../../../src/components/primitives/Select/Select.tsx"],"sourcesContent":["// Copyright 2024 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {bem, normalizeSelectOptions, SelectConstants, withVendorCSSClassPrefix} from '@thunderid/browser';\nimport type {SelectOption, SelectOptionInput} from '@thunderid/browser';\nimport {ChangeEvent, FC, RefObject, SelectHTMLAttributes, useEffect, useMemo, useRef} from 'react';\nimport useStyles from './Select.styles';\nimport useSelectMessages, {SelectMessages} from './useSelectMessages';\nimport useTheme from '../../../contexts/Theme/useTheme';\nimport {cx} from '../../../styles/emotion';\nimport FormControl from '../FormControl/FormControl';\nimport InputLabel from '../InputLabel/InputLabel';\n\nexport type {SelectOption, SelectOptionInput};\n\nexport interface SelectProps extends Omit<SelectHTMLAttributes<HTMLSelectElement>, 'className'> {\n /**\n * Additional CSS class names\n */\n className?: string;\n /**\n * Whether the field is disabled\n */\n disabled?: boolean;\n /**\n * Text shown below the select when it has no options, nothing is loading and there is nothing more to load\n */\n emptyText?: string;\n /**\n * Error message to display below the select\n */\n error?: string;\n /**\n * Helper text to display below the select\n */\n helperText?: string;\n /**\n * Label text to display above the select\n */\n label?: string;\n /**\n * Whether options are being fetched. The load more option waits, and a select with no options shows a loading message\n */\n loading?: boolean;\n /**\n * Called when the load more option, listed after the last one, is chosen. Choosing it does not change the selection.\n * The option is listed only when this is set, so pass it only while more options may be available\n */\n onLoadMore?: () => void;\n /**\n * The options to display: strings, numbers, or `{label, value, disabled?}` objects\n */\n options?: readonly (SelectOptionInput | null | undefined)[] | null;\n /**\n * Placeholder text for the default/empty option. While the field is not required, choosing it clears the selection\n */\n placeholder?: string;\n /**\n * Whether the field is required\n */\n required?: boolean;\n /**\n * Label of the selected option while `value` is not among `options`, such as a value loaded from saved data\n */\n selectedOption?: SelectOption;\n}\n\nconst Select: FC<SelectProps> = ({\n label,\n error,\n className,\n required,\n disabled,\n helperText,\n placeholder,\n options = undefined,\n emptyText = undefined,\n loading = false,\n onLoadMore = undefined,\n onChange = undefined,\n selectedOption = undefined,\n style = {},\n ...rest\n}: SelectProps) => {\n const {theme, colorScheme}: ReturnType<typeof useTheme> = useTheme();\n const messages: SelectMessages = useSelectMessages();\n const hasError = !!error;\n const styles: Record<string, string> = useStyles(theme, colorScheme, disabled ?? false, hasError);\n const items: SelectOption[] = useMemo(() => normalizeSelectOptions(options), [options]);\n const selectRef: RefObject<HTMLSelectElement | null> = useRef<HTMLSelectElement>(null);\n const chosenValueRef: RefObject<string> = useRef<string>('');\n\n // The value to put back when the load more option is chosen, since choosing it is not a selection.\n useEffect(() => {\n const current: string | undefined = selectRef.current?.value;\n\n if (current !== undefined && current !== SelectConstants.LOAD_MORE_OPTION_VALUE) {\n chosenValueRef.current = current;\n }\n });\n\n const handleChange = (event: ChangeEvent<HTMLSelectElement>): void => {\n if (event.target.value !== SelectConstants.LOAD_MORE_OPTION_VALUE) {\n chosenValueRef.current = event.target.value;\n onChange?.(event);\n\n return;\n }\n\n event.target.value = chosenValueRef.current;\n onLoadMore?.();\n };\n\n const selectClassName: string = cx(\n withVendorCSSClassPrefix(bem('select', 'input')),\n styles['select'],\n hasError && styles['selectError'],\n disabled && styles['selectDisabled'],\n );\n\n const showsSelectedOption: boolean =\n selectedOption !== undefined &&\n rest.value !== undefined &&\n String(rest.value) === selectedOption.value &&\n !items.some((item: SelectOption) => item.value === selectedOption.value);\n\n let statusText: string | undefined;\n\n if (loading) {\n statusText = items.length === 0 ? messages.loading : messages.loadingMore;\n } else if (items.length === 0 && !onLoadMore) {\n statusText = emptyText;\n }\n\n const hasStatus: boolean = onLoadMore !== undefined || emptyText !== undefined || loading;\n const showsLoadMore: boolean = onLoadMore !== undefined && (items.length > 0 || !loading);\n\n return (\n <FormControl\n error={error}\n helperText={helperText}\n className={cx(withVendorCSSClassPrefix(bem('select')), className)}\n style={style}\n >\n {label && (\n <InputLabel required={required} error={hasError}>\n {label}\n </InputLabel>\n )}\n <select\n className={selectClassName}\n disabled={disabled}\n aria-invalid={hasError}\n aria-required={required}\n aria-busy={loading || undefined}\n {...rest}\n ref={selectRef}\n onChange={onLoadMore ? handleChange : onChange}\n >\n {placeholder && (\n <option value=\"\" disabled={Boolean(required)}>\n {placeholder}\n </option>\n )}\n {showsSelectedOption && <option value={selectedOption?.value}>{selectedOption?.label}</option>}\n {items.map((option: SelectOption) => (\n <option key={option.value} value={option.value} disabled={option.disabled} className={styles['option']}>\n {option.label}\n </option>\n ))}\n {showsLoadMore && (\n <option\n value={SelectConstants.LOAD_MORE_OPTION_VALUE}\n disabled={loading}\n className={cx(withVendorCSSClassPrefix(bem('select', 'load-more')), styles['option'], styles['loadMore'])}\n >\n {loading ? messages.loadingMore : messages.loadMore}\n </option>\n )}\n </select>\n {hasStatus && (\n <span role=\"status\" className={styles['status']}>\n {statusText}\n </span>\n )}\n </FormControl>\n );\n};\n\nexport default Select;\n"],"mappings":";;;;;;;;;;;;;;;AAmEA,MAAMA,UAA2B,EAC/B,OACA,OACA,WACA,UACA,UACA,YACA,aACA,UAAU,QACV,YAAY,QACZ,UAAU,OACV,aAAa,QACb,WAAW,QACX,iBAAiB,QACjB,QAAQ,EAAE,CACV,GAAG,WACc;CACjB,MAAM,EAAC,OAAO,gBAA4CC,0BAAU;CACpE,MAAMC,WAA2BC,mCAAmB;CACpD,MAAM,WAAW,CAAC,CAAC;CACnB,MAAMC,SAAiCC,8BAAU,OAAO,aAAa,YAAY,OAAO,SAAS;CACjG,MAAMC,iFAA6D,QAAQ,EAAE,CAAC,QAAQ,CAAC;CACvF,MAAMC,8BAA2E,KAAK;CACtF,MAAMC,mCAAmD,GAAG;AAG5D,4BAAgB;EACd,MAAMC,UAA8B,UAAU,SAAS;AAEvD,MAAI,YAAY,UAAa,YAAYC,oCAAgB,uBACvD,gBAAe,UAAU;GAE3B;CAEF,MAAM,gBAAgB,UAAgD;AACpE,MAAI,MAAM,OAAO,UAAUA,oCAAgB,wBAAwB;AACjE,kBAAe,UAAU,MAAM,OAAO;AACtC,cAAW,MAAM;AAEjB;;AAGF,QAAM,OAAO,QAAQ,eAAe;AACpC,gBAAc;;CAGhB,MAAMC,kBAA0BC,kGACD,UAAU,QAAQ,CAAC,EAChD,OAAO,WACP,YAAY,OAAO,gBACnB,YAAY,OAAO,kBACpB;CAED,MAAMC,sBACJ,mBAAmB,UACnB,KAAK,UAAU,UACf,OAAO,KAAK,MAAM,KAAK,eAAe,SACtC,CAAC,MAAM,MAAM,SAAuB,KAAK,UAAU,eAAe,MAAM;CAE1E,IAAIC;AAEJ,KAAI,QACF,cAAa,MAAM,WAAW,IAAI,SAAS,UAAU,SAAS;UACrD,MAAM,WAAW,KAAK,CAAC,WAChC,cAAa;CAGf,MAAMC,YAAqB,eAAe,UAAa,cAAc,UAAa;CAClF,MAAMC,gBAAyB,eAAe,WAAc,MAAM,SAAS,KAAK,CAAC;AAEjF,QACE,4CAACC;EACQ;EACK;EACZ,WAAWL,kGAAgC,SAAS,CAAC,EAAE,UAAU;EAC1D;;GAEN,SACC,2CAACM;IAAqB;IAAU,OAAO;cACpC;KACU;GAEf,4CAAC;IACC,WAAW;IACD;IACV,gBAAc;IACd,iBAAe;IACf,aAAW,WAAW;IACtB,GAAI;IACJ,KAAK;IACL,UAAU,aAAa,eAAe;;KAErC,eACC,2CAAC;MAAO,OAAM;MAAG,UAAU,QAAQ,SAAS;gBACzC;OACM;KAEV,uBAAuB,2CAAC;MAAO,OAAO,gBAAgB;gBAAQ,gBAAgB;OAAe;KAC7F,MAAM,KAAK,WACV,2CAAC;MAA0B,OAAO,OAAO;MAAO,UAAU,OAAO;MAAU,WAAW,OAAO;gBAC1F,OAAO;QADG,OAAO,MAEX,CACT;KACD,iBACC,2CAAC;MACC,OAAOR,oCAAgB;MACvB,UAAU;MACV,WAAWE,kGAAgC,UAAU,YAAY,CAAC,EAAE,OAAO,WAAW,OAAO,YAAY;gBAExG,UAAU,SAAS,cAAc,SAAS;OACpC;;KAEJ;GACR,aACC,2CAAC;IAAK,MAAK;IAAS,WAAW,OAAO;cACnC;KACI;;GAEG;;AAIlB,qBAAe"}