@borisj74/bv-ds 0.1.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/LICENSE +21 -0
- package/README.md +94 -0
- package/dist/index.cjs +33885 -0
- package/dist/index.d.cts +2715 -0
- package/dist/index.d.ts +2715 -0
- package/dist/index.js +33717 -0
- package/package.json +67 -0
- package/src/components/ActivityFeed/ActivityFeed.tsx +48 -0
- package/src/components/ActivityFeed/index.ts +2 -0
- package/src/components/ActivityGauge/ActivityGauge.tsx +155 -0
- package/src/components/ActivityGauge/index.ts +7 -0
- package/src/components/AdvancedFilterBar/AdvancedFilterBar.tsx +80 -0
- package/src/components/AdvancedFilterBar/index.ts +2 -0
- package/src/components/Alert/Alert.tsx +210 -0
- package/src/components/Alert/index.ts +2 -0
- package/src/components/Avatar/Avatar.tsx +111 -0
- package/src/components/Avatar/index.ts +2 -0
- package/src/components/AvatarAddButton/AvatarAddButton.tsx +65 -0
- package/src/components/AvatarAddButton/index.ts +5 -0
- package/src/components/AvatarGroup/AvatarGroup.tsx +79 -0
- package/src/components/AvatarGroup/index.ts +6 -0
- package/src/components/AvatarLabelGroup/AvatarLabelGroup.tsx +62 -0
- package/src/components/AvatarLabelGroup/index.ts +5 -0
- package/src/components/AvatarProfilePhoto/AvatarProfilePhoto.tsx +117 -0
- package/src/components/AvatarProfilePhoto/index.ts +5 -0
- package/src/components/Badge/ColorBadge.tsx +36 -0
- package/src/components/Badge/ModernBadge.tsx +38 -0
- package/src/components/Badge/PillBadge.tsx +36 -0
- package/src/components/Badge/badgeShared.tsx +139 -0
- package/src/components/Badge/index.ts +7 -0
- package/src/components/BadgeCloseX/BadgeCloseX.tsx +64 -0
- package/src/components/BadgeCloseX/index.ts +2 -0
- package/src/components/BadgeGroup/BadgeGroup.tsx +61 -0
- package/src/components/BadgeGroup/index.ts +7 -0
- package/src/components/BreadcrumbButtonBase/BreadcrumbButtonBase.tsx +75 -0
- package/src/components/BreadcrumbButtonBase/index.ts +5 -0
- package/src/components/Breadcrumbs/Breadcrumbs.tsx +62 -0
- package/src/components/Breadcrumbs/index.ts +2 -0
- package/src/components/Button/Button.tsx +71 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/ButtonCloseX/ButtonCloseX.tsx +54 -0
- package/src/components/ButtonCloseX/index.ts +2 -0
- package/src/components/ButtonDestructive/ButtonDestructive.tsx +67 -0
- package/src/components/ButtonDestructive/index.ts +6 -0
- package/src/components/ButtonGroup/ButtonGroup.tsx +28 -0
- package/src/components/ButtonGroup/index.ts +2 -0
- package/src/components/ButtonGroupSegment/ButtonGroupSegment.tsx +54 -0
- package/src/components/ButtonGroupSegment/index.ts +5 -0
- package/src/components/ButtonUtility/ButtonUtility.tsx +67 -0
- package/src/components/ButtonUtility/index.ts +6 -0
- package/src/components/CalendarCell/CalendarCell.tsx +82 -0
- package/src/components/CalendarCell/index.ts +2 -0
- package/src/components/CalendarCellDayWeekView/CalendarCellDayWeekView.tsx +56 -0
- package/src/components/CalendarCellDayWeekView/index.ts +2 -0
- package/src/components/CalendarColumnHeader/CalendarColumnHeader.tsx +45 -0
- package/src/components/CalendarColumnHeader/index.ts +5 -0
- package/src/components/CalendarDateIcon/CalendarDateIcon.tsx +25 -0
- package/src/components/CalendarDateIcon/index.ts +2 -0
- package/src/components/CalendarEvent/CalendarEvent.tsx +76 -0
- package/src/components/CalendarEvent/index.ts +2 -0
- package/src/components/CalendarEventDayWeekView/CalendarEventDayWeekView.tsx +76 -0
- package/src/components/CalendarEventDayWeekView/index.ts +2 -0
- package/src/components/CalendarHeader/CalendarHeader.tsx +47 -0
- package/src/components/CalendarHeader/index.ts +2 -0
- package/src/components/CalendarRowLabel/CalendarRowLabel.tsx +21 -0
- package/src/components/CalendarRowLabel/index.ts +2 -0
- package/src/components/CalendarTimemarker/CalendarTimemarker.tsx +46 -0
- package/src/components/CalendarTimemarker/index.ts +5 -0
- package/src/components/CalendarViewDropdown/CalendarViewDropdown.tsx +101 -0
- package/src/components/CalendarViewDropdown/index.ts +6 -0
- package/src/components/CardHeader/CardHeader.tsx +57 -0
- package/src/components/CardHeader/index.ts +2 -0
- package/src/components/CarouselArrow/CarouselArrow.tsx +47 -0
- package/src/components/CarouselArrow/index.ts +6 -0
- package/src/components/CarouselImage/CarouselImage.tsx +60 -0
- package/src/components/CarouselImage/index.ts +2 -0
- package/src/components/Change/Change.tsx +73 -0
- package/src/components/Change/index.ts +2 -0
- package/src/components/ChartLegend/ChartLegend.tsx +38 -0
- package/src/components/ChartLegend/index.ts +2 -0
- package/src/components/ChartMarker/ChartMarker.tsx +54 -0
- package/src/components/ChartMarker/index.ts +2 -0
- package/src/components/ChartMini/ChartMini.tsx +86 -0
- package/src/components/ChartMini/index.ts +2 -0
- package/src/components/ChartTooltip/ChartTooltip.tsx +44 -0
- package/src/components/ChartTooltip/index.ts +2 -0
- package/src/components/Checkbox/Checkbox.tsx +65 -0
- package/src/components/Checkbox/checkboxBase.tsx +81 -0
- package/src/components/Checkbox/index.ts +3 -0
- package/src/components/CodeSnippet/CodeSnippet.tsx +94 -0
- package/src/components/CodeSnippet/index.ts +2 -0
- package/src/components/CodeSnippetTabs/CodeSnippetTabs.tsx +44 -0
- package/src/components/CodeSnippetTabs/index.ts +2 -0
- package/src/components/CommandBar/CommandBar.tsx +80 -0
- package/src/components/CommandBar/index.ts +2 -0
- package/src/components/CommandBarFooter/CommandBarFooter.tsx +125 -0
- package/src/components/CommandBarFooter/index.ts +5 -0
- package/src/components/CommandBarMenuSection/CommandBarMenuSection.tsx +28 -0
- package/src/components/CommandBarMenuSection/index.ts +2 -0
- package/src/components/CommandBarNavigationIcon/CommandBarNavigationIcon.tsx +47 -0
- package/src/components/CommandBarNavigationIcon/index.ts +2 -0
- package/src/components/CommandDropdownMenuItem/CommandDropdownMenuItem.tsx +51 -0
- package/src/components/CommandDropdownMenuItem/index.ts +2 -0
- package/src/components/CommandInput/CommandInput.tsx +74 -0
- package/src/components/CommandInput/index.ts +2 -0
- package/src/components/CommandShortcut/CommandShortcut.tsx +26 -0
- package/src/components/CommandShortcut/index.ts +2 -0
- package/src/components/ContentDivider/ContentDivider.tsx +80 -0
- package/src/components/ContentDivider/index.ts +6 -0
- package/src/components/ContentFeatureText/ContentFeatureText.tsx +60 -0
- package/src/components/ContentFeatureText/index.ts +5 -0
- package/src/components/ContentHeading/ContentHeading.tsx +43 -0
- package/src/components/ContentHeading/index.ts +5 -0
- package/src/components/ContentParagraph/ContentParagraph.tsx +39 -0
- package/src/components/ContentParagraph/index.ts +5 -0
- package/src/components/ContentQuote/ContentQuote.tsx +114 -0
- package/src/components/ContentQuote/index.ts +6 -0
- package/src/components/ContentRule/ContentRule.tsx +31 -0
- package/src/components/ContentRule/index.ts +2 -0
- package/src/components/ContextMenu/ContextMenu.tsx +67 -0
- package/src/components/ContextMenu/index.ts +7 -0
- package/src/components/ContextMenu/useContextMenu.ts +41 -0
- package/src/components/DatePickerCell/DatePickerCell.tsx +77 -0
- package/src/components/DatePickerCell/index.ts +2 -0
- package/src/components/DatePickerListItem/DatePickerListItem.tsx +39 -0
- package/src/components/DatePickerListItem/index.ts +2 -0
- package/src/components/DatePickerMenu/DatePickerMenu.tsx +131 -0
- package/src/components/DatePickerMenu/index.ts +2 -0
- package/src/components/DropdownAccountListItem/DropdownAccountListItem.tsx +69 -0
- package/src/components/DropdownAccountListItem/index.ts +2 -0
- package/src/components/DropdownMenuFooter/DropdownMenuFooter.tsx +50 -0
- package/src/components/DropdownMenuFooter/index.ts +5 -0
- package/src/components/DropdownMenuHeader/DropdownMenuHeader.tsx +93 -0
- package/src/components/DropdownMenuHeader/index.ts +5 -0
- package/src/components/DropdownMenuItemInsetIcon/DropdownMenuItemInsetIcon.tsx +89 -0
- package/src/components/DropdownMenuItemInsetIcon/index.ts +5 -0
- package/src/components/DropdownMenuListItem/DropdownMenuListItem.tsx +84 -0
- package/src/components/DropdownMenuListItem/index.ts +2 -0
- package/src/components/EmptyState/EmptyState.tsx +65 -0
- package/src/components/EmptyState/index.ts +2 -0
- package/src/components/FeedItemBase/FeedItemBase.tsx +135 -0
- package/src/components/FeedItemBase/index.ts +2 -0
- package/src/components/FileUpload/FileUpload.tsx +112 -0
- package/src/components/FileUpload/index.ts +2 -0
- package/src/components/FileUploadBase/FileUploadBase.tsx +69 -0
- package/src/components/FileUploadBase/index.ts +2 -0
- package/src/components/FileUploadItemBase/FileUploadItemBase.tsx +190 -0
- package/src/components/FileUploadItemBase/index.ts +7 -0
- package/src/components/FilterBar/FilterBar.tsx +62 -0
- package/src/components/FilterBar/index.ts +2 -0
- package/src/components/FilterTabs/FilterTabs.tsx +41 -0
- package/src/components/FilterTabs/index.ts +2 -0
- package/src/components/FiltersDropdownMenu/FiltersDropdownMenu.tsx +104 -0
- package/src/components/FiltersDropdownMenu/index.ts +2 -0
- package/src/components/FiltersSlideoutMenu/FiltersSlideoutMenu.tsx +71 -0
- package/src/components/FiltersSlideoutMenu/index.ts +2 -0
- package/src/components/HeaderNavigation/HeaderNavigation.tsx +178 -0
- package/src/components/HeaderNavigation/index.ts +6 -0
- package/src/components/HelpIcon/HelpIcon.tsx +49 -0
- package/src/components/HelpIcon/index.ts +2 -0
- package/src/components/InputField/InputField.tsx +108 -0
- package/src/components/InputField/index.ts +3 -0
- package/src/components/InputField/inputFieldShared.tsx +68 -0
- package/src/components/LeadingInputField/LeadingInputField.tsx +60 -0
- package/src/components/LeadingInputField/index.ts +2 -0
- package/src/components/LineAndBarChart/LineAndBarChart.tsx +96 -0
- package/src/components/LineAndBarChart/index.ts +2 -0
- package/src/components/LinkMessage/LinkMessage.tsx +52 -0
- package/src/components/LinkMessage/index.ts +2 -0
- package/src/components/LoadingIndicator/LoadingIndicator.tsx +108 -0
- package/src/components/LoadingIndicator/index.ts +6 -0
- package/src/components/MediaMessage/MediaMessage.tsx +109 -0
- package/src/components/MediaMessage/index.ts +2 -0
- package/src/components/MegaInputFieldBase/MegaInputFieldBase.tsx +49 -0
- package/src/components/MegaInputFieldBase/index.ts +5 -0
- package/src/components/Message/Message.tsx +85 -0
- package/src/components/Message/index.ts +3 -0
- package/src/components/Message/messageShared.tsx +73 -0
- package/src/components/MessageAction/MessageAction.tsx +221 -0
- package/src/components/MessageAction/index.ts +2 -0
- package/src/components/MessageActionButton/MessageActionButton.tsx +36 -0
- package/src/components/MessageActionButton/index.ts +2 -0
- package/src/components/MessageActionPanel/MessageActionPanel.tsx +36 -0
- package/src/components/MessageActionPanel/index.ts +2 -0
- package/src/components/MessageReaction/MessageReaction.tsx +37 -0
- package/src/components/MessageReaction/index.ts +2 -0
- package/src/components/MessageStatusIcon/MessageStatusIcon.tsx +54 -0
- package/src/components/MessageStatusIcon/index.ts +2 -0
- package/src/components/MetricItem/MetricItem.tsx +147 -0
- package/src/components/MetricItem/index.ts +2 -0
- package/src/components/ModalActions/ModalActions.tsx +57 -0
- package/src/components/ModalActions/index.ts +2 -0
- package/src/components/ModalHeader/ModalHeader.tsx +99 -0
- package/src/components/ModalHeader/index.ts +2 -0
- package/src/components/MultiSelect/MultiSelect.tsx +118 -0
- package/src/components/MultiSelect/index.ts +2 -0
- package/src/components/NavAccountCard/NavAccountCard.tsx +124 -0
- package/src/components/NavAccountCard/index.ts +2 -0
- package/src/components/NavAccountCardMenuItem/NavAccountCardMenuItem.tsx +101 -0
- package/src/components/NavAccountCardMenuItem/index.ts +5 -0
- package/src/components/NavButton/NavButton.tsx +50 -0
- package/src/components/NavButton/index.ts +2 -0
- package/src/components/NavFeaturedCard/NavFeaturedCard.tsx +82 -0
- package/src/components/NavFeaturedCard/index.ts +2 -0
- package/src/components/NavItemBase/NavItemBase.tsx +79 -0
- package/src/components/NavItemBase/index.ts +2 -0
- package/src/components/NavItemDropdownBase/NavItemDropdownBase.tsx +74 -0
- package/src/components/NavItemDropdownBase/index.ts +2 -0
- package/src/components/NavMenuButton/NavMenuButton.tsx +47 -0
- package/src/components/NavMenuButton/index.ts +2 -0
- package/src/components/Notification/Notification.tsx +102 -0
- package/src/components/Notification/index.ts +2 -0
- package/src/components/NumberInput/NumberInput.tsx +114 -0
- package/src/components/NumberInput/index.ts +2 -0
- package/src/components/PageHeader/PageHeader.tsx +88 -0
- package/src/components/PageHeader/index.ts +2 -0
- package/src/components/Pagination/Pagination.tsx +124 -0
- package/src/components/Pagination/index.ts +2 -0
- package/src/components/PaginationButtonGroupBase/PaginationButtonGroupBase.tsx +69 -0
- package/src/components/PaginationButtonGroupBase/index.ts +5 -0
- package/src/components/PaginationCards/PaginationCards.tsx +72 -0
- package/src/components/PaginationCards/index.ts +2 -0
- package/src/components/PaginationDotGroup/PaginationDotGroup.tsx +66 -0
- package/src/components/PaginationDotGroup/index.ts +2 -0
- package/src/components/PaginationDotIndicator/PaginationDotIndicator.tsx +39 -0
- package/src/components/PaginationDotIndicator/index.ts +6 -0
- package/src/components/PaginationNumberBase/PaginationNumberBase.tsx +42 -0
- package/src/components/PaginationNumberBase/index.ts +5 -0
- package/src/components/PieChart/PieChart.tsx +73 -0
- package/src/components/PieChart/index.ts +2 -0
- package/src/components/ProgressBar/ProgressBar.tsx +75 -0
- package/src/components/ProgressBar/index.ts +2 -0
- package/src/components/ProgressCircle/ProgressCircle.tsx +89 -0
- package/src/components/ProgressCircle/index.ts +6 -0
- package/src/components/RadarChart/RadarChart.tsx +62 -0
- package/src/components/RadarChart/index.ts +2 -0
- package/src/components/Radio/Radio.tsx +55 -0
- package/src/components/Radio/index.ts +2 -0
- package/src/components/RadioGroup/RadioGroup.tsx +54 -0
- package/src/components/RadioGroup/index.ts +2 -0
- package/src/components/RadioGroupItem/RadioGroupItem.tsx +118 -0
- package/src/components/RadioGroupItem/index.ts +6 -0
- package/src/components/SectionFooter/SectionFooter.tsx +40 -0
- package/src/components/SectionFooter/index.ts +2 -0
- package/src/components/SectionHeader/SectionHeader.tsx +44 -0
- package/src/components/SectionHeader/index.ts +2 -0
- package/src/components/SectionLabel/SectionLabel.tsx +51 -0
- package/src/components/SectionLabel/index.ts +2 -0
- package/src/components/Select/Select.tsx +121 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/SelectMenuItem/SelectMenuItem.tsx +85 -0
- package/src/components/SelectMenuItem/index.ts +2 -0
- package/src/components/SidebarNavigation/SidebarNavigation.tsx +100 -0
- package/src/components/SidebarNavigation/index.ts +2 -0
- package/src/components/SlideOutMenuHeader/SlideOutMenuHeader.tsx +56 -0
- package/src/components/SlideOutMenuHeader/index.ts +2 -0
- package/src/components/Slider/Slider.tsx +125 -0
- package/src/components/Slider/index.ts +2 -0
- package/src/components/SocialButton/SocialButton.tsx +88 -0
- package/src/components/SocialButton/index.ts +2 -0
- package/src/components/StatusIcon/StatusIcon.tsx +75 -0
- package/src/components/StatusIcon/index.ts +2 -0
- package/src/components/StepBase/StepBase.tsx +90 -0
- package/src/components/StepBase/index.ts +2 -0
- package/src/components/StepIconBase/StepIconBase.tsx +65 -0
- package/src/components/StepIconBase/index.ts +7 -0
- package/src/components/TabButtonBase/TabButtonBase.tsx +88 -0
- package/src/components/TabButtonBase/index.ts +2 -0
- package/src/components/TableCell/TableCell.tsx +44 -0
- package/src/components/TableCell/index.ts +2 -0
- package/src/components/TableHeaderCell/TableHeaderCell.tsx +34 -0
- package/src/components/TableHeaderCell/index.ts +2 -0
- package/src/components/TableHeaderLabel/TableHeaderLabel.tsx +37 -0
- package/src/components/TableHeaderLabel/index.ts +2 -0
- package/src/components/Tabs/Tabs.tsx +80 -0
- package/src/components/Tabs/index.ts +2 -0
- package/src/components/Tag/Tag.tsx +91 -0
- package/src/components/Tag/index.ts +2 -0
- package/src/components/TagsInputField/TagsInputField.tsx +90 -0
- package/src/components/TagsInputField/index.ts +2 -0
- package/src/components/TextEditorToolbar/TextEditorToolbar.tsx +33 -0
- package/src/components/TextEditorToolbar/index.ts +2 -0
- package/src/components/TextEditorTooltip/TextEditorTooltip.tsx +28 -0
- package/src/components/TextEditorTooltip/index.ts +2 -0
- package/src/components/TextareaInputField/TextareaInputField.tsx +45 -0
- package/src/components/TextareaInputField/index.ts +2 -0
- package/src/components/Toggle/Toggle.tsx +87 -0
- package/src/components/Toggle/index.ts +2 -0
- package/src/components/Tooltip/Tooltip.tsx +59 -0
- package/src/components/Tooltip/index.ts +2 -0
- package/src/components/TrailingInputField/TrailingInputField.tsx +62 -0
- package/src/components/TrailingInputField/index.ts +2 -0
- package/src/components/TreeView/TreeView.tsx +86 -0
- package/src/components/TreeView/index.ts +2 -0
- package/src/components/TreeViewConnector/TreeViewConnector.tsx +36 -0
- package/src/components/TreeViewConnector/index.ts +2 -0
- package/src/components/TreeViewItem/TreeViewItem.tsx +111 -0
- package/src/components/TreeViewItem/index.ts +2 -0
- package/src/components/VerificationCodeInput/VerificationCodeInput.tsx +114 -0
- package/src/components/VerificationCodeInput/index.ts +2 -0
- package/src/illustrations/BoxIllustration.tsx +13 -0
- package/src/illustrations/CloudIllustration.tsx +18 -0
- package/src/illustrations/CreditCardIllustration.tsx +13 -0
- package/src/illustrations/DocumentsIllustration.tsx +13 -0
- package/src/illustrations/index.ts +4 -0
- package/src/index.ts +147 -0
- package/src/internal/chartTheme.ts +30 -0
- package/src/internal/ringBase.tsx +82 -0
- package/src/styles.css +3 -0
- package/tailwind-preset.js +295 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
|
|
5
|
+
export type HeaderNavigationType = "Simple" | "DualTier" | "Centered" | "Tabs";
|
|
6
|
+
|
|
7
|
+
export interface HeaderNavItem {
|
|
8
|
+
label: ReactNode;
|
|
9
|
+
href?: string;
|
|
10
|
+
active?: boolean;
|
|
11
|
+
onClick?: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface HeaderNavigationProps {
|
|
15
|
+
/** Layout variant. */
|
|
16
|
+
type?: HeaderNavigationType;
|
|
17
|
+
/** Brand mark (logo). Defaults to a text wordmark. */
|
|
18
|
+
logo?: ReactNode;
|
|
19
|
+
/** Primary navigation items. */
|
|
20
|
+
items?: HeaderNavItem[];
|
|
21
|
+
/** Right-aligned actions on desktop (search/settings/notifications/account). */
|
|
22
|
+
actions?: ReactNode;
|
|
23
|
+
/** Content shown at the bottom of the open mobile drawer (e.g. an account card). */
|
|
24
|
+
mobileFooter?: ReactNode;
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function MenuIcon() {
|
|
29
|
+
return (
|
|
30
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
31
|
+
<path d="M2.5 10h15M2.5 5h15M2.5 15h15" stroke="currentColor" strokeWidth="1.667" strokeLinecap="round" strokeLinejoin="round" />
|
|
32
|
+
</svg>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function XIcon() {
|
|
37
|
+
return (
|
|
38
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
39
|
+
<path d="M15 5 5 15M5 5l10 10" stroke="currentColor" strokeWidth="1.667" strokeLinecap="round" strokeLinejoin="round" />
|
|
40
|
+
</svg>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function NavLink({ item, tab }: { item: HeaderNavItem; tab?: boolean }) {
|
|
45
|
+
const className = tab
|
|
46
|
+
? clsx(
|
|
47
|
+
"border-b-2 px-xs pb-lg pt-px text-sm font-semibold",
|
|
48
|
+
item.active
|
|
49
|
+
? "border-fg-brand-primary text-text-brand-secondary"
|
|
50
|
+
: "border-transparent text-text-quaternary hover:text-text-secondary",
|
|
51
|
+
)
|
|
52
|
+
: clsx(
|
|
53
|
+
"rounded-sm px-md py-sm text-sm font-semibold",
|
|
54
|
+
item.active
|
|
55
|
+
? "bg-bg-secondary text-text-secondary-hover"
|
|
56
|
+
: "text-text-secondary hover:bg-bg-secondary",
|
|
57
|
+
);
|
|
58
|
+
if (item.href) {
|
|
59
|
+
return (
|
|
60
|
+
<a href={item.href} onClick={item.onClick} className={className} aria-current={item.active ? "page" : undefined}>
|
|
61
|
+
{item.label}
|
|
62
|
+
</a>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return (
|
|
66
|
+
<button type="button" onClick={item.onClick} className={className}>
|
|
67
|
+
{item.label}
|
|
68
|
+
</button>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Application header navigation. One component with a `type` prop covering the
|
|
74
|
+
* Simple / DualTier / Centered / Tabs layouts. Below `md` it collapses to a
|
|
75
|
+
* logo + hamburger that opens a real (stateful) drawer.
|
|
76
|
+
*/
|
|
77
|
+
export function HeaderNavigation({
|
|
78
|
+
type = "Simple",
|
|
79
|
+
logo = <span className="text-md font-semibold text-text-primary">Untitled UI</span>,
|
|
80
|
+
items = [],
|
|
81
|
+
actions,
|
|
82
|
+
mobileFooter,
|
|
83
|
+
className,
|
|
84
|
+
}: HeaderNavigationProps) {
|
|
85
|
+
const [mobileOpen, setMobileOpen] = useState(false);
|
|
86
|
+
|
|
87
|
+
const navRow = (tabs?: boolean, centered?: boolean) => (
|
|
88
|
+
<nav
|
|
89
|
+
className={clsx(
|
|
90
|
+
"hidden items-center gap-xxs md:flex",
|
|
91
|
+
centered && "flex-1 justify-center",
|
|
92
|
+
)}
|
|
93
|
+
>
|
|
94
|
+
{items.map((item, i) => (
|
|
95
|
+
<NavLink key={i} item={item} tab={tabs} />
|
|
96
|
+
))}
|
|
97
|
+
</nav>
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const desktopActions = actions ? (
|
|
101
|
+
<div className="hidden shrink-0 items-center gap-lg md:flex">{actions}</div>
|
|
102
|
+
) : null;
|
|
103
|
+
|
|
104
|
+
// Top bar — always holds logo (left) + mobile hamburger / desktop actions (right).
|
|
105
|
+
const single = type === "Simple";
|
|
106
|
+
return (
|
|
107
|
+
<header
|
|
108
|
+
className={clsx(
|
|
109
|
+
"w-full border-b border-border-secondary bg-bg-primary font-body",
|
|
110
|
+
className,
|
|
111
|
+
)}
|
|
112
|
+
>
|
|
113
|
+
<div className="mx-auto flex min-h-16 max-w-screen-xl items-center justify-between gap-xl px-container-padding-desktop max-md:px-container-padding-mobile">
|
|
114
|
+
<div className="flex items-center gap-xl">
|
|
115
|
+
<span className="shrink-0">{logo}</span>
|
|
116
|
+
{single ? navRow(false) : null}
|
|
117
|
+
{type === "Centered" ? null : null}
|
|
118
|
+
</div>
|
|
119
|
+
|
|
120
|
+
{/* Centered/DualTier/Tabs put nav on its own row; Simple keeps it inline. */}
|
|
121
|
+
{type === "Centered" ? navRow(false, true) : null}
|
|
122
|
+
|
|
123
|
+
<div className="flex items-center gap-lg">
|
|
124
|
+
{desktopActions}
|
|
125
|
+
<button
|
|
126
|
+
type="button"
|
|
127
|
+
onClick={() => setMobileOpen(true)}
|
|
128
|
+
aria-label="Open menu"
|
|
129
|
+
className="rounded-md p-md text-text-secondary md:hidden"
|
|
130
|
+
>
|
|
131
|
+
<MenuIcon />
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Second tier for non-Simple desktop layouts. */}
|
|
137
|
+
{type === "DualTier" || type === "Tabs" ? (
|
|
138
|
+
<div className="hidden border-t border-border-secondary md:block">
|
|
139
|
+
<div className="mx-auto flex max-w-screen-xl items-center px-container-padding-desktop">
|
|
140
|
+
{navRow(type === "Tabs")}
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
) : null}
|
|
144
|
+
|
|
145
|
+
{/* Mobile drawer — real open/close state. */}
|
|
146
|
+
{mobileOpen ? (
|
|
147
|
+
<div className="fixed inset-0 z-50 flex md:hidden">
|
|
148
|
+
<div
|
|
149
|
+
className="absolute inset-0 bg-bg-overlay/70 backdrop-blur-md"
|
|
150
|
+
onClick={() => setMobileOpen(false)}
|
|
151
|
+
aria-hidden
|
|
152
|
+
/>
|
|
153
|
+
<div className="relative flex h-full w-[296px] max-w-[80%] flex-col justify-between bg-bg-primary shadow-xl">
|
|
154
|
+
<div className="flex flex-col gap-2xl px-xl pb-2xl pt-xl">
|
|
155
|
+
<div className="flex items-center justify-between">
|
|
156
|
+
<span className="shrink-0">{logo}</span>
|
|
157
|
+
<button
|
|
158
|
+
type="button"
|
|
159
|
+
onClick={() => setMobileOpen(false)}
|
|
160
|
+
aria-label="Close menu"
|
|
161
|
+
className="rounded-md p-md text-text-secondary"
|
|
162
|
+
>
|
|
163
|
+
<XIcon />
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
<nav className="flex flex-col gap-px">
|
|
167
|
+
{items.map((item, i) => (
|
|
168
|
+
<NavLink key={i} item={item} />
|
|
169
|
+
))}
|
|
170
|
+
</nav>
|
|
171
|
+
</div>
|
|
172
|
+
{mobileFooter ? <div className="px-xl pb-xl">{mobileFooter}</div> : null}
|
|
173
|
+
</div>
|
|
174
|
+
</div>
|
|
175
|
+
) : null}
|
|
176
|
+
</header>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { Tooltip, type TooltipArrow } from "../Tooltip";
|
|
4
|
+
|
|
5
|
+
export interface HelpIconProps {
|
|
6
|
+
/** Tooltip heading text. */
|
|
7
|
+
text?: ReactNode;
|
|
8
|
+
/** Optional second tooltip line. */
|
|
9
|
+
supportingText?: ReactNode;
|
|
10
|
+
/** Tooltip arrow position (default bottomCenter — tooltip sits above). */
|
|
11
|
+
arrow?: TooltipArrow;
|
|
12
|
+
/** Icon box size in px (default 16). */
|
|
13
|
+
size?: number;
|
|
14
|
+
className?: string;
|
|
15
|
+
"aria-label"?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Help "?" icon that reveals a `Tooltip` on hover/focus. A composition (icon
|
|
20
|
+
* button + `Tooltip`), not a new primitive — the tooltip shows via CSS on
|
|
21
|
+
* `group-hover`/`focus-within`, positioned above the icon.
|
|
22
|
+
*/
|
|
23
|
+
export function HelpIcon({
|
|
24
|
+
text = "This is a tooltip",
|
|
25
|
+
supportingText,
|
|
26
|
+
arrow = "bottomCenter",
|
|
27
|
+
size = 16,
|
|
28
|
+
className,
|
|
29
|
+
"aria-label": ariaLabel = "Help",
|
|
30
|
+
}: HelpIconProps) {
|
|
31
|
+
return (
|
|
32
|
+
<span className={clsx("group relative inline-flex", className)}>
|
|
33
|
+
<button
|
|
34
|
+
type="button"
|
|
35
|
+
aria-label={ariaLabel}
|
|
36
|
+
className="inline-flex items-center justify-center rounded-full text-fg-quaternary outline-none hover:text-fg-secondary focus-visible:ring-2 focus-visible:ring-utility-brand-500"
|
|
37
|
+
style={{ width: size, height: size }}
|
|
38
|
+
>
|
|
39
|
+
<svg viewBox="0 0 16 16" fill="none" className="size-full" aria-hidden>
|
|
40
|
+
<circle cx="8" cy="8" r="6.5" stroke="currentColor" strokeWidth="1.2" />
|
|
41
|
+
<path d="M6.4 6.2a1.6 1.6 0 1 1 2.2 1.5c-.4.2-.6.5-.6.9M8 11h.01" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" />
|
|
42
|
+
</svg>
|
|
43
|
+
</button>
|
|
44
|
+
<span className="pointer-events-none absolute bottom-[calc(100%+8px)] left-1/2 -translate-x-1/2 opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
|
|
45
|
+
<Tooltip text={text} supportingText={supportingText} arrow={arrow} />
|
|
46
|
+
</span>
|
|
47
|
+
</span>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import type { InputHTMLAttributes, ReactNode } from "react";
|
|
3
|
+
import clsx from "clsx";
|
|
4
|
+
import {
|
|
5
|
+
FieldWrapper,
|
|
6
|
+
boxClasses,
|
|
7
|
+
inputSizeClasses,
|
|
8
|
+
type InputFieldSize,
|
|
9
|
+
} from "./inputFieldShared";
|
|
10
|
+
|
|
11
|
+
export type InputFieldType = "default" | "password" | "date-time" | "payment";
|
|
12
|
+
|
|
13
|
+
export interface InputFieldProps
|
|
14
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "type" | "size"> {
|
|
15
|
+
/** Field variant — drives the native input type and default affordances. */
|
|
16
|
+
type?: InputFieldType;
|
|
17
|
+
size?: InputFieldSize;
|
|
18
|
+
label?: ReactNode;
|
|
19
|
+
hint?: ReactNode;
|
|
20
|
+
required?: boolean;
|
|
21
|
+
destructive?: boolean;
|
|
22
|
+
/** Leading icon (overrides the per-type default, e.g. payment card). */
|
|
23
|
+
leadingIcon?: ReactNode;
|
|
24
|
+
/** Trailing icon (overrides the per-type default). */
|
|
25
|
+
trailingIcon?: ReactNode;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function CalendarIcon() {
|
|
29
|
+
return (
|
|
30
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5 text-fg-quaternary" aria-hidden>
|
|
31
|
+
<path d="M17.5 8.333H2.5M13.333 1.667V5M6.667 1.667V5M6.5 18.333h7c1.4 0 2.1 0 2.635-.272a2.5 2.5 0 0 0 1.093-1.093C17.5 16.433 17.5 15.733 17.5 14.333v-7c0-1.4 0-2.1-.272-2.635a2.5 2.5 0 0 0-1.093-1.093C15.6 3.333 14.9 3.333 13.5 3.333h-7c-1.4 0-2.1 0-2.635.272A2.5 2.5 0 0 0 2.772 4.698C2.5 5.233 2.5 5.933 2.5 7.333v7c0 1.4 0 2.1.272 2.635a2.5 2.5 0 0 0 1.093 1.093c.535.272 1.235.272 2.635.272Z" stroke="currentColor" strokeWidth="1.667" strokeLinecap="round" strokeLinejoin="round" />
|
|
32
|
+
</svg>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function CardIcon() {
|
|
37
|
+
return (
|
|
38
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5 text-fg-quaternary" aria-hidden>
|
|
39
|
+
<path d="M18.333 8.333H1.667M1.667 7.167l0-.834c0-1.4 0-2.1.272-2.635a2.5 2.5 0 0 1 1.093-1.093C3.567 2.333 4.267 2.333 5.667 2.333h8.666c1.4 0 2.1 0 2.635.272a2.5 2.5 0 0 1 1.093 1.093c.272.535.272 1.235.272 2.635v7.334c0 1.4 0 2.1-.272 2.635a2.5 2.5 0 0 1-1.093 1.092c-.535.273-1.235.273-2.635.273H5.667c-1.4 0-2.1 0-2.635-.273a2.5 2.5 0 0 1-1.093-1.092C1.667 15.767 1.667 15.067 1.667 13.667v-.834" stroke="currentColor" strokeWidth="1.667" strokeLinecap="round" strokeLinejoin="round" />
|
|
40
|
+
</svg>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function EyeToggle({ shown, onClick }: { shown: boolean; onClick: () => void }) {
|
|
45
|
+
return (
|
|
46
|
+
<button type="button" onClick={onClick} aria-label={shown ? "Hide" : "Show"} className="shrink-0 text-fg-quaternary hover:text-fg-quaternary-hover">
|
|
47
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
48
|
+
<path d="M1.667 10S4.667 4.167 10 4.167 18.333 10 18.333 10 15.333 15.833 10 15.833 1.667 10 1.667 10Z" stroke="currentColor" strokeWidth="1.667" strokeLinecap="round" strokeLinejoin="round" />
|
|
49
|
+
<circle cx="10" cy="10" r="2.5" stroke="currentColor" strokeWidth="1.667" />
|
|
50
|
+
{shown ? <path d="M3 3l14 14" stroke="currentColor" strokeWidth="1.667" strokeLinecap="round" /> : null}
|
|
51
|
+
</svg>
|
|
52
|
+
</button>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const nativeType: Record<InputFieldType, string> = {
|
|
57
|
+
default: "text",
|
|
58
|
+
password: "password",
|
|
59
|
+
"date-time": "datetime-local",
|
|
60
|
+
payment: "text",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Standard text input. `type` covers Default / Password / Date-time / Payment —
|
|
65
|
+
* same structural markup, differing only in the native input type and default
|
|
66
|
+
* leading/trailing affordance (password eye toggle, date-time calendar, payment
|
|
67
|
+
* card). Leading/trailing icons can be overridden via slots.
|
|
68
|
+
*/
|
|
69
|
+
export function InputField({
|
|
70
|
+
type = "default",
|
|
71
|
+
size = "md",
|
|
72
|
+
label,
|
|
73
|
+
hint,
|
|
74
|
+
required,
|
|
75
|
+
destructive,
|
|
76
|
+
leadingIcon,
|
|
77
|
+
trailingIcon,
|
|
78
|
+
className,
|
|
79
|
+
...rest
|
|
80
|
+
}: InputFieldProps) {
|
|
81
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
82
|
+
|
|
83
|
+
const leading =
|
|
84
|
+
leadingIcon ?? (type === "payment" ? <CardIcon /> : null);
|
|
85
|
+
|
|
86
|
+
let trailing = trailingIcon ?? null;
|
|
87
|
+
if (!trailingIcon) {
|
|
88
|
+
if (type === "password")
|
|
89
|
+
trailing = <EyeToggle shown={showPassword} onClick={() => setShowPassword((s) => !s)} />;
|
|
90
|
+
else if (type === "date-time") trailing = <CalendarIcon />;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const resolvedType = type === "password" && showPassword ? "text" : nativeType[type];
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<FieldWrapper label={label} required={required} hint={hint} destructive={destructive} className={className}>
|
|
97
|
+
<div className={clsx(boxClasses(destructive), inputSizeClasses[size])}>
|
|
98
|
+
{leading ? <span className="flex shrink-0 items-center">{leading}</span> : null}
|
|
99
|
+
<input
|
|
100
|
+
type={resolvedType}
|
|
101
|
+
className="min-w-0 flex-1 bg-transparent text-text-primary outline-none placeholder:text-text-placeholder"
|
|
102
|
+
{...rest}
|
|
103
|
+
/>
|
|
104
|
+
{trailing ? <span className="flex shrink-0 items-center">{trailing}</span> : null}
|
|
105
|
+
</div>
|
|
106
|
+
</FieldWrapper>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
// Shared internals for the Input field family (InputField / LeadingInputField /
|
|
5
|
+
// TrailingInputField / TagsInputField). PRIVATE — not exported from the barrel;
|
|
6
|
+
// only the size type is re-exported by the public components.
|
|
7
|
+
|
|
8
|
+
export type InputFieldSize = "sm" | "md" | "lg";
|
|
9
|
+
|
|
10
|
+
/** Inner padding + text size per field size. */
|
|
11
|
+
export const inputSizeClasses: Record<InputFieldSize, string> = {
|
|
12
|
+
sm: "px-lg py-md text-sm",
|
|
13
|
+
md: "px-lg py-md text-md",
|
|
14
|
+
lg: "px-[14px] py-2.5 text-md",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/** Border/focus classes for the bordered input box. */
|
|
18
|
+
export function boxClasses(destructive?: boolean) {
|
|
19
|
+
return clsx(
|
|
20
|
+
"flex w-full items-center gap-md rounded-md border bg-bg-primary shadow-xs",
|
|
21
|
+
"transition-shadow focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-offset-bg-primary",
|
|
22
|
+
destructive
|
|
23
|
+
? "border-border-error focus-within:ring-border-error"
|
|
24
|
+
: "border-border-primary focus-within:border-border-brand focus-within:ring-border-brand",
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FieldWrapperProps {
|
|
29
|
+
label?: ReactNode;
|
|
30
|
+
required?: boolean;
|
|
31
|
+
hint?: ReactNode;
|
|
32
|
+
destructive?: boolean;
|
|
33
|
+
/** The control (bordered box) goes here. */
|
|
34
|
+
children: ReactNode;
|
|
35
|
+
className?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Label + control + hint scaffold shared by every Input field variant. */
|
|
39
|
+
export function FieldWrapper({
|
|
40
|
+
label,
|
|
41
|
+
required,
|
|
42
|
+
hint,
|
|
43
|
+
destructive,
|
|
44
|
+
children,
|
|
45
|
+
className,
|
|
46
|
+
}: FieldWrapperProps) {
|
|
47
|
+
return (
|
|
48
|
+
<div className={clsx("flex w-full flex-col gap-sm font-body", className)}>
|
|
49
|
+
{label ? (
|
|
50
|
+
<span className="flex gap-xxs text-sm font-medium text-text-secondary">
|
|
51
|
+
{label}
|
|
52
|
+
{required ? <span className="text-text-brand-tertiary">*</span> : null}
|
|
53
|
+
</span>
|
|
54
|
+
) : null}
|
|
55
|
+
{children}
|
|
56
|
+
{hint ? (
|
|
57
|
+
<span
|
|
58
|
+
className={clsx(
|
|
59
|
+
"text-sm font-normal",
|
|
60
|
+
destructive ? "text-text-error-primary" : "text-text-tertiary",
|
|
61
|
+
)}
|
|
62
|
+
>
|
|
63
|
+
{hint}
|
|
64
|
+
</span>
|
|
65
|
+
) : null}
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { InputHTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { FieldWrapper, type InputFieldSize } from "../InputField/inputFieldShared";
|
|
4
|
+
|
|
5
|
+
export interface LeadingInputFieldProps
|
|
6
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "prefix"> {
|
|
7
|
+
/**
|
|
8
|
+
* Left add-on — a text prefix (e.g. "https://") or a leading dropdown.
|
|
9
|
+
* Covers the Leading text / Leading dropdown types.
|
|
10
|
+
*/
|
|
11
|
+
prefix?: ReactNode;
|
|
12
|
+
size?: InputFieldSize;
|
|
13
|
+
label?: ReactNode;
|
|
14
|
+
hint?: ReactNode;
|
|
15
|
+
required?: boolean;
|
|
16
|
+
destructive?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const pad: Record<InputFieldSize, string> = {
|
|
20
|
+
sm: "px-lg py-md text-sm",
|
|
21
|
+
md: "px-lg py-md text-md",
|
|
22
|
+
lg: "px-[14px] py-2.5 text-md",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Input with a left add-on (text prefix or dropdown). */
|
|
26
|
+
export function LeadingInputField({
|
|
27
|
+
prefix,
|
|
28
|
+
size = "md",
|
|
29
|
+
label,
|
|
30
|
+
hint,
|
|
31
|
+
required,
|
|
32
|
+
destructive,
|
|
33
|
+
className,
|
|
34
|
+
...rest
|
|
35
|
+
}: LeadingInputFieldProps) {
|
|
36
|
+
return (
|
|
37
|
+
<FieldWrapper label={label} required={required} hint={hint} destructive={destructive} className={className}>
|
|
38
|
+
<div
|
|
39
|
+
className={clsx(
|
|
40
|
+
"flex w-full items-stretch overflow-hidden rounded-md border bg-bg-primary shadow-xs",
|
|
41
|
+
"focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-offset-bg-primary",
|
|
42
|
+
destructive
|
|
43
|
+
? "border-border-error focus-within:ring-border-error"
|
|
44
|
+
: "border-border-primary focus-within:border-border-brand focus-within:ring-border-brand",
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
<span className="flex shrink-0 items-center border-r border-border-primary px-lg text-md text-text-tertiary">
|
|
48
|
+
{prefix}
|
|
49
|
+
</span>
|
|
50
|
+
<input
|
|
51
|
+
className={clsx(
|
|
52
|
+
"min-w-0 flex-1 bg-transparent text-text-primary outline-none placeholder:text-text-placeholder",
|
|
53
|
+
pad[size],
|
|
54
|
+
)}
|
|
55
|
+
{...rest}
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
</FieldWrapper>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ResponsiveContainer,
|
|
4
|
+
LineChart,
|
|
5
|
+
BarChart,
|
|
6
|
+
Line,
|
|
7
|
+
Bar,
|
|
8
|
+
XAxis,
|
|
9
|
+
YAxis,
|
|
10
|
+
CartesianGrid,
|
|
11
|
+
Tooltip,
|
|
12
|
+
} from "recharts";
|
|
13
|
+
import { ChartTooltip } from "../ChartTooltip";
|
|
14
|
+
import { ChartLegend } from "../ChartLegend";
|
|
15
|
+
import { chartColor, CHART_GRID, tickStyle } from "../../internal/chartTheme";
|
|
16
|
+
|
|
17
|
+
export type ChartStyle = "line" | "bar";
|
|
18
|
+
export type ChartLegendPosition = "none" | "top" | "right";
|
|
19
|
+
|
|
20
|
+
export interface ChartSeries {
|
|
21
|
+
/** Key into each data row. */
|
|
22
|
+
dataKey: string;
|
|
23
|
+
/** Display name (legend/tooltip). Defaults to `dataKey`. */
|
|
24
|
+
name?: string;
|
|
25
|
+
/** Override color; defaults to the palette by index. */
|
|
26
|
+
color?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface LineAndBarChartProps {
|
|
30
|
+
data: Record<string, number | string>[];
|
|
31
|
+
series: ChartSeries[];
|
|
32
|
+
/** Key for the x-axis category. */
|
|
33
|
+
xKey: string;
|
|
34
|
+
type?: ChartStyle;
|
|
35
|
+
legend?: ChartLegendPosition;
|
|
36
|
+
axisLabels?: boolean;
|
|
37
|
+
height?: number;
|
|
38
|
+
className?: string;
|
|
39
|
+
/** Tooltip value formatter. */
|
|
40
|
+
formatValue?: (value: number | string | undefined) => ReactNode;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Line or bar chart — a themed Recharts wrapper (not a from-scratch renderer).
|
|
45
|
+
* `series` map to `<Line>`/`<Bar>` colored from the shared palette; `legend`
|
|
46
|
+
* positions a `ChartLegend` top/right; `axisLabels` toggles the X/Y ticks.
|
|
47
|
+
* Recharts is an optional peer dependency.
|
|
48
|
+
*/
|
|
49
|
+
export function LineAndBarChart({
|
|
50
|
+
data,
|
|
51
|
+
series,
|
|
52
|
+
xKey,
|
|
53
|
+
type = "line",
|
|
54
|
+
legend = "top",
|
|
55
|
+
axisLabels = true,
|
|
56
|
+
height = 240,
|
|
57
|
+
className,
|
|
58
|
+
formatValue,
|
|
59
|
+
}: LineAndBarChartProps) {
|
|
60
|
+
const colored = series.map((s, i) => ({ ...s, color: s.color ?? chartColor(i) }));
|
|
61
|
+
const Chart = type === "bar" ? BarChart : LineChart;
|
|
62
|
+
|
|
63
|
+
const legendEl =
|
|
64
|
+
legend !== "none" ? (
|
|
65
|
+
<ChartLegend
|
|
66
|
+
orientation={legend === "right" ? "vertical" : "horizontal"}
|
|
67
|
+
items={colored.map((s) => ({ label: s.name ?? s.dataKey, color: s.color! }))}
|
|
68
|
+
/>
|
|
69
|
+
) : null;
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div className={className}>
|
|
73
|
+
{legend === "top" && <div className="mb-lg">{legendEl}</div>}
|
|
74
|
+
<div className={legend === "right" ? "flex items-center gap-2xl" : undefined}>
|
|
75
|
+
<div className="min-w-0 flex-1">
|
|
76
|
+
<ResponsiveContainer width="100%" height={height}>
|
|
77
|
+
<Chart data={data} margin={{ top: 8, right: 8, bottom: 8, left: 8 }}>
|
|
78
|
+
<CartesianGrid stroke={CHART_GRID} vertical={false} />
|
|
79
|
+
{axisLabels && <XAxis dataKey={xKey} tick={tickStyle} tickLine={false} axisLine={{ stroke: CHART_GRID }} />}
|
|
80
|
+
{axisLabels && <YAxis tick={tickStyle} tickLine={false} axisLine={false} width={36} />}
|
|
81
|
+
<Tooltip cursor={{ fill: CHART_GRID }} content={<ChartTooltip formatValue={formatValue} />} />
|
|
82
|
+
{colored.map((s) =>
|
|
83
|
+
type === "bar" ? (
|
|
84
|
+
<Bar key={s.dataKey} dataKey={s.dataKey} name={s.name ?? s.dataKey} fill={s.color} radius={[4, 4, 0, 0]} />
|
|
85
|
+
) : (
|
|
86
|
+
<Line key={s.dataKey} dataKey={s.dataKey} name={s.name ?? s.dataKey} stroke={s.color} strokeWidth={2} dot={false} />
|
|
87
|
+
),
|
|
88
|
+
)}
|
|
89
|
+
</Chart>
|
|
90
|
+
</ResponsiveContainer>
|
|
91
|
+
</div>
|
|
92
|
+
{legend === "right" && legendEl}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { MessageShell, bubbleClasses } from "../Message/messageShared";
|
|
4
|
+
|
|
5
|
+
export type LinkMessageType = "minimal" | "preview";
|
|
6
|
+
|
|
7
|
+
export interface LinkMessageProps {
|
|
8
|
+
/** `minimal` = link only; `preview` = open-graph image + link. */
|
|
9
|
+
type?: LinkMessageType;
|
|
10
|
+
/** The URL (rendered as an underlined link). */
|
|
11
|
+
url?: string;
|
|
12
|
+
/** Open-graph image source (preview). */
|
|
13
|
+
image?: string;
|
|
14
|
+
/** Optional body text shown above the link. */
|
|
15
|
+
children?: ReactNode;
|
|
16
|
+
author?: ReactNode;
|
|
17
|
+
time?: ReactNode;
|
|
18
|
+
avatar?: ReactNode;
|
|
19
|
+
sent?: boolean;
|
|
20
|
+
reactions?: ReactNode;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** A chat message that unfurls a URL — minimal (link only) or preview (with OG image). */
|
|
25
|
+
export function LinkMessage({
|
|
26
|
+
type = "preview",
|
|
27
|
+
url = "",
|
|
28
|
+
image,
|
|
29
|
+
children,
|
|
30
|
+
author,
|
|
31
|
+
time,
|
|
32
|
+
avatar,
|
|
33
|
+
sent = false,
|
|
34
|
+
reactions,
|
|
35
|
+
className,
|
|
36
|
+
}: LinkMessageProps) {
|
|
37
|
+
return (
|
|
38
|
+
<MessageShell author={author} time={time} avatar={avatar} sent={sent} reactions={reactions} className={className}>
|
|
39
|
+
<div className={clsx(bubbleClasses(sent, "secondary"), "flex flex-col gap-sm p-lg")}>
|
|
40
|
+
{children ? <span className="text-md font-normal">{children}</span> : null}
|
|
41
|
+
{type === "preview" && image ? (
|
|
42
|
+
<span className="block aspect-[1200/630] w-full overflow-hidden rounded-md border-[0.5px] border-black/10">
|
|
43
|
+
<img src={image} alt="" className="size-full object-cover" />
|
|
44
|
+
</span>
|
|
45
|
+
) : null}
|
|
46
|
+
<a href={url} className="truncate text-md text-text-brand-secondary underline">
|
|
47
|
+
{url}
|
|
48
|
+
</a>
|
|
49
|
+
</div>
|
|
50
|
+
</MessageShell>
|
|
51
|
+
);
|
|
52
|
+
}
|