@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,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BarChart3,
|
|
3
|
+
CreditCard,
|
|
4
|
+
FileText,
|
|
5
|
+
FolderKanban,
|
|
6
|
+
FolderPlus,
|
|
7
|
+
Home,
|
|
8
|
+
Rocket,
|
|
9
|
+
Settings,
|
|
10
|
+
UserPlus,
|
|
11
|
+
Users,
|
|
12
|
+
} from '@gntik-ai/icons';
|
|
13
|
+
import type { CommandItem } from '@gntik-ai/ui';
|
|
14
|
+
|
|
15
|
+
export const globalSearchPages: CommandItem[] = [
|
|
16
|
+
{ id: 'page-overview', label: 'Overview', icon: Home, shortcut: ['G', 'O'] },
|
|
17
|
+
{ id: 'page-projects', label: 'Projects', icon: FolderKanban, shortcut: ['G', 'P'] },
|
|
18
|
+
{ id: 'page-deployments', label: 'Deployments', icon: Rocket },
|
|
19
|
+
{ id: 'page-analytics', label: 'Analytics', icon: BarChart3 },
|
|
20
|
+
{ id: 'page-members', label: 'Members', icon: Users, keywords: ['team', 'people'] },
|
|
21
|
+
{ id: 'page-billing', label: 'Billing', icon: CreditCard, keywords: ['invoices', 'plan'] },
|
|
22
|
+
{ id: 'page-settings', label: 'Settings', icon: Settings, shortcut: ['⌘', ','] },
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export const globalSearchRecords: CommandItem[] = [
|
|
26
|
+
{
|
|
27
|
+
id: 'record-web-frontend',
|
|
28
|
+
label: 'web-frontend',
|
|
29
|
+
icon: FolderKanban,
|
|
30
|
+
mono: true,
|
|
31
|
+
description: 'Project · eu-west',
|
|
32
|
+
},
|
|
33
|
+
{ id: 'record-billing-api', label: 'billing-api', icon: FolderKanban, mono: true, description: 'Project · us-east' },
|
|
34
|
+
{
|
|
35
|
+
id: 'record-inv-2041',
|
|
36
|
+
label: 'INV-2041',
|
|
37
|
+
icon: FileText,
|
|
38
|
+
mono: true,
|
|
39
|
+
description: 'Invoice · Orbit Freight · overdue',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'record-dana',
|
|
43
|
+
label: 'Dana Whitfield',
|
|
44
|
+
icon: Users,
|
|
45
|
+
description: 'Member · Owner',
|
|
46
|
+
keywords: ['dana@example.com'],
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
export const globalSearchActions: CommandItem[] = [
|
|
51
|
+
{ id: 'action-new-project', label: 'Create project', icon: FolderPlus, shortcut: ['⌘', 'N'], keywords: ['new'] },
|
|
52
|
+
{
|
|
53
|
+
id: 'action-deploy',
|
|
54
|
+
label: 'Deploy to production',
|
|
55
|
+
icon: Rocket,
|
|
56
|
+
description: 'Promote the latest build of the current project',
|
|
57
|
+
},
|
|
58
|
+
{ id: 'action-invite', label: 'Invite member', icon: UserPlus, keywords: ['team', 'people'] },
|
|
59
|
+
];
|
|
60
|
+
|
|
61
|
+
export const globalSearchRecent: CommandItem[] = [
|
|
62
|
+
{ id: 'recent-web-frontend', label: 'web-frontend', icon: FolderKanban, mono: true, description: 'Project' },
|
|
63
|
+
{ id: 'recent-billing', label: 'Billing', icon: CreditCard, description: 'Page' },
|
|
64
|
+
];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export * from './AppSidebar/AppSidebar';
|
|
2
|
+
export * from './AppSidebar/fixtures';
|
|
3
|
+
export { meta as appSidebarMeta } from './AppSidebar/block.meta';
|
|
4
|
+
export * from './AppTopbar/AppTopbar';
|
|
5
|
+
export * from './AppTopbar/fixtures';
|
|
6
|
+
export { meta as appTopbarMeta } from './AppTopbar/block.meta';
|
|
7
|
+
export * from './EnvironmentBadge/EnvironmentBadge';
|
|
8
|
+
export { meta as environmentBadgeMeta } from './EnvironmentBadge/block.meta';
|
|
9
|
+
export * from './GlobalSearch/GlobalSearch';
|
|
10
|
+
export * from './GlobalSearch/fixtures';
|
|
11
|
+
export { meta as globalSearchMeta } from './GlobalSearch/block.meta';
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { render, screen, waitFor, within } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { expectNoAxeViolations } from '../../test/a11y';
|
|
4
|
+
import { DataTable } from './DataTable';
|
|
5
|
+
import type { DataTableColumn } from './data-table-utils';
|
|
6
|
+
import EditableDataTable from './examples/editable';
|
|
7
|
+
import SavedViewsDataTable from './examples/saved-views';
|
|
8
|
+
import { DEPLOYMENT_ROWS, type DeploymentRow } from './fixtures';
|
|
9
|
+
import { newViewId, sameViewState, type DataTableView } from './views';
|
|
10
|
+
|
|
11
|
+
const first = DEPLOYMENT_ROWS[0]!;
|
|
12
|
+
const second = DEPLOYMENT_ROWS[1]!;
|
|
13
|
+
const editColumns: Array<DataTableColumn<DeploymentRow>> = [
|
|
14
|
+
{ id: 'name', header: 'Deployment', accessor: (r) => r.name, editable: true },
|
|
15
|
+
{ id: 'region', header: 'Region', accessor: (r) => r.region },
|
|
16
|
+
{
|
|
17
|
+
id: 'cost',
|
|
18
|
+
header: 'Cost',
|
|
19
|
+
accessor: (r) => r.cost,
|
|
20
|
+
editable: (r) => r.status !== 'failed',
|
|
21
|
+
editor: { type: 'number', min: 0, max: 1000 },
|
|
22
|
+
},
|
|
23
|
+
];
|
|
24
|
+
const rows = DEPLOYMENT_ROWS.slice(0, 3);
|
|
25
|
+
const cell = (text: string) => screen.getByRole('button', { name: text });
|
|
26
|
+
|
|
27
|
+
describe('DataTable inline editing', () => {
|
|
28
|
+
it('edits with Enter, commits with Enter and returns focus', async () => {
|
|
29
|
+
const user = userEvent.setup();
|
|
30
|
+
const onCellEdit = vi.fn();
|
|
31
|
+
render(<DataTable rows={rows} columns={editColumns} paginated={false} onCellEdit={onCellEdit} />);
|
|
32
|
+
const trigger = cell(first.name);
|
|
33
|
+
expect(trigger).toHaveAccessibleDescription('Press Enter or F2 to edit.');
|
|
34
|
+
trigger.focus();
|
|
35
|
+
await user.keyboard('{Enter}');
|
|
36
|
+
const input = screen.getByRole('textbox', { name: `Deployment for ${first.name}` });
|
|
37
|
+
expect(input).toHaveFocus();
|
|
38
|
+
await user.clear(input);
|
|
39
|
+
await user.type(input, 'renamed{Enter}');
|
|
40
|
+
expect(onCellEdit).toHaveBeenCalledWith({
|
|
41
|
+
row: first,
|
|
42
|
+
rowId: first.id,
|
|
43
|
+
columnId: 'name',
|
|
44
|
+
value: 'renamed',
|
|
45
|
+
previous: first.name,
|
|
46
|
+
});
|
|
47
|
+
await waitFor(() => expect(cell(first.name)).toHaveFocus());
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('starts with F2 and cancels with Escape', async () => {
|
|
51
|
+
const user = userEvent.setup();
|
|
52
|
+
const onCellEdit = vi.fn();
|
|
53
|
+
render(<DataTable rows={rows} columns={editColumns} paginated={false} onCellEdit={onCellEdit} />);
|
|
54
|
+
cell(first.name).focus();
|
|
55
|
+
await user.keyboard('{F2}');
|
|
56
|
+
await user.keyboard('xyz{Escape}');
|
|
57
|
+
expect(onCellEdit).not.toHaveBeenCalled();
|
|
58
|
+
expect(screen.queryByRole('textbox')).toBeNull();
|
|
59
|
+
expect(cell(first.name)).toHaveFocus();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('commits with Tab and moves to the next editable cell', async () => {
|
|
63
|
+
const user = userEvent.setup();
|
|
64
|
+
const onCellEdit = vi.fn();
|
|
65
|
+
render(<DataTable rows={rows} columns={editColumns} paginated={false} onCellEdit={onCellEdit} />);
|
|
66
|
+
cell(first.name).focus();
|
|
67
|
+
await user.keyboard('{Enter}{End}-v2{Tab}');
|
|
68
|
+
expect(onCellEdit).toHaveBeenLastCalledWith(
|
|
69
|
+
expect.objectContaining({ columnId: 'name', value: `${first.name}-v2` }),
|
|
70
|
+
);
|
|
71
|
+
await waitFor(() => expect(cell(String(first.cost))).toHaveFocus());
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('shows a rejection inline and keeps editing', async () => {
|
|
75
|
+
const user = userEvent.setup();
|
|
76
|
+
const onCellEdit = vi.fn(async () => 'That name is taken.');
|
|
77
|
+
const { container } = render(
|
|
78
|
+
<DataTable rows={rows} columns={editColumns} paginated={false} onCellEdit={onCellEdit} />,
|
|
79
|
+
);
|
|
80
|
+
cell(first.name).focus();
|
|
81
|
+
await user.keyboard(`{Enter}{Control>}a{/Control}${second.name}{Enter}`);
|
|
82
|
+
const alert = await screen.findByRole('alert');
|
|
83
|
+
expect(alert).toHaveTextContent('That name is taken.');
|
|
84
|
+
const input = screen.getByRole('textbox', { name: `Deployment for ${first.name}` });
|
|
85
|
+
expect(input).toHaveAttribute('aria-invalid', 'true');
|
|
86
|
+
expect(input).toHaveAccessibleDescription('That name is taken.');
|
|
87
|
+
expect(input).toHaveFocus();
|
|
88
|
+
await expectNoAxeViolations(container);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it('reports thrown errors and validates numbers before calling onCellEdit', async () => {
|
|
92
|
+
const user = userEvent.setup();
|
|
93
|
+
const onCellEdit = vi.fn(() => {
|
|
94
|
+
throw new Error('Server unavailable');
|
|
95
|
+
});
|
|
96
|
+
render(<DataTable rows={rows} columns={editColumns} paginated={false} onCellEdit={onCellEdit} />);
|
|
97
|
+
cell(String(first.cost)).focus();
|
|
98
|
+
await user.keyboard('{Enter}');
|
|
99
|
+
const input = screen.getByRole('spinbutton', { name: `Cost for ${first.name}` });
|
|
100
|
+
await user.clear(input);
|
|
101
|
+
await user.type(input, '5000{Enter}');
|
|
102
|
+
expect(screen.getByRole('alert')).toHaveTextContent('Enter 1000 or less.');
|
|
103
|
+
expect(onCellEdit).not.toHaveBeenCalled();
|
|
104
|
+
await user.clear(input);
|
|
105
|
+
await user.type(input, '12{Enter}');
|
|
106
|
+
expect(onCellEdit).toHaveBeenLastCalledWith(expect.objectContaining({ value: 12 }));
|
|
107
|
+
expect(await screen.findByRole('alert')).toHaveTextContent('Server unavailable');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('edits with the select editor of the example and shows server rejections', async () => {
|
|
111
|
+
const user = userEvent.setup();
|
|
112
|
+
render(<EditableDataTable />);
|
|
113
|
+
const statusTrigger = within(screen.getAllByRole('row')[1]!).getAllByRole('button')[1]!;
|
|
114
|
+
statusTrigger.focus();
|
|
115
|
+
await user.keyboard('{Enter}');
|
|
116
|
+
const select = screen.getByRole('combobox', { name: `Status for ${first.name}` });
|
|
117
|
+
await user.selectOptions(select, 'paused');
|
|
118
|
+
await user.keyboard('{Enter}');
|
|
119
|
+
await waitFor(() => expect(within(screen.getAllByRole('row')[1]!).getByText('Paused')).toBeInTheDocument());
|
|
120
|
+
// Example: budgets above $1,000 are rejected by the simulated server.
|
|
121
|
+
const budget = within(screen.getAllByRole('row')[1]!).getAllByRole('button')[2]!;
|
|
122
|
+
budget.focus();
|
|
123
|
+
await user.keyboard('{Enter}{Control>}a{/Control}1500{Enter}');
|
|
124
|
+
expect(await screen.findByRole('alert')).toHaveTextContent('Budgets above $1,000 need approval.');
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('per-row editable functions leave other cells read-only', () => {
|
|
128
|
+
const failing = { ...first, id: 'x', status: 'failed' as const, cost: 77 };
|
|
129
|
+
render(<DataTable rows={[failing]} columns={editColumns} paginated={false} onCellEdit={() => undefined} />);
|
|
130
|
+
expect(screen.queryByRole('button', { name: '77' })).toBeNull();
|
|
131
|
+
expect(screen.getByText('77').closest('td')).toBeInTheDocument();
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe('DataTable saved views', () => {
|
|
136
|
+
const views: DataTableView[] = [
|
|
137
|
+
{
|
|
138
|
+
id: 'expensive',
|
|
139
|
+
name: 'Most expensive',
|
|
140
|
+
state: { sort: { columnId: 'cost', direction: 'descending' }, hiddenColumns: ['region', 'updated'] },
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
id: 'grouped',
|
|
144
|
+
name: 'By region',
|
|
145
|
+
state: { groupBy: 'region', pinnedColumns: { left: ['name'] }, filters: { q: 'api' } },
|
|
146
|
+
},
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
it('switches views from the menu', async () => {
|
|
150
|
+
const user = userEvent.setup();
|
|
151
|
+
const onActiveViewChange = vi.fn();
|
|
152
|
+
const onFiltersChange = vi.fn();
|
|
153
|
+
const onGroupByChange = vi.fn();
|
|
154
|
+
const { container } = render(
|
|
155
|
+
<DataTable
|
|
156
|
+
defaultViews={views}
|
|
157
|
+
onActiveViewChange={onActiveViewChange}
|
|
158
|
+
onFiltersChange={onFiltersChange}
|
|
159
|
+
onGroupByChange={onGroupByChange}
|
|
160
|
+
paginated={false}
|
|
161
|
+
/>,
|
|
162
|
+
);
|
|
163
|
+
await user.click(screen.getByRole('button', { name: 'Default view' }));
|
|
164
|
+
await user.click(await screen.findByRole('menuitemradio', { name: 'Most expensive' }));
|
|
165
|
+
expect(onActiveViewChange).toHaveBeenLastCalledWith('expensive');
|
|
166
|
+
expect(screen.getByRole('columnheader', { name: /Cost/ })).toHaveAttribute('aria-sort', 'descending');
|
|
167
|
+
expect(screen.queryByRole('columnheader', { name: /Region/ })).toBeNull();
|
|
168
|
+
await user.click(screen.getByRole('button', { name: 'Most expensive' }));
|
|
169
|
+
await user.click(await screen.findByRole('menuitemradio', { name: 'By region' }));
|
|
170
|
+
expect(onFiltersChange).toHaveBeenLastCalledWith({ q: 'api' });
|
|
171
|
+
expect(onGroupByChange).toHaveBeenLastCalledWith('region');
|
|
172
|
+
expect(screen.getAllByRole('rowheader')[0]).toHaveAttribute('scope', 'rowgroup');
|
|
173
|
+
expect(screen.getByRole('columnheader', { name: /Region/ })).toBeInTheDocument();
|
|
174
|
+
expect(screen.getByRole('columnheader', { name: /Deployment/ })).toHaveClass('sticky');
|
|
175
|
+
await expectNoAxeViolations(container);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('marks changes, resets and saves them to the active view', async () => {
|
|
179
|
+
const user = userEvent.setup();
|
|
180
|
+
const onViewsChange = vi.fn();
|
|
181
|
+
render(<DataTable defaultViews={views} defaultActiveViewId="expensive" onViewsChange={onViewsChange} />);
|
|
182
|
+
// Seeded from the active view.
|
|
183
|
+
expect(screen.getByRole('columnheader', { name: /Cost/ })).toHaveAttribute('aria-sort', 'descending');
|
|
184
|
+
await user.click(screen.getByRole('button', { name: /Status/ }));
|
|
185
|
+
const trigger = screen.getByRole('button', { name: 'Most expensive (modified)' });
|
|
186
|
+
await user.click(trigger);
|
|
187
|
+
await user.click(await screen.findByRole('menuitem', { name: 'Reset changes' }));
|
|
188
|
+
expect(screen.getByRole('columnheader', { name: /Cost/ })).toHaveAttribute('aria-sort', 'descending');
|
|
189
|
+
expect(screen.getByRole('button', { name: 'Most expensive' })).toBeInTheDocument();
|
|
190
|
+
await user.click(screen.getByRole('button', { name: /Status/ }));
|
|
191
|
+
await user.click(screen.getByRole('button', { name: 'Most expensive (modified)' }));
|
|
192
|
+
await user.click(await screen.findByRole('menuitem', { name: 'Save changes' }));
|
|
193
|
+
expect(onViewsChange.mock.lastCall?.[0][0].state.sort).toEqual({ columnId: 'status', direction: 'ascending' });
|
|
194
|
+
expect(screen.getByRole('button', { name: 'Most expensive' })).toBeInTheDocument();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('saves as a new view, renames and deletes it', async () => {
|
|
198
|
+
const user = userEvent.setup();
|
|
199
|
+
const onViewsChange = vi.fn();
|
|
200
|
+
render(<DataTable defaultViews={views} onViewsChange={onViewsChange} resizable />);
|
|
201
|
+
screen.getByRole('separator', { name: 'Resize Region column' }).focus();
|
|
202
|
+
await user.keyboard('{ArrowRight}');
|
|
203
|
+
await user.click(screen.getByRole('button', { name: 'Default view (modified)' }));
|
|
204
|
+
await user.click(await screen.findByRole('menuitem', { name: 'Save as new view…' }));
|
|
205
|
+
const dialog = await screen.findByRole('dialog', { name: 'Save view' });
|
|
206
|
+
await user.type(within(dialog).getByRole('textbox', { name: 'View name' }), 'Wide region{Enter}');
|
|
207
|
+
const saved = onViewsChange.mock.lastCall?.[0] as DataTableView[];
|
|
208
|
+
expect(saved).toHaveLength(3);
|
|
209
|
+
expect(saved[2]).toMatchObject({
|
|
210
|
+
id: 'wide-region',
|
|
211
|
+
name: 'Wide region',
|
|
212
|
+
state: { columnWidths: { region: 170 } },
|
|
213
|
+
});
|
|
214
|
+
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
|
|
215
|
+
await user.click(screen.getByRole('button', { name: 'Wide region' }));
|
|
216
|
+
await user.click(await screen.findByRole('menuitem', { name: 'Rename view…' }));
|
|
217
|
+
const rename = await screen.findByRole('dialog', { name: 'Rename view' });
|
|
218
|
+
const field = within(rename).getByRole('textbox', { name: 'View name' });
|
|
219
|
+
expect(field).toHaveValue('Wide region');
|
|
220
|
+
await user.clear(field);
|
|
221
|
+
await user.type(field, 'Regions{Enter}');
|
|
222
|
+
expect((onViewsChange.mock.lastCall?.[0] as DataTableView[])[2]?.name).toBe('Regions');
|
|
223
|
+
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
|
|
224
|
+
await user.click(screen.getByRole('button', { name: 'Regions' }));
|
|
225
|
+
await user.click(await screen.findByRole('menuitem', { name: 'Delete view' }));
|
|
226
|
+
expect(onViewsChange.mock.lastCall?.[0]).toHaveLength(2);
|
|
227
|
+
expect(screen.getByRole('button', { name: /^Default view/ })).toBeInTheDocument();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it('the example restores filters with a view', async () => {
|
|
231
|
+
const user = userEvent.setup();
|
|
232
|
+
render(<SavedViewsDataTable />);
|
|
233
|
+
await user.click(screen.getByRole('button', { name: 'Default view' }));
|
|
234
|
+
await user.click(await screen.findByRole('menuitemradio', { name: 'Needs attention' }));
|
|
235
|
+
const failedCount = DEPLOYMENT_ROWS.filter((r) => r.status === 'failed').length;
|
|
236
|
+
await waitFor(() =>
|
|
237
|
+
expect(within(screen.getAllByRole('rowgroup')[1]!).getAllByRole('row')).toHaveLength(failedCount),
|
|
238
|
+
);
|
|
239
|
+
await user.click(screen.getByRole('button', { name: 'Needs attention' }));
|
|
240
|
+
await user.click(await screen.findByRole('menuitemradio', { name: 'Default view' }));
|
|
241
|
+
await waitFor(() => expect(within(screen.getAllByRole('rowgroup')[1]!).getAllByRole('row')).toHaveLength(10));
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
it('view helpers', () => {
|
|
245
|
+
expect(sameViewState({ hiddenColumns: ['b', 'a'] }, { hiddenColumns: ['a', 'b'], sort: null })).toBe(true);
|
|
246
|
+
expect(sameViewState({ columnWidths: { a: 1 } }, {})).toBe(false);
|
|
247
|
+
expect(newViewId('My view!', [{ id: 'my-view', name: 'x', state: {} }])).toBe('my-view-2');
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import { act, render, screen, waitFor } from '@testing-library/react';
|
|
2
|
+
import userEvent from '@testing-library/user-event';
|
|
3
|
+
import { I18nProvider } from '@gntik-ai/ui';
|
|
4
|
+
import { DataTable } from './DataTable';
|
|
5
|
+
import type { DataTableLabels } from './labels';
|
|
6
|
+
|
|
7
|
+
const rows = [
|
|
8
|
+
{ id: 'a', name: 'Alpha', region: 'West' },
|
|
9
|
+
{ id: 'b', name: 'Beta', region: 'West' },
|
|
10
|
+
];
|
|
11
|
+
const columns = [
|
|
12
|
+
{ id: 'name', header: 'Name', accessor: (r: (typeof rows)[number]) => r.name, width: 200 },
|
|
13
|
+
{ id: 'region', header: 'Region', accessor: (r: (typeof rows)[number]) => r.region, width: 160 },
|
|
14
|
+
];
|
|
15
|
+
const labels = {
|
|
16
|
+
scrollRegion: 'Tabla con desplazamiento',
|
|
17
|
+
columns: 'Columnas',
|
|
18
|
+
visibleColumns: 'Columnas visibles',
|
|
19
|
+
pinColumns: 'Fijar columnas',
|
|
20
|
+
pinnedLeft: 'Izquierda',
|
|
21
|
+
pinnedRight: 'Derecha',
|
|
22
|
+
pinLeft: 'Fijar izquierda',
|
|
23
|
+
pinRight: 'Fijar derecha',
|
|
24
|
+
unpinned: 'Sin fijar',
|
|
25
|
+
density: 'Densidad',
|
|
26
|
+
comfortable: 'Cómoda',
|
|
27
|
+
compact: 'Compacta',
|
|
28
|
+
defaultView: 'Vista predeterminada',
|
|
29
|
+
defaultViewOption: 'Sin vista',
|
|
30
|
+
modified: '(modificada)',
|
|
31
|
+
views: 'Vistas',
|
|
32
|
+
saveChanges: 'Guardar cambios',
|
|
33
|
+
saveAs: 'Guardar como…',
|
|
34
|
+
renameView: 'Renombrar vista…',
|
|
35
|
+
deleteView: 'Eliminar vista',
|
|
36
|
+
resetChanges: 'Restablecer cambios',
|
|
37
|
+
saveViewTitle: 'Nueva vista',
|
|
38
|
+
renameViewTitle: 'Cambiar nombre',
|
|
39
|
+
viewName: 'Nombre de vista',
|
|
40
|
+
cancel: 'Cancelar',
|
|
41
|
+
saveView: 'Guardar vista',
|
|
42
|
+
rename: 'Renombrar',
|
|
43
|
+
closeDialog: 'Cerrar diálogo',
|
|
44
|
+
selectionCount: vi.fn((n: number) => `${n} seleccionadas`),
|
|
45
|
+
selectAll: 'Seleccionar página',
|
|
46
|
+
selection: 'Selección',
|
|
47
|
+
actions: 'Acciones',
|
|
48
|
+
selectRow: vi.fn((name: string) => `Seleccionar ${name}`),
|
|
49
|
+
rowActions: vi.fn((name: string) => `Acciones de ${name}`),
|
|
50
|
+
resizeColumn: vi.fn((name: string) => `Cambiar ancho de ${name}`),
|
|
51
|
+
columnWidth: vi.fn((width: number) => `${width} píxeles`),
|
|
52
|
+
} satisfies Partial<DataTableLabels>;
|
|
53
|
+
|
|
54
|
+
it('replaces table menus, selection announcements, resize names and saved-view dialogs', async () => {
|
|
55
|
+
const user = userEvent.setup();
|
|
56
|
+
const { container } = render(
|
|
57
|
+
<DataTable
|
|
58
|
+
rows={rows}
|
|
59
|
+
columns={columns}
|
|
60
|
+
caption="Items"
|
|
61
|
+
paginated={false}
|
|
62
|
+
resizable
|
|
63
|
+
showPinMenu
|
|
64
|
+
defaultViews={[]}
|
|
65
|
+
labels={labels}
|
|
66
|
+
rowActions={() => [{ label: 'Inspect', onClick: () => {} }]}
|
|
67
|
+
/>,
|
|
68
|
+
);
|
|
69
|
+
expect(screen.getByRole('group', { name: 'Densidad' })).toBeVisible();
|
|
70
|
+
await user.click(screen.getByRole('button', { name: 'Compacta' }));
|
|
71
|
+
await user.click(screen.getByRole('button', { name: 'Cómoda' }));
|
|
72
|
+
expect(screen.getByRole('checkbox', { name: 'Seleccionar página' })).toBeVisible();
|
|
73
|
+
await user.click(screen.getByRole('checkbox', { name: 'Seleccionar Alpha' }));
|
|
74
|
+
expect(container.querySelector('[aria-live]')?.textContent).toBe('1 seleccionadas');
|
|
75
|
+
expect(labels.selectionCount).toHaveBeenLastCalledWith(1);
|
|
76
|
+
expect(labels.selectRow).toHaveBeenCalledWith('Alpha');
|
|
77
|
+
expect(screen.getByRole('button', { name: 'Acciones de Alpha' })).toBeVisible();
|
|
78
|
+
const handle = screen.getByRole('separator', { name: 'Cambiar ancho de Name' });
|
|
79
|
+
expect(handle).toHaveAttribute('aria-valuetext', '200 píxeles');
|
|
80
|
+
expect(labels.resizeColumn).toHaveBeenCalledWith('Name');
|
|
81
|
+
expect(labels.columnWidth).toHaveBeenCalledWith(200);
|
|
82
|
+
await user.click(screen.getByRole('button', { name: 'Columnas' }));
|
|
83
|
+
expect(await screen.findByText('Columnas visibles')).toBeVisible();
|
|
84
|
+
expect(screen.getByText('Fijar columnas')).toBeVisible();
|
|
85
|
+
await user.click(screen.getByRole('menuitemcheckbox', { name: 'Region' }));
|
|
86
|
+
await user.keyboard('{Escape}');
|
|
87
|
+
await user.click(screen.getByRole('button', { name: 'Vista predeterminada (modificada)' }));
|
|
88
|
+
expect(await screen.findByText('Vistas', { exact: true })).toBeVisible();
|
|
89
|
+
expect(screen.getByRole('menuitemradio', { name: 'Sin vista' })).toBeVisible();
|
|
90
|
+
for (const name of ['Guardar cambios', 'Renombrar vista…', 'Eliminar vista', 'Restablecer cambios'])
|
|
91
|
+
expect(screen.getByRole('menuitem', { name })).toBeVisible();
|
|
92
|
+
await user.click(screen.getByRole('menuitem', { name: 'Guardar como…' }));
|
|
93
|
+
expect(await screen.findByRole('dialog', { name: 'Nueva vista' })).toBeVisible();
|
|
94
|
+
expect(screen.getByRole('button', { name: 'Cerrar diálogo' })).toBeVisible();
|
|
95
|
+
await user.type(screen.getByRole('textbox', { name: 'Nombre de vista' }), 'My view');
|
|
96
|
+
await user.click(screen.getByRole('button', { name: 'Guardar vista' }));
|
|
97
|
+
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
|
|
98
|
+
await user.click(screen.getByRole('button', { name: 'My view' }));
|
|
99
|
+
await user.click(await screen.findByRole('menuitem', { name: 'Renombrar vista…' }));
|
|
100
|
+
expect(await screen.findByRole('dialog', { name: 'Cambiar nombre' })).toBeVisible();
|
|
101
|
+
expect(screen.getByRole('button', { name: 'Renombrar' })).toBeVisible();
|
|
102
|
+
await user.click(screen.getByRole('button', { name: 'Cancelar' }));
|
|
103
|
+
await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('replaces group counts, ungrouped names, empty and loading states, and the nested footer', async () => {
|
|
107
|
+
const selectGroup = vi.fn((name: string) => `Todo en ${name}`);
|
|
108
|
+
const groupRows = vi.fn((count: number) => `filas: ${count}`);
|
|
109
|
+
const loading = vi.fn((caption: string) => `Cargando ${caption}`);
|
|
110
|
+
const extra: Partial<DataTableLabels> = {
|
|
111
|
+
selectGroup,
|
|
112
|
+
groupRows,
|
|
113
|
+
noGroup: 'Sin grupo',
|
|
114
|
+
emptyTitle: 'Sin resultados',
|
|
115
|
+
emptyDescription: 'Cambie los filtros',
|
|
116
|
+
loading,
|
|
117
|
+
rowsPerPage: 'Filas',
|
|
118
|
+
rowsPerPageLabel: 'Por página',
|
|
119
|
+
rangeSummary: (a, b, n) => `${a}/${b}/${n}`,
|
|
120
|
+
paginationNav: (noun) => `Páginas de ${noun}`,
|
|
121
|
+
previous: 'Atrás',
|
|
122
|
+
next: 'Adelante',
|
|
123
|
+
page: (n) => `Ir a ${n}`,
|
|
124
|
+
};
|
|
125
|
+
const { rerender } = render(
|
|
126
|
+
<DataTable rows={rows} columns={columns} groupBy={() => ''} labels={extra} caption="Items" />,
|
|
127
|
+
);
|
|
128
|
+
expect(screen.getByRole('checkbox', { name: 'Todo en Sin grupo' })).toBeVisible();
|
|
129
|
+
expect(screen.getByRole('button', { name: 'Sin grupo 2 filas: 2' })).toBeVisible();
|
|
130
|
+
expect(selectGroup).toHaveBeenCalledWith('Sin grupo');
|
|
131
|
+
expect(groupRows).toHaveBeenCalledWith(2);
|
|
132
|
+
expect(screen.getByRole('navigation', { name: 'Páginas de Items' })).toBeVisible();
|
|
133
|
+
expect(screen.getByRole('combobox', { name: 'Por página' })).toBeVisible();
|
|
134
|
+
expect(screen.getByText('1/2/2')).toBeVisible();
|
|
135
|
+
rerender(<DataTable rows={[]} columns={columns} labels={extra} caption="Items" />);
|
|
136
|
+
expect(screen.getByText('Sin resultados')).toBeVisible();
|
|
137
|
+
expect(screen.getByText('Cambie los filtros')).toBeVisible();
|
|
138
|
+
rerender(<DataTable rows={rows} columns={columns} labels={extra} caption="Items" loading />);
|
|
139
|
+
expect(screen.getByRole('status').textContent).toBe('Cargando Items');
|
|
140
|
+
expect(loading).toHaveBeenCalledWith('Items');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('replaces built-in inline editor names, hints and validation errors', async () => {
|
|
144
|
+
const user = userEvent.setup();
|
|
145
|
+
const editLabel = vi.fn((column: string, row: string) => `${column} de ${row}`);
|
|
146
|
+
const minimumValue = vi.fn((n: number) => `Mínimo ${n}`);
|
|
147
|
+
const maximumValue = vi.fn((n: number) => `Máximo ${n}`);
|
|
148
|
+
const editable = [
|
|
149
|
+
{
|
|
150
|
+
id: 'name',
|
|
151
|
+
header: 'Name',
|
|
152
|
+
accessor: (r: (typeof rows)[number]) => r.name,
|
|
153
|
+
editable: true,
|
|
154
|
+
editor: { type: 'number' as const, min: 1, max: 10 },
|
|
155
|
+
},
|
|
156
|
+
];
|
|
157
|
+
render(
|
|
158
|
+
<DataTable
|
|
159
|
+
rows={rows}
|
|
160
|
+
columns={editable}
|
|
161
|
+
paginated={false}
|
|
162
|
+
onCellEdit={() => false}
|
|
163
|
+
labels={{
|
|
164
|
+
editHint: 'Pulse Intro para editar',
|
|
165
|
+
editLabel,
|
|
166
|
+
editError: 'No se guardó',
|
|
167
|
+
enterNumber: 'Escriba un número',
|
|
168
|
+
minimumValue,
|
|
169
|
+
maximumValue,
|
|
170
|
+
}}
|
|
171
|
+
/>,
|
|
172
|
+
);
|
|
173
|
+
expect(screen.getByRole('button', { name: 'Alpha' })).toHaveAccessibleDescription('Pulse Intro para editar');
|
|
174
|
+
await user.click(screen.getByRole('button', { name: 'Alpha' }));
|
|
175
|
+
const input = screen.getByRole('spinbutton', { name: 'Name de Alpha' });
|
|
176
|
+
expect(editLabel).toHaveBeenCalledWith('Name', 'Alpha');
|
|
177
|
+
await user.clear(input);
|
|
178
|
+
await user.type(input, '0{Enter}');
|
|
179
|
+
expect(await screen.findByRole('alert')).toHaveTextContent('Mínimo 1');
|
|
180
|
+
expect(minimumValue).toHaveBeenCalledWith(1);
|
|
181
|
+
await user.clear(input);
|
|
182
|
+
await user.type(input, '11{Enter}');
|
|
183
|
+
expect(screen.getByRole('alert')).toHaveTextContent('Máximo 10');
|
|
184
|
+
expect(maximumValue).toHaveBeenCalledWith(10);
|
|
185
|
+
await user.clear(input);
|
|
186
|
+
await user.type(input, '5{Enter}');
|
|
187
|
+
expect(await screen.findByText('No se guardó')).toBeVisible();
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('keeps provider translations and legacy literals under a partial override', () => {
|
|
191
|
+
const { container, rerender } = render(
|
|
192
|
+
<I18nProvider locale="es">
|
|
193
|
+
<DataTable rows={rows} columns={columns} paginated={false} defaultViews={[]} defaultSelectedIds={['a']} />
|
|
194
|
+
</I18nProvider>,
|
|
195
|
+
);
|
|
196
|
+
const table = container.querySelector('table')?.outerHTML;
|
|
197
|
+
const groupName = screen.getByRole('group').getAttribute('aria-label');
|
|
198
|
+
rerender(
|
|
199
|
+
<I18nProvider locale="es">
|
|
200
|
+
<DataTable
|
|
201
|
+
rows={rows}
|
|
202
|
+
columns={columns}
|
|
203
|
+
paginated={false}
|
|
204
|
+
defaultViews={[]}
|
|
205
|
+
defaultSelectedIds={['a']}
|
|
206
|
+
labels={{ defaultView: 'Custom view' }}
|
|
207
|
+
/>
|
|
208
|
+
</I18nProvider>,
|
|
209
|
+
);
|
|
210
|
+
expect(container.querySelector('table')?.outerHTML).toBe(table);
|
|
211
|
+
expect(screen.getByRole('group')).toHaveAttribute('aria-label', groupName);
|
|
212
|
+
expect(screen.getByRole('button', { name: 'Custom view' })).toBeVisible();
|
|
213
|
+
expect(screen.getByText('1 selected')).toBeVisible();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it('retains the published text-node boundaries for default selection and loading summaries', () => {
|
|
217
|
+
const { container, rerender } = render(
|
|
218
|
+
<DataTable rows={rows} columns={columns} defaultSelectedIds={['a']} paginated={false} caption="Items" />,
|
|
219
|
+
);
|
|
220
|
+
expect([...container.querySelector('[aria-live]')!.childNodes].map((node) => node.textContent)).toEqual([
|
|
221
|
+
'1',
|
|
222
|
+
' selected',
|
|
223
|
+
]);
|
|
224
|
+
rerender(<DataTable rows={rows} columns={columns} loading caption="Items" />);
|
|
225
|
+
expect([...screen.getByRole('status').childNodes].map((node) => node.textContent)).toEqual([
|
|
226
|
+
'Loading ',
|
|
227
|
+
'items',
|
|
228
|
+
'…',
|
|
229
|
+
]);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it('replaces every pin option and the single-selection heading without translating caller radio names', async () => {
|
|
233
|
+
const user = userEvent.setup({ pointerEventsCheck: 0 });
|
|
234
|
+
const full: DataTableLabels = {
|
|
235
|
+
...labels,
|
|
236
|
+
selectGroup: (group) => `Grupo ${group}`,
|
|
237
|
+
groupRows: (count) => `${count} filas`,
|
|
238
|
+
noGroup: 'Sin grupo',
|
|
239
|
+
emptyTitle: 'Vacío',
|
|
240
|
+
emptyDescription: 'Sin coincidencias',
|
|
241
|
+
loading: (caption) => `Cargando ${caption}`,
|
|
242
|
+
editHint: 'Editar',
|
|
243
|
+
editLabel: (column, row) => `${column} de ${row}`,
|
|
244
|
+
editError: 'Error al guardar',
|
|
245
|
+
enterNumber: 'Número requerido',
|
|
246
|
+
minimumValue: (min) => `Mínimo ${min}`,
|
|
247
|
+
maximumValue: (max) => `Máximo ${max}`,
|
|
248
|
+
rowsPerPage: 'Filas por página',
|
|
249
|
+
rowsPerPageLabel: 'Elegir tamaño',
|
|
250
|
+
rangeSummary: (from, to, total) => `${from}|${to}|${total}`,
|
|
251
|
+
paginationNav: (noun) => `Páginas de ${noun}`,
|
|
252
|
+
previous: 'Anterior',
|
|
253
|
+
next: 'Siguiente',
|
|
254
|
+
page: (n) => `Página ${n}`,
|
|
255
|
+
};
|
|
256
|
+
const { rerender } = render(
|
|
257
|
+
<I18nProvider locale="es">
|
|
258
|
+
<DataTable rows={rows} columns={columns} paginated={false} showPinMenu labels={full} />
|
|
259
|
+
</I18nProvider>,
|
|
260
|
+
);
|
|
261
|
+
await user.click(screen.getByRole('button', { name: 'Columnas' }));
|
|
262
|
+
const submenu = await screen.findByRole('menuitem', { name: 'Name' });
|
|
263
|
+
act(() => submenu.focus());
|
|
264
|
+
await user.keyboard('{ArrowRight}');
|
|
265
|
+
for (const name of ['Fijar izquierda', 'Fijar derecha', 'Sin fijar'])
|
|
266
|
+
expect(await screen.findByRole('menuitemradio', { name })).toBeVisible();
|
|
267
|
+
await user.click(screen.getByRole('menuitemradio', { name: 'Fijar izquierda' }));
|
|
268
|
+
await waitFor(() => expect(screen.queryByRole('menu')).toBeNull());
|
|
269
|
+
await waitFor(() => expect(document.querySelector('[data-base-ui-focus-guard]')).toBeNull());
|
|
270
|
+
await user.click(screen.getByRole('button', { name: 'Columnas' }));
|
|
271
|
+
await screen.findByRole('menu');
|
|
272
|
+
expect(await screen.findByRole('menuitem', { name: /Name.*Izquierda/ })).toHaveAccessibleName('NameIzquierda');
|
|
273
|
+
act(() => screen.getByRole('menuitem', { name: /Name.*Izquierda/ }).focus());
|
|
274
|
+
await user.keyboard('{ArrowRight}');
|
|
275
|
+
await user.click(await screen.findByRole('menuitemradio', { name: 'Fijar derecha' }));
|
|
276
|
+
await waitFor(() => expect(screen.queryByRole('menu')).toBeNull());
|
|
277
|
+
await waitFor(() => expect(document.querySelector('[data-base-ui-focus-guard]')).toBeNull());
|
|
278
|
+
await user.click(screen.getByRole('button', { name: 'Columnas' }));
|
|
279
|
+
expect(await screen.findByRole('menuitem', { name: /Name.*Derecha/ })).toBeVisible();
|
|
280
|
+
await user.keyboard('{Escape}');
|
|
281
|
+
await waitFor(() => expect(screen.queryByRole('menu')).toBeNull());
|
|
282
|
+
await waitFor(() => expect(document.querySelector('[data-base-ui-focus-guard]')).toBeNull());
|
|
283
|
+
rerender(
|
|
284
|
+
<I18nProvider locale="es">
|
|
285
|
+
<DataTable rows={rows} columns={columns} labels={full} selectionMode="single" legend="Items" />
|
|
286
|
+
</I18nProvider>,
|
|
287
|
+
);
|
|
288
|
+
expect(screen.getByRole('columnheader', { name: 'Selección' })).toBeInTheDocument();
|
|
289
|
+
expect(screen.getByRole('radio', { name: 'Alpha' })).toBeVisible();
|
|
290
|
+
expect(screen.queryByRole('radio', { name: 'Seleccionar Alpha' })).toBeNull();
|
|
291
|
+
});
|