@data-club/ai-hub 0.0.1
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.
- package/dist/auth/passwordPolicy.d.ts +55 -0
- package/dist/auth/passwordPolicy.d.ts.map +1 -0
- package/dist/auth/passwordPolicy.js +63 -0
- package/dist/auth/passwordPolicy.js.map +1 -0
- package/dist/auth/texts.d.ts +815 -0
- package/dist/auth/texts.d.ts.map +1 -0
- package/dist/auth/texts.js +429 -0
- package/dist/auth/texts.js.map +1 -0
- package/dist/client/AIHubClient.d.ts +1058 -0
- package/dist/client/AIHubClient.d.ts.map +1 -0
- package/dist/client/AIHubClient.js +950 -0
- package/dist/client/AIHubClient.js.map +1 -0
- package/dist/client/index.d.ts +11 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +9 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/sseParser.d.ts +41 -0
- package/dist/client/sseParser.d.ts.map +1 -0
- package/dist/client/sseParser.js +153 -0
- package/dist/client/sseParser.js.map +1 -0
- package/dist/client/tokenStorage.d.ts +56 -0
- package/dist/client/tokenStorage.d.ts.map +1 -0
- package/dist/client/tokenStorage.js +116 -0
- package/dist/client/tokenStorage.js.map +1 -0
- package/dist/components/AgentsPicker/index.d.ts +70 -0
- package/dist/components/AgentsPicker/index.d.ts.map +1 -0
- package/dist/components/AgentsPicker/index.js +63 -0
- package/dist/components/AgentsPicker/index.js.map +1 -0
- package/dist/components/AuthGate/index.d.ts +79 -0
- package/dist/components/AuthGate/index.d.ts.map +1 -0
- package/dist/components/AuthGate/index.js +61 -0
- package/dist/components/AuthGate/index.js.map +1 -0
- package/dist/components/CacheBadge/index.d.ts +88 -0
- package/dist/components/CacheBadge/index.d.ts.map +1 -0
- package/dist/components/CacheBadge/index.js +127 -0
- package/dist/components/CacheBadge/index.js.map +1 -0
- package/dist/components/ChangePassword/index.d.ts +109 -0
- package/dist/components/ChangePassword/index.d.ts.map +1 -0
- package/dist/components/ChangePassword/index.js +142 -0
- package/dist/components/ChangePassword/index.js.map +1 -0
- package/dist/components/ChatShell/ShellHeader.d.ts +44 -0
- package/dist/components/ChatShell/ShellHeader.d.ts.map +1 -0
- package/dist/components/ChatShell/ShellHeader.js +14 -0
- package/dist/components/ChatShell/ShellHeader.js.map +1 -0
- package/dist/components/ChatShell/UserMenu.d.ts +48 -0
- package/dist/components/ChatShell/UserMenu.d.ts.map +1 -0
- package/dist/components/ChatShell/UserMenu.js +86 -0
- package/dist/components/ChatShell/UserMenu.js.map +1 -0
- package/dist/components/ChatShell/index.d.ts +98 -0
- package/dist/components/ChatShell/index.d.ts.map +1 -0
- package/dist/components/ChatShell/index.js +124 -0
- package/dist/components/ChatShell/index.js.map +1 -0
- package/dist/components/DataClubAIHub/index.d.ts +132 -0
- package/dist/components/DataClubAIHub/index.d.ts.map +1 -0
- package/dist/components/DataClubAIHub/index.js +124 -0
- package/dist/components/DataClubAIHub/index.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/AgentEditorModal.d.ts +89 -0
- package/dist/components/DataClubAIHubAdmin/AgentEditorModal.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/AgentEditorModal.js +199 -0
- package/dist/components/DataClubAIHubAdmin/AgentEditorModal.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/AgentsPage.d.ts +33 -0
- package/dist/components/DataClubAIHubAdmin/AgentsPage.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/AgentsPage.js +137 -0
- package/dist/components/DataClubAIHubAdmin/AgentsPage.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/CreateUserModal.d.ts +50 -0
- package/dist/components/DataClubAIHubAdmin/CreateUserModal.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/CreateUserModal.js +93 -0
- package/dist/components/DataClubAIHubAdmin/CreateUserModal.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.d.ts +49 -0
- package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.js +94 -0
- package/dist/components/DataClubAIHubAdmin/PasswordRevealModal.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.d.ts +37 -0
- package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.js +44 -0
- package/dist/components/DataClubAIHubAdmin/ResetPasswordModal.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.d.ts +56 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.js +158 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessageEditorModal.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.d.ts +29 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.js +183 -0
- package/dist/components/DataClubAIHubAdmin/RotatingMessagesPage.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.d.ts +38 -0
- package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.js +294 -0
- package/dist/components/DataClubAIHubAdmin/SessionsOverviewPage.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts +36 -0
- package/dist/components/DataClubAIHubAdmin/SettingsPage.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/SettingsPage.js +373 -0
- package/dist/components/DataClubAIHubAdmin/SettingsPage.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/UsersPage.d.ts +47 -0
- package/dist/components/DataClubAIHubAdmin/UsersPage.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/UsersPage.js +233 -0
- package/dist/components/DataClubAIHubAdmin/UsersPage.js.map +1 -0
- package/dist/components/DataClubAIHubAdmin/index.d.ts +87 -0
- package/dist/components/DataClubAIHubAdmin/index.d.ts.map +1 -0
- package/dist/components/DataClubAIHubAdmin/index.js +137 -0
- package/dist/components/DataClubAIHubAdmin/index.js.map +1 -0
- package/dist/components/DataClubAIHubLogin/index.d.ts +113 -0
- package/dist/components/DataClubAIHubLogin/index.d.ts.map +1 -0
- package/dist/components/DataClubAIHubLogin/index.js +123 -0
- package/dist/components/DataClubAIHubLogin/index.js.map +1 -0
- package/dist/components/EffortControl/index.d.ts +57 -0
- package/dist/components/EffortControl/index.d.ts.map +1 -0
- package/dist/components/EffortControl/index.js +84 -0
- package/dist/components/EffortControl/index.js.map +1 -0
- package/dist/components/Layout/index.d.ts +63 -0
- package/dist/components/Layout/index.d.ts.map +1 -0
- package/dist/components/Layout/index.js +45 -0
- package/dist/components/Layout/index.js.map +1 -0
- package/dist/components/MainColumn/index.d.ts +69 -0
- package/dist/components/MainColumn/index.d.ts.map +1 -0
- package/dist/components/MainColumn/index.js +47 -0
- package/dist/components/MainColumn/index.js.map +1 -0
- package/dist/components/MarkdownRenderer/index.d.ts +66 -0
- package/dist/components/MarkdownRenderer/index.d.ts.map +1 -0
- package/dist/components/MarkdownRenderer/index.js +153 -0
- package/dist/components/MarkdownRenderer/index.js.map +1 -0
- package/dist/components/MessageInput/index.d.ts +85 -0
- package/dist/components/MessageInput/index.d.ts.map +1 -0
- package/dist/components/MessageInput/index.js +139 -0
- package/dist/components/MessageInput/index.js.map +1 -0
- package/dist/components/MessageList/index.d.ts +156 -0
- package/dist/components/MessageList/index.d.ts.map +1 -0
- package/dist/components/MessageList/index.js +352 -0
- package/dist/components/MessageList/index.js.map +1 -0
- package/dist/components/ReasoningSelector/index.d.ts +77 -0
- package/dist/components/ReasoningSelector/index.d.ts.map +1 -0
- package/dist/components/ReasoningSelector/index.js +156 -0
- package/dist/components/ReasoningSelector/index.js.map +1 -0
- package/dist/components/ReasoningView/index.d.ts +94 -0
- package/dist/components/ReasoningView/index.d.ts.map +1 -0
- package/dist/components/ReasoningView/index.js +83 -0
- package/dist/components/ReasoningView/index.js.map +1 -0
- package/dist/components/ScrollToPresentButton/index.d.ts +54 -0
- package/dist/components/ScrollToPresentButton/index.d.ts.map +1 -0
- package/dist/components/ScrollToPresentButton/index.js +44 -0
- package/dist/components/ScrollToPresentButton/index.js.map +1 -0
- package/dist/components/SessionsPanel/SessionTitleModal.d.ts +79 -0
- package/dist/components/SessionsPanel/SessionTitleModal.d.ts.map +1 -0
- package/dist/components/SessionsPanel/SessionTitleModal.js +102 -0
- package/dist/components/SessionsPanel/SessionTitleModal.js.map +1 -0
- package/dist/components/SessionsPanel/index.d.ts +100 -0
- package/dist/components/SessionsPanel/index.d.ts.map +1 -0
- package/dist/components/SessionsPanel/index.js +176 -0
- package/dist/components/SessionsPanel/index.js.map +1 -0
- package/dist/components/SystemNotice/index.d.ts +66 -0
- package/dist/components/SystemNotice/index.d.ts.map +1 -0
- package/dist/components/SystemNotice/index.js +81 -0
- package/dist/components/SystemNotice/index.js.map +1 -0
- package/dist/components/ThemeSwitcher/index.d.ts +66 -0
- package/dist/components/ThemeSwitcher/index.d.ts.map +1 -0
- package/dist/components/ThemeSwitcher/index.js +73 -0
- package/dist/components/ThemeSwitcher/index.js.map +1 -0
- package/dist/components/ToolCallView/formatContent.d.ts +36 -0
- package/dist/components/ToolCallView/formatContent.d.ts.map +1 -0
- package/dist/components/ToolCallView/formatContent.js +53 -0
- package/dist/components/ToolCallView/formatContent.js.map +1 -0
- package/dist/components/ToolCallView/index.d.ts +120 -0
- package/dist/components/ToolCallView/index.d.ts.map +1 -0
- package/dist/components/ToolCallView/index.js +104 -0
- package/dist/components/ToolCallView/index.js.map +1 -0
- package/dist/components/TpmBanner/index.d.ts +53 -0
- package/dist/components/TpmBanner/index.d.ts.map +1 -0
- package/dist/components/TpmBanner/index.js +116 -0
- package/dist/components/TpmBanner/index.js.map +1 -0
- package/dist/components/TurnStatusIndicator/index.d.ts +77 -0
- package/dist/components/TurnStatusIndicator/index.d.ts.map +1 -0
- package/dist/components/TurnStatusIndicator/index.js +122 -0
- package/dist/components/TurnStatusIndicator/index.js.map +1 -0
- package/dist/components/internal/PasswordInput.d.ts +62 -0
- package/dist/components/internal/PasswordInput.d.ts.map +1 -0
- package/dist/components/internal/PasswordInput.js +42 -0
- package/dist/components/internal/PasswordInput.js.map +1 -0
- package/dist/context/AIHubContext.d.ts +220 -0
- package/dist/context/AIHubContext.d.ts.map +1 -0
- package/dist/context/AIHubContext.js +19 -0
- package/dist/context/AIHubContext.js.map +1 -0
- package/dist/context/AIHubProvider.d.ts +75 -0
- package/dist/context/AIHubProvider.d.ts.map +1 -0
- package/dist/context/AIHubProvider.js +954 -0
- package/dist/context/AIHubProvider.js.map +1 -0
- package/dist/context/index.d.ts +9 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +9 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/theme.d.ts +43 -0
- package/dist/context/theme.d.ts.map +1 -0
- package/dist/context/theme.js +70 -0
- package/dist/context/theme.js.map +1 -0
- package/dist/context/useAIHub.d.ts +104 -0
- package/dist/context/useAIHub.d.ts.map +1 -0
- package/dist/context/useAIHub.js +63 -0
- package/dist/context/useAIHub.js.map +1 -0
- package/dist/context/useAIHubAuth.d.ts +44 -0
- package/dist/context/useAIHubAuth.d.ts.map +1 -0
- package/dist/context/useAIHubAuth.js +35 -0
- package/dist/context/useAIHubAuth.js.map +1 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/primitives/index.d.ts +99 -0
- package/dist/primitives/index.d.ts.map +1 -0
- package/dist/primitives/index.js +97 -0
- package/dist/primitives/index.js.map +1 -0
- package/dist/state/affordances.d.ts +182 -0
- package/dist/state/affordances.d.ts.map +1 -0
- package/dist/state/affordances.js +189 -0
- package/dist/state/affordances.js.map +1 -0
- package/dist/state/stateReducer.d.ts +72 -0
- package/dist/state/stateReducer.d.ts.map +1 -0
- package/dist/state/stateReducer.js +122 -0
- package/dist/state/stateReducer.js.map +1 -0
- package/dist/stories/EffortControl.stories.d.ts +14 -0
- package/dist/stories/EffortControl.stories.d.ts.map +1 -0
- package/dist/stories/EffortControl.stories.js +43 -0
- package/dist/stories/EffortControl.stories.js.map +1 -0
- package/dist/stories/MarkdownRenderer.stories.d.ts +25 -0
- package/dist/stories/MarkdownRenderer.stories.d.ts.map +1 -0
- package/dist/stories/MarkdownRenderer.stories.js +178 -0
- package/dist/stories/MarkdownRenderer.stories.js.map +1 -0
- package/dist/stories/MessageBubble.stories.d.ts +21 -0
- package/dist/stories/MessageBubble.stories.d.ts.map +1 -0
- package/dist/stories/MessageBubble.stories.js +59 -0
- package/dist/stories/MessageBubble.stories.js.map +1 -0
- package/dist/stories/ReasoningView.stories.d.ts +18 -0
- package/dist/stories/ReasoningView.stories.d.ts.map +1 -0
- package/dist/stories/ReasoningView.stories.js +62 -0
- package/dist/stories/ReasoningView.stories.js.map +1 -0
- package/dist/stories/ScrollToPresentButton.stories.d.ts +18 -0
- package/dist/stories/ScrollToPresentButton.stories.d.ts.map +1 -0
- package/dist/stories/ScrollToPresentButton.stories.js +34 -0
- package/dist/stories/ScrollToPresentButton.stories.js.map +1 -0
- package/dist/stories/SystemNotice.stories.d.ts +17 -0
- package/dist/stories/SystemNotice.stories.d.ts.map +1 -0
- package/dist/stories/SystemNotice.stories.js +42 -0
- package/dist/stories/SystemNotice.stories.js.map +1 -0
- package/dist/stories/ThemeSwitcher.stories.d.ts +15 -0
- package/dist/stories/ThemeSwitcher.stories.d.ts.map +1 -0
- package/dist/stories/ThemeSwitcher.stories.js +35 -0
- package/dist/stories/ThemeSwitcher.stories.js.map +1 -0
- package/dist/stories/ToolCallView.stories.d.ts +20 -0
- package/dist/stories/ToolCallView.stories.d.ts.map +1 -0
- package/dist/stories/ToolCallView.stories.js +65 -0
- package/dist/stories/ToolCallView.stories.js.map +1 -0
- package/dist/stories/TpmBanner.stories.d.ts +24 -0
- package/dist/stories/TpmBanner.stories.d.ts.map +1 -0
- package/dist/stories/TpmBanner.stories.js +63 -0
- package/dist/stories/TpmBanner.stories.js.map +1 -0
- package/dist/stories/TurnStatusIndicator.stories.d.ts +25 -0
- package/dist/stories/TurnStatusIndicator.stories.d.ts.map +1 -0
- package/dist/stories/TurnStatusIndicator.stories.js +56 -0
- package/dist/stories/TurnStatusIndicator.stories.js.map +1 -0
- package/dist/stories/_mockProvider.d.ts +44 -0
- package/dist/stories/_mockProvider.d.ts.map +1 -0
- package/dist/stories/_mockProvider.js +84 -0
- package/dist/stories/_mockProvider.js.map +1 -0
- package/dist/types.d.ts +787 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/dist/types.js.map +1 -0
- package/dist/webcomponent/defineWebComponent.d.ts +106 -0
- package/dist/webcomponent/defineWebComponent.d.ts.map +1 -0
- package/dist/webcomponent/defineWebComponent.js +126 -0
- package/dist/webcomponent/defineWebComponent.js.map +1 -0
- package/dist/webcomponent/index.d.ts +7 -0
- package/dist/webcomponent/index.d.ts.map +1 -0
- package/dist/webcomponent/index.js +7 -0
- package/dist/webcomponent/index.js.map +1 -0
- package/package.json +63 -0
- package/src/__lint-canary__/step1-lint-canary.ts.disabled +28 -0
- package/src/auth/passwordPolicy.ts +75 -0
- package/src/auth/texts.ts +1294 -0
- package/src/client/AIHubClient.ts +1718 -0
- package/src/client/index.ts +30 -0
- package/src/client/sseParser.ts +168 -0
- package/src/client/tokenStorage.ts +131 -0
- package/src/components/AgentsPicker/index.tsx +147 -0
- package/src/components/AuthGate/index.tsx +132 -0
- package/src/components/CacheBadge/index.tsx +245 -0
- package/src/components/ChangePassword/index.tsx +375 -0
- package/src/components/ChatShell/ShellHeader.tsx +80 -0
- package/src/components/ChatShell/UserMenu.tsx +196 -0
- package/src/components/ChatShell/index.tsx +307 -0
- package/src/components/DataClubAIHub/index.tsx +300 -0
- package/src/components/DataClubAIHubAdmin/AgentEditorModal.tsx +505 -0
- package/src/components/DataClubAIHubAdmin/AgentsPage.tsx +342 -0
- package/src/components/DataClubAIHubAdmin/CreateUserModal.tsx +214 -0
- package/src/components/DataClubAIHubAdmin/PasswordRevealModal.tsx +204 -0
- package/src/components/DataClubAIHubAdmin/ResetPasswordModal.tsx +122 -0
- package/src/components/DataClubAIHubAdmin/RotatingMessageEditorModal.tsx +408 -0
- package/src/components/DataClubAIHubAdmin/RotatingMessagesPage.tsx +373 -0
- package/src/components/DataClubAIHubAdmin/SessionsOverviewPage.tsx +639 -0
- package/src/components/DataClubAIHubAdmin/SettingsPage.tsx +979 -0
- package/src/components/DataClubAIHubAdmin/UsersPage.tsx +495 -0
- package/src/components/DataClubAIHubAdmin/index.tsx +277 -0
- package/src/components/DataClubAIHubLogin/index.tsx +282 -0
- package/src/components/EffortControl/index.tsx +138 -0
- package/src/components/Layout/index.tsx +92 -0
- package/src/components/MainColumn/index.tsx +131 -0
- package/src/components/MarkdownRenderer/index.tsx +256 -0
- package/src/components/MessageInput/index.tsx +283 -0
- package/src/components/MessageList/index.tsx +753 -0
- package/src/components/ReasoningSelector/index.tsx +268 -0
- package/src/components/ReasoningView/index.tsx +183 -0
- package/src/components/ScrollToPresentButton/index.tsx +85 -0
- package/src/components/SessionsPanel/SessionTitleModal.tsx +256 -0
- package/src/components/SessionsPanel/index.tsx +406 -0
- package/src/components/SystemNotice/index.tsx +151 -0
- package/src/components/ThemeSwitcher/index.tsx +136 -0
- package/src/components/ToolCallView/formatContent.ts +50 -0
- package/src/components/ToolCallView/index.tsx +274 -0
- package/src/components/TpmBanner/index.tsx +172 -0
- package/src/components/TurnStatusIndicator/index.tsx +203 -0
- package/src/components/internal/PasswordInput.tsx +145 -0
- package/src/context/AIHubContext.ts +249 -0
- package/src/context/AIHubProvider.tsx +1084 -0
- package/src/context/index.ts +9 -0
- package/src/context/theme.ts +76 -0
- package/src/context/useAIHub.ts +151 -0
- package/src/context/useAIHubAuth.ts +65 -0
- package/src/index.ts +275 -0
- package/src/primitives/index.ts +101 -0
- package/src/state/affordances.ts +321 -0
- package/src/state/stateReducer.ts +131 -0
- package/src/stories/EffortControl.stories.tsx +63 -0
- package/src/stories/MarkdownRenderer.stories.tsx +201 -0
- package/src/stories/MessageBubble.stories.tsx +101 -0
- package/src/stories/ReasoningView.stories.tsx +81 -0
- package/src/stories/ScrollToPresentButton.stories.tsx +52 -0
- package/src/stories/SystemNotice.stories.tsx +59 -0
- package/src/stories/ThemeSwitcher.stories.tsx +55 -0
- package/src/stories/ToolCallView.stories.tsx +95 -0
- package/src/stories/TpmBanner.stories.tsx +93 -0
- package/src/stories/TurnStatusIndicator.stories.tsx +86 -0
- package/src/stories/_mockProvider.tsx +116 -0
- package/src/styles/CLASSES.md +356 -0
- package/src/styles/default.css +2937 -0
- package/src/types.ts +867 -0
- package/src/webcomponent/defineWebComponent.tsx +193 -0
- package/src/webcomponent/index.ts +11 -0
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<TpmBanner />` — TPM-recovery banner with countdown.
|
|
3
|
+
*
|
|
4
|
+
* Specialization of `<SystemNotice />` that:
|
|
5
|
+
*
|
|
6
|
+
* 1. Reads the active turn's `tpm_retry_at` from `useAIHub().state` and
|
|
7
|
+
* computes the remaining seconds at ~1Hz.
|
|
8
|
+
* 2. Switches variant based on §10.4: `tpm-wait` while `phase ===
|
|
9
|
+
* waiting_provider`, transient `resumed` flash on the `running →
|
|
10
|
+
* running(model_*)` recovery transition, and `error` when the
|
|
11
|
+
* `lastTurnError` is `tpm_exhausted`.
|
|
12
|
+
* 3. Renders nothing when none of the conditions apply — components can
|
|
13
|
+
* mount it unconditionally without flicker.
|
|
14
|
+
*
|
|
15
|
+
* The provider's reducer already clears `tpm_retry_at` on the `resumed`
|
|
16
|
+
* notice; the banner observes that change and shows the brief "Resumed."
|
|
17
|
+
* flash for 2s. The exhausted-error path renders a Retry button that
|
|
18
|
+
* re-issues the last user message (delegated to the parent via
|
|
19
|
+
* `onRetry`).
|
|
20
|
+
*
|
|
21
|
+
* @see SPECIFICATIONS.md §6.5 — TPM recovery
|
|
22
|
+
* @see SPECIFICATIONS.md §10.4 — predicates bound to (status, phase)
|
|
23
|
+
* @see ui-sketches/03-chat.md — system_notice variants
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { useEffect, useState, type ReactNode } from "react";
|
|
27
|
+
|
|
28
|
+
import {
|
|
29
|
+
resolveAIHubTexts,
|
|
30
|
+
type AIHubTexts,
|
|
31
|
+
type DeepPartial,
|
|
32
|
+
} from "../../auth/texts.js";
|
|
33
|
+
import { useAIHub } from "../../context/useAIHub.js";
|
|
34
|
+
|
|
35
|
+
import { SystemNotice } from "../SystemNotice/index.js";
|
|
36
|
+
|
|
37
|
+
/** Duration of the post-recovery "Resumed." flash, in milliseconds. */
|
|
38
|
+
const RESUMED_FLASH_MS = 2000;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Renders the countdown text by replacing `{seconds}` in the template.
|
|
42
|
+
*
|
|
43
|
+
* @param template - i18n template with `{seconds}` placeholder.
|
|
44
|
+
* @param seconds - integer seconds remaining; floors at 0.
|
|
45
|
+
* @returns the resolved text.
|
|
46
|
+
*/
|
|
47
|
+
function renderCountdown(template: string, seconds: number): string {
|
|
48
|
+
const safe = Math.max(0, Math.floor(seconds));
|
|
49
|
+
return template.replace("{seconds}", String(safe));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Computes seconds-until against an ISO timestamp.
|
|
54
|
+
*
|
|
55
|
+
* @param retryAt - ISO timestamp from `tpm_retry_at`.
|
|
56
|
+
* @param now - epoch ms (test injection point).
|
|
57
|
+
* @returns seconds remaining (>= 0). Returns 0 when `retryAt` is invalid.
|
|
58
|
+
*/
|
|
59
|
+
function secondsUntil(retryAt: string, now: number): number {
|
|
60
|
+
const target = Date.parse(retryAt);
|
|
61
|
+
if (!Number.isFinite(target)) return 0;
|
|
62
|
+
return Math.max(0, (target - now) / 1000);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Props for `<TpmBanner />`. */
|
|
66
|
+
export interface TpmBannerProps {
|
|
67
|
+
/**
|
|
68
|
+
* Called when the user clicks the Retry button on the `--error`
|
|
69
|
+
* variant. Wraps `sendMessage` of the last user message; the parent
|
|
70
|
+
* decides what "last user message" means (the monolith threads its
|
|
71
|
+
* own ref through).
|
|
72
|
+
*/
|
|
73
|
+
onRetry?: () => void;
|
|
74
|
+
/** Wrapper-supplied i18n. */
|
|
75
|
+
texts?: DeepPartial<AIHubTexts>;
|
|
76
|
+
/** Extra class merged onto the root. */
|
|
77
|
+
className?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Override the resolved "now" — test injection point. Production code
|
|
80
|
+
* leaves this unset (defaults to `Date.now()` at each tick).
|
|
81
|
+
*/
|
|
82
|
+
nowProvider?: () => number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Renders the banner.
|
|
87
|
+
*
|
|
88
|
+
* @param props - see `TpmBannerProps`.
|
|
89
|
+
* @returns the rendered banner, or `null` when nothing should render.
|
|
90
|
+
*/
|
|
91
|
+
export function TpmBanner(props: TpmBannerProps): ReactNode {
|
|
92
|
+
const { state, lastTurnError } = useAIHub();
|
|
93
|
+
const texts = resolveAIHubTexts(props.texts);
|
|
94
|
+
|
|
95
|
+
// Tick at 1Hz while we're in the wait phase. The interval is
|
|
96
|
+
// re-installed when the deadline changes (new wait) or removed when
|
|
97
|
+
// we leave the wait phase.
|
|
98
|
+
const [, forceTick] = useState(0);
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
if (state.phase !== "waiting_provider") return;
|
|
101
|
+
const tick = setInterval(() => {
|
|
102
|
+
forceTick((n) => n + 1);
|
|
103
|
+
}, 1000);
|
|
104
|
+
return () => clearInterval(tick);
|
|
105
|
+
}, [state.phase, state.tpm_retry_at]);
|
|
106
|
+
|
|
107
|
+
// Track the *previous* phase so the `running(waiting_provider) →
|
|
108
|
+
// running(model_*)` transition triggers the "Resumed." flash. We hold
|
|
109
|
+
// the flash on the screen for RESUMED_FLASH_MS, then clear.
|
|
110
|
+
const [showResumed, setShowResumed] = useState(false);
|
|
111
|
+
const [prevPhase, setPrevPhase] = useState(state.phase);
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (
|
|
114
|
+
prevPhase === "waiting_provider" &&
|
|
115
|
+
state.phase !== "waiting_provider" &&
|
|
116
|
+
state.status === "running"
|
|
117
|
+
) {
|
|
118
|
+
setShowResumed(true);
|
|
119
|
+
const t = setTimeout(() => setShowResumed(false), RESUMED_FLASH_MS);
|
|
120
|
+
setPrevPhase(state.phase);
|
|
121
|
+
return () => clearTimeout(t);
|
|
122
|
+
}
|
|
123
|
+
setPrevPhase(state.phase);
|
|
124
|
+
return undefined;
|
|
125
|
+
}, [state.phase, state.status]);
|
|
126
|
+
|
|
127
|
+
// Wait phase: render `--tpm-wait` with countdown.
|
|
128
|
+
if (state.phase === "waiting_provider" && state.tpm_retry_at) {
|
|
129
|
+
const now = (props.nowProvider ?? Date.now)();
|
|
130
|
+
const remaining = secondsUntil(state.tpm_retry_at, now);
|
|
131
|
+
return (
|
|
132
|
+
<SystemNotice
|
|
133
|
+
variant="tpm-wait"
|
|
134
|
+
message={renderCountdown(texts.chat.tpmWaitText, remaining)}
|
|
135
|
+
{...(props.className !== undefined ? { className: props.className } : {})}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Brief "Resumed." flash right after a successful recovery.
|
|
141
|
+
if (showResumed) {
|
|
142
|
+
return (
|
|
143
|
+
<SystemNotice
|
|
144
|
+
variant="resumed"
|
|
145
|
+
message={texts.chat.systemResumedText}
|
|
146
|
+
{...(props.className !== undefined ? { className: props.className } : {})}
|
|
147
|
+
/>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Exhausted-error path — the provider sets `lastTurnError` on
|
|
152
|
+
// `error{tpm_exhausted}` + `stream_end{error}`. The retry button only
|
|
153
|
+
// surfaces here (the generic `--failed` Retry button on the message
|
|
154
|
+
// bubble is the equivalent for non-TPM failures).
|
|
155
|
+
if (
|
|
156
|
+
lastTurnError &&
|
|
157
|
+
lastTurnError.toLowerCase().includes("rate")
|
|
158
|
+
) {
|
|
159
|
+
return (
|
|
160
|
+
<SystemNotice
|
|
161
|
+
variant="error"
|
|
162
|
+
message={texts.chat.tpmExhaustedText}
|
|
163
|
+
{...(props.onRetry !== undefined
|
|
164
|
+
? { action: { label: texts.chat.tpmRetryButton, onClick: props.onRetry } }
|
|
165
|
+
: {})}
|
|
166
|
+
{...(props.className !== undefined ? { className: props.className } : {})}
|
|
167
|
+
/>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<TurnStatusIndicator />` — a small `●` dot + short label reflecting the
|
|
3
|
+
* active turn's `(status, phase)`.
|
|
4
|
+
*
|
|
5
|
+
* Pure function of state (via `turnStatusVisual` in `state/affordances.ts`).
|
|
6
|
+
* Two soft-state behaviors are local to the component:
|
|
7
|
+
*
|
|
8
|
+
* 1. **`done` flash.** On the transition `running → done`, the indicator
|
|
9
|
+
* briefly shows "Done" (~2s) and then settles to "Ready" — this is the
|
|
10
|
+
* visual "the turn finished" affordance the predicate table calls for.
|
|
11
|
+
* 2. **`cancelled` auto-clear.** After ~3s the indicator clears back to
|
|
12
|
+
* "Ready" so the cancelled stamp doesn't sit forever in the header.
|
|
13
|
+
*
|
|
14
|
+
* Both timers respect `prefers-reduced-motion` by skipping the pulse
|
|
15
|
+
* animation; the timer logic still runs, but the dot stays static.
|
|
16
|
+
*
|
|
17
|
+
* Rendering is gated by:
|
|
18
|
+
*
|
|
19
|
+
* - the `showTurnStatus` React prop (default true) AND
|
|
20
|
+
* - the `turn_status_visible` server toggle (§8.4) — precedence: server > prop.
|
|
21
|
+
*
|
|
22
|
+
* Both gates are AND-combined in `useAffordanceFlags` upstream so the
|
|
23
|
+
* primitive itself only checks `if (!visible) return null`.
|
|
24
|
+
*
|
|
25
|
+
* Exported under `AIHub.TurnStatusIndicator`.
|
|
26
|
+
*
|
|
27
|
+
* @see SPECIFICATIONS.md §10.4 — predicate table + label map
|
|
28
|
+
* @see ui-sketches/03-chat.md — turn_status_indicator
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { useEffect, useRef, useState, type ReactNode } from "react";
|
|
32
|
+
|
|
33
|
+
import { useAIHub } from "../../context/useAIHub.js";
|
|
34
|
+
import { turnStatusVisual } from "../../state/affordances.js";
|
|
35
|
+
import type {
|
|
36
|
+
AIHubState,
|
|
37
|
+
RunningPhase,
|
|
38
|
+
TurnStatus,
|
|
39
|
+
} from "../../types.js";
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* BEM class names emitted by `<TurnStatusIndicator />`.
|
|
43
|
+
*/
|
|
44
|
+
export interface TurnStatusIndicatorClassNames {
|
|
45
|
+
/** Outer wrapper. */
|
|
46
|
+
root: string;
|
|
47
|
+
/** Dot element — pulse animation hooks here. */
|
|
48
|
+
dot: string;
|
|
49
|
+
/** Modifier on the dot — `--idle` (gray static). */
|
|
50
|
+
dotIdle: string;
|
|
51
|
+
/** Modifier on the dot — `--running` (accent pulsing). */
|
|
52
|
+
dotRunning: string;
|
|
53
|
+
/** Modifier on the dot — `--warning` (yellow pulsing). */
|
|
54
|
+
dotWarning: string;
|
|
55
|
+
/** Modifier on the dot — `--error` (red static). */
|
|
56
|
+
dotError: string;
|
|
57
|
+
/** Modifier added when pulsing is allowed (drops under reduced-motion). */
|
|
58
|
+
dotPulse: string;
|
|
59
|
+
/** Label text. */
|
|
60
|
+
label: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const DEFAULT_CLASS_NAMES: TurnStatusIndicatorClassNames = Object.freeze({
|
|
64
|
+
root: "data-club-ai-hub__turn-status",
|
|
65
|
+
dot: "data-club-ai-hub__turn-status__dot",
|
|
66
|
+
dotIdle: "data-club-ai-hub__turn-status__dot--idle",
|
|
67
|
+
dotRunning: "data-club-ai-hub__turn-status__dot--running",
|
|
68
|
+
dotWarning: "data-club-ai-hub__turn-status__dot--warning",
|
|
69
|
+
dotError: "data-club-ai-hub__turn-status__dot--error",
|
|
70
|
+
dotPulse: "data-club-ai-hub__turn-status__dot--pulse",
|
|
71
|
+
label: "data-club-ai-hub__turn-status__label",
|
|
72
|
+
}) as TurnStatusIndicatorClassNames;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Maps the visual variant to its modifier class.
|
|
76
|
+
*/
|
|
77
|
+
function dotModifier(
|
|
78
|
+
variant: "idle" | "running" | "warning" | "error",
|
|
79
|
+
cls: TurnStatusIndicatorClassNames,
|
|
80
|
+
): string {
|
|
81
|
+
switch (variant) {
|
|
82
|
+
case "running":
|
|
83
|
+
return cls.dotRunning;
|
|
84
|
+
case "warning":
|
|
85
|
+
return cls.dotWarning;
|
|
86
|
+
case "error":
|
|
87
|
+
return cls.dotError;
|
|
88
|
+
case "idle":
|
|
89
|
+
return cls.dotIdle;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Props for `<TurnStatusIndicator />`.
|
|
95
|
+
*
|
|
96
|
+
* `state` is optional — when omitted the component reads it from the
|
|
97
|
+
* provider via `useAIHub()`. Passing it explicitly is useful for the
|
|
98
|
+
* Storybook stories (Step 10 ships them for every cell of the §10.4
|
|
99
|
+
* table) and for the test harness, both of which render the indicator
|
|
100
|
+
* in isolation without a real reducer in scope.
|
|
101
|
+
*/
|
|
102
|
+
export interface TurnStatusIndicatorProps {
|
|
103
|
+
/** Override the state read from context (used by stories + tests). */
|
|
104
|
+
state?: AIHubState;
|
|
105
|
+
/** Extra class merged onto the root. */
|
|
106
|
+
className?: string;
|
|
107
|
+
/** Per-element class overrides. */
|
|
108
|
+
classNames?: Partial<TurnStatusIndicatorClassNames>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Duration of the post-done "Done" flash, in milliseconds. After this
|
|
113
|
+
* expires the indicator settles back to "Ready" (`idle` dot).
|
|
114
|
+
*/
|
|
115
|
+
const DONE_FLASH_MS = 2000;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Duration of the post-cancel "Cancelled" stamp, in milliseconds.
|
|
119
|
+
*/
|
|
120
|
+
const CANCELLED_CLEAR_MS = 3000;
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Renders the indicator.
|
|
124
|
+
*
|
|
125
|
+
* @param props - see `TurnStatusIndicatorProps`.
|
|
126
|
+
* @returns the rendered indicator.
|
|
127
|
+
*/
|
|
128
|
+
export function TurnStatusIndicator(
|
|
129
|
+
props: TurnStatusIndicatorProps,
|
|
130
|
+
): ReactNode {
|
|
131
|
+
const ctx = useAIHub();
|
|
132
|
+
const state = props.state ?? ctx.state;
|
|
133
|
+
const cls = { ...DEFAULT_CLASS_NAMES, ...(props.classNames ?? {}) };
|
|
134
|
+
|
|
135
|
+
// Capture the *effective* status / phase for rendering. After done /
|
|
136
|
+
// cancelled clear timers expire we treat the state as `idle` so the
|
|
137
|
+
// dot doesn't sit on "Done" forever.
|
|
138
|
+
const [effective, setEffective] = useState<{
|
|
139
|
+
status: TurnStatus;
|
|
140
|
+
phase: RunningPhase | null;
|
|
141
|
+
}>({ status: state.status, phase: state.phase });
|
|
142
|
+
|
|
143
|
+
// Track the previous status so we know when to start the
|
|
144
|
+
// done/cancelled clear timer (state only updates on transitions).
|
|
145
|
+
const prevStatusRef = useRef<TurnStatus>(state.status);
|
|
146
|
+
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
const prev = prevStatusRef.current;
|
|
149
|
+
prevStatusRef.current = state.status;
|
|
150
|
+
|
|
151
|
+
// No timer needed when the status is steady at running or idle.
|
|
152
|
+
if (state.status === prev) {
|
|
153
|
+
setEffective({ status: state.status, phase: state.phase });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Fresh state — lift it.
|
|
158
|
+
setEffective({ status: state.status, phase: state.phase });
|
|
159
|
+
|
|
160
|
+
if (state.status === "done") {
|
|
161
|
+
const t = setTimeout(() => {
|
|
162
|
+
setEffective({ status: "idle", phase: null });
|
|
163
|
+
}, DONE_FLASH_MS);
|
|
164
|
+
return () => clearTimeout(t);
|
|
165
|
+
}
|
|
166
|
+
if (state.status === "cancelled") {
|
|
167
|
+
const t = setTimeout(() => {
|
|
168
|
+
setEffective({ status: "idle", phase: null });
|
|
169
|
+
}, CANCELLED_CLEAR_MS);
|
|
170
|
+
return () => clearTimeout(t);
|
|
171
|
+
}
|
|
172
|
+
return undefined;
|
|
173
|
+
}, [state.status, state.phase]);
|
|
174
|
+
|
|
175
|
+
const visual = turnStatusVisual(effective.status, effective.phase);
|
|
176
|
+
const rootClass = props.className
|
|
177
|
+
? `${cls.root} ${props.className}`
|
|
178
|
+
: cls.root;
|
|
179
|
+
const dotClass = [
|
|
180
|
+
cls.dot,
|
|
181
|
+
dotModifier(visual.dot, cls),
|
|
182
|
+
visual.pulse ? cls.dotPulse : null,
|
|
183
|
+
]
|
|
184
|
+
.filter(Boolean)
|
|
185
|
+
.join(" ");
|
|
186
|
+
|
|
187
|
+
return (
|
|
188
|
+
<div
|
|
189
|
+
className={rootClass}
|
|
190
|
+
data-testid="turn-status-indicator"
|
|
191
|
+
data-status={effective.status}
|
|
192
|
+
data-phase={effective.phase ?? "none"}
|
|
193
|
+
data-variant={visual.dot}
|
|
194
|
+
role="status"
|
|
195
|
+
aria-live="polite"
|
|
196
|
+
>
|
|
197
|
+
<span className={dotClass} aria-hidden="true" />
|
|
198
|
+
<span className={cls.label} data-testid="turn-status-label">
|
|
199
|
+
{visual.label}
|
|
200
|
+
</span>
|
|
201
|
+
</div>
|
|
202
|
+
);
|
|
203
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<PasswordInput>` — internal helper used by `<DataClubAIHubLogin />` and
|
|
3
|
+
* `<DataClubAIHubChangePassword />` to render a password field with its own
|
|
4
|
+
* show/hide eye-icon toggle.
|
|
5
|
+
*
|
|
6
|
+
* The toggle is **per-field** (each input owns its own state) so a user
|
|
7
|
+
* can reveal one field while keeping another masked — useful for visually
|
|
8
|
+
* verifying a match between New and Confirm without exposing both at once.
|
|
9
|
+
*
|
|
10
|
+
* Not exported from the package (no JSDoc-required interface surfaces here);
|
|
11
|
+
* lives under `components/internal/` to make that explicit.
|
|
12
|
+
*
|
|
13
|
+
* @see ui-sketches/01-login.md — show_password_toggle
|
|
14
|
+
* @see ui-sketches/02-change-password.md — per-field toggle
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { useState, type ReactNode } from "react";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Props for the internal `<PasswordInput>` helper.
|
|
21
|
+
*/
|
|
22
|
+
export interface PasswordInputProps {
|
|
23
|
+
/** Stable DOM id used by the label + `aria-describedby` if any. */
|
|
24
|
+
id: string;
|
|
25
|
+
/** Input value. */
|
|
26
|
+
value: string;
|
|
27
|
+
/** Change handler — receives the next plaintext value. */
|
|
28
|
+
onChange: (next: string) => void;
|
|
29
|
+
/** Native `name` attribute for autofill / form submission. */
|
|
30
|
+
name: string;
|
|
31
|
+
/** Native `autoComplete` value — caller picks per field. */
|
|
32
|
+
autoComplete?: string;
|
|
33
|
+
/** Placeholder text. Defaults to "••••••••". */
|
|
34
|
+
placeholder?: string;
|
|
35
|
+
/** Whether the input is disabled (e.g. during submit). */
|
|
36
|
+
disabled?: boolean;
|
|
37
|
+
/** Whether to autofocus on mount. */
|
|
38
|
+
autoFocus?: boolean;
|
|
39
|
+
/** Required field — mirrors HTML `required` for browser-side validation. */
|
|
40
|
+
required?: boolean;
|
|
41
|
+
/** `aria-label` for the toggle when the password is currently hidden. */
|
|
42
|
+
showAriaLabel: string;
|
|
43
|
+
/** `aria-label` for the toggle when the password is currently shown. */
|
|
44
|
+
hideAriaLabel: string;
|
|
45
|
+
/** BEM class for the wrapper `<div>` that holds the input + toggle. */
|
|
46
|
+
wrapperClassName: string;
|
|
47
|
+
/** BEM class for the `<input>` itself. */
|
|
48
|
+
inputClassName: string;
|
|
49
|
+
/** BEM class for the toggle `<button>`. */
|
|
50
|
+
toggleClassName: string;
|
|
51
|
+
/** Optional `data-testid` on the wrapper — used by RTL tests. */
|
|
52
|
+
testId?: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Renders a password field with a show/hide eye-icon toggle.
|
|
57
|
+
*
|
|
58
|
+
* The toggle is `type="button"` (not submit) and keyboard-accessible
|
|
59
|
+
* (Enter / Space activate without submitting the surrounding form).
|
|
60
|
+
*
|
|
61
|
+
* @param props - the input props.
|
|
62
|
+
* @returns the input + toggle pair.
|
|
63
|
+
*/
|
|
64
|
+
export function PasswordInput(props: PasswordInputProps): ReactNode {
|
|
65
|
+
const {
|
|
66
|
+
id,
|
|
67
|
+
value,
|
|
68
|
+
onChange,
|
|
69
|
+
name,
|
|
70
|
+
autoComplete,
|
|
71
|
+
placeholder = "••••••••",
|
|
72
|
+
disabled,
|
|
73
|
+
autoFocus,
|
|
74
|
+
required,
|
|
75
|
+
showAriaLabel,
|
|
76
|
+
hideAriaLabel,
|
|
77
|
+
wrapperClassName,
|
|
78
|
+
inputClassName,
|
|
79
|
+
toggleClassName,
|
|
80
|
+
testId,
|
|
81
|
+
} = props;
|
|
82
|
+
|
|
83
|
+
const [revealed, setRevealed] = useState(false);
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div
|
|
87
|
+
className={wrapperClassName}
|
|
88
|
+
{...(testId !== undefined ? { "data-testid": testId } : {})}
|
|
89
|
+
>
|
|
90
|
+
<input
|
|
91
|
+
id={id}
|
|
92
|
+
type={revealed ? "text" : "password"}
|
|
93
|
+
value={value}
|
|
94
|
+
onChange={(e) => onChange(e.target.value)}
|
|
95
|
+
name={name}
|
|
96
|
+
{...(autoComplete !== undefined ? { autoComplete } : {})}
|
|
97
|
+
placeholder={placeholder}
|
|
98
|
+
disabled={disabled}
|
|
99
|
+
autoFocus={autoFocus}
|
|
100
|
+
required={required}
|
|
101
|
+
className={inputClassName}
|
|
102
|
+
/>
|
|
103
|
+
<button
|
|
104
|
+
type="button"
|
|
105
|
+
className={toggleClassName}
|
|
106
|
+
aria-label={revealed ? hideAriaLabel : showAriaLabel}
|
|
107
|
+
aria-pressed={revealed}
|
|
108
|
+
onClick={() => setRevealed((r) => !r)}
|
|
109
|
+
tabIndex={0}
|
|
110
|
+
>
|
|
111
|
+
{/* Inline SVG — sized via CSS, color follows currentColor. */}
|
|
112
|
+
<EyeIcon open={revealed} />
|
|
113
|
+
</button>
|
|
114
|
+
</div>
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Inline eye icon. Two paths so the open / closed variants share weight
|
|
120
|
+
* and stroke caps without two `<svg>` trees.
|
|
121
|
+
*
|
|
122
|
+
* @param props - icon props.
|
|
123
|
+
* @param props.open - true → "show" (eye), false → "hide" (eye with slash).
|
|
124
|
+
*/
|
|
125
|
+
function EyeIcon(props: { open: boolean }): ReactNode {
|
|
126
|
+
return (
|
|
127
|
+
<svg
|
|
128
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
129
|
+
viewBox="0 0 24 24"
|
|
130
|
+
width="18"
|
|
131
|
+
height="18"
|
|
132
|
+
fill="none"
|
|
133
|
+
stroke="currentColor"
|
|
134
|
+
strokeWidth="2"
|
|
135
|
+
strokeLinecap="round"
|
|
136
|
+
strokeLinejoin="round"
|
|
137
|
+
aria-hidden="true"
|
|
138
|
+
focusable="false"
|
|
139
|
+
>
|
|
140
|
+
<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z" />
|
|
141
|
+
<circle cx="12" cy="12" r="3" />
|
|
142
|
+
{!props.open ? <path d="M3 3l18 18" /> : null}
|
|
143
|
+
</svg>
|
|
144
|
+
);
|
|
145
|
+
}
|