@gntik-ai/blocks 0.0.0-stage → 0.5.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 +3494 -0
- package/dist/index.js +14911 -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.test.tsx +33 -0
- package/src/auth/SignUpForm/SignUpForm.tsx +154 -0
- package/src/auth/SignUpForm/block.meta.ts +9 -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 +15 -0
- package/src/auth/shared.tsx +113 -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 +23 -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.layout.test.tsx +188 -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 +520 -0
- package/src/tables/DataTable/block.meta.ts +27 -0
- package/src/tables/DataTable/column-menu.tsx +109 -0
- package/src/tables/DataTable/column-sizing.ts +166 -0
- package/src/tables/DataTable/data-table-body.tsx +194 -0
- package/src/tables/DataTable/data-table-props.ts +156 -0
- package/src/tables/DataTable/data-table-row.tsx +220 -0
- package/src/tables/DataTable/data-table-utils.ts +195 -0
- package/src/tables/DataTable/density-toggle.tsx +30 -0
- package/src/tables/DataTable/editable-cell.tsx +274 -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 +86 -0
- package/src/tables/DataTable/resize-handle.tsx +88 -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 +186 -0
- package/src/tables/FilterBar/FilterBar.test.tsx +43 -0
- package/src/tables/FilterBar/FilterBar.tsx +199 -0
- package/src/tables/FilterBar/block.meta.ts +10 -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.test.tsx +29 -0
- package/src/tables/PaginationFooter/PaginationFooter.tsx +94 -0
- package/src/tables/PaginationFooter/block.meta.ts +10 -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,69 @@
|
|
|
1
|
+
import { Alert, Button } from '@gntik-ai/ui';
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { CredentialDisclosureDialog, type CredentialDisclosure } from '../CredentialDisclosureDialog';
|
|
4
|
+
import { storedDisclosure } from '../fixtures';
|
|
5
|
+
|
|
6
|
+
/** Pending/error UI belongs to the consumer; only a successful response sets disclosure. */
|
|
7
|
+
export default function ResponseGating() {
|
|
8
|
+
const [disclosure, setDisclosure] = useState<CredentialDisclosure | null>(null);
|
|
9
|
+
const [status, setStatus] = useState<'idle' | 'pending' | 'failed'>('idle');
|
|
10
|
+
const heading = useRef<HTMLHeadingElement>(null);
|
|
11
|
+
const requestId = useRef(0);
|
|
12
|
+
const request = async (result: 'pending' | 'failed' | 'success') => {
|
|
13
|
+
const id = ++requestId.current;
|
|
14
|
+
setDisclosure(null);
|
|
15
|
+
setStatus('pending');
|
|
16
|
+
try {
|
|
17
|
+
const response = await (result === 'pending'
|
|
18
|
+
? new Promise<CredentialDisclosure>(() => {})
|
|
19
|
+
: result === 'failed'
|
|
20
|
+
? Promise.reject(new Error('Synthetic request failure'))
|
|
21
|
+
: Promise.resolve(storedDisclosure));
|
|
22
|
+
if (id === requestId.current) {
|
|
23
|
+
setStatus('idle');
|
|
24
|
+
setDisclosure(response);
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
if (id === requestId.current) setStatus('failed');
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
return (
|
|
31
|
+
<>
|
|
32
|
+
<h1 ref={heading} tabIndex={-1}>
|
|
33
|
+
Credentials
|
|
34
|
+
</h1>
|
|
35
|
+
<div className="flex flex-wrap gap-2">
|
|
36
|
+
<Button
|
|
37
|
+
onClick={() => {
|
|
38
|
+
void request('pending');
|
|
39
|
+
}}
|
|
40
|
+
>
|
|
41
|
+
Request pending response
|
|
42
|
+
</Button>
|
|
43
|
+
<Button
|
|
44
|
+
onClick={() => {
|
|
45
|
+
void request('failed');
|
|
46
|
+
}}
|
|
47
|
+
>
|
|
48
|
+
Request rejected response
|
|
49
|
+
</Button>
|
|
50
|
+
<Button
|
|
51
|
+
onClick={() => {
|
|
52
|
+
void request('success');
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
Request successful response
|
|
56
|
+
</Button>
|
|
57
|
+
</div>
|
|
58
|
+
{status === 'pending' && <p>Request pending</p>}
|
|
59
|
+
{status === 'failed' && (
|
|
60
|
+
<Alert tone="destructive" title="Request failed" description="No credential was disclosed." />
|
|
61
|
+
)}
|
|
62
|
+
<CredentialDisclosureDialog
|
|
63
|
+
disclosure={disclosure}
|
|
64
|
+
onClose={() => setDisclosure(null)}
|
|
65
|
+
fallbackFocus={() => heading.current}
|
|
66
|
+
/>
|
|
67
|
+
</>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Button } from '@gntik-ai/ui';
|
|
2
|
+
import { useRef, useState } from 'react';
|
|
3
|
+
import { CredentialDisclosureDialog, type CredentialDisclosure } from '../CredentialDisclosureDialog';
|
|
4
|
+
import { storedDisclosure } from '../fixtures';
|
|
5
|
+
|
|
6
|
+
/** The initial synthetic stored value stays masked in catalog previews and visual baselines. */
|
|
7
|
+
export default function Stored() {
|
|
8
|
+
const [disclosure, setDisclosure] = useState<CredentialDisclosure | null>(storedDisclosure);
|
|
9
|
+
const heading = useRef<HTMLHeadingElement>(null);
|
|
10
|
+
const trigger = useRef<HTMLButtonElement>(null);
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<h1 ref={heading} tabIndex={-1}>
|
|
14
|
+
Credentials
|
|
15
|
+
</h1>
|
|
16
|
+
<Button
|
|
17
|
+
ref={trigger}
|
|
18
|
+
onClick={() => {
|
|
19
|
+
void Promise.resolve(storedDisclosure).then(setDisclosure);
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
Disclose stored secret
|
|
23
|
+
</Button>
|
|
24
|
+
<CredentialDisclosureDialog
|
|
25
|
+
disclosure={disclosure}
|
|
26
|
+
onClose={() => setDisclosure(null)}
|
|
27
|
+
resolveReturnFocus={() => trigger.current}
|
|
28
|
+
fallbackFocus={() => heading.current}
|
|
29
|
+
/>
|
|
30
|
+
</>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { CredentialDisclosure } from './CredentialDisclosureDialog';
|
|
2
|
+
|
|
3
|
+
/** Synthetic values only: never use live credentials in previews or tests. */
|
|
4
|
+
export const storedDisclosure: CredentialDisclosure = {
|
|
5
|
+
variant: 'stored',
|
|
6
|
+
credentialId: 'cred_demo_01',
|
|
7
|
+
expiresAt: 'January 1, 2030',
|
|
8
|
+
secret: 'synthetic_stored_secret_for_demo_only',
|
|
9
|
+
};
|
|
10
|
+
export const freshDisclosure: CredentialDisclosure = {
|
|
11
|
+
...storedDisclosure,
|
|
12
|
+
variant: 'fresh',
|
|
13
|
+
secret: 'synthetic_fresh_secret_for_demo_only',
|
|
14
|
+
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AlertDialog,
|
|
3
|
+
AlertDialogCancel,
|
|
4
|
+
AlertDialogContent,
|
|
5
|
+
AlertDialogDescription,
|
|
6
|
+
AlertDialogFooter,
|
|
7
|
+
AlertDialogHeader,
|
|
8
|
+
AlertDialogTitle,
|
|
9
|
+
Alert,
|
|
10
|
+
Button,
|
|
11
|
+
Field,
|
|
12
|
+
FieldLabel,
|
|
13
|
+
Input,
|
|
14
|
+
} from '@gntik-ai/ui';
|
|
15
|
+
import { useState, type ReactNode } from 'react';
|
|
16
|
+
|
|
17
|
+
export interface ConfirmDestructiveDialogProps {
|
|
18
|
+
open: boolean;
|
|
19
|
+
onOpenChange: (open: boolean) => void;
|
|
20
|
+
title: ReactNode;
|
|
21
|
+
description: ReactNode;
|
|
22
|
+
/** Text that must be typed exactly to enable the confirming button. */
|
|
23
|
+
confirmText?: string;
|
|
24
|
+
confirmLabel: string;
|
|
25
|
+
/** Runs on confirm; a returned promise shows a loading state and keeps the dialog open until it settles. */
|
|
26
|
+
onConfirm?: () => void | Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Destructive confirmation (alertdialog) that can require typing the resource name first. */
|
|
30
|
+
export function ConfirmDestructiveDialog({
|
|
31
|
+
open,
|
|
32
|
+
onOpenChange,
|
|
33
|
+
title,
|
|
34
|
+
description,
|
|
35
|
+
confirmText,
|
|
36
|
+
confirmLabel,
|
|
37
|
+
onConfirm,
|
|
38
|
+
}: ConfirmDestructiveDialogProps) {
|
|
39
|
+
const [typed, setTyped] = useState('');
|
|
40
|
+
const [loading, setLoading] = useState(false);
|
|
41
|
+
const [error, setError] = useState<string | null>(null);
|
|
42
|
+
const matches = confirmText == null || typed === confirmText;
|
|
43
|
+
|
|
44
|
+
const change = (next: boolean) => {
|
|
45
|
+
if (loading) return;
|
|
46
|
+
if (!next) {
|
|
47
|
+
setTyped('');
|
|
48
|
+
setError(null);
|
|
49
|
+
}
|
|
50
|
+
onOpenChange(next);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const confirm = async () => {
|
|
54
|
+
if (!matches) return;
|
|
55
|
+
setError(null);
|
|
56
|
+
setLoading(true);
|
|
57
|
+
try {
|
|
58
|
+
await onConfirm?.();
|
|
59
|
+
setLoading(false);
|
|
60
|
+
setTyped('');
|
|
61
|
+
onOpenChange(false);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
setLoading(false);
|
|
64
|
+
setError(e instanceof Error ? e.message : 'Something went wrong. Try again.');
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<AlertDialog open={open} onOpenChange={change}>
|
|
70
|
+
<AlertDialogContent size="md">
|
|
71
|
+
<AlertDialogHeader tone="destructive">
|
|
72
|
+
<AlertDialogTitle>{title}</AlertDialogTitle>
|
|
73
|
+
<AlertDialogDescription>{description}</AlertDialogDescription>
|
|
74
|
+
</AlertDialogHeader>
|
|
75
|
+
<form
|
|
76
|
+
className="mt-5 grid gap-4"
|
|
77
|
+
onSubmit={(e) => {
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
void confirm();
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{confirmText != null && (
|
|
83
|
+
<Field>
|
|
84
|
+
<FieldLabel>
|
|
85
|
+
Type <span className="font-mono font-semibold text-foreground">{confirmText}</span> to confirm
|
|
86
|
+
</FieldLabel>
|
|
87
|
+
<Input value={typed} onValueChange={setTyped} autoComplete="off" spellCheck={false} disabled={loading} />
|
|
88
|
+
</Field>
|
|
89
|
+
)}
|
|
90
|
+
{error && <Alert tone="destructive" description={error} />}
|
|
91
|
+
<AlertDialogFooter className="mt-1">
|
|
92
|
+
<AlertDialogCancel disabled={loading}>Cancel</AlertDialogCancel>
|
|
93
|
+
<Button type="submit" variant="destructive" disabled={!matches} loading={loading}>
|
|
94
|
+
{confirmLabel}
|
|
95
|
+
</Button>
|
|
96
|
+
</AlertDialogFooter>
|
|
97
|
+
</form>
|
|
98
|
+
</AlertDialogContent>
|
|
99
|
+
</AlertDialog>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expectNoAxeViolations } from '../../test/a11y';
|
|
4
|
+
import { DangerZone } from './DangerZone';
|
|
5
|
+
|
|
6
|
+
describe('DangerZone', () => {
|
|
7
|
+
it('renders the default actions', async () => {
|
|
8
|
+
const { container } = render(<DangerZone />);
|
|
9
|
+
expect(screen.getByRole('region', { name: 'Danger zone' })).toBeInTheDocument();
|
|
10
|
+
expect(screen.getByRole('button', { name: 'Delete project' })).toBeInTheDocument();
|
|
11
|
+
await expectNoAxeViolations(container);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('requires typing the name before confirming', async () => {
|
|
15
|
+
const onConfirm = vi.fn();
|
|
16
|
+
render(<DangerZone onConfirm={onConfirm} />);
|
|
17
|
+
await userEvent.click(screen.getByRole('button', { name: 'Delete project' }));
|
|
18
|
+
const dialog = await screen.findByRole('alertdialog');
|
|
19
|
+
const confirm = screen.getAllByRole('button', { name: 'Delete project' }).at(-1)!;
|
|
20
|
+
expect(confirm).toBeDisabled();
|
|
21
|
+
await expectNoAxeViolations(dialog);
|
|
22
|
+
await userEvent.type(screen.getByLabelText(/to confirm/), 'billing-dashboard');
|
|
23
|
+
expect(confirm).toBeEnabled();
|
|
24
|
+
await userEvent.click(confirm);
|
|
25
|
+
expect(onConfirm).toHaveBeenCalledWith('delete');
|
|
26
|
+
await waitFor(() => expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument());
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Button, cn } from '@gntik-ai/ui';
|
|
2
|
+
import { useId, useState, type ReactNode } from 'react';
|
|
3
|
+
import { ConfirmDestructiveDialog } from './ConfirmDestructiveDialog';
|
|
4
|
+
import { dangerZoneActions, type DangerZoneAction } from './fixtures';
|
|
5
|
+
|
|
6
|
+
export type { DangerZoneAction } from './fixtures';
|
|
7
|
+
|
|
8
|
+
export interface DangerZoneProps {
|
|
9
|
+
title?: ReactNode;
|
|
10
|
+
description?: ReactNode;
|
|
11
|
+
actions?: DangerZoneAction[];
|
|
12
|
+
/** Called after the user confirms an action. A returned promise shows a loading state. */
|
|
13
|
+
onConfirm?: (actionId: string) => void | Promise<void>;
|
|
14
|
+
headingLevel?: 'h2' | 'h3' | 'h4';
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Bordered destructive section. Each action opens a confirmation dialog; actions with
|
|
20
|
+
* `confirmText` require typing it (e.g. the project name) before the confirm button enables.
|
|
21
|
+
*/
|
|
22
|
+
export function DangerZone({
|
|
23
|
+
title = 'Danger zone',
|
|
24
|
+
description = 'These actions affect the whole project and every member who can access it.',
|
|
25
|
+
actions = dangerZoneActions,
|
|
26
|
+
onConfirm,
|
|
27
|
+
headingLevel: Heading = 'h2',
|
|
28
|
+
className,
|
|
29
|
+
}: DangerZoneProps) {
|
|
30
|
+
const id = useId();
|
|
31
|
+
const [pending, setPending] = useState<DangerZoneAction | null>(null);
|
|
32
|
+
const [open, setOpen] = useState(false);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<section
|
|
36
|
+
aria-labelledby={`${id}-title`}
|
|
37
|
+
className={cn('rounded-lg border border-destructive/35 bg-card shadow-sm', className)}
|
|
38
|
+
>
|
|
39
|
+
<div className="border-b border-destructive/20 px-6 py-4">
|
|
40
|
+
<Heading id={`${id}-title`} className="text-[15px] font-semibold tracking-tight text-destructive-text">
|
|
41
|
+
{title}
|
|
42
|
+
</Heading>
|
|
43
|
+
{description != null && (
|
|
44
|
+
<p className="mt-1 text-[13px] leading-6 text-pretty text-muted-foreground">{description}</p>
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
47
|
+
<ul className="divide-y divide-border">
|
|
48
|
+
{actions.map((action) => (
|
|
49
|
+
<li
|
|
50
|
+
key={action.id}
|
|
51
|
+
className="flex flex-col gap-3 px-6 py-4 sm:flex-row sm:items-center sm:justify-between sm:gap-6"
|
|
52
|
+
>
|
|
53
|
+
<div className="min-w-0">
|
|
54
|
+
<div className="text-[13.5px] font-semibold text-foreground">{action.title}</div>
|
|
55
|
+
<p className="mt-0.5 max-w-xl text-[12.5px] leading-5 text-pretty text-muted-foreground">
|
|
56
|
+
{action.description}
|
|
57
|
+
</p>
|
|
58
|
+
</div>
|
|
59
|
+
<Button
|
|
60
|
+
variant={action.destructive === false ? 'secondary' : 'destructive'}
|
|
61
|
+
className="shrink-0 self-start sm:self-auto"
|
|
62
|
+
onClick={() => {
|
|
63
|
+
setPending(action);
|
|
64
|
+
setOpen(true);
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{action.actionLabel}
|
|
68
|
+
</Button>
|
|
69
|
+
</li>
|
|
70
|
+
))}
|
|
71
|
+
</ul>
|
|
72
|
+
{pending && (
|
|
73
|
+
<ConfirmDestructiveDialog
|
|
74
|
+
key={pending.id}
|
|
75
|
+
open={open}
|
|
76
|
+
onOpenChange={setOpen}
|
|
77
|
+
title={`${pending.title}?`}
|
|
78
|
+
description={pending.description}
|
|
79
|
+
confirmText={pending.confirmText}
|
|
80
|
+
confirmLabel={pending.actionLabel}
|
|
81
|
+
onConfirm={() => onConfirm?.(pending.id)}
|
|
82
|
+
/>
|
|
83
|
+
)}
|
|
84
|
+
</section>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BlockMeta } from '../../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: BlockMeta = {
|
|
4
|
+
name: 'Danger zone',
|
|
5
|
+
family: 'forms',
|
|
6
|
+
status: 'stable',
|
|
7
|
+
description:
|
|
8
|
+
'Bordered destructive section whose actions open an alert dialog that can require typing the resource name.',
|
|
9
|
+
uses: ['Button', 'AlertDialog', 'Field', 'Input', 'Alert'],
|
|
10
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface DangerZoneAction {
|
|
2
|
+
id: string;
|
|
3
|
+
title: string;
|
|
4
|
+
description: string;
|
|
5
|
+
/** Label of the row button and of the confirming button. */
|
|
6
|
+
actionLabel: string;
|
|
7
|
+
/** Text the user must type to confirm (e.g. the resource name). Omit for a plain confirmation. */
|
|
8
|
+
confirmText?: string;
|
|
9
|
+
/** Use the destructive button style in the row (default true); `false` renders a secondary button. */
|
|
10
|
+
destructive?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const dangerZoneActions: DangerZoneAction[] = [
|
|
14
|
+
{
|
|
15
|
+
id: 'transfer',
|
|
16
|
+
title: 'Transfer project',
|
|
17
|
+
description: 'Move this project and its deployments to another workspace you administer.',
|
|
18
|
+
actionLabel: 'Transfer',
|
|
19
|
+
destructive: false,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'archive',
|
|
23
|
+
title: 'Archive project',
|
|
24
|
+
description: 'Stops every deployment and makes the project read-only. You can restore it later.',
|
|
25
|
+
actionLabel: 'Archive',
|
|
26
|
+
confirmText: 'billing-dashboard',
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: 'delete',
|
|
30
|
+
title: 'Delete project',
|
|
31
|
+
description:
|
|
32
|
+
'Permanently deletes billing-dashboard, its deployments, secrets and audit log. This cannot be undone.',
|
|
33
|
+
actionLabel: 'Delete project',
|
|
34
|
+
confirmText: 'billing-dashboard',
|
|
35
|
+
},
|
|
36
|
+
];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expectNoAxeViolations } from '../../test/a11y';
|
|
4
|
+
import { DateRangeFilter } from './DateRangeFilter';
|
|
5
|
+
|
|
6
|
+
const today = new Date(2026, 5, 15);
|
|
7
|
+
|
|
8
|
+
describe('DateRangeFilter', () => {
|
|
9
|
+
it('renders the last 7 days by default', async () => {
|
|
10
|
+
const { container } = render(<DateRangeFilter today={today} />);
|
|
11
|
+
const field = screen.getByRole('combobox', { name: 'Date range' });
|
|
12
|
+
expect((field as HTMLInputElement).value).toMatch(/Jun 9/);
|
|
13
|
+
await expectNoAxeViolations(container);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('applies a preset and clears', async () => {
|
|
17
|
+
const onValueChange = vi.fn();
|
|
18
|
+
render(<DateRangeFilter today={today} onValueChange={onValueChange} />);
|
|
19
|
+
await userEvent.click(screen.getByRole('combobox', { name: 'Date range' }));
|
|
20
|
+
await userEvent.click(await screen.findByRole('button', { name: 'Today' }));
|
|
21
|
+
expect(onValueChange).toHaveBeenLastCalledWith({ start: today, end: today });
|
|
22
|
+
await userEvent.click(screen.getByRole('button', { name: 'Clear date range' }));
|
|
23
|
+
expect(onValueChange).toHaveBeenLastCalledWith({ start: null, end: null });
|
|
24
|
+
expect(screen.getByRole('combobox', { name: 'Date range' })).toHaveValue('');
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DEFAULT_DATE_RANGE_PRESETS,
|
|
3
|
+
DateRangePicker,
|
|
4
|
+
IconButton,
|
|
5
|
+
cn,
|
|
6
|
+
type DateRange,
|
|
7
|
+
type DateRangePreset,
|
|
8
|
+
useI18n,
|
|
9
|
+
} from '@gntik-ai/ui';
|
|
10
|
+
import { X } from '@gntik-ai/icons';
|
|
11
|
+
import { useState } from 'react';
|
|
12
|
+
|
|
13
|
+
export interface DateRangeFilterProps {
|
|
14
|
+
value?: DateRange;
|
|
15
|
+
/** Initial range; defaults to the "Last 7 days" preset. Pass `{ start: null, end: null }` for none. */
|
|
16
|
+
defaultValue?: DateRange;
|
|
17
|
+
/** Called with a complete range, or an empty one when cleared. */
|
|
18
|
+
onValueChange?: (range: DateRange) => void;
|
|
19
|
+
presets?: DateRangePreset[];
|
|
20
|
+
/** "Today" for the presets (defaults to the current date). */
|
|
21
|
+
today?: Date;
|
|
22
|
+
/** Accessible name of the field. */
|
|
23
|
+
label?: string;
|
|
24
|
+
placeholder?: string;
|
|
25
|
+
/** Show a clear button while a range is set. */
|
|
26
|
+
clearable?: boolean;
|
|
27
|
+
disabled?: boolean;
|
|
28
|
+
className?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const EMPTY: DateRange = { start: null, end: null };
|
|
32
|
+
const SHORT: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric' };
|
|
33
|
+
|
|
34
|
+
/** Compact DateRangePicker for toolbars: small trigger, short dates, presets and a clear button. */
|
|
35
|
+
export function DateRangeFilter({
|
|
36
|
+
value,
|
|
37
|
+
defaultValue,
|
|
38
|
+
onValueChange,
|
|
39
|
+
presets = DEFAULT_DATE_RANGE_PRESETS,
|
|
40
|
+
today,
|
|
41
|
+
label: labelProp,
|
|
42
|
+
placeholder: placeholderProp,
|
|
43
|
+
clearable = true,
|
|
44
|
+
disabled,
|
|
45
|
+
className,
|
|
46
|
+
}: DateRangeFilterProps) {
|
|
47
|
+
const { t } = useI18n();
|
|
48
|
+
const label = labelProp ?? t('dateRange.label');
|
|
49
|
+
const placeholder = placeholderProp ?? t('dateRange.any');
|
|
50
|
+
const [inner, setInner] = useState<DateRange>(
|
|
51
|
+
() => defaultValue ?? presets.find((p) => p.id === 'last-7')?.range?.(today ?? new Date()) ?? EMPTY,
|
|
52
|
+
);
|
|
53
|
+
const range = value ?? inner;
|
|
54
|
+
const set = (next: DateRange) => {
|
|
55
|
+
setInner(next);
|
|
56
|
+
onValueChange?.(next);
|
|
57
|
+
};
|
|
58
|
+
return (
|
|
59
|
+
<div className={cn('inline-flex items-center gap-1', className)}>
|
|
60
|
+
<DateRangePicker
|
|
61
|
+
size="sm"
|
|
62
|
+
aria-label={label}
|
|
63
|
+
value={range}
|
|
64
|
+
onValueChange={set}
|
|
65
|
+
presets={presets}
|
|
66
|
+
today={today}
|
|
67
|
+
placeholder={placeholder}
|
|
68
|
+
formatOptions={SHORT}
|
|
69
|
+
disabled={disabled}
|
|
70
|
+
className="w-[13.5rem]"
|
|
71
|
+
/>
|
|
72
|
+
{clearable && range.start && (
|
|
73
|
+
<IconButton
|
|
74
|
+
icon={X}
|
|
75
|
+
size="sm"
|
|
76
|
+
label={t('common.clearItem', { label: label.toLowerCase() })}
|
|
77
|
+
disabled={disabled}
|
|
78
|
+
onClick={() => set(EMPTY)}
|
|
79
|
+
/>
|
|
80
|
+
)}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BlockMeta } from '../../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: BlockMeta = {
|
|
4
|
+
name: 'Date range filter',
|
|
5
|
+
family: 'forms',
|
|
6
|
+
status: 'stable',
|
|
7
|
+
description: 'Compact date range picker for toolbars: small trigger with short dates, presets and a clear button.',
|
|
8
|
+
uses: ['DateRangePicker', 'IconButton'],
|
|
9
|
+
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { render, screen } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { DescriptionListCard } from '../../data-display/DescriptionListCard/DescriptionListCard';
|
|
4
|
+
import { ValidationSummary } from '../ValidationSummary/ValidationSummary';
|
|
5
|
+
import { DialogWizard } from './DialogWizard';
|
|
6
|
+
import { expectNoAxeViolations } from '../../test/a11y';
|
|
7
|
+
|
|
8
|
+
describe('DialogWizard', () => {
|
|
9
|
+
it('uses consumer labels and shows validation and review slots with the shared step footer', async () => {
|
|
10
|
+
const user = userEvent.setup();
|
|
11
|
+
const props = {
|
|
12
|
+
open: true,
|
|
13
|
+
title: 'Provision database',
|
|
14
|
+
description: 'Configure storage',
|
|
15
|
+
steps: [{ label: 'Engine' }, { label: 'Check answers' }],
|
|
16
|
+
cancelLabel: 'Abandon',
|
|
17
|
+
backLabel: 'Previous',
|
|
18
|
+
nextLabel: 'Continue',
|
|
19
|
+
finishLabel: 'Provision',
|
|
20
|
+
exitLabel: 'Close flow',
|
|
21
|
+
stepsLabel: 'Stages',
|
|
22
|
+
stepsSummaryLabel: 'Stage count',
|
|
23
|
+
pendingLabel: 'Provisioning',
|
|
24
|
+
stepAnnouncement: ({ position }: { position: number }) => `Stage ${position}`,
|
|
25
|
+
};
|
|
26
|
+
const { rerender } = render(
|
|
27
|
+
<DialogWizard
|
|
28
|
+
{...props}
|
|
29
|
+
current={0}
|
|
30
|
+
nextDisabled
|
|
31
|
+
stepError={
|
|
32
|
+
<ValidationSummary title="Fix the engine" errors={[{ fieldId: 'engine', message: 'Choose an engine' }]} />
|
|
33
|
+
}
|
|
34
|
+
>
|
|
35
|
+
<input id="engine" aria-label="Engine choice" />
|
|
36
|
+
</DialogWizard>,
|
|
37
|
+
);
|
|
38
|
+
expect(await screen.findByRole('dialog', { name: 'Provision database' })).toHaveAccessibleDescription(
|
|
39
|
+
'Configure storage',
|
|
40
|
+
);
|
|
41
|
+
expect(screen.getByRole('button', { name: 'Continue' })).toBeDisabled();
|
|
42
|
+
expect(screen.getByText('Fix the engine')).toBeInTheDocument();
|
|
43
|
+
expect(screen.getByRole('button', { name: 'Abandon' })).toBeInTheDocument();
|
|
44
|
+
await user.click(screen.getByRole('link', { name: 'Choose an engine' }));
|
|
45
|
+
expect(screen.getByRole('textbox')).toHaveFocus();
|
|
46
|
+
const onFinish = vi.fn();
|
|
47
|
+
rerender(
|
|
48
|
+
<DialogWizard
|
|
49
|
+
{...props}
|
|
50
|
+
current={1}
|
|
51
|
+
onFinish={onFinish}
|
|
52
|
+
reviewStep={
|
|
53
|
+
<DescriptionListCard
|
|
54
|
+
title="Your answers"
|
|
55
|
+
description="Review storage"
|
|
56
|
+
items={[{ id: 'engine', label: 'Engine', value: 'Standard' }]}
|
|
57
|
+
/>
|
|
58
|
+
}
|
|
59
|
+
/>,
|
|
60
|
+
);
|
|
61
|
+
expect(screen.getByText('Your answers')).toBeInTheDocument();
|
|
62
|
+
expect(screen.getByRole('button', { name: 'Previous' })).toBeInTheDocument();
|
|
63
|
+
await user.click(screen.getByRole('button', { name: 'Provision' }));
|
|
64
|
+
expect(onFinish).toHaveBeenCalledTimes(1);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
for (const theme of ['dark', 'light', 'high_contrast']) {
|
|
68
|
+
for (const size of ['dialog', 'fullscreen'] as const) {
|
|
69
|
+
it.each([1, 2])(`has no axe violations in ${theme}, ${size}, step %s`, async (current) => {
|
|
70
|
+
document.documentElement.className = theme === 'light' ? '' : theme;
|
|
71
|
+
render(
|
|
72
|
+
<DialogWizard
|
|
73
|
+
open
|
|
74
|
+
title="Create resource"
|
|
75
|
+
description="Configure storage"
|
|
76
|
+
size={size}
|
|
77
|
+
steps={[{ label: 'Details' }, { label: 'Configuration' }, { label: 'Review' }]}
|
|
78
|
+
current={current}
|
|
79
|
+
reviewStep={
|
|
80
|
+
<DescriptionListCard
|
|
81
|
+
title="Answers"
|
|
82
|
+
description="Review your choices"
|
|
83
|
+
items={[{ id: 'name', label: 'Name', value: 'Resource' }]}
|
|
84
|
+
/>
|
|
85
|
+
}
|
|
86
|
+
>
|
|
87
|
+
<h2>Configuration</h2>
|
|
88
|
+
<input aria-label="Region" />
|
|
89
|
+
</DialogWizard>,
|
|
90
|
+
);
|
|
91
|
+
await expectNoAxeViolations(await screen.findByRole('dialog'));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useI18n, WizardLayout, type WizardLayoutProps } from '@gntik-ai/ui';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
export interface DialogWizardProps extends Omit<WizardLayoutProps, 'mode' | 'steps' | 'current'> {
|
|
5
|
+
steps?: WizardLayoutProps['steps'];
|
|
6
|
+
current?: number;
|
|
7
|
+
/** Step-local validation surface, normally a ValidationSummary with consumer copy. */
|
|
8
|
+
stepError?: ReactNode;
|
|
9
|
+
/** Replaces the last step body, normally a DescriptionListCard with consumer copy. */
|
|
10
|
+
reviewStep?: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** Modal variant of create-wizard for a flow over its parent page. State stays consumer-owned. */
|
|
14
|
+
export function DialogWizard({
|
|
15
|
+
steps,
|
|
16
|
+
current = 0,
|
|
17
|
+
size = 'dialog',
|
|
18
|
+
stepError,
|
|
19
|
+
reviewStep,
|
|
20
|
+
children,
|
|
21
|
+
title,
|
|
22
|
+
...props
|
|
23
|
+
}: DialogWizardProps) {
|
|
24
|
+
const { t } = useI18n();
|
|
25
|
+
const items = steps ?? [{ label: t('wizard.details') }, { label: t('wizard.review') }];
|
|
26
|
+
return (
|
|
27
|
+
<WizardLayout
|
|
28
|
+
{...props}
|
|
29
|
+
mode="overlay"
|
|
30
|
+
size={size}
|
|
31
|
+
steps={items}
|
|
32
|
+
current={current}
|
|
33
|
+
title={title ?? t('wizard.title')}
|
|
34
|
+
>
|
|
35
|
+
{stepError}
|
|
36
|
+
{current >= items.length - 1 && reviewStep != null ? reviewStep : children}
|
|
37
|
+
</WizardLayout>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { BlockMeta } from '../../meta';
|
|
2
|
+
|
|
3
|
+
export const meta: BlockMeta = {
|
|
4
|
+
name: 'Dialog wizard',
|
|
5
|
+
family: 'forms',
|
|
6
|
+
status: 'beta',
|
|
7
|
+
description:
|
|
8
|
+
'Modal variant of create-wizard for create flows over their parent page, without a route change. Defaults to dialog size (fullscreen on mobile); size="fullscreen" fills every viewport. Consumer-controlled step state survives closing. Use the create-wizard page template for a dedicated page flow, or WizardLayout mode="overlay" for a fullscreen flow. Supply all labels, pendingLabel and stepAnnouncement from your own i18n; stepError accepts ValidationSummary and reviewStep accepts DescriptionListCard.',
|
|
9
|
+
uses: ['WizardLayout', 'Dialog', 'Stepper', 'Button'],
|
|
10
|
+
};
|