@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,90 @@
|
|
|
1
|
+
import { ArrowDown, ArrowUp, ChevronsUpDown } from '@gntik-ai/icons';
|
|
2
|
+
import { cn, nextSortDirection, TableHead, tableVariants, type SortDirection } from '@gntik-ai/ui';
|
|
3
|
+
import { stickyTint, type CellLayout } from './column-sizing';
|
|
4
|
+
import { columnAlign, type DataTableColumn } from './data-table-utils';
|
|
5
|
+
import type { DataTableLabels } from './labels';
|
|
6
|
+
import { ColumnResizeHandle } from './resize-handle';
|
|
7
|
+
|
|
8
|
+
export interface DataTableHeadCellProps<T> {
|
|
9
|
+
labels?: Partial<DataTableLabels>;
|
|
10
|
+
column: DataTableColumn<T>;
|
|
11
|
+
sortDirection: SortDirection;
|
|
12
|
+
onSortChange: (direction: Exclude<SortDirection, 'none'>) => void;
|
|
13
|
+
layout?: CellLayout;
|
|
14
|
+
/** Resize handle props; omitted, the column is not resizable. */
|
|
15
|
+
resize?: { width: number; min: number; max: number; onResize: (width: number) => void };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Column header of the DataTable. Without a resize handle it is the kit TableHead; with one, the
|
|
20
|
+
* sort button and the handle sit side by side (the handle never nests inside the button).
|
|
21
|
+
*/
|
|
22
|
+
export function DataTableHeadCell<T>({
|
|
23
|
+
column,
|
|
24
|
+
sortDirection,
|
|
25
|
+
onSortChange,
|
|
26
|
+
layout,
|
|
27
|
+
resize,
|
|
28
|
+
labels,
|
|
29
|
+
}: DataTableHeadCellProps<T>) {
|
|
30
|
+
const align = columnAlign(column);
|
|
31
|
+
const pinnedClass = layout?.pinned
|
|
32
|
+
? cn(layout.className, 'z-[11]', stickyTint(layout, { head: true }), 'bg-card')
|
|
33
|
+
: undefined;
|
|
34
|
+
if (!resize) {
|
|
35
|
+
return (
|
|
36
|
+
<TableHead
|
|
37
|
+
align={align}
|
|
38
|
+
className={cn(column.headClassName, pinnedClass)}
|
|
39
|
+
style={layout?.style}
|
|
40
|
+
sortable={column.sortable}
|
|
41
|
+
sortDirection={sortDirection}
|
|
42
|
+
onSortChange={onSortChange}
|
|
43
|
+
data-column-id={column.id}
|
|
44
|
+
>
|
|
45
|
+
{column.header}
|
|
46
|
+
</TableHead>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const v = tableVariants({ align: align ?? 'left', active: sortDirection !== 'none' });
|
|
50
|
+
const SortIcon =
|
|
51
|
+
sortDirection === 'ascending' ? ArrowUp : sortDirection === 'descending' ? ArrowDown : ChevronsUpDown;
|
|
52
|
+
return (
|
|
53
|
+
<TableHead
|
|
54
|
+
align={align}
|
|
55
|
+
className={cn('relative overflow-visible', column.headClassName, pinnedClass)}
|
|
56
|
+
style={layout?.style}
|
|
57
|
+
aria-sort={column.sortable ? sortDirection : undefined}
|
|
58
|
+
data-column-id={column.id}
|
|
59
|
+
>
|
|
60
|
+
<span
|
|
61
|
+
className={cn(
|
|
62
|
+
'block min-w-0 truncate pe-2',
|
|
63
|
+
align === 'right' && 'text-end',
|
|
64
|
+
align === 'center' && 'text-center',
|
|
65
|
+
)}
|
|
66
|
+
>
|
|
67
|
+
{column.sortable ? (
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
className={cn(v.sortButton(), 'max-w-full')}
|
|
71
|
+
onClick={() => onSortChange(nextSortDirection(sortDirection))}
|
|
72
|
+
>
|
|
73
|
+
<span className="truncate">{column.header}</span>
|
|
74
|
+
<SortIcon size={13} aria-hidden className={sortDirection === 'none' ? 'opacity-40' : undefined} />
|
|
75
|
+
</button>
|
|
76
|
+
) : (
|
|
77
|
+
column.header
|
|
78
|
+
)}
|
|
79
|
+
</span>
|
|
80
|
+
<ColumnResizeHandle
|
|
81
|
+
labels={labels}
|
|
82
|
+
label={column.header}
|
|
83
|
+
width={resize.width}
|
|
84
|
+
min={resize.min}
|
|
85
|
+
max={resize.max}
|
|
86
|
+
onResize={resize.onResize}
|
|
87
|
+
/>
|
|
88
|
+
</TableHead>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { PaginationFooterLabels } from '../PaginationFooter/PaginationFooter';
|
|
2
|
+
|
|
3
|
+
/** Per-instance table chrome, including the composed PaginationFooter. Caller data stays caller-owned. */
|
|
4
|
+
export interface DataTableLabels extends PaginationFooterLabels {
|
|
5
|
+
/** Non-virtual table overflow region; virtualized regions keep the caller caption. */
|
|
6
|
+
scrollRegion: string;
|
|
7
|
+
selectionCount: (count: number) => string;
|
|
8
|
+
columns: string;
|
|
9
|
+
visibleColumns: string;
|
|
10
|
+
pinColumns: string;
|
|
11
|
+
pinnedLeft: string;
|
|
12
|
+
pinnedRight: string;
|
|
13
|
+
pinLeft: string;
|
|
14
|
+
pinRight: string;
|
|
15
|
+
unpinned: string;
|
|
16
|
+
density: string;
|
|
17
|
+
comfortable: string;
|
|
18
|
+
compact: string;
|
|
19
|
+
resizeColumn: (label: string) => string;
|
|
20
|
+
columnWidth: (width: number) => string;
|
|
21
|
+
defaultView: string;
|
|
22
|
+
defaultViewOption: string;
|
|
23
|
+
modified: string;
|
|
24
|
+
views: string;
|
|
25
|
+
saveChanges: string;
|
|
26
|
+
saveAs: string;
|
|
27
|
+
renameView: string;
|
|
28
|
+
deleteView: string;
|
|
29
|
+
resetChanges: string;
|
|
30
|
+
saveViewTitle: string;
|
|
31
|
+
renameViewTitle: string;
|
|
32
|
+
viewName: string;
|
|
33
|
+
cancel: string;
|
|
34
|
+
saveView: string;
|
|
35
|
+
rename: string;
|
|
36
|
+
closeDialog: string;
|
|
37
|
+
selection: string;
|
|
38
|
+
selectAll: string;
|
|
39
|
+
selectRow: (label: string) => string;
|
|
40
|
+
selectGroup: (group: string) => string;
|
|
41
|
+
groupRows: (count: number) => string;
|
|
42
|
+
noGroup: string;
|
|
43
|
+
actions: string;
|
|
44
|
+
rowActions: (label: string) => string;
|
|
45
|
+
emptyTitle: string;
|
|
46
|
+
emptyDescription: string;
|
|
47
|
+
loading: (caption: string) => string;
|
|
48
|
+
editHint: string;
|
|
49
|
+
editLabel: (column: string, row: string) => string;
|
|
50
|
+
editError: string;
|
|
51
|
+
enterNumber: string;
|
|
52
|
+
minimumValue: (min: number) => string;
|
|
53
|
+
maximumValue: (max: number) => string;
|
|
54
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { cn } from '@gntik-ai/ui';
|
|
2
|
+
import { useRef, type KeyboardEvent, type PointerEvent } from 'react';
|
|
3
|
+
|
|
4
|
+
import type { DataTableLabels } from './labels';
|
|
5
|
+
|
|
6
|
+
export interface ColumnResizeHandleProps {
|
|
7
|
+
labels?: Partial<DataTableLabels>;
|
|
8
|
+
/** Column name for the accessible label ("Resize Region column"). */
|
|
9
|
+
label: string;
|
|
10
|
+
width: number;
|
|
11
|
+
min: number;
|
|
12
|
+
max: number;
|
|
13
|
+
onResize: (width: number) => void;
|
|
14
|
+
/** Called once a drag or a key press ends a resize. */
|
|
15
|
+
onResizeEnd?: (width: number) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const STEP = 10;
|
|
19
|
+
const BIG_STEP = 50;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Column resize handle at the end edge of a header cell: a focusable `role="separator"` with
|
|
23
|
+
* aria-valuenow/min/max (the width in px). Drag it with a pointer; from the keyboard,
|
|
24
|
+
* ArrowLeft/ArrowRight change the width by 10px (Shift: 50px), Home/End jump to the limits.
|
|
25
|
+
*/
|
|
26
|
+
export function ColumnResizeHandle({ label, width, min, max, onResize, onResizeEnd, labels }: ColumnResizeHandleProps) {
|
|
27
|
+
const drag = useRef<{ x: number; width: number; dir: 1 | -1 } | null>(null);
|
|
28
|
+
const clamp = (w: number) => Math.round(Math.min(max, Math.max(min, w)));
|
|
29
|
+
|
|
30
|
+
const onPointerDown = (e: PointerEvent<HTMLDivElement>) => {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
e.stopPropagation();
|
|
33
|
+
e.currentTarget.setPointerCapture?.(e.pointerId);
|
|
34
|
+
const rtl = typeof getComputedStyle === 'function' && getComputedStyle(e.currentTarget).direction === 'rtl';
|
|
35
|
+
drag.current = { x: e.clientX, width, dir: rtl ? -1 : 1 };
|
|
36
|
+
};
|
|
37
|
+
const onPointerMove = (e: PointerEvent<HTMLDivElement>) => {
|
|
38
|
+
const d = drag.current;
|
|
39
|
+
if (!d) return;
|
|
40
|
+
onResize(clamp(d.width + (e.clientX - d.x) * d.dir));
|
|
41
|
+
};
|
|
42
|
+
const onPointerUp = (e: PointerEvent<HTMLDivElement>) => {
|
|
43
|
+
const d = drag.current;
|
|
44
|
+
drag.current = null;
|
|
45
|
+
if (d) onResizeEnd?.(clamp(d.width + (e.clientX - d.x) * d.dir));
|
|
46
|
+
};
|
|
47
|
+
const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
|
|
48
|
+
const step = e.shiftKey ? BIG_STEP : STEP;
|
|
49
|
+
const rtl = typeof getComputedStyle === 'function' && getComputedStyle(e.currentTarget).direction === 'rtl';
|
|
50
|
+
const grow = rtl ? 'ArrowLeft' : 'ArrowRight';
|
|
51
|
+
const shrink = rtl ? 'ArrowRight' : 'ArrowLeft';
|
|
52
|
+
let next: number | null = null;
|
|
53
|
+
if (e.key === grow || e.key === 'ArrowUp') next = width + step;
|
|
54
|
+
else if (e.key === shrink || e.key === 'ArrowDown') next = width - step;
|
|
55
|
+
else if (e.key === 'Home') next = min;
|
|
56
|
+
else if (e.key === 'End') next = max;
|
|
57
|
+
if (next == null) return;
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
const w = clamp(next);
|
|
60
|
+
onResize(w);
|
|
61
|
+
onResizeEnd?.(w);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div
|
|
66
|
+
role="separator"
|
|
67
|
+
tabIndex={0}
|
|
68
|
+
aria-orientation="vertical"
|
|
69
|
+
aria-label={labels?.resizeColumn?.(label) ?? `Resize ${label} column`}
|
|
70
|
+
aria-valuenow={width}
|
|
71
|
+
aria-valuemin={min}
|
|
72
|
+
aria-valuemax={max}
|
|
73
|
+
aria-valuetext={labels?.columnWidth?.(width) ?? `${width} pixels`}
|
|
74
|
+
data-column-resizer=""
|
|
75
|
+
onPointerDown={onPointerDown}
|
|
76
|
+
onPointerMove={onPointerMove}
|
|
77
|
+
onPointerUp={onPointerUp}
|
|
78
|
+
onPointerCancel={onPointerUp}
|
|
79
|
+
onKeyDown={onKeyDown}
|
|
80
|
+
className={cn(
|
|
81
|
+
'group/resizer absolute inset-y-0 -end-1.5 z-[2] flex w-3 cursor-col-resize touch-none justify-center',
|
|
82
|
+
'focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-focus-ring',
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
<span
|
|
86
|
+
aria-hidden
|
|
87
|
+
className="my-2 w-px bg-border transition-colors group-hover/resizer:w-0.5 group-hover/resizer:bg-primary group-focus-visible/resizer:w-0.5 group-focus-visible/resizer:bg-primary motion-reduce:transition-none"
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { useEffect, useId, useRef, useState, type ComponentProps, type ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
const mountedNames = new Map<string, number>();
|
|
4
|
+
|
|
5
|
+
export interface SingleSelection<T> {
|
|
6
|
+
selectedId: string | null;
|
|
7
|
+
inputProps: (row: T) => ComponentProps<'input'>;
|
|
8
|
+
entryProps: (row: T) => ComponentProps<'span'>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function useSingleSelection<T>(options: {
|
|
12
|
+
selectionName?: string;
|
|
13
|
+
selectedId?: string | null;
|
|
14
|
+
defaultSelectedId?: string | null;
|
|
15
|
+
onSelectedIdChange?: (id: string) => void;
|
|
16
|
+
getRowId: (row: T) => string;
|
|
17
|
+
getRowLabel: (row: T) => string;
|
|
18
|
+
rows: readonly T[];
|
|
19
|
+
isRowDisabled?: (row: T) => boolean;
|
|
20
|
+
}): SingleSelection<T> {
|
|
21
|
+
const generatedName = useId();
|
|
22
|
+
const [innerId, setInnerId] = useState(options.defaultSelectedId ?? null);
|
|
23
|
+
const selectedId = options.selectedId === undefined ? innerId : options.selectedId;
|
|
24
|
+
const inputs = useRef(new Map<string, HTMLInputElement>());
|
|
25
|
+
const entry = useRef<HTMLSpanElement>(null);
|
|
26
|
+
const enabledIds = options.rows.filter((row) => !options.isRowDisabled?.(row)).map(options.getRowId);
|
|
27
|
+
const tabId = selectedId != null && enabledIds.includes(selectedId) ? selectedId : enabledIds[0];
|
|
28
|
+
// Chromium excludes every unchecked same-name radio from sequential focus when
|
|
29
|
+
// a disabled radio is checked. Put the fallback tab stop beside the first
|
|
30
|
+
// enabled radio so other table controls retain their normal Tab order.
|
|
31
|
+
const redirectEntry =
|
|
32
|
+
tabId !== undefined &&
|
|
33
|
+
options.rows.some((row) => options.getRowId(row) === selectedId && options.isRowDisabled?.(row));
|
|
34
|
+
const select = (id: string) => {
|
|
35
|
+
if (id === selectedId) return;
|
|
36
|
+
setInnerId(id);
|
|
37
|
+
options.onSelectedIdChange?.(id);
|
|
38
|
+
};
|
|
39
|
+
return {
|
|
40
|
+
selectedId,
|
|
41
|
+
entryProps: (row) =>
|
|
42
|
+
redirectEntry && options.getRowId(row) === tabId
|
|
43
|
+
? {
|
|
44
|
+
ref: entry,
|
|
45
|
+
tabIndex: 0,
|
|
46
|
+
onFocus: (event) => {
|
|
47
|
+
if (event.target === event.currentTarget && tabId !== undefined)
|
|
48
|
+
inputs.current.get(tabId)?.focus({ preventScroll: true });
|
|
49
|
+
},
|
|
50
|
+
onPointerDown: (event) => {
|
|
51
|
+
// Only keyboard entry redirects focus; clicking the wrapper's blank
|
|
52
|
+
// space must not focus it or interfere with native radio clicks.
|
|
53
|
+
if (event.target === event.currentTarget) event.preventDefault();
|
|
54
|
+
},
|
|
55
|
+
onBlur: (event) => {
|
|
56
|
+
if (!event.currentTarget.contains(event.relatedTarget)) event.currentTarget.tabIndex = 0;
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
: {},
|
|
60
|
+
inputProps: (row) => {
|
|
61
|
+
const id = options.getRowId(row);
|
|
62
|
+
const disabled = options.isRowDisabled?.(row) ?? false;
|
|
63
|
+
return {
|
|
64
|
+
type: 'radio',
|
|
65
|
+
name: options.selectionName ?? generatedName,
|
|
66
|
+
'aria-label': options.getRowLabel(row),
|
|
67
|
+
checked: selectedId === id,
|
|
68
|
+
disabled,
|
|
69
|
+
tabIndex: !disabled && id === tabId ? 0 : -1,
|
|
70
|
+
ref: (input) => {
|
|
71
|
+
if (input) inputs.current.set(id, input);
|
|
72
|
+
else inputs.current.delete(id);
|
|
73
|
+
},
|
|
74
|
+
onChange: () => {
|
|
75
|
+
if (!disabled) select(id);
|
|
76
|
+
},
|
|
77
|
+
onKeyDown: (event) => {
|
|
78
|
+
// Avoid a second stop on the entry wrapper when leaving its radio.
|
|
79
|
+
// Its blur handler restores keyboard entry after focus has left.
|
|
80
|
+
if (event.key === 'Tab' && entry.current) entry.current.tabIndex = -1;
|
|
81
|
+
const direction = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];
|
|
82
|
+
if (direction == null || disabled || enabledIds.length === 0) return;
|
|
83
|
+
event.preventDefault();
|
|
84
|
+
const index = enabledIds.indexOf(id);
|
|
85
|
+
const nextId = enabledIds[(index + direction + enabledIds.length) % enabledIds.length]!;
|
|
86
|
+
inputs.current.get(nextId)?.focus();
|
|
87
|
+
select(nextId);
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function useSingleSelectionWarnings({
|
|
95
|
+
single,
|
|
96
|
+
selectionName,
|
|
97
|
+
paginated,
|
|
98
|
+
hasLabel,
|
|
99
|
+
}: {
|
|
100
|
+
single: boolean;
|
|
101
|
+
selectionName?: string;
|
|
102
|
+
paginated?: boolean;
|
|
103
|
+
hasLabel: boolean;
|
|
104
|
+
}) {
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
if (single && paginated === true && process.env.NODE_ENV === 'development') {
|
|
107
|
+
console.warn('DataTable single selection: paginated=true is unsupported; rendering all rows without pagination.');
|
|
108
|
+
}
|
|
109
|
+
}, [single, paginated]);
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (!single || process.env.NODE_ENV !== 'development') return;
|
|
112
|
+
if (!hasLabel) console.warn('DataTable single selection: supply legend, aria-label or aria-labelledby.');
|
|
113
|
+
}, [single, hasLabel]);
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!single || selectionName === undefined || process.env.NODE_ENV !== 'development') return;
|
|
116
|
+
const count = mountedNames.get(selectionName) ?? 0;
|
|
117
|
+
if (count > 0)
|
|
118
|
+
console.warn(
|
|
119
|
+
`DataTable single selection: duplicate selectionName "${selectionName}"; supplied names must be unique per page.`,
|
|
120
|
+
);
|
|
121
|
+
mountedNames.set(selectionName, count + 1);
|
|
122
|
+
return () => {
|
|
123
|
+
const remaining = (mountedNames.get(selectionName) ?? 1) - 1;
|
|
124
|
+
if (remaining === 0) mountedNames.delete(selectionName);
|
|
125
|
+
else mountedNames.set(selectionName, remaining);
|
|
126
|
+
};
|
|
127
|
+
}, [single, selectionName]);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function SelectionGroup({
|
|
131
|
+
single,
|
|
132
|
+
legend,
|
|
133
|
+
children,
|
|
134
|
+
...label
|
|
135
|
+
}: {
|
|
136
|
+
single: boolean;
|
|
137
|
+
legend?: ReactNode;
|
|
138
|
+
'aria-label'?: string;
|
|
139
|
+
'aria-labelledby'?: string;
|
|
140
|
+
children: ReactNode;
|
|
141
|
+
}) {
|
|
142
|
+
if (!single) return <>{children}</>;
|
|
143
|
+
return (
|
|
144
|
+
<fieldset className="m-0 min-w-0 border-0 p-0" {...label}>
|
|
145
|
+
{legend != null && <legend className="px-3 py-2 text-sm font-medium text-foreground">{legend}</legend>}
|
|
146
|
+
{children}
|
|
147
|
+
</fieldset>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { newViewId, sameViewState, type DataTableView, type DataTableViewState } from './views';
|
|
3
|
+
|
|
4
|
+
/** A value that is controlled when `value` is defined, otherwise kept in state. */
|
|
5
|
+
export function useControllable<V>(
|
|
6
|
+
value: V | undefined,
|
|
7
|
+
initial: V | (() => V),
|
|
8
|
+
onChange?: (next: V) => void,
|
|
9
|
+
): [V, (next: V) => void] {
|
|
10
|
+
const [inner, setInner] = useState<V>(initial);
|
|
11
|
+
const current = value === undefined ? inner : value;
|
|
12
|
+
const set = (next: V) => {
|
|
13
|
+
setInner(next);
|
|
14
|
+
onChange?.(next);
|
|
15
|
+
};
|
|
16
|
+
return [current, set];
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseTableViewsOptions {
|
|
20
|
+
views?: readonly DataTableView[];
|
|
21
|
+
defaultViews?: readonly DataTableView[];
|
|
22
|
+
onViewsChange?: (views: DataTableView[]) => void;
|
|
23
|
+
activeViewId?: string | null;
|
|
24
|
+
defaultActiveViewId?: string | null;
|
|
25
|
+
onActiveViewChange?: (id: string | null) => void;
|
|
26
|
+
/** The table's state right now. */
|
|
27
|
+
current: DataTableViewState;
|
|
28
|
+
/** The table's state with no view applied (props' defaults). */
|
|
29
|
+
defaults: DataTableViewState;
|
|
30
|
+
/** Applies a complete state to the table. */
|
|
31
|
+
apply: (state: DataTableViewState) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Saved-views state machine for the DataTable: select, save as, save, rename, delete, reset. */
|
|
35
|
+
export function useTableViews({
|
|
36
|
+
views: viewsProp,
|
|
37
|
+
defaultViews = [],
|
|
38
|
+
onViewsChange,
|
|
39
|
+
activeViewId,
|
|
40
|
+
defaultActiveViewId = null,
|
|
41
|
+
onActiveViewChange,
|
|
42
|
+
current,
|
|
43
|
+
defaults,
|
|
44
|
+
apply,
|
|
45
|
+
}: UseTableViewsOptions) {
|
|
46
|
+
const [views, setViews] = useControllable<readonly DataTableView[]>(viewsProp, defaultViews, (next) =>
|
|
47
|
+
onViewsChange?.([...next]),
|
|
48
|
+
);
|
|
49
|
+
const [activeId, setActiveId] = useControllable<string | null>(activeViewId, defaultActiveViewId, onActiveViewChange);
|
|
50
|
+
const active = views.find((v) => v.id === activeId) ?? null;
|
|
51
|
+
const target = (view: DataTableView | null): DataTableViewState => ({ ...defaults, ...(view?.state ?? {}) });
|
|
52
|
+
const dirty = !sameViewState(current, target(active));
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
views,
|
|
56
|
+
activeId: active ? active.id : null,
|
|
57
|
+
dirty,
|
|
58
|
+
select: (id: string | null) => {
|
|
59
|
+
const view = views.find((v) => v.id === id) ?? null;
|
|
60
|
+
setActiveId(view ? view.id : null);
|
|
61
|
+
apply(target(view));
|
|
62
|
+
},
|
|
63
|
+
saveAs: (name: string) => {
|
|
64
|
+
const view: DataTableView = { id: newViewId(name, views), name, state: current };
|
|
65
|
+
setViews([...views, view]);
|
|
66
|
+
setActiveId(view.id);
|
|
67
|
+
},
|
|
68
|
+
save: () => {
|
|
69
|
+
if (!active) return;
|
|
70
|
+
setViews(views.map((v) => (v.id === active.id ? { ...v, state: current } : v)));
|
|
71
|
+
},
|
|
72
|
+
rename: (id: string, name: string) => setViews(views.map((v) => (v.id === id ? { ...v, name } : v))),
|
|
73
|
+
remove: (id: string) => {
|
|
74
|
+
setViews(views.filter((v) => v.id !== id));
|
|
75
|
+
if (id === activeId) setActiveId(null);
|
|
76
|
+
},
|
|
77
|
+
reset: () => apply(target(active)),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { useEffect, useState, type RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
/** The items to render: `[start, end)` of the flattened body, each `rowHeight` px tall. */
|
|
4
|
+
export interface VirtualWindow {
|
|
5
|
+
start: number;
|
|
6
|
+
end: number;
|
|
7
|
+
rowHeight: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UseVirtualRowsOptions {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
count: number;
|
|
13
|
+
rowHeight: number;
|
|
14
|
+
/** The `<table>`; its parent is the scroll container. */
|
|
15
|
+
tableRef: RefObject<HTMLTableElement | null>;
|
|
16
|
+
/** Fallback viewport height before the container is measured (and in jsdom). */
|
|
17
|
+
viewportHeight: number;
|
|
18
|
+
/** Extra rows rendered above and below the viewport. */
|
|
19
|
+
overscan?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Computes a virtual window from the table container's scroll position. */
|
|
23
|
+
export function windowFor(
|
|
24
|
+
count: number,
|
|
25
|
+
scrollTop: number,
|
|
26
|
+
viewport: number,
|
|
27
|
+
rowHeight: number,
|
|
28
|
+
overscan: number,
|
|
29
|
+
): VirtualWindow {
|
|
30
|
+
const h = Math.max(1, rowHeight);
|
|
31
|
+
const first = Math.floor(Math.max(0, scrollTop) / h);
|
|
32
|
+
const visible = Math.ceil(viewport / h) + 1;
|
|
33
|
+
const start = Math.max(0, Math.min(count, first - overscan));
|
|
34
|
+
const end = Math.max(start, Math.min(count, first + visible + overscan));
|
|
35
|
+
return { start, end, rowHeight: h };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Row virtualization for the DataTable: listens to the scroll container of the table and returns
|
|
40
|
+
* the window of items to render, or `undefined` when disabled.
|
|
41
|
+
*/
|
|
42
|
+
export function useVirtualRows({
|
|
43
|
+
enabled,
|
|
44
|
+
count,
|
|
45
|
+
rowHeight,
|
|
46
|
+
tableRef,
|
|
47
|
+
viewportHeight,
|
|
48
|
+
overscan = 8,
|
|
49
|
+
}: UseVirtualRowsOptions): VirtualWindow | undefined {
|
|
50
|
+
const [scroll, setScroll] = useState({ top: 0, height: viewportHeight });
|
|
51
|
+
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
const el = tableRef.current?.parentElement;
|
|
54
|
+
if (!enabled || !el) return undefined;
|
|
55
|
+
let frame = 0;
|
|
56
|
+
const read = () => {
|
|
57
|
+
frame = 0;
|
|
58
|
+
setScroll((prev) => {
|
|
59
|
+
const height = el.clientHeight || prev.height;
|
|
60
|
+
return prev.top === el.scrollTop && prev.height === height ? prev : { top: el.scrollTop, height };
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
const schedule = () => {
|
|
64
|
+
if (frame) return;
|
|
65
|
+
frame =
|
|
66
|
+
typeof requestAnimationFrame === 'function'
|
|
67
|
+
? requestAnimationFrame(read)
|
|
68
|
+
: (setTimeout(read, 0) as unknown as number);
|
|
69
|
+
};
|
|
70
|
+
el.addEventListener('scroll', schedule, { passive: true });
|
|
71
|
+
const ro = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(schedule);
|
|
72
|
+
ro?.observe(el);
|
|
73
|
+
schedule();
|
|
74
|
+
return () => {
|
|
75
|
+
el.removeEventListener('scroll', schedule);
|
|
76
|
+
ro?.disconnect();
|
|
77
|
+
if (frame && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(frame);
|
|
78
|
+
};
|
|
79
|
+
}, [enabled, tableRef]);
|
|
80
|
+
|
|
81
|
+
if (!enabled) return undefined;
|
|
82
|
+
return windowFor(count, scroll.top, scroll.height, rowHeight, overscan);
|
|
83
|
+
}
|