@gntik-ai/blocks 0.0.0-stage → 0.6.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.
- package/dist/index.d.ts +3626 -0
- package/dist/index.js +15417 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -3
- package/src/ai/AssistantCardGrid/AssistantCardGrid.test.tsx +32 -0
- package/src/ai/AssistantCardGrid/AssistantCardGrid.tsx +98 -0
- package/src/ai/AssistantCardGrid/block.meta.ts +10 -0
- package/src/ai/AssistantCardGrid/fixtures.ts +72 -0
- package/src/ai/EvaluationScorecard/EvaluationScorecard.test.tsx +39 -0
- package/src/ai/EvaluationScorecard/EvaluationScorecard.tsx +233 -0
- package/src/ai/EvaluationScorecard/block.meta.ts +10 -0
- package/src/ai/EvaluationScorecard/fixtures.ts +79 -0
- package/src/ai/ModelKeysList/ModelKeysList.test.tsx +44 -0
- package/src/ai/ModelKeysList/ModelKeysList.tsx +203 -0
- package/src/ai/ModelKeysList/block.meta.ts +10 -0
- package/src/ai/ModelKeysList/fixtures.ts +44 -0
- package/src/ai/PromptEditor/PromptEditor.test.tsx +97 -0
- package/src/ai/PromptEditor/PromptEditor.tsx +202 -0
- package/src/ai/PromptEditor/block.meta.ts +10 -0
- package/src/ai/PromptEditor/examples/controlled-variables.tsx +23 -0
- package/src/ai/PromptEditor/fixtures.ts +12 -0
- package/src/ai/TokenCostCard/TokenCostCard.test.tsx +36 -0
- package/src/ai/TokenCostCard/TokenCostCard.tsx +130 -0
- package/src/ai/TokenCostCard/block.meta.ts +9 -0
- package/src/ai/TokenCostCard/fixtures.ts +18 -0
- package/src/ai/TraceWaterfall/TraceWaterfall.test.tsx +119 -0
- package/src/ai/TraceWaterfall/TraceWaterfall.tsx +317 -0
- package/src/ai/TraceWaterfall/block.meta.ts +10 -0
- package/src/ai/TraceWaterfall/examples/no-details.tsx +6 -0
- package/src/ai/TraceWaterfall/examples/running.tsx +16 -0
- package/src/ai/TraceWaterfall/fixtures.ts +133 -0
- package/src/ai/TraceWaterfall/span-details.tsx +51 -0
- package/src/ai/index.ts +13 -0
- package/src/auth/ForgotPasswordForm/ForgotPasswordForm.test.tsx +209 -0
- package/src/auth/ForgotPasswordForm/ForgotPasswordForm.tsx +211 -0
- package/src/auth/ForgotPasswordForm/block.meta.ts +10 -0
- package/src/auth/ForgotPasswordForm/examples/UsernameOrEmail.tsx +24 -0
- package/src/auth/MfaChallenge/MfaChallenge.test.tsx +35 -0
- package/src/auth/MfaChallenge/MfaChallenge.tsx +170 -0
- package/src/auth/MfaChallenge/block.meta.ts +9 -0
- package/src/auth/ResetPasswordForm/ResetPasswordForm.test.tsx +27 -0
- package/src/auth/ResetPasswordForm/ResetPasswordForm.tsx +100 -0
- package/src/auth/ResetPasswordForm/block.meta.ts +10 -0
- package/src/auth/SignInForm/SignInForm.test.tsx +222 -0
- package/src/auth/SignInForm/SignInForm.tsx +226 -0
- package/src/auth/SignInForm/block.meta.ts +22 -0
- package/src/auth/SignInForm/examples/Username.tsx +26 -0
- package/src/auth/SignUpForm/SignUpForm.configured.test.tsx +316 -0
- package/src/auth/SignUpForm/SignUpForm.test.tsx +33 -0
- package/src/auth/SignUpForm/SignUpForm.tsx +298 -0
- package/src/auth/SignUpForm/block.meta.ts +22 -0
- package/src/auth/SignUpForm/examples/CustomFields.tsx +56 -0
- package/src/auth/SignUpForm/examples/Error.tsx +5 -0
- package/src/auth/SignUpForm/examples/Loading.tsx +6 -0
- package/src/auth/SignUpForm/examples/NoTerms.tsx +6 -0
- package/src/auth/SignUpForm/examples/Submitting.tsx +5 -0
- package/src/auth/SignUpForm/examples/Unavailable.tsx +15 -0
- package/src/auth/SsoButtons/SsoButtons.test.tsx +23 -0
- package/src/auth/SsoButtons/SsoButtons.tsx +52 -0
- package/src/auth/SsoButtons/block.meta.ts +9 -0
- package/src/auth/SsoButtons/fixtures.ts +15 -0
- package/src/auth/index.ts +16 -0
- package/src/auth/shared.tsx +147 -0
- package/src/billing/CostBreakdown/CostBreakdown.test.tsx +24 -0
- package/src/billing/CostBreakdown/CostBreakdown.tsx +95 -0
- package/src/billing/CostBreakdown/block.meta.ts +10 -0
- package/src/billing/CostBreakdown/fixtures.ts +9 -0
- package/src/billing/InvoiceTable/InvoiceTable.test.tsx +29 -0
- package/src/billing/InvoiceTable/InvoiceTable.tsx +130 -0
- package/src/billing/InvoiceTable/block.meta.ts +10 -0
- package/src/billing/InvoiceTable/fixtures.ts +38 -0
- package/src/billing/PaymentMethodCard/PaymentMethodCard.test.tsx +28 -0
- package/src/billing/PaymentMethodCard/PaymentMethodCard.tsx +103 -0
- package/src/billing/PaymentMethodCard/block.meta.ts +10 -0
- package/src/billing/PaymentMethodCard/fixtures.ts +12 -0
- package/src/billing/PlanCard/PlanCard.baseline.test.tsx +26 -0
- package/src/billing/PlanCard/PlanCard.summary.test.tsx +227 -0
- package/src/billing/PlanCard/PlanCard.test.tsx +27 -0
- package/src/billing/PlanCard/PlanCard.tsx +140 -0
- package/src/billing/PlanCard/PlanSummary.tsx +101 -0
- package/src/billing/PlanCard/README.md +23 -0
- package/src/billing/PlanCard/__snapshots__/PlanCard.baseline.test.tsx.snap +5 -0
- package/src/billing/PlanCard/block.meta.ts +60 -0
- package/src/billing/PlanCard/examples/SummaryActionsOnly.tsx +12 -0
- package/src/billing/PlanCard/examples/SummaryFeaturesOnly.tsx +14 -0
- package/src/billing/PlanCard/examples/SummaryFull.tsx +28 -0
- package/src/billing/PlanCard/examples/SummaryMinimal.tsx +5 -0
- package/src/billing/PlanCard/examples/SummaryPriceOnly.tsx +15 -0
- package/src/billing/PlanCard/examples/SummaryRenewalOnly.tsx +14 -0
- package/src/billing/PlanCard/examples/SummaryUnassigned.tsx +5 -0
- package/src/billing/PlanCard/examples/SummaryUnassignedNoLabel.tsx +5 -0
- package/src/billing/PlanCard/examples/SummaryZeroPrice.tsx +15 -0
- package/src/billing/PlanCard/fixtures.ts +19 -0
- package/src/billing/PlanDefinitionCard/PlanDefinitionCard.test.tsx +111 -0
- package/src/billing/PlanDefinitionCard/PlanDefinitionCard.tsx +86 -0
- package/src/billing/PlanDefinitionCard/README.md +29 -0
- package/src/billing/PlanDefinitionCard/block.meta.ts +46 -0
- package/src/billing/PlanDefinitionCard/examples/Compact.tsx +23 -0
- package/src/billing/PlanDefinitionCard/examples/Full.tsx +23 -0
- package/src/billing/PlanDefinitionCard/fixtures.ts +12 -0
- package/src/billing/QuotaMeters/QuotaMeters.test.tsx +23 -0
- package/src/billing/QuotaMeters/QuotaMeters.tsx +92 -0
- package/src/billing/QuotaMeters/block.meta.ts +10 -0
- package/src/billing/QuotaMeters/fixtures.ts +9 -0
- package/src/billing/SpendVsBudget/SpendVsBudget.test.tsx +24 -0
- package/src/billing/SpendVsBudget/SpendVsBudget.tsx +105 -0
- package/src/billing/SpendVsBudget/block.meta.ts +10 -0
- package/src/billing/SpendVsBudget/fixtures.ts +18 -0
- package/src/billing/format.ts +42 -0
- package/src/billing/index.ts +21 -0
- package/src/builders/CodePanel/CodePanel.test.tsx +280 -0
- package/src/builders/CodePanel/CodePanel.tsx +239 -0
- package/src/builders/CodePanel/block.meta.ts +10 -0
- package/src/builders/CodePanel/examples/controlled.tsx +15 -0
- package/src/builders/CodePanel/examples/diagnostics.tsx +40 -0
- package/src/builders/CodePanel/fixtures.ts +95 -0
- package/src/builders/CodePanel/marker-store.ts +90 -0
- package/src/builders/CodePanel/markers.ts +62 -0
- package/src/builders/FlowBuilder/FlowBuilder.test.tsx +480 -0
- package/src/builders/FlowBuilder/FlowBuilder.tsx +343 -0
- package/src/builders/FlowBuilder/block.meta.ts +20 -0
- package/src/builders/FlowBuilder/connections.ts +67 -0
- package/src/builders/FlowBuilder/decorated-nodes.ts +80 -0
- package/src/builders/FlowBuilder/examples/extensions.tsx +120 -0
- package/src/builders/FlowBuilder/extensions.ts +37 -0
- package/src/builders/FlowBuilder/fixtures.ts +80 -0
- package/src/builders/FlowBuilder/parts.tsx +196 -0
- package/src/builders/FlowBuilder/problems.tsx +40 -0
- package/src/builders/LogViewer/LogViewer.test.tsx +97 -0
- package/src/builders/LogViewer/LogViewer.tsx +253 -0
- package/src/builders/LogViewer/block.meta.ts +10 -0
- package/src/builders/LogViewer/fixtures.ts +49 -0
- package/src/builders/index.ts +15 -0
- package/src/data-display/ActivityFeed/ActivityFeed.test.tsx +62 -0
- package/src/data-display/ActivityFeed/ActivityFeed.tsx +118 -0
- package/src/data-display/ActivityFeed/block.meta.ts +10 -0
- package/src/data-display/ActivityFeed/fixtures.ts +29 -0
- package/src/data-display/ChartCard/ChartCard.ranges.test.tsx +282 -0
- package/src/data-display/ChartCard/ChartCard.test.tsx +54 -0
- package/src/data-display/ChartCard/ChartCard.tsx +218 -0
- package/src/data-display/ChartCard/block.meta.ts +10 -0
- package/src/data-display/ChartCard/examples/custom-ranges.tsx +37 -0
- package/src/data-display/ChartCard/examples/heatmap.tsx +20 -0
- package/src/data-display/ChartCard/examples/toolbar-owned-range.tsx +34 -0
- package/src/data-display/ChartCard/fixtures.ts +82 -0
- package/src/data-display/CommentThread/CommentThread.test.tsx +29 -0
- package/src/data-display/CommentThread/CommentThread.tsx +120 -0
- package/src/data-display/CommentThread/block.meta.ts +10 -0
- package/src/data-display/CommentThread/fixtures.ts +28 -0
- package/src/data-display/DescriptionListCard/DescriptionListCard.test.tsx +28 -0
- package/src/data-display/DescriptionListCard/DescriptionListCard.tsx +120 -0
- package/src/data-display/DescriptionListCard/block.meta.ts +9 -0
- package/src/data-display/DescriptionListCard/fixtures.ts +12 -0
- package/src/data-display/KpiRow/KpiRow.test.tsx +29 -0
- package/src/data-display/KpiRow/KpiRow.tsx +101 -0
- package/src/data-display/KpiRow/block.meta.ts +10 -0
- package/src/data-display/KpiRow/fixtures.ts +34 -0
- package/src/data-display/ResourceCardGrid/ResourceCardGrid.test.tsx +22 -0
- package/src/data-display/ResourceCardGrid/ResourceCardGrid.tsx +98 -0
- package/src/data-display/ResourceCardGrid/block.meta.ts +10 -0
- package/src/data-display/ResourceCardGrid/fixtures.ts +72 -0
- package/src/data-display/StatCard/StatCard.test.tsx +30 -0
- package/src/data-display/StatCard/StatCard.tsx +138 -0
- package/src/data-display/StatCard/block.meta.ts +10 -0
- package/src/data-display/StatCard/fixtures.ts +26 -0
- package/src/data-display/StatusTimeline/StatusTimeline.test.tsx +153 -0
- package/src/data-display/StatusTimeline/StatusTimeline.tsx +114 -0
- package/src/data-display/StatusTimeline/block.meta.ts +10 -0
- package/src/data-display/StatusTimeline/examples/Empty.tsx +6 -0
- package/src/data-display/StatusTimeline/fixtures.ts +62 -0
- package/src/data-display/index.ts +34 -0
- package/src/data-display/shared/TrendDelta.tsx +60 -0
- package/src/feedback/ConfirmDestructive/ConfirmDestructive.test.tsx +47 -0
- package/src/feedback/ConfirmDestructive/ConfirmDestructive.tsx +145 -0
- package/src/feedback/ConfirmDestructive/block.meta.ts +10 -0
- package/src/feedback/EmptyStates/EmptyStates.test.tsx +48 -0
- package/src/feedback/EmptyStates/EmptyStates.tsx +191 -0
- package/src/feedback/EmptyStates/block.meta.ts +10 -0
- package/src/feedback/EmptyStates/fixtures.ts +6 -0
- package/src/feedback/ErrorPanel/ErrorPanel.test.tsx +37 -0
- package/src/feedback/ErrorPanel/ErrorPanel.tsx +146 -0
- package/src/feedback/ErrorPanel/block.meta.ts +10 -0
- package/src/feedback/InlineCallout/InlineCallout.test.tsx +33 -0
- package/src/feedback/InlineCallout/InlineCallout.tsx +81 -0
- package/src/feedback/InlineCallout/block.meta.ts +9 -0
- package/src/feedback/Skeletons/Skeletons.test.tsx +25 -0
- package/src/feedback/Skeletons/Skeletons.tsx +142 -0
- package/src/feedback/Skeletons/block.meta.ts +10 -0
- package/src/feedback/SystemBanner/SystemBanner.test.tsx +29 -0
- package/src/feedback/SystemBanner/SystemBanner.tsx +105 -0
- package/src/feedback/SystemBanner/block.meta.ts +9 -0
- package/src/feedback/index.ts +14 -0
- package/src/feedback/types.ts +13 -0
- package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.test.tsx +36 -0
- package/src/forms/CreateApiKeyDialog/CreateApiKeyDialog.tsx +197 -0
- package/src/forms/CreateApiKeyDialog/block.meta.ts +10 -0
- package/src/forms/CreateApiKeyDialog/fixtures.ts +31 -0
- package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.test.tsx +339 -0
- package/src/forms/CredentialDisclosureDialog/CredentialDisclosureDialog.tsx +193 -0
- package/src/forms/CredentialDisclosureDialog/block.meta.ts +10 -0
- package/src/forms/CredentialDisclosureDialog/examples/Fresh.tsx +32 -0
- package/src/forms/CredentialDisclosureDialog/examples/ResponseGating.tsx +69 -0
- package/src/forms/CredentialDisclosureDialog/examples/Stored.tsx +32 -0
- package/src/forms/CredentialDisclosureDialog/fixtures.ts +14 -0
- package/src/forms/DangerZone/ConfirmDestructiveDialog.tsx +101 -0
- package/src/forms/DangerZone/DangerZone.test.tsx +28 -0
- package/src/forms/DangerZone/DangerZone.tsx +86 -0
- package/src/forms/DangerZone/block.meta.ts +10 -0
- package/src/forms/DangerZone/fixtures.ts +36 -0
- package/src/forms/DateRangeFilter/DateRangeFilter.test.tsx +26 -0
- package/src/forms/DateRangeFilter/DateRangeFilter.tsx +83 -0
- package/src/forms/DateRangeFilter/block.meta.ts +9 -0
- package/src/forms/DialogWizard/DialogWizard.test.tsx +95 -0
- package/src/forms/DialogWizard/DialogWizard.tsx +39 -0
- package/src/forms/DialogWizard/block.meta.ts +10 -0
- package/src/forms/DialogWizard/examples/CreateResource.tsx +65 -0
- package/src/forms/DialogWizard/fixtures.ts +20 -0
- package/src/forms/FileUploadPanel/FileUploadPanel.test.tsx +43 -0
- package/src/forms/FileUploadPanel/FileUploadPanel.tsx +244 -0
- package/src/forms/FileUploadPanel/block.meta.ts +10 -0
- package/src/forms/FileUploadPanel/fixtures.ts +66 -0
- package/src/forms/FormSection/FormSection.test.tsx +34 -0
- package/src/forms/FormSection/FormSection.tsx +77 -0
- package/src/forms/FormSection/block.meta.ts +9 -0
- package/src/forms/InviteMembersDialog/InviteMembersDialog.test.tsx +37 -0
- package/src/forms/InviteMembersDialog/InviteMembersDialog.tsx +183 -0
- package/src/forms/InviteMembersDialog/block.meta.ts +10 -0
- package/src/forms/InviteMembersDialog/fixtures.ts +14 -0
- package/src/forms/SettingsRow/SettingsRow.test.tsx +50 -0
- package/src/forms/SettingsRow/SettingsRow.tsx +74 -0
- package/src/forms/SettingsRow/block.meta.ts +9 -0
- package/src/forms/ValidationSummary/ValidationSummary.test.tsx +46 -0
- package/src/forms/ValidationSummary/ValidationSummary.tsx +75 -0
- package/src/forms/ValidationSummary/block.meta.ts +9 -0
- package/src/forms/ValidationSummary/fixtures.ts +13 -0
- package/src/forms/index.ts +27 -0
- package/src/index.ts +14 -0
- package/src/marketing/ComparisonTable/ComparisonTable.test.tsx +69 -0
- package/src/marketing/ComparisonTable/ComparisonTable.tsx +245 -0
- package/src/marketing/ComparisonTable/block.meta.ts +10 -0
- package/src/marketing/ComparisonTable/examples/options.tsx +48 -0
- package/src/marketing/ComparisonTable/fixtures.ts +83 -0
- package/src/marketing/Faq/Faq.test.tsx +25 -0
- package/src/marketing/Faq/Faq.tsx +68 -0
- package/src/marketing/Faq/block.meta.ts +10 -0
- package/src/marketing/Faq/fixtures.ts +32 -0
- package/src/marketing/FeatureGrid/FeatureGrid.test.tsx +19 -0
- package/src/marketing/FeatureGrid/FeatureGrid.tsx +79 -0
- package/src/marketing/FeatureGrid/block.meta.ts +10 -0
- package/src/marketing/FeatureGrid/fixtures.ts +36 -0
- package/src/marketing/Hero/Hero.test.tsx +33 -0
- package/src/marketing/Hero/Hero.tsx +107 -0
- package/src/marketing/Hero/block.meta.ts +10 -0
- package/src/marketing/PricingTable/PricingTable.test.tsx +92 -0
- package/src/marketing/PricingTable/PricingTable.tsx +323 -0
- package/src/marketing/PricingTable/block.meta.ts +10 -0
- package/src/marketing/PricingTable/fixtures.ts +34 -0
- package/src/marketing/actions.tsx +40 -0
- package/src/marketing/index.ts +15 -0
- package/src/meta.ts +25 -0
- package/src/page-chrome/BulkActionBar/BulkActionBar.test.tsx +34 -0
- package/src/page-chrome/BulkActionBar/BulkActionBar.tsx +76 -0
- package/src/page-chrome/BulkActionBar/block.meta.ts +9 -0
- package/src/page-chrome/PageHeader/PageHeader.test.tsx +68 -0
- package/src/page-chrome/PageHeader/PageHeader.tsx +152 -0
- package/src/page-chrome/PageHeader/block.meta.ts +10 -0
- package/src/page-chrome/PageHeader/fixtures.ts +29 -0
- package/src/page-chrome/PageToolbar/PageToolbar.test.tsx +38 -0
- package/src/page-chrome/PageToolbar/PageToolbar.tsx +138 -0
- package/src/page-chrome/PageToolbar/block.meta.ts +10 -0
- package/src/page-chrome/PageToolbar/fixtures.ts +8 -0
- package/src/page-chrome/SectionHeader/SectionHeader.test.tsx +28 -0
- package/src/page-chrome/SectionHeader/SectionHeader.tsx +73 -0
- package/src/page-chrome/SectionHeader/block.meta.ts +9 -0
- package/src/page-chrome/StickyActionBar/StickyActionBar.test.tsx +29 -0
- package/src/page-chrome/StickyActionBar/StickyActionBar.tsx +66 -0
- package/src/page-chrome/StickyActionBar/block.meta.ts +9 -0
- package/src/page-chrome/index.ts +13 -0
- package/src/page-chrome/types.ts +25 -0
- package/src/scheduling/AgendaList/AgendaList.test.tsx +28 -0
- package/src/scheduling/AgendaList/AgendaList.tsx +165 -0
- package/src/scheduling/AgendaList/block.meta.ts +10 -0
- package/src/scheduling/MonthCalendar/MonthCalendar.test.tsx +32 -0
- package/src/scheduling/MonthCalendar/MonthCalendar.tsx +223 -0
- package/src/scheduling/MonthCalendar/block.meta.ts +10 -0
- package/src/scheduling/TimeSlotPicker/TimeSlotPicker.test.tsx +26 -0
- package/src/scheduling/TimeSlotPicker/TimeSlotPicker.tsx +115 -0
- package/src/scheduling/TimeSlotPicker/block.meta.ts +10 -0
- package/src/scheduling/TimeSlotPicker/fixtures.ts +10 -0
- package/src/scheduling/WeekCalendar/WeekCalendar.test.tsx +27 -0
- package/src/scheduling/WeekCalendar/WeekCalendar.tsx +274 -0
- package/src/scheduling/WeekCalendar/block.meta.ts +10 -0
- package/src/scheduling/index.ts +11 -0
- package/src/scheduling/shared/CalendarHeader.tsx +54 -0
- package/src/scheduling/shared/EventTile.tsx +21 -0
- package/src/scheduling/shared/dates.ts +98 -0
- package/src/scheduling/shared/events.ts +90 -0
- package/src/scheduling/shared/fixtures.ts +54 -0
- package/src/shell/AppSidebar/AppSidebar.test.tsx +38 -0
- package/src/shell/AppSidebar/AppSidebar.tsx +226 -0
- package/src/shell/AppSidebar/block.meta.ts +10 -0
- package/src/shell/AppSidebar/fixtures.ts +58 -0
- package/src/shell/AppTopbar/AppTopbar.test.tsx +41 -0
- package/src/shell/AppTopbar/AppTopbar.tsx +82 -0
- package/src/shell/AppTopbar/block.meta.ts +19 -0
- package/src/shell/AppTopbar/fixtures.ts +22 -0
- package/src/shell/EnvironmentBadge/EnvironmentBadge.test.tsx +22 -0
- package/src/shell/EnvironmentBadge/EnvironmentBadge.tsx +55 -0
- package/src/shell/EnvironmentBadge/block.meta.ts +10 -0
- package/src/shell/GlobalSearch/GlobalSearch.test.tsx +28 -0
- package/src/shell/GlobalSearch/GlobalSearch.tsx +80 -0
- package/src/shell/GlobalSearch/block.meta.ts +10 -0
- package/src/shell/GlobalSearch/fixtures.ts +64 -0
- package/src/shell/index.ts +11 -0
- package/src/styles.css +2 -0
- package/src/tables/DataTable/DataTable.edit-views.test.tsx +249 -0
- package/src/tables/DataTable/DataTable.labels.test.tsx +291 -0
- package/src/tables/DataTable/DataTable.layout.test.tsx +188 -0
- package/src/tables/DataTable/DataTable.overflow-labels.test.tsx +59 -0
- package/src/tables/DataTable/DataTable.single-select.test.tsx +501 -0
- package/src/tables/DataTable/DataTable.test.tsx +268 -0
- package/src/tables/DataTable/DataTable.tsx +531 -0
- package/src/tables/DataTable/block.meta.ts +129 -0
- package/src/tables/DataTable/column-menu.tsx +112 -0
- package/src/tables/DataTable/column-sizing.ts +166 -0
- package/src/tables/DataTable/data-table-body.tsx +195 -0
- package/src/tables/DataTable/data-table-props.ts +159 -0
- package/src/tables/DataTable/data-table-row.tsx +231 -0
- package/src/tables/DataTable/data-table-utils.ts +195 -0
- package/src/tables/DataTable/density-toggle.tsx +33 -0
- package/src/tables/DataTable/editable-cell.tsx +287 -0
- package/src/tables/DataTable/examples/editable.tsx +71 -0
- package/src/tables/DataTable/examples/grouped.tsx +18 -0
- package/src/tables/DataTable/examples/resizable-pinned.tsx +20 -0
- package/src/tables/DataTable/examples/saved-views.tsx +61 -0
- package/src/tables/DataTable/examples/single-selection.tsx +55 -0
- package/src/tables/DataTable/examples/virtualized.tsx +20 -0
- package/src/tables/DataTable/fixtures.ts +119 -0
- package/src/tables/DataTable/header-cell.tsx +90 -0
- package/src/tables/DataTable/labels.ts +54 -0
- package/src/tables/DataTable/resize-handle.tsx +91 -0
- package/src/tables/DataTable/single-selection.tsx +149 -0
- package/src/tables/DataTable/use-table-views.ts +79 -0
- package/src/tables/DataTable/use-virtual-rows.ts +83 -0
- package/src/tables/DataTable/views.tsx +193 -0
- package/src/tables/FilterBar/FilterBar.labels.test.tsx +69 -0
- package/src/tables/FilterBar/FilterBar.test.tsx +43 -0
- package/src/tables/FilterBar/FilterBar.tsx +232 -0
- package/src/tables/FilterBar/block.meta.ts +32 -0
- package/src/tables/FilterBar/fixtures.ts +32 -0
- package/src/tables/InlineEditRow/InlineEditRow.test.tsx +41 -0
- package/src/tables/InlineEditRow/InlineEditRow.tsx +215 -0
- package/src/tables/InlineEditRow/block.meta.ts +10 -0
- package/src/tables/InlineEditRow/fixtures.ts +28 -0
- package/src/tables/PaginationFooter/PaginationFooter.labels.test.tsx +57 -0
- package/src/tables/PaginationFooter/PaginationFooter.test.tsx +29 -0
- package/src/tables/PaginationFooter/PaginationFooter.tsx +118 -0
- package/src/tables/PaginationFooter/block.meta.ts +33 -0
- package/src/tables/SubordinateList/SubordinateList.test.tsx +351 -0
- package/src/tables/SubordinateList/SubordinateList.tsx +140 -0
- package/src/tables/SubordinateList/block.meta.ts +10 -0
- package/src/tables/SubordinateList/examples/ResourceForm.tsx +129 -0
- package/src/tables/SubordinateList/examples/empty.tsx +5 -0
- package/src/tables/SubordinateList/examples/error.tsx +5 -0
- package/src/tables/SubordinateList/examples/loading.tsx +5 -0
- package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.test.tsx +159 -0
- package/src/tables/TruncatedCollectionFooter/TruncatedCollectionFooter.tsx +60 -0
- package/src/tables/TruncatedCollectionFooter/block.meta.ts +10 -0
- package/src/tables/TruncatedCollectionFooter/examples/exact.tsx +6 -0
- package/src/tables/TruncatedCollectionFooter/examples/has-more.tsx +12 -0
- package/src/tables/TruncatedCollectionFooter/fixtures.ts +2 -0
- package/src/tables/index.ts +45 -0
- package/src/team/MembersTable/MembersTable.test.tsx +29 -0
- package/src/team/MembersTable/MembersTable.tsx +141 -0
- package/src/team/MembersTable/block.meta.ts +9 -0
- package/src/team/MembersTable/fixtures.ts +19 -0
- package/src/team/NotificationMatrix/NotificationMatrix.test.tsx +36 -0
- package/src/team/NotificationMatrix/NotificationMatrix.tsx +144 -0
- package/src/team/NotificationMatrix/block.meta.ts +9 -0
- package/src/team/NotificationMatrix/fixtures.ts +43 -0
- package/src/team/PendingInvitations/PendingInvitations.test.tsx +30 -0
- package/src/team/PendingInvitations/PendingInvitations.tsx +132 -0
- package/src/team/PendingInvitations/block.meta.ts +9 -0
- package/src/team/PendingInvitations/fixtures.ts +39 -0
- package/src/team/ProfileHeader/ProfileHeader.test.tsx +29 -0
- package/src/team/ProfileHeader/ProfileHeader.tsx +106 -0
- package/src/team/ProfileHeader/block.meta.ts +10 -0
- package/src/team/ProfileHeader/fixtures.ts +28 -0
- package/src/team/SessionsDevices/SessionsDevices.test.tsx +30 -0
- package/src/team/SessionsDevices/SessionsDevices.tsx +126 -0
- package/src/team/SessionsDevices/block.meta.ts +10 -0
- package/src/team/SessionsDevices/fixtures.ts +58 -0
- package/src/team/index.ts +15 -0
- package/src/test/a11y.ts +18 -0
- package/src/test/heading-levels.test.tsx +69 -0
- package/src/test/setup.ts +36 -0
- package/README.md +0 -3
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export type SpanKind = 'chain' | 'llm' | 'tool' | 'retrieval' | 'http';
|
|
2
|
+
|
|
3
|
+
export interface TraceSpan {
|
|
4
|
+
id: string;
|
|
5
|
+
/** Parent span id; omit for the root span(s). */
|
|
6
|
+
parentId?: string;
|
|
7
|
+
name: string;
|
|
8
|
+
kind: SpanKind;
|
|
9
|
+
/** Start offset from the trace start, in ms. */
|
|
10
|
+
start: number;
|
|
11
|
+
/** Duration in ms. */
|
|
12
|
+
duration: number;
|
|
13
|
+
/** `running`: still in progress (its `duration` is the time elapsed so far). */
|
|
14
|
+
status: 'ok' | 'error' | 'running';
|
|
15
|
+
/** Extra key/value details shown in the side panel. */
|
|
16
|
+
attributes?: Record<string, string | number>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const traceSpans: TraceSpan[] = [
|
|
20
|
+
{
|
|
21
|
+
id: 's1',
|
|
22
|
+
name: 'POST /v1/answer',
|
|
23
|
+
kind: 'http',
|
|
24
|
+
start: 0,
|
|
25
|
+
duration: 2450,
|
|
26
|
+
status: 'ok',
|
|
27
|
+
attributes: { 'http.status': 200, 'request.id': 'req_7f3a' },
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 's2',
|
|
31
|
+
parentId: 's1',
|
|
32
|
+
name: 'retrieve context',
|
|
33
|
+
kind: 'retrieval',
|
|
34
|
+
start: 40,
|
|
35
|
+
duration: 380,
|
|
36
|
+
status: 'ok',
|
|
37
|
+
attributes: { documents: 8 },
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
id: 's3',
|
|
41
|
+
parentId: 's2',
|
|
42
|
+
name: 'embed query',
|
|
43
|
+
kind: 'llm',
|
|
44
|
+
start: 45,
|
|
45
|
+
duration: 115,
|
|
46
|
+
status: 'ok',
|
|
47
|
+
attributes: { model: 'embed-small', 'tokens.input': 24 },
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 's4',
|
|
51
|
+
parentId: 's2',
|
|
52
|
+
name: 'vector search',
|
|
53
|
+
kind: 'retrieval',
|
|
54
|
+
start: 170,
|
|
55
|
+
duration: 240,
|
|
56
|
+
status: 'ok',
|
|
57
|
+
attributes: { index: 'docs-v3', topK: 8 },
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 's5',
|
|
61
|
+
parentId: 's1',
|
|
62
|
+
name: 'generate answer',
|
|
63
|
+
kind: 'llm',
|
|
64
|
+
start: 440,
|
|
65
|
+
duration: 1760,
|
|
66
|
+
status: 'ok',
|
|
67
|
+
attributes: { model: 'general-large', 'tokens.input': 3120, 'tokens.output': 412, temperature: 0.2 },
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: 's6',
|
|
71
|
+
parentId: 's5',
|
|
72
|
+
name: 'tool: lookup_order',
|
|
73
|
+
kind: 'tool',
|
|
74
|
+
start: 900,
|
|
75
|
+
duration: 150,
|
|
76
|
+
status: 'error',
|
|
77
|
+
attributes: { error: 'timeout after 150ms' },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 's7',
|
|
81
|
+
parentId: 's5',
|
|
82
|
+
name: 'tool: lookup_order (retry)',
|
|
83
|
+
kind: 'tool',
|
|
84
|
+
start: 1060,
|
|
85
|
+
duration: 240,
|
|
86
|
+
status: 'ok',
|
|
87
|
+
attributes: { attempt: 2 },
|
|
88
|
+
},
|
|
89
|
+
{ id: 's8', parentId: 's1', name: 'format response', kind: 'chain', start: 2210, duration: 220, status: 'ok' },
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
/** A trace still in progress: the answer is being generated. */
|
|
93
|
+
export const runningTraceSpans: TraceSpan[] = [
|
|
94
|
+
{
|
|
95
|
+
id: 'r1',
|
|
96
|
+
name: 'POST /v1/answer',
|
|
97
|
+
kind: 'http',
|
|
98
|
+
start: 0,
|
|
99
|
+
duration: 1840,
|
|
100
|
+
status: 'running',
|
|
101
|
+
attributes: { 'request.id': 'req_9c21' },
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: 'r2',
|
|
105
|
+
parentId: 'r1',
|
|
106
|
+
name: 'retrieve context',
|
|
107
|
+
kind: 'retrieval',
|
|
108
|
+
start: 30,
|
|
109
|
+
duration: 410,
|
|
110
|
+
status: 'ok',
|
|
111
|
+
attributes: { documents: 6 },
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'r3',
|
|
115
|
+
parentId: 'r1',
|
|
116
|
+
name: 'generate answer',
|
|
117
|
+
kind: 'llm',
|
|
118
|
+
start: 450,
|
|
119
|
+
duration: 1390,
|
|
120
|
+
status: 'running',
|
|
121
|
+
attributes: { model: 'general-large', 'tokens.output': 188 },
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'r4',
|
|
125
|
+
parentId: 'r3',
|
|
126
|
+
name: 'tool: search_docs',
|
|
127
|
+
kind: 'tool',
|
|
128
|
+
start: 620,
|
|
129
|
+
duration: 310,
|
|
130
|
+
status: 'ok',
|
|
131
|
+
attributes: { results: 4 },
|
|
132
|
+
},
|
|
133
|
+
];
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Badge, StatusTag } from '@gntik-ai/ui';
|
|
2
|
+
import type { TraceSpan } from './fixtures';
|
|
3
|
+
|
|
4
|
+
/** StatusTag dictionary of span statuses (running is announced by its label, not colour alone). */
|
|
5
|
+
export const SPAN_STATUSES = {
|
|
6
|
+
ok: { label: 'OK', tone: 'success' },
|
|
7
|
+
error: { label: 'Error', tone: 'destructive' },
|
|
8
|
+
running: { label: 'Running', tone: 'info' },
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export function formatSpanDuration(ms: number) {
|
|
12
|
+
return ms >= 1000 ? `${(ms / 1000).toFixed(ms >= 10000 ? 1 : 2)}s` : `${Math.round(ms)}ms`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SpanDetailsProps {
|
|
16
|
+
span: TraceSpan;
|
|
17
|
+
/** Heading level of the span name. */
|
|
18
|
+
headingAs: 'h3' | 'h4' | 'h5';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Default content of the details pane: name, kind, status, timing and attributes. */
|
|
22
|
+
export function SpanDetails({ span, headingAs: Heading }: SpanDetailsProps) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="flex flex-col gap-3">
|
|
25
|
+
<div>
|
|
26
|
+
<Heading className="text-[13.5px] font-semibold break-words text-foreground">{span.name}</Heading>
|
|
27
|
+
<div className="mt-1.5 flex flex-wrap items-center gap-1.5">
|
|
28
|
+
<Badge variant="soft" size="sm">
|
|
29
|
+
{span.kind}
|
|
30
|
+
</Badge>
|
|
31
|
+
<StatusTag size="sm" status={span.status} statuses={SPAN_STATUSES} />
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
<dl className="grid grid-cols-[auto_minmax(0,1fr)] gap-x-4 gap-y-1.5 text-[12px]">
|
|
35
|
+
<dt className="text-muted-foreground">Start</dt>
|
|
36
|
+
<dd className="font-mono text-foreground">+{formatSpanDuration(span.start)}</dd>
|
|
37
|
+
<dt className="text-muted-foreground">{span.status === 'running' ? 'Elapsed' : 'Duration'}</dt>
|
|
38
|
+
<dd className="font-mono text-foreground">
|
|
39
|
+
{formatSpanDuration(span.duration)}
|
|
40
|
+
{span.status === 'running' && <span className="font-sans text-muted-foreground"> (in progress)</span>}
|
|
41
|
+
</dd>
|
|
42
|
+
{Object.entries(span.attributes ?? {}).map(([k, v]) => (
|
|
43
|
+
<div key={k} className="contents">
|
|
44
|
+
<dt className="truncate text-muted-foreground">{k}</dt>
|
|
45
|
+
<dd className="font-mono break-words text-foreground">{String(v)}</dd>
|
|
46
|
+
</div>
|
|
47
|
+
))}
|
|
48
|
+
</dl>
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
package/src/ai/index.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export * from './AssistantCardGrid/AssistantCardGrid';
|
|
2
|
+
export { meta as assistantCardGridMeta } from './AssistantCardGrid/block.meta';
|
|
3
|
+
export * from './TraceWaterfall/TraceWaterfall';
|
|
4
|
+
export { meta as traceWaterfallMeta } from './TraceWaterfall/block.meta';
|
|
5
|
+
export { traceSpans, runningTraceSpans } from './TraceWaterfall/fixtures';
|
|
6
|
+
export * from './TokenCostCard/TokenCostCard';
|
|
7
|
+
export { meta as tokenCostCardMeta } from './TokenCostCard/block.meta';
|
|
8
|
+
export * from './PromptEditor/PromptEditor';
|
|
9
|
+
export { meta as promptEditorMeta } from './PromptEditor/block.meta';
|
|
10
|
+
export * from './EvaluationScorecard/EvaluationScorecard';
|
|
11
|
+
export { meta as evaluationScorecardMeta } from './EvaluationScorecard/block.meta';
|
|
12
|
+
export * from './ModelKeysList/ModelKeysList';
|
|
13
|
+
export { meta as modelKeysListMeta } from './ModelKeysList/block.meta';
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
import { ThemeProvider } from '@gntik-ai/ui';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
4
|
+
import { createRef } from 'react';
|
|
5
|
+
import { expectNoAxeViolations } from '../../test/a11y';
|
|
6
|
+
import { ForgotPasswordForm, type ForgotPasswordFormRef } from './ForgotPasswordForm';
|
|
7
|
+
|
|
8
|
+
describe('ForgotPasswordForm', () => {
|
|
9
|
+
it('can hide or replace inbox copy and both resend actions', () => {
|
|
10
|
+
const { rerender } = render(
|
|
11
|
+
<ForgotPasswordForm defaultSent sentTitle={null} sentDescription={null} sentStatus={null} resendActions={null} />,
|
|
12
|
+
);
|
|
13
|
+
expect(screen.queryByText('Check your email')).not.toBeInTheDocument();
|
|
14
|
+
expect(screen.queryByText(/If an account exists/)).not.toBeInTheDocument();
|
|
15
|
+
expect(screen.queryByText('Reset link sent.')).not.toBeInTheDocument();
|
|
16
|
+
expect(screen.queryByRole('button', { name: 'Resend link' })).not.toBeInTheDocument();
|
|
17
|
+
expect(screen.queryByRole('button', { name: 'Use a different email' })).not.toBeInTheDocument();
|
|
18
|
+
rerender(
|
|
19
|
+
<ForgotPasswordForm
|
|
20
|
+
sentTitle="Request received"
|
|
21
|
+
sentDescription="If eligible, instructions will arrive."
|
|
22
|
+
sentStatus="Request received."
|
|
23
|
+
resendActions={<a href="#help">Get help</a>}
|
|
24
|
+
/>,
|
|
25
|
+
);
|
|
26
|
+
expect(screen.getByRole('heading', { name: 'Request received' })).toBeInTheDocument();
|
|
27
|
+
expect(screen.getByRole('status')).toHaveTextContent('Request received.');
|
|
28
|
+
expect(screen.getByRole('link', { name: 'Get help' })).toBeInTheDocument();
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('configures request and sent copy and heading levels, including suppression', () => {
|
|
32
|
+
const { container, rerender } = render(
|
|
33
|
+
<ForgotPasswordForm title="Recover access" description="Enter your account name." headingLevel="h2" />,
|
|
34
|
+
);
|
|
35
|
+
expect(screen.getByRole('heading', { level: 2, name: 'Recover access' })).toBeInTheDocument();
|
|
36
|
+
expect(screen.getByText('Enter your account name.')).toBeInTheDocument();
|
|
37
|
+
rerender(<ForgotPasswordForm headingLevel={null} aria-label="Recover access" />);
|
|
38
|
+
expect(container.querySelector('h1,h2,h3,h4,h5,h6,[role="heading"]')).toBeNull();
|
|
39
|
+
expect(screen.getByRole('form', { name: 'Recover access' })).toBeInTheDocument();
|
|
40
|
+
rerender(
|
|
41
|
+
<ForgotPasswordForm
|
|
42
|
+
key="sent"
|
|
43
|
+
defaultSent
|
|
44
|
+
sentTitle="Request received"
|
|
45
|
+
sentDescription="If eligible, you will receive instructions."
|
|
46
|
+
headingLevel="h2"
|
|
47
|
+
/>,
|
|
48
|
+
);
|
|
49
|
+
expect(screen.getByRole('heading', { level: 2, name: 'Request received' })).toBeInTheDocument();
|
|
50
|
+
expect(screen.getByText('If eligible, you will receive instructions.')).toBeInTheDocument();
|
|
51
|
+
rerender(<ForgotPasswordForm key="sent" headingLevel={null} />);
|
|
52
|
+
expect(container.querySelector('h1,h2,h3,h4,h5,h6,[role="heading"]')).toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('exposes identifier focus and preserves server feedback and help associations', () => {
|
|
56
|
+
const ref = createRef<ForgotPasswordFormRef>();
|
|
57
|
+
render(
|
|
58
|
+
<ForgotPasswordForm
|
|
59
|
+
ref={ref}
|
|
60
|
+
identifier={{ help: 'Account email', helpId: 'email-help' }}
|
|
61
|
+
fieldInvalid={{ identifier: true }}
|
|
62
|
+
feedbackId="recovery-feedback"
|
|
63
|
+
aria-describedby="form-feedback"
|
|
64
|
+
error="Request could not be completed."
|
|
65
|
+
/>,
|
|
66
|
+
);
|
|
67
|
+
const input = screen.getByLabelText('Email');
|
|
68
|
+
expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
69
|
+
expect(input.getAttribute('aria-describedby')?.split(' ')).toEqual(
|
|
70
|
+
expect.arrayContaining(['email-help', 'recovery-feedback']),
|
|
71
|
+
);
|
|
72
|
+
expect(screen.getByRole('form', { name: 'Reset password request' })).toHaveAttribute(
|
|
73
|
+
'aria-describedby',
|
|
74
|
+
'form-feedback',
|
|
75
|
+
);
|
|
76
|
+
expect(screen.getByRole('alert')).toHaveAttribute('id', 'recovery-feedback');
|
|
77
|
+
ref.current?.focus('identifier');
|
|
78
|
+
expect(input).toHaveFocus();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('uses consumer validation messages, focuses the identifier and chains help and errors', async () => {
|
|
82
|
+
const onSubmit = vi.fn();
|
|
83
|
+
render(
|
|
84
|
+
<ForgotPasswordForm
|
|
85
|
+
identifier={{ type: 'text', label: 'Username', help: 'Account name', helpId: 'recovery-help', minLength: 3 }}
|
|
86
|
+
messages={{ identifierRequired: 'Username is required.', identifierTooShort: 'Username is too short.' }}
|
|
87
|
+
onSubmit={onSubmit}
|
|
88
|
+
/>,
|
|
89
|
+
);
|
|
90
|
+
const input = screen.getByLabelText('Username');
|
|
91
|
+
expect(input).not.toHaveAttribute('aria-invalid');
|
|
92
|
+
expect(screen.getByRole('form', { name: 'Reset password request' })).toHaveAttribute('novalidate');
|
|
93
|
+
await userEvent.click(screen.getByRole('button', { name: 'Send reset link' }));
|
|
94
|
+
expect(input).toHaveFocus();
|
|
95
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
96
|
+
const error = screen.getByText('Username is required.').closest('[role="alert"]');
|
|
97
|
+
expect(error).toBeInTheDocument();
|
|
98
|
+
expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
99
|
+
expect(input.getAttribute('aria-describedby')?.split(' ')).toEqual(
|
|
100
|
+
expect.arrayContaining(['recovery-help', error?.id]),
|
|
101
|
+
);
|
|
102
|
+
await userEvent.type(input, 'ab');
|
|
103
|
+
expect(screen.getByText('Username is too short.')).toBeInTheDocument();
|
|
104
|
+
await userEvent.type(input, 'c');
|
|
105
|
+
expect(input).not.toHaveAttribute('aria-invalid');
|
|
106
|
+
expect(input).toHaveAccessibleDescription('Account name');
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('accepts a username-or-email configuration and submits the trimmed value', async () => {
|
|
110
|
+
const onSubmit = vi.fn();
|
|
111
|
+
render(
|
|
112
|
+
<ForgotPasswordForm
|
|
113
|
+
identifier={{
|
|
114
|
+
type: 'text',
|
|
115
|
+
autoComplete: 'username',
|
|
116
|
+
label: 'Username or email',
|
|
117
|
+
help: 'Use your account name or email.',
|
|
118
|
+
placeholder: 'Account name',
|
|
119
|
+
maxLength: 255,
|
|
120
|
+
}}
|
|
121
|
+
onSubmit={onSubmit}
|
|
122
|
+
/>,
|
|
123
|
+
);
|
|
124
|
+
const input = screen.getByRole('textbox', { name: 'Username or email' });
|
|
125
|
+
expect(input).toHaveAttribute('type', 'text');
|
|
126
|
+
expect(input).toHaveAttribute('autocomplete', 'username');
|
|
127
|
+
expect(input).toHaveAttribute('maxlength', '255');
|
|
128
|
+
expect(input).toHaveAttribute('placeholder', 'Account name');
|
|
129
|
+
expect(input).toHaveAccessibleDescription('Use your account name or email.');
|
|
130
|
+
await userEvent.type(input, ' avery ');
|
|
131
|
+
await userEvent.click(screen.getByRole('button', { name: 'Send reset link' }));
|
|
132
|
+
expect(onSubmit).toHaveBeenCalledWith('avery');
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('renders the request step', async () => {
|
|
136
|
+
const { container } = render(<ForgotPasswordForm />);
|
|
137
|
+
expect(screen.getByRole('heading', { name: 'Forgot your password?' })).toBeInTheDocument();
|
|
138
|
+
await expectNoAxeViolations(container);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('sends the link and shows the sent state', async () => {
|
|
142
|
+
const onSubmit = vi.fn().mockResolvedValue(undefined);
|
|
143
|
+
const { container } = render(<ForgotPasswordForm onSubmit={onSubmit} />);
|
|
144
|
+
await userEvent.type(screen.getByLabelText('Email'), 'avery@example.com');
|
|
145
|
+
await userEvent.click(screen.getByRole('button', { name: 'Send reset link' }));
|
|
146
|
+
expect(onSubmit).toHaveBeenCalledWith('avery@example.com');
|
|
147
|
+
expect(await screen.findByRole('heading', { name: 'Check your email' })).toBeInTheDocument();
|
|
148
|
+
expect(screen.getByText('avery@example.com')).toBeInTheDocument();
|
|
149
|
+
await userEvent.click(screen.getByRole('button', { name: 'Resend link' }));
|
|
150
|
+
expect(onSubmit).toHaveBeenCalledTimes(2);
|
|
151
|
+
await expectNoAxeViolations(container);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
// Public rendering seams: identifier mode, theme, heading policy and validation/recovery state.
|
|
156
|
+
describe('ForgotPasswordForm accessibility matrix', () => {
|
|
157
|
+
for (const theme of ['dark', 'light', 'high_contrast'] as const) {
|
|
158
|
+
for (const mode of ['email', 'username'] as const) {
|
|
159
|
+
for (const suppressed of [false, true]) {
|
|
160
|
+
for (const state of ['initial', 'errors', 'sent'] as const) {
|
|
161
|
+
it(`${theme} / ${mode} / headings ${!suppressed} / ${state}`, async () => {
|
|
162
|
+
const onSubmit = vi.fn();
|
|
163
|
+
const { container } = render(
|
|
164
|
+
<ThemeProvider defaultMode={theme} storageKey={null}>
|
|
165
|
+
<h1>Account access</h1>
|
|
166
|
+
<ForgotPasswordForm
|
|
167
|
+
identifier={
|
|
168
|
+
mode === 'username'
|
|
169
|
+
? {
|
|
170
|
+
type: 'text',
|
|
171
|
+
autoComplete: 'username',
|
|
172
|
+
label: 'Username',
|
|
173
|
+
help: 'Use your account name.',
|
|
174
|
+
minLength: 3,
|
|
175
|
+
maxLength: 255,
|
|
176
|
+
}
|
|
177
|
+
: undefined
|
|
178
|
+
}
|
|
179
|
+
headingLevel={suppressed ? null : undefined}
|
|
180
|
+
defaultSent={state === 'sent'}
|
|
181
|
+
messages={{
|
|
182
|
+
identifierRequired: 'Account name is required.',
|
|
183
|
+
passwordRequired: 'Password is required.',
|
|
184
|
+
}}
|
|
185
|
+
onSubmit={onSubmit}
|
|
186
|
+
/>
|
|
187
|
+
</ThemeProvider>,
|
|
188
|
+
);
|
|
189
|
+
if (state === 'initial' || state === 'errors') {
|
|
190
|
+
const input = screen.getByRole('textbox', { name: mode === 'username' ? 'Username' : 'Email' });
|
|
191
|
+
expect(input).toHaveAttribute('type', mode === 'username' ? 'text' : 'email');
|
|
192
|
+
expect(input).toHaveAttribute('autocomplete', mode === 'username' ? 'username' : 'email');
|
|
193
|
+
expect(screen.getByRole('form', { name: 'Reset password request' })).toHaveAttribute('novalidate');
|
|
194
|
+
if (state === 'errors') {
|
|
195
|
+
await userEvent.click(screen.getByRole('button', { name: 'Send reset link' }));
|
|
196
|
+
expect(input).toHaveFocus();
|
|
197
|
+
expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
198
|
+
expect(onSubmit).not.toHaveBeenCalled();
|
|
199
|
+
expect(screen.getByText('Account name is required.')).toBeInTheDocument();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
if (suppressed) expect(container.querySelectorAll('h1,h2,h3,h4,h5,h6,[role="heading"]')).toHaveLength(1);
|
|
203
|
+
await expectNoAxeViolations(container);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { Alert, Button, Field, FieldDescription, FieldError, FieldLabel, Input, Link, cn } from '@gntik-ai/ui';
|
|
2
|
+
import { ArrowLeft, Mail, MailCheck } from '@gntik-ai/icons';
|
|
3
|
+
import { useImperativeHandle, useRef, useState, type MouseEvent, type ReactNode, type Ref } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
AuthHeader,
|
|
6
|
+
authIdentifierError,
|
|
7
|
+
authFormClass,
|
|
8
|
+
useAsyncSubmit,
|
|
9
|
+
type AuthIdentifierConfig,
|
|
10
|
+
type AuthValidationMessages,
|
|
11
|
+
type AuthFormAccessibilityProps,
|
|
12
|
+
type AuthHeaderProps,
|
|
13
|
+
} from '../shared';
|
|
14
|
+
|
|
15
|
+
export interface ForgotPasswordFormRef {
|
|
16
|
+
focus(field: 'identifier'): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ForgotPasswordFormProps extends AuthFormAccessibilityProps {
|
|
20
|
+
/** React 19 ref for focus after a server response; available in the request state. */
|
|
21
|
+
ref?: Ref<ForgotPasswordFormRef>;
|
|
22
|
+
identifier?: AuthIdentifierConfig;
|
|
23
|
+
/** The kit validates before submit. Supply copy here; do not add a second inline validator. */
|
|
24
|
+
messages?: AuthValidationMessages;
|
|
25
|
+
/** Requests the reset link. A returned promise shows the loading state; a rejection shows in the alert. */
|
|
26
|
+
onSubmit?: (email: string) => void | Promise<void>;
|
|
27
|
+
error?: string | null;
|
|
28
|
+
defaultEmail?: string;
|
|
29
|
+
/** Start on the "sent" step (e.g. after a redirect). */
|
|
30
|
+
defaultSent?: boolean;
|
|
31
|
+
signInHref?: string;
|
|
32
|
+
onBackToSignIn?: () => void;
|
|
33
|
+
eyebrow?: ReactNode;
|
|
34
|
+
title?: ReactNode;
|
|
35
|
+
description?: ReactNode;
|
|
36
|
+
sentTitle?: ReactNode;
|
|
37
|
+
sentDescription?: ReactNode;
|
|
38
|
+
/** Replaces the screen-reader sent announcement; `null` hides it. */
|
|
39
|
+
sentStatus?: ReactNode;
|
|
40
|
+
/** Replaces both resend/change-identifier actions; `null` hides them. */
|
|
41
|
+
resendActions?: ReactNode;
|
|
42
|
+
/** Applies to both request and sent states. `null` omits the internal heading. */
|
|
43
|
+
headingLevel?: AuthHeaderProps['headingLevel'];
|
|
44
|
+
className?: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Request a reset link by email, then a "check your inbox" state with resend and a way back. */
|
|
48
|
+
export function ForgotPasswordForm({
|
|
49
|
+
identifier,
|
|
50
|
+
messages,
|
|
51
|
+
ref,
|
|
52
|
+
fieldInvalid,
|
|
53
|
+
feedbackId,
|
|
54
|
+
'aria-describedby': describedBy,
|
|
55
|
+
'aria-label': ariaLabel = 'Reset password request',
|
|
56
|
+
'aria-labelledby': labelledBy,
|
|
57
|
+
onSubmit,
|
|
58
|
+
error: errorProp,
|
|
59
|
+
defaultEmail = '',
|
|
60
|
+
defaultSent = false,
|
|
61
|
+
signInHref = '#',
|
|
62
|
+
onBackToSignIn,
|
|
63
|
+
eyebrow = 'Account recovery',
|
|
64
|
+
title = 'Forgot your password?',
|
|
65
|
+
description = "Enter the email you sign in with and we'll send you a reset link.",
|
|
66
|
+
sentTitle = 'Check your email',
|
|
67
|
+
sentDescription,
|
|
68
|
+
sentStatus = 'Reset link sent.',
|
|
69
|
+
resendActions,
|
|
70
|
+
headingLevel,
|
|
71
|
+
className,
|
|
72
|
+
}: ForgotPasswordFormProps) {
|
|
73
|
+
const [email, setEmail] = useState(defaultEmail);
|
|
74
|
+
const [submitted, setSubmitted] = useState(false);
|
|
75
|
+
const [sent, setSent] = useState(defaultSent);
|
|
76
|
+
const { loading, error, run } = useAsyncSubmit('The reset link could not be sent.');
|
|
77
|
+
const alert = errorProp ?? error;
|
|
78
|
+
const identifierInput = useRef<HTMLInputElement>(null);
|
|
79
|
+
useImperativeHandle(ref, () => ({ focus: () => identifierInput.current?.focus() }), []);
|
|
80
|
+
const identifierError = authIdentifierError(email, identifier, messages);
|
|
81
|
+
const emailInvalid = submitted && identifierError !== null;
|
|
82
|
+
|
|
83
|
+
const send = async () => {
|
|
84
|
+
const ok = await run(() => onSubmit?.(email.trim()));
|
|
85
|
+
if (ok) setSent(true);
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const back = (
|
|
89
|
+
<Link
|
|
90
|
+
href={signInHref}
|
|
91
|
+
tone="muted"
|
|
92
|
+
className="inline-flex items-center gap-1.5 text-[13px]"
|
|
93
|
+
onClick={
|
|
94
|
+
onBackToSignIn
|
|
95
|
+
? (e: MouseEvent) => {
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
onBackToSignIn();
|
|
98
|
+
}
|
|
99
|
+
: undefined
|
|
100
|
+
}
|
|
101
|
+
>
|
|
102
|
+
<ArrowLeft size={14} aria-hidden className="rtl:-scale-x-100" />
|
|
103
|
+
Back to sign in
|
|
104
|
+
</Link>
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (sent) {
|
|
108
|
+
return (
|
|
109
|
+
<div className={cn(authFormClass, className)}>
|
|
110
|
+
<span className="mb-5 grid size-11 place-items-center rounded-xl bg-primary/14 text-primary-chip-text">
|
|
111
|
+
<MailCheck size={20} aria-hidden />
|
|
112
|
+
</span>
|
|
113
|
+
<AuthHeader
|
|
114
|
+
eyebrow={eyebrow}
|
|
115
|
+
title={sentTitle}
|
|
116
|
+
headingLevel={headingLevel}
|
|
117
|
+
description={
|
|
118
|
+
sentDescription === undefined ? (
|
|
119
|
+
<>
|
|
120
|
+
If an account exists for{' '}
|
|
121
|
+
<span className="font-medium text-foreground">{email.trim() || 'that address'}</span>, you will receive
|
|
122
|
+
a link to reset your password. It expires in 1 hour.
|
|
123
|
+
</>
|
|
124
|
+
) : (
|
|
125
|
+
sentDescription
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
/>
|
|
129
|
+
{sentStatus != null && (
|
|
130
|
+
<div role="status" className="sr-only">
|
|
131
|
+
{sentStatus}
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
{alert && <Alert id={feedbackId} tone="destructive" description={alert} className="mb-4" />}
|
|
135
|
+
{resendActions !== null && (
|
|
136
|
+
<div className="flex flex-col gap-2.5">
|
|
137
|
+
{resendActions === undefined ? (
|
|
138
|
+
<>
|
|
139
|
+
<Button variant="secondary" size="lg" loading={loading} className="w-full" onClick={() => void send()}>
|
|
140
|
+
Resend link
|
|
141
|
+
</Button>
|
|
142
|
+
<Button
|
|
143
|
+
variant="ghost"
|
|
144
|
+
size="lg"
|
|
145
|
+
className="w-full"
|
|
146
|
+
onClick={() => {
|
|
147
|
+
setSent(false);
|
|
148
|
+
setSubmitted(false);
|
|
149
|
+
}}
|
|
150
|
+
>
|
|
151
|
+
Use a different email
|
|
152
|
+
</Button>
|
|
153
|
+
</>
|
|
154
|
+
) : (
|
|
155
|
+
resendActions
|
|
156
|
+
)}
|
|
157
|
+
</div>
|
|
158
|
+
)}
|
|
159
|
+
<p className="mt-7 text-center">{back}</p>
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return (
|
|
165
|
+
<div className={cn(authFormClass, className)}>
|
|
166
|
+
<AuthHeader eyebrow={eyebrow} title={title} description={description} headingLevel={headingLevel} />
|
|
167
|
+
<form
|
|
168
|
+
noValidate
|
|
169
|
+
aria-label={ariaLabel}
|
|
170
|
+
aria-labelledby={labelledBy}
|
|
171
|
+
aria-describedby={describedBy}
|
|
172
|
+
className="flex flex-col gap-4"
|
|
173
|
+
onSubmit={(e) => {
|
|
174
|
+
e.preventDefault();
|
|
175
|
+
setSubmitted(true);
|
|
176
|
+
if (identifierError !== null) {
|
|
177
|
+
identifierInput.current?.focus();
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
void send();
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
{alert && <Alert id={feedbackId} tone="destructive" description={alert} />}
|
|
184
|
+
<Field invalid={emailInvalid || fieldInvalid?.identifier === true}>
|
|
185
|
+
<FieldLabel>{identifier?.label ?? 'Email'}</FieldLabel>
|
|
186
|
+
<Input
|
|
187
|
+
ref={identifierInput}
|
|
188
|
+
aria-describedby={fieldInvalid?.identifier ? feedbackId : undefined}
|
|
189
|
+
type={identifier?.type ?? 'email'}
|
|
190
|
+
leadingIcon={Mail}
|
|
191
|
+
autoComplete={identifier?.autoComplete ?? 'email'}
|
|
192
|
+
placeholder={identifier?.placeholder ?? 'name@company.com'}
|
|
193
|
+
minLength={identifier?.minLength}
|
|
194
|
+
maxLength={identifier?.maxLength}
|
|
195
|
+
value={email}
|
|
196
|
+
onValueChange={setEmail}
|
|
197
|
+
required
|
|
198
|
+
/>
|
|
199
|
+
{identifier?.help != null && <FieldDescription id={identifier.helpId}>{identifier.help}</FieldDescription>}
|
|
200
|
+
<FieldError role="alert" match={emailInvalid}>
|
|
201
|
+
{identifierError}
|
|
202
|
+
</FieldError>
|
|
203
|
+
</Field>
|
|
204
|
+
<Button type="submit" size="lg" loading={loading} className="mt-2 w-full">
|
|
205
|
+
Send reset link
|
|
206
|
+
</Button>
|
|
207
|
+
</form>
|
|
208
|
+
<p className="mt-7 text-center">{back}</p>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BlockMeta } from '../../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: BlockMeta = {
|
|
4
|
+
name: 'Forgot password form',
|
|
5
|
+
family: 'auth',
|
|
6
|
+
status: 'stable',
|
|
7
|
+
description:
|
|
8
|
+
'Email (default) or username-or-email password recovery; onSubmit receives the trimmed identifier. Supports identifier (type, autoComplete, label, help/helpId, placeholder, minLength/maxLength), messages (identifierRequired/identifierInvalid/identifierTooShort/passwordRequired), and React 19 ref.focus. The kit owns inline validation and focuses the first invalid field; consumers supply messages, not a second inline validator. Server errors use error + fieldInvalid + feedbackId; aria-describedby describes the form, while flagged inputs also retain their help and error ids. headingLevel (h1/h2/h3/null) suppresses the heading without losing the form name; aria-label/aria-labelledby override the default name. ref.focus accepts identifier. title/description and sentTitle/sentDescription configure request and sent copy; sentTitle, sentDescription, sentStatus and resendActions accept null to hide inbox copy, announcements and resend/change-identifier actions. Preview: forgot-password-form--username-or-email.',
|
|
9
|
+
uses: ['Field', 'FieldLabel', 'FieldDescription', 'FieldError', 'Input', 'Button', 'Alert', 'Link'],
|
|
10
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ForgotPasswordForm, type ForgotPasswordFormProps } from '../ForgotPasswordForm';
|
|
2
|
+
|
|
3
|
+
/** /preview.html?kind=block&id=forgot-password-form--username-or-email */
|
|
4
|
+
export default function UsernameOrEmail(props: Pick<ForgotPasswordFormProps, 'headingLevel'>) {
|
|
5
|
+
return (
|
|
6
|
+
<ForgotPasswordForm
|
|
7
|
+
{...props}
|
|
8
|
+
identifier={{
|
|
9
|
+
type: 'text',
|
|
10
|
+
autoComplete: 'username',
|
|
11
|
+
label: 'Username or email',
|
|
12
|
+
help: 'Use your account name or email address.',
|
|
13
|
+
placeholder: 'Account name or email',
|
|
14
|
+
maxLength: 255,
|
|
15
|
+
}}
|
|
16
|
+
messages={{ identifierRequired: 'Enter your username or email.' }}
|
|
17
|
+
description="Enter your account name or email to request recovery instructions."
|
|
18
|
+
sentTitle="Request received"
|
|
19
|
+
sentDescription="If the account is eligible, you will receive recovery instructions."
|
|
20
|
+
sentStatus="Recovery request received."
|
|
21
|
+
resendActions={null}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
}
|