@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,60 @@
|
|
|
1
|
+
import { type HTMLAttributes } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { CarouselArrow, type CarouselArrowSize } from "../CarouselArrow";
|
|
4
|
+
import { PaginationDotGroup } from "../PaginationDotGroup";
|
|
5
|
+
|
|
6
|
+
export interface CarouselImageProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
imageUrl: string;
|
|
8
|
+
size?: CarouselArrowSize;
|
|
9
|
+
/** Total slides (drives the dot pager). */
|
|
10
|
+
count?: number;
|
|
11
|
+
/** Active slide index. */
|
|
12
|
+
current?: number;
|
|
13
|
+
onPrev?: () => void;
|
|
14
|
+
onNext?: () => void;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Carousel slide — a full-bleed image with overlaid prev/next `CarouselArrow`s
|
|
19
|
+
* and a bottom-centre `PaginationDotGroup` (framed). Aspect 8:5. Composition
|
|
20
|
+
* over the arrow + dot-group primitives.
|
|
21
|
+
*/
|
|
22
|
+
export function CarouselImage({
|
|
23
|
+
imageUrl,
|
|
24
|
+
size = "md",
|
|
25
|
+
count = 3,
|
|
26
|
+
current = 0,
|
|
27
|
+
onPrev,
|
|
28
|
+
onNext,
|
|
29
|
+
className,
|
|
30
|
+
...rest
|
|
31
|
+
}: CarouselImageProps) {
|
|
32
|
+
const inset = size === "lg" ? "left-5 right-5" : "left-4 right-4";
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
className={clsx("relative aspect-[8/5] w-full overflow-hidden rounded-xl", className)}
|
|
36
|
+
{...rest}
|
|
37
|
+
>
|
|
38
|
+
<img src={imageUrl} alt="" className="absolute inset-0 size-full object-cover" />
|
|
39
|
+
<CarouselArrow
|
|
40
|
+
direction="left"
|
|
41
|
+
size={size}
|
|
42
|
+
onClick={onPrev}
|
|
43
|
+
className={clsx("absolute top-1/2 -translate-y-1/2", inset.split(" ")[0])}
|
|
44
|
+
/>
|
|
45
|
+
<CarouselArrow
|
|
46
|
+
direction="right"
|
|
47
|
+
size={size}
|
|
48
|
+
onClick={onNext}
|
|
49
|
+
className={clsx("absolute top-1/2 -translate-y-1/2", inset.split(" ")[1])}
|
|
50
|
+
/>
|
|
51
|
+
<PaginationDotGroup
|
|
52
|
+
framed
|
|
53
|
+
size={size}
|
|
54
|
+
count={count}
|
|
55
|
+
current={current}
|
|
56
|
+
className="absolute bottom-4 left-1/2 -translate-x-1/2"
|
|
57
|
+
/>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type ChangeType = "01" | "02" | "03";
|
|
5
|
+
export type ChangeTrend = "positive" | "negative";
|
|
6
|
+
|
|
7
|
+
export interface ChangeProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
/** 01 = plain arrow · 02 = trend line · 03 = bordered pill (neutral text). */
|
|
9
|
+
type?: ChangeType;
|
|
10
|
+
trend?: ChangeTrend;
|
|
11
|
+
/** The delta value, e.g. "100%". */
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const Stroke = ({ className, d }: { className?: string; d: string }) => (
|
|
16
|
+
<svg className={className} viewBox="0 0 16 16" fill="none" aria-hidden>
|
|
17
|
+
<path d={d} stroke="currentColor" strokeWidth="1.33" strokeLinecap="round" strokeLinejoin="round" />
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const ICONS: Record<ChangeType, Record<ChangeTrend, ReactNode>> = {
|
|
22
|
+
"01": {
|
|
23
|
+
positive: <Stroke className="size-4" d="M8 12.67V3.33M8 3.33 3.33 8M8 3.33 12.67 8" />,
|
|
24
|
+
negative: <Stroke className="size-4" d="M8 3.33v9.34M8 12.67 3.33 8M8 12.67 12.67 8" />,
|
|
25
|
+
},
|
|
26
|
+
"02": {
|
|
27
|
+
positive: <Stroke className="size-4" d="M14.67 4.67 9 10.33 6.33 7.67 1.33 12.67M14.67 4.67h-4M14.67 4.67v4" />,
|
|
28
|
+
negative: <Stroke className="size-4" d="M14.67 11.33 9 5.67 6.33 8.33 1.33 3.33M14.67 11.33h-4M14.67 11.33v-4" />,
|
|
29
|
+
},
|
|
30
|
+
"03": {
|
|
31
|
+
positive: <Stroke className="size-3" d="M4.5 11.5 11.5 4.5M11.5 4.5h-6M11.5 4.5v6" />,
|
|
32
|
+
negative: <Stroke className="size-3" d="M4.5 4.5 11.5 11.5M11.5 11.5h-6M11.5 11.5v-6" />,
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Trend delta chip used by `MetricItem` (and standalone). Three shapes:
|
|
38
|
+
* - **01** arrow + value, success/error tinted.
|
|
39
|
+
* - **02** trend-line + value, success/error tinted.
|
|
40
|
+
* - **03** bordered pill, diagonal arrow, **neutral** text regardless of trend.
|
|
41
|
+
*/
|
|
42
|
+
export function Change({
|
|
43
|
+
type = "01",
|
|
44
|
+
trend = "positive",
|
|
45
|
+
children,
|
|
46
|
+
className,
|
|
47
|
+
...rest
|
|
48
|
+
}: ChangeProps) {
|
|
49
|
+
const tint =
|
|
50
|
+
type === "03"
|
|
51
|
+
? "text-text-secondary"
|
|
52
|
+
: trend === "positive"
|
|
53
|
+
? "text-text-success-primary"
|
|
54
|
+
: "text-text-error-primary";
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div
|
|
58
|
+
className={clsx(
|
|
59
|
+
"inline-flex items-center",
|
|
60
|
+
type === "03" &&
|
|
61
|
+
"gap-xs rounded-sm border border-border-primary bg-bg-primary py-xxs pl-sm pr-md shadow-xs",
|
|
62
|
+
type === "02" && "justify-center gap-xs",
|
|
63
|
+
type === "01" && "justify-center gap-xxs",
|
|
64
|
+
tint,
|
|
65
|
+
className,
|
|
66
|
+
)}
|
|
67
|
+
{...rest}
|
|
68
|
+
>
|
|
69
|
+
{ICONS[type][trend]}
|
|
70
|
+
<span className="text-sm font-medium">{children}</span>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export interface ChartLegendItem {
|
|
5
|
+
label: ReactNode;
|
|
6
|
+
color: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ChartLegendProps {
|
|
10
|
+
items: ChartLegendItem[];
|
|
11
|
+
/** Layout direction — `horizontal` (top) or `vertical` (right). */
|
|
12
|
+
orientation?: "horizontal" | "vertical";
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Chart legend — a row (or column) of colored-dot + label entries. Use standalone
|
|
18
|
+
* or feed Recharts' `<Legend content={...} />`. `color` is a real CSS color (the
|
|
19
|
+
* series color), matching the `CHART_COLORS` palette.
|
|
20
|
+
*/
|
|
21
|
+
export function ChartLegend({ items, orientation = "horizontal", className }: ChartLegendProps) {
|
|
22
|
+
return (
|
|
23
|
+
<ul
|
|
24
|
+
className={clsx(
|
|
25
|
+
"flex font-body",
|
|
26
|
+
orientation === "vertical" ? "flex-col gap-md" : "flex-wrap items-center gap-lg",
|
|
27
|
+
className,
|
|
28
|
+
)}
|
|
29
|
+
>
|
|
30
|
+
{items.map((item, i) => (
|
|
31
|
+
<li key={i} className="flex items-center gap-sm text-sm text-text-tertiary">
|
|
32
|
+
<span className="size-2.5 shrink-0 rounded-full" style={{ backgroundColor: item.color }} aria-hidden />
|
|
33
|
+
{item.label}
|
|
34
|
+
</li>
|
|
35
|
+
))}
|
|
36
|
+
</ul>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
|
|
3
|
+
export type ChartMarkerLine = "solid" | "dashed" | "none";
|
|
4
|
+
|
|
5
|
+
export interface ChartMarkerProps {
|
|
6
|
+
/** Primary value shown in the bubble (e.g. "1,218"). */
|
|
7
|
+
value: string;
|
|
8
|
+
/** Optional secondary line (e.g. "Jan 10, 2027"). */
|
|
9
|
+
date?: string;
|
|
10
|
+
/** The drop line beneath the marker. */
|
|
11
|
+
line?: ChartMarkerLine;
|
|
12
|
+
/** Drop-line height. Defaults to h-32 (128px). */
|
|
13
|
+
lineHeightClass?: string;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function ChartMarker({
|
|
18
|
+
value,
|
|
19
|
+
date,
|
|
20
|
+
line = "solid",
|
|
21
|
+
lineHeightClass = "h-32",
|
|
22
|
+
className,
|
|
23
|
+
}: ChartMarkerProps) {
|
|
24
|
+
return (
|
|
25
|
+
<div className={clsx("inline-flex flex-col items-center font-body", className)}>
|
|
26
|
+
{/* Tooltip-style bubble */}
|
|
27
|
+
<div className="rounded-md bg-bg-primary-solid px-md py-xs text-center shadow-lg">
|
|
28
|
+
<div className="text-xs font-semibold text-text-white">{value}</div>
|
|
29
|
+
{/*
|
|
30
|
+
TOKEN NOTE: the source colour is `Component colors/Components/Tooltips/
|
|
31
|
+
tooltip-supporting-text` (#d4d4d4). It's approximated here as
|
|
32
|
+
`utility-neutral-300`, which is an exact HEX match but a SEMANTIC
|
|
33
|
+
mismatch (a tooltip-specific token mapped onto a generic utility
|
|
34
|
+
shade). If a real `tooltip-supporting-text` token is added to the
|
|
35
|
+
preset later (e.g. when Tooltip is built), swap this for it.
|
|
36
|
+
*/}
|
|
37
|
+
{date ? (
|
|
38
|
+
<div className="text-xs font-medium text-utility-neutral-300">{date}</div>
|
|
39
|
+
) : null}
|
|
40
|
+
</div>
|
|
41
|
+
{/* Marker dot (ring) */}
|
|
42
|
+
<span className="mt-xs size-3 rounded-full border-2 border-utility-brand-600 bg-bg-primary" aria-hidden />
|
|
43
|
+
{/* Drop line */}
|
|
44
|
+
{line === "solid" ? (
|
|
45
|
+
<span className={clsx("w-0.5 bg-utility-brand-600", lineHeightClass)} aria-hidden />
|
|
46
|
+
) : line === "dashed" ? (
|
|
47
|
+
<span
|
|
48
|
+
className={clsx("border-l-2 border-dashed border-utility-brand-600", lineHeightClass)}
|
|
49
|
+
aria-hidden
|
|
50
|
+
/>
|
|
51
|
+
) : null}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type CSSProperties } from "react";
|
|
2
|
+
import { Line, LineChart, ResponsiveContainer } from "recharts";
|
|
3
|
+
|
|
4
|
+
export interface ChartMiniDatum {
|
|
5
|
+
value: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type ChartMiniTrend = "positive" | "negative" | "neutral";
|
|
9
|
+
|
|
10
|
+
export interface ChartMiniProps {
|
|
11
|
+
/** Series points — only `value` is read. */
|
|
12
|
+
data: ChartMiniDatum[];
|
|
13
|
+
/**
|
|
14
|
+
* Stroke colour. A design-token name (e.g. `"utility-brand-500"`,
|
|
15
|
+
* `"utility-success-500"`) resolved to its hex, or a raw `#hex`. Defaults to
|
|
16
|
+
* the `trend` colour (`utility-brand-500` for neutral).
|
|
17
|
+
*/
|
|
18
|
+
color?: string;
|
|
19
|
+
/** Drives the default stroke colour when `color` is omitted. */
|
|
20
|
+
trend?: ChartMiniTrend;
|
|
21
|
+
/** Container size (px). Figma sparkline is 112×56. */
|
|
22
|
+
width?: number | string;
|
|
23
|
+
height?: number;
|
|
24
|
+
className?: string;
|
|
25
|
+
style?: CSSProperties;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Token hex values mirrored from `tailwind-preset.js`. Recharts colour props
|
|
30
|
+
* take hex strings (not Tailwind class names), so the chart palette is resolved
|
|
31
|
+
* here. Keep in sync with the preset's `utility-*` ramp.
|
|
32
|
+
*/
|
|
33
|
+
const TOKEN_HEX: Record<string, string> = {
|
|
34
|
+
"utility-brand-400": "#b692f6",
|
|
35
|
+
"utility-brand-500": "#9e77ed",
|
|
36
|
+
"utility-brand-600": "#7f56d9",
|
|
37
|
+
"utility-brand-700": "#6941c6",
|
|
38
|
+
"utility-success-500": "#22c55e",
|
|
39
|
+
"utility-error-500": "#dc2626",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const TREND_COLOR: Record<ChartMiniTrend, string> = {
|
|
43
|
+
positive: TOKEN_HEX["utility-success-500"],
|
|
44
|
+
negative: TOKEN_HEX["utility-error-500"],
|
|
45
|
+
neutral: TOKEN_HEX["utility-brand-500"],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function resolveColor(color: string | undefined, trend: ChartMiniTrend): string {
|
|
49
|
+
if (!color) return TREND_COLOR[trend];
|
|
50
|
+
if (color.startsWith("#")) return color;
|
|
51
|
+
return TOKEN_HEX[color] ?? TREND_COLOR[trend];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Sparkline — a thin Recharts `LineChart` wrapper. No axes, legend, tooltip or
|
|
56
|
+
* dots: line only. Consumed by `MetricItem`'s `chart` slot. Recharts is an
|
|
57
|
+
* optional peer dependency — install it in the consuming app to use this.
|
|
58
|
+
*/
|
|
59
|
+
export function ChartMini({
|
|
60
|
+
data,
|
|
61
|
+
color,
|
|
62
|
+
trend = "neutral",
|
|
63
|
+
width = 112,
|
|
64
|
+
height = 56,
|
|
65
|
+
className,
|
|
66
|
+
style,
|
|
67
|
+
}: ChartMiniProps) {
|
|
68
|
+
const stroke = resolveColor(color, trend);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className={className} style={{ width, height, ...style }}>
|
|
72
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
73
|
+
<LineChart data={data} margin={{ top: 4, right: 4, bottom: 4, left: 4 }}>
|
|
74
|
+
<Line
|
|
75
|
+
type="monotone"
|
|
76
|
+
dataKey="value"
|
|
77
|
+
stroke={stroke}
|
|
78
|
+
strokeWidth={2}
|
|
79
|
+
dot={false}
|
|
80
|
+
isAnimationActive={false}
|
|
81
|
+
/>
|
|
82
|
+
</LineChart>
|
|
83
|
+
</ResponsiveContainer>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
|
|
3
|
+
export interface ChartTooltipPayloadItem {
|
|
4
|
+
name?: string;
|
|
5
|
+
value?: number | string;
|
|
6
|
+
color?: string;
|
|
7
|
+
dataKey?: string | number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface ChartTooltipProps {
|
|
11
|
+
/** Recharts passes this when the tooltip is shown. */
|
|
12
|
+
active?: boolean;
|
|
13
|
+
/** Series entries at the hovered point (Recharts shape). */
|
|
14
|
+
payload?: ChartTooltipPayloadItem[];
|
|
15
|
+
/** The category/x label at the hovered point. */
|
|
16
|
+
label?: ReactNode;
|
|
17
|
+
/** Format a value for display. */
|
|
18
|
+
formatValue?: (value: number | string | undefined) => ReactNode;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Themed tooltip card for the Recharts chart wrappers — pass as the chart's
|
|
23
|
+
* `<Tooltip content={<ChartTooltip />} />`. Renders a `bg-primary` card with the
|
|
24
|
+
* point label and one colored-dot row per series. Returns null when inactive.
|
|
25
|
+
*/
|
|
26
|
+
export function ChartTooltip({ active, payload, label, formatValue }: ChartTooltipProps) {
|
|
27
|
+
if (!active || !payload?.length) return null;
|
|
28
|
+
return (
|
|
29
|
+
<div className="rounded-md border border-border-secondary-alt bg-bg-primary px-lg py-md font-body shadow-lg">
|
|
30
|
+
{label != null && <div className="mb-xs text-xs font-semibold text-text-secondary">{label}</div>}
|
|
31
|
+
<div className="flex flex-col gap-xxs">
|
|
32
|
+
{payload.map((item, i) => (
|
|
33
|
+
<div key={i} className="flex items-center gap-sm text-xs">
|
|
34
|
+
<span className="size-2 shrink-0 rounded-full" style={{ backgroundColor: item.color }} aria-hidden />
|
|
35
|
+
<span className="text-text-tertiary">{item.name ?? item.dataKey}</span>
|
|
36
|
+
<span className="ml-auto font-semibold text-text-primary">
|
|
37
|
+
{formatValue ? formatValue(item.value) : item.value}
|
|
38
|
+
</span>
|
|
39
|
+
</div>
|
|
40
|
+
))}
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { CheckControlVisual, type CheckControlSize } from "./checkboxBase";
|
|
4
|
+
|
|
5
|
+
export interface CheckboxProps
|
|
6
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
|
|
7
|
+
size?: CheckControlSize;
|
|
8
|
+
/** Indeterminate (mixed) visual state. */
|
|
9
|
+
indeterminate?: boolean;
|
|
10
|
+
label?: string;
|
|
11
|
+
supportingText?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const textSize: Record<CheckControlSize, string> = { sm: "text-sm", md: "text-md" };
|
|
15
|
+
|
|
16
|
+
export function Checkbox({
|
|
17
|
+
size = "sm",
|
|
18
|
+
indeterminate = false,
|
|
19
|
+
checked,
|
|
20
|
+
disabled,
|
|
21
|
+
label,
|
|
22
|
+
supportingText,
|
|
23
|
+
className,
|
|
24
|
+
...rest
|
|
25
|
+
}: CheckboxProps) {
|
|
26
|
+
return (
|
|
27
|
+
<label
|
|
28
|
+
className={clsx(
|
|
29
|
+
"inline-flex items-start gap-md font-body",
|
|
30
|
+
disabled ? "cursor-not-allowed opacity-70" : "cursor-pointer",
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
<input
|
|
35
|
+
type="checkbox"
|
|
36
|
+
checked={checked}
|
|
37
|
+
disabled={disabled}
|
|
38
|
+
className="peer sr-only"
|
|
39
|
+
aria-checked={indeterminate ? "mixed" : checked}
|
|
40
|
+
{...rest}
|
|
41
|
+
/>
|
|
42
|
+
<CheckControlVisual
|
|
43
|
+
type="checkbox"
|
|
44
|
+
checked={checked}
|
|
45
|
+
indeterminate={indeterminate}
|
|
46
|
+
size={size}
|
|
47
|
+
disabled={disabled}
|
|
48
|
+
/>
|
|
49
|
+
{label || supportingText ? (
|
|
50
|
+
<span className="flex flex-col">
|
|
51
|
+
{label ? (
|
|
52
|
+
<span className={clsx("font-medium text-text-secondary", textSize[size])}>
|
|
53
|
+
{label}
|
|
54
|
+
</span>
|
|
55
|
+
) : null}
|
|
56
|
+
{supportingText ? (
|
|
57
|
+
<span className={clsx("font-normal text-text-tertiary", textSize[size])}>
|
|
58
|
+
{supportingText}
|
|
59
|
+
</span>
|
|
60
|
+
) : null}
|
|
61
|
+
</span>
|
|
62
|
+
) : null}
|
|
63
|
+
</label>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
|
|
3
|
+
export type CheckControlSize = "sm" | "md";
|
|
4
|
+
export type CheckControlType = "checkbox" | "radio";
|
|
5
|
+
|
|
6
|
+
const boxSize: Record<CheckControlSize, string> = {
|
|
7
|
+
sm: "size-4", // 16px
|
|
8
|
+
md: "size-5", // 20px
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const glyphSize: Record<CheckControlSize, string> = {
|
|
12
|
+
sm: "size-3",
|
|
13
|
+
md: "size-3.5",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export interface CheckControlVisualProps {
|
|
17
|
+
type: CheckControlType;
|
|
18
|
+
checked?: boolean;
|
|
19
|
+
indeterminate?: boolean;
|
|
20
|
+
size?: CheckControlSize;
|
|
21
|
+
disabled?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Shared visual control for Checkbox and Radio (the `_Checkbox base` in Figma).
|
|
26
|
+
* Renders the box/circle + glyph from props; the wrapping component owns the
|
|
27
|
+
* native input, label, and events. Place it immediately AFTER a `peer` input so
|
|
28
|
+
* the focus ring (peer-focus-visible) works.
|
|
29
|
+
*/
|
|
30
|
+
export function CheckControlVisual({
|
|
31
|
+
type,
|
|
32
|
+
checked = false,
|
|
33
|
+
indeterminate = false,
|
|
34
|
+
size = "sm",
|
|
35
|
+
disabled = false,
|
|
36
|
+
}: CheckControlVisualProps) {
|
|
37
|
+
const active = checked || indeterminate;
|
|
38
|
+
return (
|
|
39
|
+
<span
|
|
40
|
+
aria-hidden
|
|
41
|
+
className={clsx(
|
|
42
|
+
"flex shrink-0 items-center justify-center border transition-colors",
|
|
43
|
+
"peer-focus-visible:ring-2 peer-focus-visible:ring-border-brand peer-focus-visible:ring-offset-2 peer-focus-visible:ring-offset-bg-primary",
|
|
44
|
+
type === "radio" ? "rounded-full" : "rounded-sm",
|
|
45
|
+
boxSize[size],
|
|
46
|
+
disabled
|
|
47
|
+
? "border-border-primary bg-bg-tertiary"
|
|
48
|
+
: active
|
|
49
|
+
? "border-brand-solid bg-brand-solid"
|
|
50
|
+
: "border-border-primary bg-bg-primary",
|
|
51
|
+
)}
|
|
52
|
+
>
|
|
53
|
+
{type === "checkbox" && checked && !indeterminate ? (
|
|
54
|
+
<svg viewBox="0 0 16 16" fill="none" className={glyphSize[size]} aria-hidden>
|
|
55
|
+
<path
|
|
56
|
+
d="m13 4-7 7-3-3"
|
|
57
|
+
stroke="currentColor"
|
|
58
|
+
strokeWidth="2"
|
|
59
|
+
strokeLinecap="round"
|
|
60
|
+
strokeLinejoin="round"
|
|
61
|
+
className="text-white"
|
|
62
|
+
/>
|
|
63
|
+
</svg>
|
|
64
|
+
) : null}
|
|
65
|
+
{type === "checkbox" && indeterminate ? (
|
|
66
|
+
<svg viewBox="0 0 16 16" fill="none" className={glyphSize[size]} aria-hidden>
|
|
67
|
+
<path d="M4 8h8" stroke="currentColor" strokeWidth="2" strokeLinecap="round" className="text-white" />
|
|
68
|
+
</svg>
|
|
69
|
+
) : null}
|
|
70
|
+
{type === "radio" && checked ? (
|
|
71
|
+
<span className={clsx("rounded-full bg-white", size === "sm" ? "size-1.5" : "size-2")} />
|
|
72
|
+
) : null}
|
|
73
|
+
</span>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface CheckControlLabelProps {
|
|
78
|
+
label?: string;
|
|
79
|
+
supportingText?: string;
|
|
80
|
+
size?: CheckControlSize;
|
|
81
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export interface CodeSnippetProps {
|
|
5
|
+
/** Raw code string. Rendered verbatim — see the highlighting note below. */
|
|
6
|
+
code: string;
|
|
7
|
+
/** Optional language label (shown in the toolbar). */
|
|
8
|
+
language?: string;
|
|
9
|
+
/** Show the line-number gutter. */
|
|
10
|
+
showLineNumbers?: boolean;
|
|
11
|
+
/** Override the copy handler (defaults to navigator.clipboard). */
|
|
12
|
+
onCopy?: (code: string) => void;
|
|
13
|
+
className?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/*
|
|
17
|
+
SCOPE NOTE: this is the DS *shell* only — dark container, toolbar (copy +
|
|
18
|
+
expand), and an optional line-number gutter. It renders `code` as plain
|
|
19
|
+
monospace text. SYNTAX HIGHLIGHTING IS NOT IMPLEMENTED HERE: Untitled UI's
|
|
20
|
+
snippet uses per-token colours (utility-blue/green/pink/red-600), which means
|
|
21
|
+
real tokenization via a library (Prism / Shiki / highlight.js) rather than
|
|
22
|
+
hand-rolled regex. Treat the <code> body as the plug-point: wrap or replace it
|
|
23
|
+
with a highlighter's output once a library is chosen (same "wrap a lib" caution
|
|
24
|
+
flagged in figma-map.md). Tabs (npm/yarn/bun) variant is also not modelled yet.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
function CopyIcon() {
|
|
28
|
+
return (
|
|
29
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-4" aria-hidden>
|
|
30
|
+
<path
|
|
31
|
+
d="M6.667 6.667V4.333c0-.92.746-1.666 1.666-1.666h7.334c.92 0 1.666.746 1.666 1.666v7.334c0 .92-.746 1.666-1.666 1.666h-2.334M11.667 6.667H4.333c-.92 0-1.666.746-1.666 1.666v7.334c0 .92.746 1.666 1.666 1.666h7.334c.92 0 1.666-.746 1.666-1.666V8.333c0-.92-.746-1.666-1.666-1.666Z"
|
|
32
|
+
stroke="currentColor"
|
|
33
|
+
strokeWidth="1.667"
|
|
34
|
+
strokeLinecap="round"
|
|
35
|
+
strokeLinejoin="round"
|
|
36
|
+
/>
|
|
37
|
+
</svg>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function CodeSnippet({
|
|
42
|
+
code,
|
|
43
|
+
language,
|
|
44
|
+
showLineNumbers = false,
|
|
45
|
+
onCopy,
|
|
46
|
+
className,
|
|
47
|
+
}: CodeSnippetProps) {
|
|
48
|
+
const [copied, setCopied] = useState(false);
|
|
49
|
+
const lines = code.replace(/\n$/, "").split("\n");
|
|
50
|
+
|
|
51
|
+
const handleCopy = () => {
|
|
52
|
+
if (onCopy) onCopy(code);
|
|
53
|
+
else void navigator.clipboard?.writeText(code);
|
|
54
|
+
setCopied(true);
|
|
55
|
+
setTimeout(() => setCopied(false), 1500);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return (
|
|
59
|
+
<div
|
|
60
|
+
className={clsx(
|
|
61
|
+
"overflow-hidden rounded-lg bg-bg-primary-solid font-body",
|
|
62
|
+
className,
|
|
63
|
+
)}
|
|
64
|
+
>
|
|
65
|
+
<div className="flex items-center justify-between px-xl py-md">
|
|
66
|
+
<span className="text-xs font-medium text-utility-neutral-400">
|
|
67
|
+
{language ?? "code"}
|
|
68
|
+
</span>
|
|
69
|
+
<button
|
|
70
|
+
type="button"
|
|
71
|
+
onClick={handleCopy}
|
|
72
|
+
aria-label="Copy code"
|
|
73
|
+
className="text-utility-neutral-400 transition-colors hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-brand"
|
|
74
|
+
>
|
|
75
|
+
{copied ? <span className="text-xs">Copied</span> : <CopyIcon />}
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
<pre className="overflow-x-auto px-xl pb-xl text-sm leading-5">
|
|
79
|
+
<code className="font-mono text-white">
|
|
80
|
+
{showLineNumbers
|
|
81
|
+
? lines.map((line, i) => (
|
|
82
|
+
<div key={i} className="grid grid-cols-[2rem_1fr] gap-md">
|
|
83
|
+
<span className="select-none text-right text-utility-neutral-500">
|
|
84
|
+
{i + 1}
|
|
85
|
+
</span>
|
|
86
|
+
<span className="whitespace-pre">{line || " "}</span>
|
|
87
|
+
</div>
|
|
88
|
+
))
|
|
89
|
+
: code}
|
|
90
|
+
</code>
|
|
91
|
+
</pre>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import clsx from "clsx";
|
|
2
|
+
|
|
3
|
+
export interface CodeSnippetTabsProps {
|
|
4
|
+
/** Tab labels (e.g. ["npm", "yarn", "bun"]). */
|
|
5
|
+
tabs: string[];
|
|
6
|
+
/** Currently-selected tab. */
|
|
7
|
+
value: string;
|
|
8
|
+
onChange?: (tab: string) => void;
|
|
9
|
+
className?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** Tab strip for CodeSnippet's "Vertical with tabs" type. */
|
|
13
|
+
export function CodeSnippetTabs({
|
|
14
|
+
tabs,
|
|
15
|
+
value,
|
|
16
|
+
onChange,
|
|
17
|
+
className,
|
|
18
|
+
}: CodeSnippetTabsProps) {
|
|
19
|
+
return (
|
|
20
|
+
<div role="tablist" className={clsx("inline-flex items-center gap-xs font-body", className)}>
|
|
21
|
+
{tabs.map((tab) => {
|
|
22
|
+
const current = tab === value;
|
|
23
|
+
return (
|
|
24
|
+
<button
|
|
25
|
+
key={tab}
|
|
26
|
+
type="button"
|
|
27
|
+
role="tab"
|
|
28
|
+
aria-selected={current}
|
|
29
|
+
onClick={() => onChange?.(tab)}
|
|
30
|
+
className={clsx(
|
|
31
|
+
"rounded-sm px-md py-xs text-sm font-semibold transition-colors",
|
|
32
|
+
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-brand",
|
|
33
|
+
current
|
|
34
|
+
? "border border-border-brand text-text-primary"
|
|
35
|
+
: "border border-transparent text-text-quaternary hover:text-text-primary",
|
|
36
|
+
)}
|
|
37
|
+
>
|
|
38
|
+
{tab}
|
|
39
|
+
</button>
|
|
40
|
+
);
|
|
41
|
+
})}
|
|
42
|
+
</div>
|
|
43
|
+
);
|
|
44
|
+
}
|