@flikk/ui 1.0.0-beta.30 → 1.0.0-beta.31
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/CHANGELOG.md +97 -0
- package/dist/components/ai/AgentStatus/AgentStatus.d.ts +3 -0
- package/dist/components/ai/AgentStatus/AgentStatus.js +144 -0
- package/dist/components/ai/AgentStatus/AgentStatus.theme.d.ts +2 -0
- package/dist/components/ai/AgentStatus/AgentStatus.theme.js +37 -0
- package/dist/components/ai/AgentStatus/AgentStatus.types.d.ts +116 -0
- package/dist/components/ai/AgentStatus/index.d.ts +3 -0
- package/dist/components/ai/CodeBlock/CodeBlock.js +3 -3
- package/dist/components/ai/CodeBlock/CodeBlock.theme.js +1 -1
- package/dist/components/ai/PromptInput/PromptInput.js +13 -14
- package/dist/components/ai/PromptInput/PromptInput.theme.js +1 -1
- package/dist/components/ai/PromptInput/VoiceRecorder.js +3 -3
- package/dist/components/ai/Reasoning/Reasoning.d.ts +5 -0
- package/dist/components/ai/Reasoning/Reasoning.js +16 -0
- package/dist/components/ai/Reasoning/Reasoning.types.d.ts +20 -0
- package/dist/components/ai/Reasoning/index.d.ts +2 -0
- package/dist/components/ai/SourceCitation/SourceCitation.d.ts +4 -0
- package/dist/components/ai/SourceCitation/SourceCitation.js +29 -0
- package/dist/components/ai/SourceCitation/SourceCitation.theme.d.ts +2 -0
- package/dist/components/ai/SourceCitation/SourceCitation.theme.js +23 -0
- package/dist/components/ai/SourceCitation/SourceCitation.types.d.ts +55 -0
- package/dist/components/ai/SourceCitation/index.d.ts +3 -0
- package/dist/components/ai/StreamingResponse/ErrorDisplay.js +2 -2
- package/dist/components/ai/StreamingResponse/StreamingResponse.js +1 -3
- package/dist/components/ai/StreamingResponse/StreamingResponse.theme.js +3 -1
- package/dist/components/ai/TokenCounter/TokenCounter.js +2 -2
- package/dist/components/ai/index.d.ts +3 -0
- package/dist/components/ai/index.js +6 -3
- package/dist/components/ai/shared/StatusIndicator.d.ts +8 -0
- package/dist/components/ai/shared/StatusIndicator.js +34 -0
- package/dist/components/canvas/CanvasToolbar.js +2 -2
- package/dist/components/canvas/NodeControls.js +2 -2
- package/dist/components/canvas/NodeRenderer.js +2 -2
- package/dist/components/charts/ActivityRings/ActivityRings.js +2 -2
- package/dist/components/charts/AreaChart/AreaChart.js +21 -32
- package/dist/components/charts/AreaChart/AreaChart.theme.d.ts +1 -6
- package/dist/components/charts/AreaChart/AreaChart.theme.js +7 -19
- package/dist/components/charts/AreaChart/AreaChart.types.d.ts +18 -11
- package/dist/components/charts/AreaChart/AreaChart.types.js +10 -3
- package/dist/components/charts/AreaChart/index.d.ts +1 -1
- package/dist/components/charts/BarChart/BarChart.js +9 -28
- package/dist/components/charts/BarChart/BarChart.types.d.ts +20 -16
- package/dist/components/charts/BarChart/BarChart.types.js +11 -4
- package/dist/components/charts/ComboChart/BarRenderer.js +8 -5
- package/dist/components/charts/ComboChart/ComboChart.js +11 -24
- package/dist/components/charts/ComboChart/ComboChart.types.d.ts +15 -13
- package/dist/components/charts/ComboChart/ComboChart.types.js +14 -2
- package/dist/components/charts/ComboChart/LineRenderer.js +15 -10
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.d.ts +16 -0
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.js +237 -0
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.types.d.ts +62 -0
- package/dist/components/charts/HorizontalBarChart/HorizontalBarChart.types.js +21 -0
- package/dist/components/charts/HorizontalBarChart/index.d.ts +3 -0
- package/dist/components/charts/LineChart/LineChart.js +9 -26
- package/dist/components/charts/LineChart/LineChart.types.d.ts +11 -14
- package/dist/components/charts/LineChart/LineChart.types.js +11 -3
- package/dist/components/charts/LollipopChart/LollipopChart.d.ts +3 -0
- package/dist/components/charts/LollipopChart/LollipopChart.js +361 -0
- package/dist/components/charts/LollipopChart/LollipopChart.types.d.ts +42 -0
- package/dist/components/charts/LollipopChart/LollipopChart.types.js +12 -0
- package/dist/components/charts/LollipopChart/index.d.ts +3 -0
- package/dist/components/charts/ScatterPlot/ScatterPlot.js +5 -4
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.d.ts +14 -19
- package/dist/components/charts/ScatterPlot/ScatterPlot.types.js +9 -2
- package/dist/components/charts/StackedBarChart/StackedBarChart.js +150 -132
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.d.ts +20 -18
- package/dist/components/charts/StackedBarChart/StackedBarChart.types.js +10 -4
- package/dist/components/charts/hooks/useChartAccessibility.d.ts +1 -1
- package/dist/components/charts/hooks/useChartAccessibility.js +3 -0
- package/dist/components/charts/hooks/useTooltipPosition.js +18 -6
- package/dist/components/charts/index.d.ts +6 -0
- package/dist/components/charts/index.js +6 -0
- package/dist/components/charts/shared/ChartAxis/CategoryYAxis.d.ts +14 -0
- package/dist/components/charts/shared/ChartAxis/CategoryYAxis.js +35 -0
- package/dist/components/charts/shared/ChartAxis/ChartAxis.types.d.ts +69 -8
- package/dist/components/charts/shared/ChartAxis/ValueXAxis.d.ts +14 -0
- package/dist/components/charts/shared/ChartAxis/ValueXAxis.js +64 -0
- package/dist/components/charts/shared/ChartAxis/XAxis.js +1 -1
- package/dist/components/charts/shared/ChartAxis/YAxis.js +2 -2
- package/dist/components/charts/shared/ChartAxis/index.d.ts +3 -1
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.d.ts +0 -2
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.js +1 -1
- package/dist/components/charts/shared/ChartGrid/VerticalGrid.d.ts +29 -0
- package/dist/components/charts/shared/ChartGrid/VerticalGrid.js +26 -0
- package/dist/components/charts/shared/ChartGrid/index.d.ts +2 -0
- package/dist/components/charts/shared/ChartMarker/ChartMarker.js +7 -1
- package/dist/components/charts/theme/chart.theme.js +18 -23
- package/dist/components/charts/types/chart.types.d.ts +9 -5
- package/dist/components/charts/types/chart.types.js +1 -7
- package/dist/components/charts/utils/animation-utils.js +1 -3
- package/dist/components/charts/utils/bar-emphasis.d.ts +32 -3
- package/dist/components/charts/utils/bar-emphasis.js +24 -8
- package/dist/components/charts/utils/index.d.ts +1 -0
- package/dist/components/charts/utils/series-color.d.ts +7 -0
- package/dist/components/charts/utils/series-color.js +20 -0
- package/dist/components/core/Accordion/Accordion.theme.js +2 -2
- package/dist/components/core/Accordion/AccordionTrigger.js +2 -2
- package/dist/components/core/Alert/Alert.animations.d.ts +2 -1
- package/dist/components/core/Alert/Alert.animations.js +21 -6
- package/dist/components/core/Alert/Alert.js +7 -7
- package/dist/components/core/Avatar/Avatar.js +50 -42
- package/dist/components/core/Avatar/Avatar.theme.d.ts +8 -4
- package/dist/components/core/Avatar/Avatar.theme.js +64 -8
- package/dist/components/core/Avatar/Avatar.types.d.ts +13 -2
- package/dist/components/core/Avatar/index.d.ts +1 -1
- package/dist/components/core/AvatarGroup/AvatarGroup.js +6 -8
- package/dist/components/core/AvatarGroup/AvatarGroup.theme.d.ts +1 -2
- package/dist/components/core/AvatarGroup/AvatarGroup.theme.js +2 -6
- package/dist/components/core/AvatarGroup/AvatarGroup.types.d.ts +0 -6
- package/dist/components/core/AvatarGroup/index.d.ts +1 -1
- package/dist/components/core/Badge/Badge.animations.js +4 -2
- package/dist/components/core/Badge/Badge.js +9 -4
- package/dist/components/core/Badge/Badge.theme.js +18 -12
- package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.d.ts +1 -4
- package/dist/components/core/Breadcrumbs/Breadcrumbs.theme.js +2 -2
- package/dist/components/core/Button/Button.js +9 -17
- package/dist/components/core/Button/Button.ripple.js +2 -2
- package/dist/components/core/Button/Button.theme.d.ts +2 -0
- package/dist/components/core/Button/Button.theme.js +34 -21
- package/dist/components/core/Button/Button.types.d.ts +18 -1
- package/dist/components/core/Calendar/Calendar.js +20 -5
- package/dist/components/core/Calendar/Calendar.theme.js +14 -22
- package/dist/components/core/Calendar/Calendar.types.d.ts +0 -1
- package/dist/components/core/Calendar/CalendarMini/CalendarMini.js +3 -3
- package/dist/components/core/Card/Card.theme.js +12 -4
- package/dist/components/core/Card/CardBody.js +1 -1
- package/dist/components/core/Card/CardFooter.js +1 -1
- package/dist/components/core/Card/CardHeader.js +1 -1
- package/dist/components/core/Carousel/CarouselNext.js +2 -2
- package/dist/components/core/Carousel/CarouselPrevious.js +2 -2
- package/dist/components/core/CommandPalette/CommandPalette.js +5 -4
- package/dist/components/core/CommandPalette/CommandPalette.theme.js +3 -7
- package/dist/components/core/CommandPalette/CommandPalette.types.d.ts +2 -0
- package/dist/components/core/ContextMenu/ContextMenuItem.js +2 -2
- package/dist/components/core/ContextMenu/ContextMenuSubTrigger.js +2 -2
- package/dist/components/core/Drawer/Drawer.theme.js +7 -4
- package/dist/components/core/Drawer/DrawerHeader.js +2 -2
- package/dist/components/core/Dropdown/Dropdown.theme.js +3 -1
- package/dist/components/core/Dropdown/DropdownItem.js +2 -2
- package/dist/components/core/Dropdown/DropdownTrigger.js +16 -5
- package/dist/components/core/HeroCard/HeroCard.theme.js +5 -2
- package/dist/components/core/Kbd/Kbd.theme.js +4 -2
- package/dist/components/core/Link/Link.js +6 -4
- package/dist/components/core/Message/Message.js +2 -2
- package/dist/components/core/Message/Message.theme.js +5 -7
- package/dist/components/core/Message/MessageAudio.js +2 -2
- package/dist/components/core/Message/MessageFile.js +2 -2
- package/dist/components/core/Message/MessageLink.js +2 -2
- package/dist/components/core/Message/MessageStatusIndicator.js +5 -5
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +2 -2
- package/dist/components/core/Modal/Modal.theme.js +9 -4
- package/dist/components/core/Modal/ModalHeader.js +2 -2
- package/dist/components/core/ModalStack/ModalStackModal.js +2 -2
- package/dist/components/core/NavItem/NavItem.js +6 -6
- package/dist/components/core/OfflineIndicator/OfflineIndicator.animations.d.ts +22 -0
- package/dist/components/core/OfflineIndicator/OfflineIndicator.animations.js +54 -0
- package/dist/components/core/OfflineIndicator/OfflineIndicator.js +23 -24
- package/dist/components/core/OfflineIndicator/OfflineIndicator.theme.js +25 -6
- package/dist/components/core/OfflineIndicator/OfflineIndicator.types.d.ts +17 -0
- package/dist/components/core/PageHeading/PageHeading.theme.js +1 -1
- package/dist/components/core/PageHeading/PageHeadingBackButton.js +2 -2
- package/dist/components/core/Pagination/Pagination.js +3 -3
- package/dist/components/core/Pagination/Pagination.theme.js +3 -3
- package/dist/components/core/Pill/Pill.js +4 -2
- package/dist/components/core/Pill/Pill.theme.js +6 -4
- package/dist/components/core/Rating/Rating.js +4 -5
- package/dist/components/core/Rating/Rating.theme.js +1 -1
- package/dist/components/core/Segmented/Segmented.theme.js +9 -6
- package/dist/components/core/Sidebar/Sidebar.types.d.ts +4 -2
- package/dist/components/core/Sidebar/SidebarHeader.js +2 -2
- package/dist/components/core/Sidebar/SidebarMobileTrigger.js +2 -2
- package/dist/components/core/Sidebar/SidebarSearch.js +3 -3
- package/dist/components/core/Sidebar/SidebarToggle.js +3 -3
- package/dist/components/core/Sidebar/SidebarUserProfile.js +2 -2
- package/dist/components/core/Sortable/Sortable.js +2 -2
- package/dist/components/core/Tag/Tag.js +2 -2
- package/dist/components/core/Tag/Tag.theme.js +4 -2
- package/dist/components/core/Toast/Toast.js +3 -3
- package/dist/components/core/Toast/Toast.types.d.ts +1 -1
- package/dist/components/core/Toast/useToast.js +5 -5
- package/dist/components/core/Tooltip/Tooltip.d.ts +1 -1
- package/dist/components/core/Tooltip/Tooltip.js +42 -10
- package/dist/components/core/Tooltip/Tooltip.types.d.ts +25 -0
- package/dist/components/core/Tree/Tree.js +2 -2
- package/dist/components/core/shared/interaction.animations.d.ts +51 -0
- package/dist/components/core/shared/interaction.animations.js +59 -0
- package/dist/components/data-display/Feed/Feed.js +2 -2
- package/dist/components/data-display/GanttChart/GanttChart.theme.js +1 -1
- package/dist/components/data-display/GanttChart/GanttChartPanel.js +2 -2
- package/dist/components/data-display/GanttChart/GanttTableGroupRow.js +3 -3
- package/dist/components/data-display/GanttChart/GanttTaskContextMenu.js +2 -2
- package/dist/components/data-display/GanttChart/GanttToolbar.js +2 -2
- package/dist/components/data-display/KPI/KPI.js +2 -2
- package/dist/components/data-display/KPI/KPI.theme.js +4 -4
- package/dist/components/data-display/Metric/Metric.js +2 -2
- package/dist/components/data-display/Metric/Metric.theme.js +25 -8
- package/dist/components/data-display/Metric/Metric.types.d.ts +1 -1
- package/dist/components/data-display/Table/Table.theme.js +9 -9
- package/dist/components/data-display/Table/TableActions.js +2 -2
- package/dist/components/data-display/Table/TableActionsMenu.js +1 -1
- package/dist/components/data-display/Table/TableColumnManager.js +2 -2
- package/dist/components/data-display/Table/TableDeclarative.js +3 -3
- package/dist/components/data-display/Table/TableFilter.js +4 -4
- package/dist/components/data-display/Table/TableHeader.js +3 -3
- package/dist/components/data-display/Table/TableRow.js +2 -2
- package/dist/components/data-display/Table/TableSelectionHeader.js +3 -3
- package/dist/components/data-display/Timeline/TimelineMarker.js +2 -2
- package/dist/components/effects/CustomCursor/CustomCursor.js +2 -2
- package/dist/components/effects/CustomCursor/CustomCursor.theme.js +2 -2
- package/dist/components/effects/DotPattern/DotPattern.js +6 -0
- package/dist/components/effects/DotPattern/DotPattern.types.d.ts +4 -2
- package/dist/components/effects/MeshGradient/MeshGradient.utils.d.ts +1 -1
- package/dist/components/effects/MeshGradient/MeshGradient.utils.js +1 -1
- package/dist/components/forms/Checkbox/Checkbox.js +2 -1
- package/dist/components/forms/Checkbox/Checkbox.theme.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerEyeDropper.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerGradient.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerHeader.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerImage.js +3 -3
- package/dist/components/forms/ColorPicker/ColorPickerInput.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerPresets.js +2 -2
- package/dist/components/forms/ColorPicker/ColorPickerSavedColors.js +2 -2
- package/dist/components/forms/Combobox/Combobox.js +3 -3
- package/dist/components/forms/Combobox/Combobox.theme.js +2 -2
- package/dist/components/forms/DatePicker/DatePicker.animations.js +16 -25
- package/dist/components/forms/DatePicker/DatePicker.theme.js +2 -2
- package/dist/components/forms/DatePicker/DatePickerBody.js +1 -1
- package/dist/components/forms/DatePicker/DatePickerTrigger.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePicker.theme.js +3 -3
- package/dist/components/forms/DateRangePicker/DateRangePickerBody.js +1 -1
- package/dist/components/forms/DateRangePicker/DateRangePickerPresets.js +2 -2
- package/dist/components/forms/DateRangePicker/DateRangePickerTrigger.js +2 -2
- package/dist/components/forms/FileUpload/FileUpload.js +4 -4
- package/dist/components/forms/FileUpload/FileUpload.theme.js +5 -1
- package/dist/components/forms/FileUpload/FileUploadProgress.js +7 -7
- package/dist/components/forms/Input/Input.js +6 -7
- package/dist/components/forms/Input/Input.theme.js +19 -15
- package/dist/components/forms/InputAddress/InputAddress.js +10 -3
- package/dist/components/forms/InputCounter/InputCounter.js +3 -3
- package/dist/components/forms/InputOTP/InputOTP.d.ts +3 -12
- package/dist/components/forms/InputOTP/InputOTP.js +140 -151
- package/dist/components/forms/InputOTP/InputOTP.theme.d.ts +5 -1
- package/dist/components/forms/InputOTP/InputOTP.theme.js +40 -5
- package/dist/components/forms/InputOTP/InputOTP.types.d.ts +45 -2
- package/dist/components/forms/InputOTP/index.d.ts +1 -1
- package/dist/components/forms/InputTag/InputTag.js +5 -5
- package/dist/components/forms/InputTag/InputTag.theme.js +1 -2
- package/dist/components/forms/Mention/Mention.theme.js +3 -5
- package/dist/components/forms/Radio/Radio.js +3 -2
- package/dist/components/forms/Radio/Radio.theme.js +2 -2
- package/dist/components/forms/RichTextEditor/RichTextEditor.js +22 -23
- package/dist/components/forms/Select/Select.d.ts +5 -1
- package/dist/components/forms/Select/Select.js +12 -11
- package/dist/components/forms/Select/Select.theme.js +4 -4
- package/dist/components/forms/Select/Select.types.d.ts +4 -1
- package/dist/components/forms/SelectableCard/SelectableCard.animations.d.ts +1 -2
- package/dist/components/forms/SelectableCard/SelectableCard.animations.js +6 -7
- package/dist/components/forms/SelectableCard/SelectableCard.js +1 -1
- package/dist/components/forms/Signature/Signature.js +1 -1
- package/dist/components/forms/Slider/Slider.js +24 -11
- package/dist/components/forms/Slider/Slider.theme.js +9 -3
- package/dist/components/forms/Switch/Switch.theme.js +1 -1
- package/dist/components/forms/TimePicker/TimePicker.theme.js +6 -7
- package/dist/components/forms/TimePicker/TimePickerContent.js +1 -1
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +3 -3
- package/dist/components/forms/forms.theme.js +23 -12
- package/dist/components/forms/index.js +1 -1
- package/dist/components/generative/registry.js +195 -2
- package/dist/components/generative/resolvers.js +16 -16
- package/dist/components/generative/schema.generated.js +2687 -379
- package/dist/components/layout/Section/Section.theme.js +1 -1
- package/dist/generative.schema.json +2687 -379
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/useElapsedTime.d.ts +28 -0
- package/dist/hooks/useElapsedTime.js +37 -0
- package/dist/hooks/useSelectPortal.d.ts +10 -2
- package/dist/hooks/useSelectPortal.js +6 -3
- package/dist/index.js +1 -1
- package/dist/registry.json +835 -261
- package/dist/shadcn-compat.css +1 -1
- package/dist/styles.css +1 -1
- package/dist/theme.css +5 -1
- package/dist/themes/ember.css +131 -0
- package/dist/themes/iris.css +94 -0
- package/dist/themes/jade.css +139 -0
- package/dist/tools.json +2705 -361
- package/dist/utils/colorUtils.d.ts +6 -6
- package/dist/utils/colorUtils.js +8 -8
- package/package.json +9 -6
- package/src/global.scss +15 -4
- package/src/styles/theme.css +440 -152
- package/tailwind.preset.cjs +52 -34
- package/dist/components/core/Pill/Pill.animations.d.ts +0 -32
- package/dist/components/core/Pill/Pill.animations.js +0 -16
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { useId, useState, useEffect, useMemo, useRef } from 'react';
|
|
4
|
+
import { useReducedMotion, motion, AnimatePresence } from 'motion/react';
|
|
5
|
+
import { cn } from '../../../utils/cn.js';
|
|
6
|
+
import { contrastTone } from '../../../utils/colorUtils.js';
|
|
7
|
+
import { useChartDimensions } from '../hooks/useChartDimensions.js';
|
|
8
|
+
import { useChartValidation } from '../hooks/useChartValidation.js';
|
|
9
|
+
import { useChartAccessibility } from '../hooks/useChartAccessibility.js';
|
|
10
|
+
import { useTooltipPosition } from '../hooks/useTooltipPosition.js';
|
|
11
|
+
import { ChartErrorBoundary } from '../shared/ChartErrorBoundary/ChartErrorBoundary.js';
|
|
12
|
+
import { ChartTooltip } from '../shared/ChartTooltip/ChartTooltip.js';
|
|
13
|
+
import { HorizontalGrid } from '../shared/ChartGrid/HorizontalGrid.js';
|
|
14
|
+
import { YAxis } from '../shared/ChartAxis/YAxis.js';
|
|
15
|
+
import { XAxis } from '../shared/ChartAxis/XAxis.js';
|
|
16
|
+
import { CARTESIAN_ENTRANCE_DURATION, LINE_DOT_STAGGER, dotEntranceTransition, RISE_FROM_BASELINE, barEntranceTransition, BAR_GROUP_STAGGER } from '../utils/animation-utils.js';
|
|
17
|
+
import { springs } from '../../../theme/animations.js';
|
|
18
|
+
import { X_AXIS_MARGIN } from '../types/chart.types.js';
|
|
19
|
+
import { LOLLIPOP_CHART_DEFAULTS } from './LollipopChart.types.js';
|
|
20
|
+
import { Warning } from '@phosphor-icons/react';
|
|
21
|
+
|
|
22
|
+
/** Top margin reserved so the selected mark's pill has clearance from the top edge. */
|
|
23
|
+
const LOLLIPOP_TOP_MARGIN = 48;
|
|
24
|
+
const LollipopChart = ({ data, config, showGrid = LOLLIPOP_CHART_DEFAULTS.showGrid, showXAxis = LOLLIPOP_CHART_DEFAULTS.showXAxis, showYAxis = LOLLIPOP_CHART_DEFAULTS.showYAxis, minValue: propMinValue, maxValue: propMaxValue, dotRadius = LOLLIPOP_CHART_DEFAULTS.dotRadius, stemWidth = LOLLIPOP_CHART_DEFAULTS.stemWidth, color, showAnimation = LOLLIPOP_CHART_DEFAULTS.showAnimation, enableKeyboardNavigation = LOLLIPOP_CHART_DEFAULTS.enableKeyboardNavigation, showValidationWarnings = process.env.NODE_ENV === "development", darkMode = false, selectedIndex: selectedIndexProp, defaultSelectedIndex = LOLLIPOP_CHART_DEFAULTS.defaultSelectedIndex, onSelectedIndexChange, valueFormatter, className, title, description, width, height, ...props }) => {
|
|
25
|
+
var _a, _b, _c;
|
|
26
|
+
const chartId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
27
|
+
const shouldReduceMotion = useReducedMotion();
|
|
28
|
+
const [hasAnimated, setHasAnimated] = useState(false);
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
setHasAnimated(true);
|
|
31
|
+
}, [shouldReduceMotion, showAnimation]);
|
|
32
|
+
// Single-series only: warn (dev-only) if the caller passes more than one
|
|
33
|
+
// config key, and always render the first.
|
|
34
|
+
const dataKeys = useMemo(() => Object.keys(config || {}), [config]);
|
|
35
|
+
if (process.env.NODE_ENV !== "production" &&
|
|
36
|
+
dataKeys.length > 1) {
|
|
37
|
+
console.warn("[LollipopChart]: `config` should have exactly one key — LollipopChart is single-series only. " +
|
|
38
|
+
`Rendering "${dataKeys[0]}" and ignoring the rest.`);
|
|
39
|
+
}
|
|
40
|
+
const dataKey = dataKeys[0];
|
|
41
|
+
const isSelectionControlled = selectedIndexProp !== undefined;
|
|
42
|
+
const [internalSelectedIndex, setInternalSelectedIndex] = useState(defaultSelectedIndex);
|
|
43
|
+
const resolvedSelectedIndex = isSelectionControlled ? selectedIndexProp : internalSelectedIndex;
|
|
44
|
+
if (process.env.NODE_ENV !== "production") {
|
|
45
|
+
if (isSelectionControlled && defaultSelectedIndex !== LOLLIPOP_CHART_DEFAULTS.defaultSelectedIndex) {
|
|
46
|
+
console.warn("[LollipopChart]: both `selectedIndex` and `defaultSelectedIndex` were provided — `selectedIndex` (controlled) wins.");
|
|
47
|
+
}
|
|
48
|
+
if (isSelectionControlled && !onSelectedIndexChange) {
|
|
49
|
+
console.warn("[LollipopChart]: `selectedIndex` was provided without `onSelectedIndexChange` — the chart will not be able to update selection.");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const handleSelect = (index) => {
|
|
53
|
+
if (!isSelectionControlled) {
|
|
54
|
+
setInternalSelectedIndex(index);
|
|
55
|
+
}
|
|
56
|
+
onSelectedIndexChange === null || onSelectedIndexChange === void 0 ? void 0 : onSelectedIndexChange(index);
|
|
57
|
+
};
|
|
58
|
+
const formatValue = (value, item) => valueFormatter ? valueFormatter(value, item) : String(value);
|
|
59
|
+
const { validation, sanitizedData, isValid, hasWarnings, safeScale, safeMath, } = useChartValidation(data, config, { autoSanitize: true });
|
|
60
|
+
const seriesColor = (_b = color !== null && color !== void 0 ? color : (dataKey && ((_a = config[dataKey]) === null || _a === void 0 ? void 0 : _a.color))) !== null && _b !== void 0 ? _b : "var(--color-primary)";
|
|
61
|
+
const containerRef = useRef(null);
|
|
62
|
+
const { dimensions, measured } = useChartDimensions(containerRef);
|
|
63
|
+
const isMobile = typeof window !== "undefined" &&
|
|
64
|
+
("ontouchstart" in window || navigator.maxTouchPoints > 0);
|
|
65
|
+
const { tooltipData, tooltipRef, handleMouseEnter, handleMouseMove, handleMouseLeave, } = useTooltipPosition({ containerRef, isMobile });
|
|
66
|
+
const { chartAccessibilityProps, descriptionProps, focusedElementIndex, isKeyboardMode, handleMouseInteraction, } = useChartAccessibility({
|
|
67
|
+
chartType: "lollipop",
|
|
68
|
+
data: sanitizedData,
|
|
69
|
+
config,
|
|
70
|
+
title,
|
|
71
|
+
description,
|
|
72
|
+
enableKeyboardNavigation,
|
|
73
|
+
});
|
|
74
|
+
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
75
|
+
const activeIndex = hoveredIndex !== null
|
|
76
|
+
? hoveredIndex
|
|
77
|
+
: isKeyboardMode && focusedElementIndex >= 0
|
|
78
|
+
? focusedElementIndex
|
|
79
|
+
: null;
|
|
80
|
+
const isAnyActive = activeIndex !== null;
|
|
81
|
+
const margin = useMemo(() => ({
|
|
82
|
+
top: LOLLIPOP_TOP_MARGIN,
|
|
83
|
+
right: showYAxis ? 20 : 16,
|
|
84
|
+
bottom: showXAxis ? X_AXIS_MARGIN : 0,
|
|
85
|
+
left: showYAxis ? 60 : 16,
|
|
86
|
+
}), [showXAxis, showYAxis]);
|
|
87
|
+
const innerWidth = Math.max(0, dimensions.width - margin.left - margin.right);
|
|
88
|
+
const innerHeight = Math.max(0, dimensions.height - margin.top - margin.bottom);
|
|
89
|
+
const scaleRange = safeScale.calculateRange({
|
|
90
|
+
minValue: propMinValue,
|
|
91
|
+
maxValue: propMaxValue,
|
|
92
|
+
includeZero: true,
|
|
93
|
+
});
|
|
94
|
+
const { min: minValue, max: maxValue, hasValidData } = scaleRange;
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
var _a, _b;
|
|
97
|
+
if (isKeyboardMode &&
|
|
98
|
+
focusedElementIndex >= 0 &&
|
|
99
|
+
focusedElementIndex < sanitizedData.length &&
|
|
100
|
+
dataKey) {
|
|
101
|
+
const item = sanitizedData[focusedElementIndex];
|
|
102
|
+
const rawValue = item[dataKey];
|
|
103
|
+
if (typeof rawValue === "number" && isFinite(rawValue)) {
|
|
104
|
+
const categoryGap = 24;
|
|
105
|
+
const totalGap = Math.max(0, (sanitizedData.length - 1) * categoryGap);
|
|
106
|
+
const categoryWidth = safeMath.divide(Math.max(0, innerWidth - totalGap), sanitizedData.length, 0);
|
|
107
|
+
const cx = margin.left + focusedElementIndex * (categoryWidth + categoryGap) + categoryWidth / 2;
|
|
108
|
+
const range = maxValue - minValue;
|
|
109
|
+
const scaledZero = range > 0 ? safeMath.scale(0, minValue, maxValue, 0, innerHeight) : 0;
|
|
110
|
+
const zeroY = margin.top + innerHeight - scaledZero;
|
|
111
|
+
const scaledValue = range > 0 ? safeMath.scale(rawValue, minValue, maxValue, 0, innerHeight) : 0;
|
|
112
|
+
const isNeg = rawValue < 0;
|
|
113
|
+
const dotY = isNeg
|
|
114
|
+
? zeroY + Math.abs(scaledValue - scaledZero)
|
|
115
|
+
: zeroY - Math.abs(scaledValue - scaledZero);
|
|
116
|
+
const syntheticEvent = new MouseEvent("mouseenter", { clientX: cx, clientY: dotY });
|
|
117
|
+
handleMouseEnter(syntheticEvent, {
|
|
118
|
+
category: ((_a = item.name) !== null && _a !== void 0 ? _a : `Category ${focusedElementIndex + 1}`).toString(),
|
|
119
|
+
series: [
|
|
120
|
+
{ key: dataKey, label: ((_b = config[dataKey]) === null || _b === void 0 ? void 0 : _b.label) || dataKey, value: rawValue, color: seriesColor },
|
|
121
|
+
],
|
|
122
|
+
}, { x: cx, y: dotY, dimensions });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (isKeyboardMode && focusedElementIndex === -1) {
|
|
126
|
+
handleMouseLeave();
|
|
127
|
+
}
|
|
128
|
+
}, [
|
|
129
|
+
isKeyboardMode, focusedElementIndex, sanitizedData, config, dimensions, margin,
|
|
130
|
+
innerWidth, innerHeight, minValue, maxValue, handleMouseEnter, handleMouseLeave,
|
|
131
|
+
dataKey, seriesColor, safeMath,
|
|
132
|
+
]);
|
|
133
|
+
// --- Every hook above this line runs unconditionally, every render — same
|
|
134
|
+
// discipline BarChart documents (BarChart.tsx ~line 674): a chart going
|
|
135
|
+
// from populated -> empty/invalid data must never change its hook count.
|
|
136
|
+
if (!isValid) {
|
|
137
|
+
return (jsxs("div", { className: cn("chart-container flex flex-col items-center justify-center p-6 border border-[var(--color-border)] rounded-lg text-2xl", className), role: "alert", "aria-live": "polite", ...props, children: [jsxs("div", { className: "flex flex-col items-center gap-4 mb-1", children: [jsx(Warning, { className: "size-6 text-[var(--color-danger)]" }), jsx("div", { className: "text-base font-semibold text-[var(--color-text-secondary)]", children: "Invalid Chart Data" })] }), jsx("p", { className: "text-base text-[var(--color-text-secondary)]/70 mb-2", children: ((_c = validation.errors[0]) === null || _c === void 0 ? void 0 : _c.message) ||
|
|
138
|
+
"Unable to render chart due to data validation errors" })] }));
|
|
139
|
+
}
|
|
140
|
+
if (!sanitizedData || sanitizedData.length === 0) {
|
|
141
|
+
return (jsx("div", { className: cn("chart-container flex items-center justify-center text-[var(--color-text-muted)]", className), role: "img", "aria-label": "Empty lollipop chart", ...props, children: jsx("span", { children: "No data available" }) }));
|
|
142
|
+
}
|
|
143
|
+
if (!hasValidData) {
|
|
144
|
+
return (jsx("div", { className: cn("chart-container flex items-center justify-center text-[var(--color-text-muted)]", className), role: "img", "aria-label": "Empty lollipop chart - no valid data", ...props, children: jsx("span", { children: "No valid numeric data available" }) }));
|
|
145
|
+
}
|
|
146
|
+
return (jsx(ChartErrorBoundary, { className: className, showErrorDetails: process.env.NODE_ENV === "development", children: jsxs("div", { ref: containerRef, className: cn("chart-container w-full h-full flex flex-col", darkMode && "dark", className), style: {
|
|
147
|
+
width: width !== undefined
|
|
148
|
+
? typeof width === "number"
|
|
149
|
+
? `${width}px`
|
|
150
|
+
: width
|
|
151
|
+
: undefined,
|
|
152
|
+
height: height !== undefined
|
|
153
|
+
? typeof height === "number"
|
|
154
|
+
? `${height}px`
|
|
155
|
+
: height
|
|
156
|
+
: undefined,
|
|
157
|
+
}, ...chartAccessibilityProps, onKeyDown: (e) => {
|
|
158
|
+
chartAccessibilityProps.onKeyDown(e);
|
|
159
|
+
if ((e.key === "Enter" || e.key === " ") && isKeyboardMode && focusedElementIndex >= 0) {
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
handleSelect(resolvedSelectedIndex === focusedElementIndex ? null : focusedElementIndex);
|
|
162
|
+
}
|
|
163
|
+
}, onMouseEnter: handleMouseInteraction, onMouseMove: handleMouseInteraction, ...props, children: [descriptionProps && jsx("div", { ...descriptionProps }), showValidationWarnings && hasWarnings && (jsxs("div", { className: "absolute top-2 left-2 right-2 z-10 p-2 bg-[var(--color-warning-50)] border border-[var(--color-warning-200)] rounded text-sm text-[var(--color-warning)] shadow-sm", children: [jsx("strong", { children: "Chart Warnings:" }), jsx("ul", { className: "mt-1", children: validation.warnings.map((warning, index) => (jsxs("li", { children: ["\u2022 ", warning.message] }, index))) })] })), jsx("div", { className: "relative flex-1 min-h-0", children: jsxs("svg", { width: "100%", height: "100%", viewBox: `0 0 ${dimensions.width} ${dimensions.height}`, style: { overflow: "visible" }, children: [jsxs("defs", { children: [jsxs("linearGradient", { id: `${chartId}-lollipop-stem-gradient`, gradientUnits: "userSpaceOnUse", x1: 0, y1: margin.top, x2: 0, y2: margin.top + innerHeight, children: [jsx("stop", { offset: "0%", stopColor: seriesColor, stopOpacity: 1 }), jsx("stop", { offset: "100%", stopColor: seriesColor, stopOpacity: 0.4 })] }), jsx("filter", { id: `${chartId}-lollipop-stem-glow`, filterUnits: "userSpaceOnUse", x: -20, y: margin.top - 20, width: dimensions.width + 40, height: innerHeight + 40, colorInterpolationFilters: "sRGB", children: jsx("feDropShadow", { dx: "0", dy: "0", stdDeviation: "4", floodColor: seriesColor, floodOpacity: "0.45" }) })] }), jsx(HorizontalGrid, { show: showGrid, x: margin.left, y: margin.top, width: innerWidth, height: innerHeight, lineCount: 5 }), jsx(YAxis, { show: showYAxis, min: minValue, max: maxValue, x: margin.left, y: margin.top, height: innerHeight, showGrid: false, gridWidth: innerWidth }), (() => {
|
|
164
|
+
const categoryGap = 24;
|
|
165
|
+
const totalGap = Math.max(0, (sanitizedData.length - 1) * categoryGap);
|
|
166
|
+
const categoryWidth = safeMath.divide(Math.max(0, innerWidth - totalGap), sanitizedData.length, 0);
|
|
167
|
+
return (jsx(XAxis, { show: showXAxis, data: sanitizedData, x: margin.left, y: margin.top + innerHeight, width: innerWidth, categoryWidth: categoryWidth, categoryGap: categoryGap }));
|
|
168
|
+
})(), measured &&
|
|
169
|
+
(() => {
|
|
170
|
+
const categoryGap = 24;
|
|
171
|
+
const totalGap = Math.max(0, (sanitizedData.length - 1) * categoryGap);
|
|
172
|
+
const categoryWidth = safeMath.divide(Math.max(0, innerWidth - totalGap), sanitizedData.length, 0);
|
|
173
|
+
const range = maxValue - minValue;
|
|
174
|
+
const scaledZero = range > 0 ? safeMath.scale(0, minValue, maxValue, 0, innerHeight) : 0;
|
|
175
|
+
const zeroY = margin.top + innerHeight - scaledZero;
|
|
176
|
+
return (jsx("g", { className: "chart-lollipops", children: sanitizedData.map((item, index) => {
|
|
177
|
+
const rawValue = item[dataKey];
|
|
178
|
+
const value = typeof rawValue === "number" && isFinite(rawValue) ? rawValue : 0;
|
|
179
|
+
const isNull = rawValue === null ||
|
|
180
|
+
rawValue === undefined ||
|
|
181
|
+
(typeof rawValue === "number" && !isFinite(rawValue));
|
|
182
|
+
const cx = margin.left + index * (categoryWidth + categoryGap) + categoryWidth / 2;
|
|
183
|
+
// Hoisted above the null gate on purpose: these depend only on the
|
|
184
|
+
// index, never on the value, and the null marker must dim off the
|
|
185
|
+
// SAME expression as every other mark. Recomputing it inline there
|
|
186
|
+
// drifted — `isAnyActive ? 0.35 : 1` misses the
|
|
187
|
+
// !isCurrentActiveOrSelected term, so a null sitting under an
|
|
188
|
+
// explicit `defaultSelectedIndex` dimmed while a real mark wouldn't.
|
|
189
|
+
const isSelected = resolvedSelectedIndex === index;
|
|
190
|
+
const isCurrentActiveOrSelected = index === activeIndex || index === resolvedSelectedIndex;
|
|
191
|
+
const lineOpacity = isAnyActive && !isCurrentActiveOrSelected ? 0.35 : 1;
|
|
192
|
+
// A null category has nothing to plot, so it gets a marker resting on
|
|
193
|
+
// the baseline and no stem — the axis label then anchors to something
|
|
194
|
+
// instead of hanging under empty space.
|
|
195
|
+
//
|
|
196
|
+
// 🔴 This is deliberately INDISTINGUISHABLE from a real `value: 0`,
|
|
197
|
+
// whose dot also lands on the baseline (dotY === zeroY once
|
|
198
|
+
// scaledValue === scaledZero). That collision is a known, accepted
|
|
199
|
+
// trade-off — don't "fix" it by nudging this marker off the axis.
|
|
200
|
+
//
|
|
201
|
+
// No hit area, stem, tooltip or selection: there is no value to
|
|
202
|
+
// report, so the marker is presentational only, and null stays
|
|
203
|
+
// skipped everywhere it would have to state a number.
|
|
204
|
+
if (isNull) {
|
|
205
|
+
return (jsx(motion.g, {
|
|
206
|
+
// initial/animate/transition mirror the real mark's motion.g
|
|
207
|
+
// exactly — same springs, same opacity timing — only `y` differs
|
|
208
|
+
// (pinned to the baseline instead of a plotted markerY). Keep
|
|
209
|
+
// them in lockstep: every past divergence here was invisible in
|
|
210
|
+
// a screenshot and only showed up in motion.
|
|
211
|
+
initial: showAnimation && !shouldReduceMotion
|
|
212
|
+
? { x: cx, y: zeroY, scale: 0, opacity: 0 }
|
|
213
|
+
: { x: cx, y: zeroY, scale: 1, opacity: 1 }, animate: {
|
|
214
|
+
x: cx,
|
|
215
|
+
y: zeroY,
|
|
216
|
+
scale: 1,
|
|
217
|
+
opacity: lineOpacity,
|
|
218
|
+
}, transition: {
|
|
219
|
+
...dotEntranceTransition(index, shouldReduceMotion),
|
|
220
|
+
// x/y ride springs.snappy so the marker tracks a resize or a
|
|
221
|
+
// data change identically to its neighbours; left to the
|
|
222
|
+
// entrance spring it lagged behind them.
|
|
223
|
+
x: springs.snappy,
|
|
224
|
+
y: springs.snappy,
|
|
225
|
+
// Opacity needs its own transition too. Left to the entrance
|
|
226
|
+
// spring it inherits that spring's per-index stagger delay and
|
|
227
|
+
// the hover dim crawls — measured at 0.58 a full second in,
|
|
228
|
+
// while every real mark had long since settled on 0.35.
|
|
229
|
+
opacity: {
|
|
230
|
+
delay: hasAnimated
|
|
231
|
+
? 0
|
|
232
|
+
: shouldReduceMotion
|
|
233
|
+
? 0
|
|
234
|
+
: CARTESIAN_ENTRANCE_DURATION + index * LINE_DOT_STAGGER,
|
|
235
|
+
duration: 0.2,
|
|
236
|
+
},
|
|
237
|
+
}, style: {
|
|
238
|
+
originX: "50%",
|
|
239
|
+
originY: "50%",
|
|
240
|
+
transformBox: "fill-box",
|
|
241
|
+
}, children: jsx("rect", { x: -dotRadius, y: -dotRadius, width: dotRadius * 2, height: dotRadius * 2, rx: dotRadius, fill: seriesColor, stroke: "var(--color-surface)", strokeWidth: 2 }) }, index));
|
|
242
|
+
}
|
|
243
|
+
const scaledValue = range > 0
|
|
244
|
+
? safeMath.scale(value, minValue, maxValue, 0, innerHeight)
|
|
245
|
+
: 0;
|
|
246
|
+
const isNeg = value < 0;
|
|
247
|
+
const dotY = isNeg
|
|
248
|
+
? zeroY + Math.abs(scaledValue - scaledZero)
|
|
249
|
+
: zeroY - Math.abs(scaledValue - scaledZero);
|
|
250
|
+
const delay = index * BAR_GROUP_STAGGER;
|
|
251
|
+
return (jsxs("g", { onClick: () => handleSelect(isSelected ? null : index), onMouseEnter: (e) => {
|
|
252
|
+
var _a, _b;
|
|
253
|
+
setHoveredIndex(index);
|
|
254
|
+
if (isSelected)
|
|
255
|
+
return;
|
|
256
|
+
handleMouseEnter(e, {
|
|
257
|
+
category: ((_a = item.name) !== null && _a !== void 0 ? _a : `Category ${index + 1}`).toString(),
|
|
258
|
+
series: [
|
|
259
|
+
{
|
|
260
|
+
key: dataKey,
|
|
261
|
+
label: ((_b = config[dataKey]) === null || _b === void 0 ? void 0 : _b.label) || dataKey,
|
|
262
|
+
value,
|
|
263
|
+
color: seriesColor,
|
|
264
|
+
},
|
|
265
|
+
],
|
|
266
|
+
}, { x: cx, y: dotY, dimensions });
|
|
267
|
+
}, onMouseMove: (e) => {
|
|
268
|
+
if (isSelected)
|
|
269
|
+
return;
|
|
270
|
+
handleMouseMove(e, { x: cx, y: dotY, dimensions });
|
|
271
|
+
}, onMouseLeave: () => {
|
|
272
|
+
setHoveredIndex(null);
|
|
273
|
+
if (isSelected)
|
|
274
|
+
return;
|
|
275
|
+
handleMouseLeave();
|
|
276
|
+
}, style: { cursor: "pointer" }, children: [jsx("rect", { x: cx - categoryWidth / 2, y: margin.top, width: categoryWidth, height: innerHeight, fill: "transparent" }), jsx(motion.line, { x1: cx, x2: cx, y1: zeroY, y2: dotY, stroke: `url(#${chartId}-lollipop-stem-gradient)`, strokeWidth: stemWidth,
|
|
277
|
+
// butt, not round: a round cap overhangs BOTH ends by
|
|
278
|
+
// strokeWidth/2, so the stem bled past the baseline and sat
|
|
279
|
+
// visibly below the axis rule. Butt terminates it exactly on
|
|
280
|
+
// the axis. The flat top costs nothing — the dot marker
|
|
281
|
+
// covers it.
|
|
282
|
+
strokeLinecap: "butt", filter: `url(#${chartId}-lollipop-stem-glow)`, initial: showAnimation && !shouldReduceMotion
|
|
283
|
+
? { scaleY: 0 }
|
|
284
|
+
: { scaleY: 1 }, animate: {
|
|
285
|
+
scaleY: 1,
|
|
286
|
+
x1: cx,
|
|
287
|
+
x2: cx,
|
|
288
|
+
y1: zeroY,
|
|
289
|
+
y2: dotY,
|
|
290
|
+
opacity: lineOpacity,
|
|
291
|
+
}, transition: showAnimation && !shouldReduceMotion
|
|
292
|
+
? {
|
|
293
|
+
scaleY: barEntranceTransition(delay, hasAnimated),
|
|
294
|
+
x1: springs.premium,
|
|
295
|
+
x2: springs.premium,
|
|
296
|
+
y1: springs.premium,
|
|
297
|
+
y2: springs.premium,
|
|
298
|
+
opacity: { duration: 0.2 },
|
|
299
|
+
}
|
|
300
|
+
: undefined, style: { ...RISE_FROM_BASELINE, transformBox: "fill-box" } }), (() => {
|
|
301
|
+
// The selected mark morphs from a small dot into a
|
|
302
|
+
// pill showing the value, in place of the old
|
|
303
|
+
// separate floating badge — same seriesColor on
|
|
304
|
+
// both shapes so the swap reads as one mark
|
|
305
|
+
// changing shape, not a switch to a different
|
|
306
|
+
// element. Pill dimensions clamp to the plot's
|
|
307
|
+
// vertical bounds so a very high/low value's pill
|
|
308
|
+
// never clips past the top or into the axis row.
|
|
309
|
+
const badgeText = formatValue(value, item);
|
|
310
|
+
const pillWidth = Math.max(56, badgeText.length * 9 + 24);
|
|
311
|
+
const pillHeight = 28;
|
|
312
|
+
const pillCenterY = Math.min(Math.max(dotY, margin.top + pillHeight / 2), margin.top + innerHeight - pillHeight / 2);
|
|
313
|
+
const markerY = isSelected ? pillCenterY : dotY;
|
|
314
|
+
// When seriesColor is the (default) primary token,
|
|
315
|
+
// reuse the design system's own vetted contrast
|
|
316
|
+
// pairing — the same one every other filled-primary
|
|
317
|
+
// surface (Button, Badge, ...) uses — rather than a
|
|
318
|
+
// runtime luminance heuristic. A custom per-series
|
|
319
|
+
// `color`/config color isn't necessarily primary,
|
|
320
|
+
// so it still falls back to `contrastTone()`.
|
|
321
|
+
const pillTextFill = seriesColor === "var(--color-primary)"
|
|
322
|
+
? "var(--color-primary-contrast)"
|
|
323
|
+
: contrastTone(seriesColor) === "dark"
|
|
324
|
+
? "var(--color-mono-950)"
|
|
325
|
+
: "var(--color-mono-50)";
|
|
326
|
+
return (jsxs(motion.g, { initial: showAnimation && !shouldReduceMotion
|
|
327
|
+
? { x: cx, y: markerY, scale: 0, opacity: 0 }
|
|
328
|
+
: { x: cx, y: markerY, scale: 1, opacity: 1 }, animate: {
|
|
329
|
+
x: cx,
|
|
330
|
+
y: markerY,
|
|
331
|
+
scale: 1,
|
|
332
|
+
opacity: lineOpacity,
|
|
333
|
+
}, transition: {
|
|
334
|
+
...dotEntranceTransition(index, shouldReduceMotion),
|
|
335
|
+
x: springs.snappy,
|
|
336
|
+
y: springs.snappy,
|
|
337
|
+
opacity: {
|
|
338
|
+
delay: hasAnimated
|
|
339
|
+
? 0
|
|
340
|
+
: shouldReduceMotion
|
|
341
|
+
? 0
|
|
342
|
+
: CARTESIAN_ENTRANCE_DURATION + index * LINE_DOT_STAGGER,
|
|
343
|
+
duration: 0.2,
|
|
344
|
+
},
|
|
345
|
+
}, style: {
|
|
346
|
+
originX: "50%",
|
|
347
|
+
originY: "50%",
|
|
348
|
+
transformBox: "fill-box",
|
|
349
|
+
}, children: [jsx(motion.rect, { animate: {
|
|
350
|
+
width: isSelected ? pillWidth : dotRadius * 2,
|
|
351
|
+
height: isSelected ? pillHeight : dotRadius * 2,
|
|
352
|
+
rx: isSelected ? pillHeight / 2 : dotRadius,
|
|
353
|
+
x: isSelected ? -pillWidth / 2 : -dotRadius,
|
|
354
|
+
y: isSelected ? -pillHeight / 2 : -dotRadius,
|
|
355
|
+
}, transition: springs.snappy, fill: seriesColor, stroke: "var(--color-surface)", strokeWidth: 2 }), jsx(AnimatePresence, { children: isSelected && (jsx(motion.text, { initial: { opacity: 0, scale: 0.6 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.6 }, transition: springs.snappy, x: 0, y: 0, textAnchor: "middle", dominantBaseline: "central", className: "text-sm font-semibold select-none pointer-events-none", fill: pillTextFill, children: badgeText }, "text")) })] }));
|
|
356
|
+
})()] }, index));
|
|
357
|
+
}) }));
|
|
358
|
+
})()] }) }), jsx(ChartTooltip, { tooltipRef: tooltipRef, content: tooltipData === null || tooltipData === void 0 ? void 0 : tooltipData.content, active: !!tooltipData, position: tooltipData ? { x: tooltipData.x, y: tooltipData.y } : { x: 0, y: 0 } })] }) }));
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
export { LollipopChart };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { BaseChartProps, ChartConfig, ChartDataPoint } from "../types/chart.types";
|
|
2
|
+
export interface LollipopChartProps extends BaseChartProps {
|
|
3
|
+
config: ChartConfig;
|
|
4
|
+
showGrid?: boolean;
|
|
5
|
+
showXAxis?: boolean;
|
|
6
|
+
showYAxis?: boolean;
|
|
7
|
+
minValue?: number;
|
|
8
|
+
maxValue?: number;
|
|
9
|
+
/** Controlled selected category index. `null`/`undefined` = no selection. */
|
|
10
|
+
selectedIndex?: number | null;
|
|
11
|
+
/** Uncontrolled initial selection. */
|
|
12
|
+
defaultSelectedIndex?: number | null;
|
|
13
|
+
/** Payload is the index, not the event (CLAUDE.md §9 naming convention). */
|
|
14
|
+
onSelectedIndexChange?: (index: number | null) => void;
|
|
15
|
+
/** Formats the pinned badge's value text. Defaults to `String(value)`. */
|
|
16
|
+
valueFormatter?: (value: number, item: ChartDataPoint) => string;
|
|
17
|
+
dotRadius?: number;
|
|
18
|
+
stemWidth?: number;
|
|
19
|
+
/** Series color for the dot + stem. Falls back to the single `config` key's
|
|
20
|
+
* `color`, then `var(--color-primary)`. */
|
|
21
|
+
color?: string;
|
|
22
|
+
showAnimation?: boolean;
|
|
23
|
+
enableKeyboardNavigation?: boolean;
|
|
24
|
+
showValidationWarnings?: boolean;
|
|
25
|
+
darkMode?: boolean;
|
|
26
|
+
className?: string;
|
|
27
|
+
title?: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
width?: string | number;
|
|
30
|
+
height?: string | number;
|
|
31
|
+
}
|
|
32
|
+
export declare const LOLLIPOP_CHART_DEFAULTS: {
|
|
33
|
+
readonly showGrid: false;
|
|
34
|
+
readonly showXAxis: true;
|
|
35
|
+
readonly showYAxis: false;
|
|
36
|
+
readonly dotRadius: 6;
|
|
37
|
+
readonly stemWidth: 3;
|
|
38
|
+
readonly defaultSelectedIndex: number | null;
|
|
39
|
+
readonly showAnimation: true;
|
|
40
|
+
readonly enableKeyboardNavigation: true;
|
|
41
|
+
};
|
|
42
|
+
export type LollipopChartData = ChartDataPoint;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const LOLLIPOP_CHART_DEFAULTS = {
|
|
2
|
+
showGrid: false,
|
|
3
|
+
showXAxis: true,
|
|
4
|
+
showYAxis: false,
|
|
5
|
+
dotRadius: 6,
|
|
6
|
+
stemWidth: 3,
|
|
7
|
+
defaultSelectedIndex: null,
|
|
8
|
+
showAnimation: true,
|
|
9
|
+
enableKeyboardNavigation: true,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export { LOLLIPOP_CHART_DEFAULTS };
|
|
@@ -9,7 +9,7 @@ import { useChartDimensions } from '../hooks/useChartDimensions.js';
|
|
|
9
9
|
import { XAxis } from '../shared/ChartAxis/XAxis.js';
|
|
10
10
|
import { YAxis } from '../shared/ChartAxis/YAxis.js';
|
|
11
11
|
import { ChartTooltip } from '../shared/ChartTooltip/ChartTooltip.js';
|
|
12
|
-
import { ChartLegend } from '../shared/ChartLegend/ChartLegend.js';
|
|
12
|
+
import { resolveLegendPosition, ChartLegend } from '../shared/ChartLegend/ChartLegend.js';
|
|
13
13
|
import { SCATTER_PLOT_DEFAULTS } from './ScatterPlot.types.js';
|
|
14
14
|
import { getCategoryColor, scatterPlotTheme } from './ScatterPlot.theme.js';
|
|
15
15
|
import { SCATTER_POINT_DURATION, SCATTER_POINT_STAGGER } from '../utils/animation-utils.js';
|
|
@@ -21,7 +21,7 @@ import { SCATTER_POINT_DURATION, SCATTER_POINT_STAGGER } from '../utils/animatio
|
|
|
21
21
|
* Now uses shared axis components with tick marks and value labels like other charts.
|
|
22
22
|
*/
|
|
23
23
|
const ScatterPlot = ({ data, xLabel, yLabel, dotRadius: propDotRadius, pointSize, // @deprecated
|
|
24
|
-
showGrid = SCATTER_PLOT_DEFAULTS.showGrid, showXAxis = SCATTER_PLOT_DEFAULTS.showXAxis, showYAxis = SCATTER_PLOT_DEFAULTS.showYAxis, showTooltip = SCATTER_PLOT_DEFAULTS.showTooltip, colors, minValue: propMinValue, maxValue: propMaxValue, theme: themeOverrides, className, svgClassName, dotClassName, gridClassName, axisClassName, title, description, enableKeyboardNavigation = SCATTER_PLOT_DEFAULTS.enableKeyboardNavigation, width, height, ...props }) => {
|
|
24
|
+
showGrid = SCATTER_PLOT_DEFAULTS.showGrid, showXAxis = SCATTER_PLOT_DEFAULTS.showXAxis, showYAxis = SCATTER_PLOT_DEFAULTS.showYAxis, showTooltip = SCATTER_PLOT_DEFAULTS.showTooltip, legend, colors, minValue: propMinValue, maxValue: propMaxValue, theme: themeOverrides, className, svgClassName, dotClassName, gridClassName, axisClassName, title, description, enableKeyboardNavigation = SCATTER_PLOT_DEFAULTS.enableKeyboardNavigation, width, height, ...props }) => {
|
|
25
25
|
var _a;
|
|
26
26
|
const theme = { ...scatterPlotTheme, ...(themeOverrides || {}) };
|
|
27
27
|
const chartColors = colors || theme.colors || scatterPlotTheme.colors;
|
|
@@ -114,6 +114,7 @@ showGrid = SCATTER_PLOT_DEFAULTS.showGrid, showXAxis = SCATTER_PLOT_DEFAULTS.sho
|
|
|
114
114
|
label: cat,
|
|
115
115
|
color: getCategoryColor(chartColors, idx),
|
|
116
116
|
})), [categories, chartColors]);
|
|
117
|
+
const resolvedLegendPosition = resolveLegendPosition(legend, config);
|
|
117
118
|
const chartDataForAccessibility = useMemo(() => {
|
|
118
119
|
return validData.map((point) => ({
|
|
119
120
|
category: point.category || 'Point',
|
|
@@ -155,7 +156,7 @@ showGrid = SCATTER_PLOT_DEFAULTS.showGrid, showXAxis = SCATTER_PLOT_DEFAULTS.sho
|
|
|
155
156
|
minHeight: height === undefined ? '400px' : undefined,
|
|
156
157
|
width: width !== undefined ? (typeof width === "number" ? `${width}px` : width) : undefined,
|
|
157
158
|
height: height !== undefined ? (typeof height === "number" ? `${height}px` : height) : undefined,
|
|
158
|
-
}, ...chartAccessibilityProps, ...props, children: [jsxs("svg", { width: dimensions.width, height: dimensions.height, className: cn(theme.svgStyle, svgClassName), ...descriptionProps, children: [jsx(YAxis, { show: showYAxis, min: yMin, max: yMax, x: margin.left, y: margin.top, height: innerHeight, showGrid: showGrid, gridWidth: innerWidth, tickCount: 5, className: cn(axisClassName) }), measured &&
|
|
159
|
+
}, ...chartAccessibilityProps, ...props, children: [resolvedLegendPosition === 'top' && (jsx(ChartLegend, { items: legendItems, className: "shrink-0 mb-4" })), jsxs("svg", { width: dimensions.width, height: dimensions.height, className: cn(theme.svgStyle, svgClassName), ...descriptionProps, children: [jsx(YAxis, { show: showYAxis, min: yMin, max: yMax, x: margin.left, y: margin.top, height: innerHeight, showGrid: showGrid, gridWidth: innerWidth, gridClassName: gridClassName, tickCount: 5, className: cn(axisClassName) }), measured &&
|
|
159
160
|
validData.map((point, index) => {
|
|
160
161
|
const cx = scaleX(point.x);
|
|
161
162
|
const cy = scaleY(point.y);
|
|
@@ -200,7 +201,7 @@ showGrid = SCATTER_PLOT_DEFAULTS.showGrid, showXAxis = SCATTER_PLOT_DEFAULTS.sho
|
|
|
200
201
|
}
|
|
201
202
|
handleMouseInteraction();
|
|
202
203
|
}, onMouseMove: handleMouseMove, onMouseLeave: handleMouseLeave }, index));
|
|
203
|
-
}), jsx(XAxis, { show: showXAxis, data: xAxisData, x: margin.left, y: margin.top + innerHeight, width: innerWidth, categoryWidth: innerWidth / (xAxisData.length - 1), categoryGap: 0, alignWithEdge: true, className: cn(axisClassName) }), xLabel && showXAxis && (jsx("text", { x: margin.left + innerWidth / 2, y: dimensions.height - 5, textAnchor: "middle", className: "text-sm fill-[var(--color-text-secondary)]", children: xLabel })), yLabel && showYAxis && (jsx("text", { x: 15, y: margin.top + innerHeight / 2, textAnchor: "middle", className: "text-sm fill-[var(--color-text-secondary)]", transform: `rotate(-90 15 ${margin.top + innerHeight / 2})`, children: yLabel }))] }), jsx(ChartLegend, { items: legendItems, className: "mt-4" }), showTooltip && (jsx(ChartTooltip, { active: !!tooltipData, position: tooltipData ? { x: tooltipData.x, y: tooltipData.y } : { x: 0, y: 0 }, tooltipRef: tooltipRef, content: tooltipData === null || tooltipData === void 0 ? void 0 : tooltipData.content, chartType: "scatter" }))] }));
|
|
204
|
+
}), jsx(XAxis, { show: showXAxis, data: xAxisData, x: margin.left, y: margin.top + innerHeight, width: innerWidth, categoryWidth: innerWidth / (xAxisData.length - 1), categoryGap: 0, alignWithEdge: true, className: cn(axisClassName) }), xLabel && showXAxis && (jsx("text", { x: margin.left + innerWidth / 2, y: dimensions.height - 5, textAnchor: "middle", className: "text-sm fill-[var(--color-text-secondary)]", children: xLabel })), yLabel && showYAxis && (jsx("text", { x: 15, y: margin.top + innerHeight / 2, textAnchor: "middle", className: "text-sm fill-[var(--color-text-secondary)]", transform: `rotate(-90 15 ${margin.top + innerHeight / 2})`, children: yLabel }))] }), resolvedLegendPosition === 'bottom' && (jsx(ChartLegend, { items: legendItems, className: "shrink-0 mt-4" })), showTooltip && (jsx(ChartTooltip, { active: !!tooltipData, position: tooltipData ? { x: tooltipData.x, y: tooltipData.y } : { x: 0, y: 0 }, tooltipRef: tooltipRef, content: tooltipData === null || tooltipData === void 0 ? void 0 : tooltipData.content, chartType: "scatter" }))] }));
|
|
204
205
|
};
|
|
205
206
|
ScatterPlot.displayName = 'ScatterPlot';
|
|
206
207
|
|
|
@@ -16,8 +16,13 @@ export interface ScatterPlotDataPoint {
|
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
18
|
* ScatterPlot props - extends standardized chart props
|
|
19
|
+
*
|
|
20
|
+
* 🔴 `variant` is Omitted, not inherited: scatter points are flat, single-colour marks —
|
|
21
|
+
* there is no gradient/minimal treatment to select. Leaving the inherited prop in place
|
|
22
|
+
* would publish a control that silently does nothing (and would reach the generative
|
|
23
|
+
* schema, where an LLM would emit it). Add it back only alongside an implementation.
|
|
19
24
|
*/
|
|
20
|
-
export interface ScatterPlotProps extends StandardChartDisplayProps, Omit<BaseChartProps, 'data' | 'config'> {
|
|
25
|
+
export interface ScatterPlotProps extends Omit<StandardChartDisplayProps, 'variant'>, Omit<BaseChartProps, 'data' | 'config'> {
|
|
21
26
|
/** Data points */
|
|
22
27
|
data: ScatterPlotDataPoint[];
|
|
23
28
|
/** X-axis label */
|
|
@@ -38,28 +43,18 @@ export interface ScatterPlotProps extends StandardChartDisplayProps, Omit<BaseCh
|
|
|
38
43
|
axisClassName?: string;
|
|
39
44
|
}
|
|
40
45
|
/**
|
|
41
|
-
* Default values using centralized defaults
|
|
46
|
+
* Default values using centralized defaults.
|
|
47
|
+
*
|
|
48
|
+
* 🔴 Picks explicitly rather than spreading `...CHART_DEFAULTS` — the spread pulled in a
|
|
49
|
+
* default for every other chart's props, so this object advertised values ScatterPlot
|
|
50
|
+
* never reads. List only what the component actually consumes.
|
|
42
51
|
*/
|
|
43
52
|
export declare const SCATTER_PLOT_DEFAULTS: {
|
|
44
|
-
readonly dotRadius: 4;
|
|
45
|
-
readonly pointSize: 4;
|
|
46
|
-
readonly showTooltip: true;
|
|
47
53
|
readonly showGrid: true;
|
|
48
54
|
readonly showXAxis: true;
|
|
49
55
|
readonly showYAxis: true;
|
|
50
|
-
readonly barRadius: 32;
|
|
51
|
-
readonly cellRadius: 4;
|
|
52
|
-
readonly radius: 20;
|
|
53
|
-
readonly strokeWidth: 2;
|
|
54
|
-
readonly gap: 4;
|
|
55
|
-
readonly categoryGap: 16;
|
|
56
|
-
readonly curved: true;
|
|
57
|
-
readonly curveType: import("..").CurveType;
|
|
58
|
-
readonly fillOpacity: 0.3;
|
|
59
|
-
readonly showStroke: true;
|
|
60
|
-
readonly showDots: false;
|
|
61
|
-
readonly orientation: "vertical" | "horizontal";
|
|
62
|
-
readonly stacked: false;
|
|
63
|
-
readonly variant: import("..").ChartVariant;
|
|
64
56
|
readonly enableKeyboardNavigation: true;
|
|
57
|
+
readonly dotRadius: 4;
|
|
58
|
+
readonly pointSize: 4;
|
|
59
|
+
readonly showTooltip: true;
|
|
65
60
|
};
|
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
import { CHART_DEFAULTS } from '../types/chart.types.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Default values using centralized defaults
|
|
4
|
+
* Default values using centralized defaults.
|
|
5
|
+
*
|
|
6
|
+
* 🔴 Picks explicitly rather than spreading `...CHART_DEFAULTS` — the spread pulled in a
|
|
7
|
+
* default for every other chart's props, so this object advertised values ScatterPlot
|
|
8
|
+
* never reads. List only what the component actually consumes.
|
|
5
9
|
*/
|
|
6
10
|
const SCATTER_PLOT_DEFAULTS = {
|
|
7
|
-
|
|
11
|
+
showGrid: CHART_DEFAULTS.showGrid,
|
|
12
|
+
showXAxis: CHART_DEFAULTS.showXAxis,
|
|
13
|
+
showYAxis: CHART_DEFAULTS.showYAxis,
|
|
14
|
+
enableKeyboardNavigation: CHART_DEFAULTS.enableKeyboardNavigation,
|
|
8
15
|
dotRadius: 4,
|
|
9
16
|
showTooltip: true,
|
|
10
17
|
};
|