@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,39 @@
|
|
|
1
|
+
import { type HTMLAttributes } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type PaginationDotSize = "md" | "lg";
|
|
5
|
+
export type PaginationDotVariant = "dot" | "line";
|
|
6
|
+
|
|
7
|
+
export interface PaginationDotIndicatorProps
|
|
8
|
+
extends HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
current?: boolean;
|
|
10
|
+
size?: PaginationDotSize;
|
|
11
|
+
variant?: PaginationDotVariant;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Single pagination indicator — a dot or a line. `current` fills it with the
|
|
16
|
+
* brand colour, otherwise `bg-quaternary`. Dimensions per size/variant match
|
|
17
|
+
* Figma (dot md 8 / lg 10; line md h6 / lg h8, both 40 wide).
|
|
18
|
+
*/
|
|
19
|
+
export function PaginationDotIndicator({
|
|
20
|
+
current = false,
|
|
21
|
+
size = "md",
|
|
22
|
+
variant = "dot",
|
|
23
|
+
className,
|
|
24
|
+
...rest
|
|
25
|
+
}: PaginationDotIndicatorProps) {
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
className={clsx(
|
|
29
|
+
"rounded-full",
|
|
30
|
+
current ? "bg-fg-brand-primary-alt" : "bg-bg-quaternary",
|
|
31
|
+
variant === "dot" && (size === "lg" ? "size-2.5" : "size-2"),
|
|
32
|
+
variant === "line" && "w-10",
|
|
33
|
+
variant === "line" && (size === "lg" ? "h-2" : "h-1.5"),
|
|
34
|
+
className,
|
|
35
|
+
)}
|
|
36
|
+
{...rest}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type PaginationNumberShape = "square" | "circle";
|
|
5
|
+
|
|
6
|
+
export interface PaginationNumberBaseProps
|
|
7
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
|
|
8
|
+
shape?: PaginationNumberShape;
|
|
9
|
+
/** Active/current page — applies the selected background + text colour. */
|
|
10
|
+
active?: boolean;
|
|
11
|
+
children: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Single 36px page-number cell. `shape` square (rounded-md) or circle (full);
|
|
16
|
+
* `active` (current page) uses the hover bg + darker text. Brand focus ring.
|
|
17
|
+
*/
|
|
18
|
+
export function PaginationNumberBase({
|
|
19
|
+
shape = "square",
|
|
20
|
+
active = false,
|
|
21
|
+
children,
|
|
22
|
+
className,
|
|
23
|
+
type = "button",
|
|
24
|
+
...rest
|
|
25
|
+
}: PaginationNumberBaseProps) {
|
|
26
|
+
return (
|
|
27
|
+
<button
|
|
28
|
+
type={type}
|
|
29
|
+
aria-current={active ? "page" : undefined}
|
|
30
|
+
className={clsx(
|
|
31
|
+
"flex size-9 items-center justify-center p-md text-sm font-medium outline-none transition-colors",
|
|
32
|
+
shape === "circle" ? "rounded-full" : "rounded-md",
|
|
33
|
+
active ? "bg-bg-primary-hover text-text-secondary" : "text-text-quaternary hover:bg-bg-primary-hover",
|
|
34
|
+
"focus-visible:ring-2 focus-visible:ring-utility-brand-500 focus-visible:ring-offset-2",
|
|
35
|
+
className,
|
|
36
|
+
)}
|
|
37
|
+
{...rest}
|
|
38
|
+
>
|
|
39
|
+
{children}
|
|
40
|
+
</button>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import {
|
|
3
|
+
ResponsiveContainer,
|
|
4
|
+
PieChart as RePieChart,
|
|
5
|
+
Pie,
|
|
6
|
+
Cell,
|
|
7
|
+
Tooltip,
|
|
8
|
+
} from "recharts";
|
|
9
|
+
import { ChartTooltip } from "../ChartTooltip";
|
|
10
|
+
import { ChartLegend } from "../ChartLegend";
|
|
11
|
+
import { chartColor } from "../../internal/chartTheme";
|
|
12
|
+
|
|
13
|
+
export type PieChartSize = "xxs" | "xs" | "sm" | "md" | "lg";
|
|
14
|
+
|
|
15
|
+
export interface PieSlice {
|
|
16
|
+
name: string;
|
|
17
|
+
value: number;
|
|
18
|
+
color?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface PieChartProps {
|
|
22
|
+
data: PieSlice[];
|
|
23
|
+
size?: PieChartSize;
|
|
24
|
+
legend?: boolean;
|
|
25
|
+
/** Donut hole (0 = full pie, 0–1 fraction of radius). */
|
|
26
|
+
innerRadiusRatio?: number;
|
|
27
|
+
className?: string;
|
|
28
|
+
formatValue?: (value: number | string | undefined) => ReactNode;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const DIAMETER: Record<PieChartSize, number> = { xxs: 80, xs: 120, sm: 160, md: 200, lg: 240 };
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Donut/pie chart — a themed Recharts `PieChart` wrapper. Slices take colors from
|
|
35
|
+
* the shared palette (override per-slice via `color`); `size` sets the diameter,
|
|
36
|
+
* `legend` renders a `ChartLegend` beside it. Recharts is an optional peer dep.
|
|
37
|
+
*/
|
|
38
|
+
export function PieChart({
|
|
39
|
+
data,
|
|
40
|
+
size = "md",
|
|
41
|
+
legend = true,
|
|
42
|
+
innerRadiusRatio = 0.6,
|
|
43
|
+
className,
|
|
44
|
+
formatValue,
|
|
45
|
+
}: PieChartProps) {
|
|
46
|
+
const d = DIAMETER[size];
|
|
47
|
+
const colored = data.map((s, i) => ({ ...s, color: s.color ?? chartColor(i) }));
|
|
48
|
+
const outer = d / 2;
|
|
49
|
+
const inner = outer * innerRadiusRatio;
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className={`flex items-center gap-2xl ${className ?? ""}`}>
|
|
53
|
+
<div style={{ width: d, height: d }} className="shrink-0">
|
|
54
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
55
|
+
<RePieChart>
|
|
56
|
+
<Pie data={colored} dataKey="value" nameKey="name" innerRadius={inner} outerRadius={outer} paddingAngle={1} stroke="none">
|
|
57
|
+
{colored.map((s, i) => (
|
|
58
|
+
<Cell key={i} fill={s.color} />
|
|
59
|
+
))}
|
|
60
|
+
</Pie>
|
|
61
|
+
<Tooltip content={<ChartTooltip formatValue={formatValue} />} />
|
|
62
|
+
</RePieChart>
|
|
63
|
+
</ResponsiveContainer>
|
|
64
|
+
</div>
|
|
65
|
+
{legend && (
|
|
66
|
+
<ChartLegend
|
|
67
|
+
orientation="vertical"
|
|
68
|
+
items={colored.map((s) => ({ label: s.name, color: s.color! }))}
|
|
69
|
+
/>
|
|
70
|
+
)}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type HTMLAttributes } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type ProgressBarLabel = "none" | "right" | "bottom" | "topFloating" | "bottomFloating";
|
|
5
|
+
|
|
6
|
+
export interface ProgressBarProps extends HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** 0–100. Clamped. */
|
|
8
|
+
value: number;
|
|
9
|
+
/** Label placement. `*Floating` shows a tooltip bubble tracking the fill. */
|
|
10
|
+
label?: ProgressBarLabel;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const Track = ({ pct }: { pct: number }) => (
|
|
14
|
+
<div className="relative h-2 w-full overflow-hidden rounded-full bg-bg-quaternary">
|
|
15
|
+
<div
|
|
16
|
+
className="absolute inset-y-0 left-0 rounded-full bg-fg-brand-primary transition-[width] duration-300"
|
|
17
|
+
style={{ width: `${pct}%` }}
|
|
18
|
+
/>
|
|
19
|
+
</div>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const Bubble = ({ pct, where }: { pct: number; where: "top" | "bottom" }) => (
|
|
23
|
+
<div
|
|
24
|
+
className={clsx("absolute -translate-x-1/2", where === "top" ? "bottom-full mb-2" : "top-full mt-2")}
|
|
25
|
+
style={{ left: `${pct}%` }}
|
|
26
|
+
>
|
|
27
|
+
<div className="rounded-md border border-border-secondary-alt bg-bg-primary-alt px-lg py-md text-xs font-semibold text-text-secondary shadow-lg">
|
|
28
|
+
{pct}%
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Progress bar — pure CSS, no chart lib. `value` (0–100) drives an animated fill
|
|
35
|
+
* (`fg-brand-primary` on a `bg-quaternary` track). `label`: none / right (inline
|
|
36
|
+
* %) / bottom (% under) / top|bottom-floating (tooltip bubble tracking the fill).
|
|
37
|
+
*/
|
|
38
|
+
export function ProgressBar({ value, label = "none", className, ...rest }: ProgressBarProps) {
|
|
39
|
+
const pct = Math.max(0, Math.min(100, value));
|
|
40
|
+
|
|
41
|
+
if (label === "right") {
|
|
42
|
+
return (
|
|
43
|
+
<div className={clsx("flex w-full items-center gap-lg", className)} {...rest}>
|
|
44
|
+
<Track pct={pct} />
|
|
45
|
+
<span className="shrink-0 text-sm font-medium text-text-secondary">{pct}%</span>
|
|
46
|
+
</div>
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (label === "bottom") {
|
|
51
|
+
return (
|
|
52
|
+
<div className={clsx("flex w-full flex-col items-end gap-md", className)} {...rest}>
|
|
53
|
+
<Track pct={pct} />
|
|
54
|
+
<span className="text-sm font-medium text-text-secondary">{pct}%</span>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (label === "topFloating" || label === "bottomFloating") {
|
|
60
|
+
return (
|
|
61
|
+
<div className={clsx("w-full", label === "topFloating" ? "pt-10" : "pb-10", className)} {...rest}>
|
|
62
|
+
<div className="relative">
|
|
63
|
+
<Track pct={pct} />
|
|
64
|
+
<Bubble pct={pct} where={label === "topFloating" ? "top" : "bottom"} />
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div className={clsx("w-full", className)} {...rest}>
|
|
72
|
+
<Track pct={pct} />
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { ringGeometry, RingPair } from "../../internal/ringBase";
|
|
4
|
+
|
|
5
|
+
export type ProgressCircleSize = "xxs" | "xs" | "sm" | "md" | "lg";
|
|
6
|
+
export type ProgressCircleShape = "circle" | "half";
|
|
7
|
+
|
|
8
|
+
export interface ProgressCircleProps {
|
|
9
|
+
/** 0–100. Clamped. */
|
|
10
|
+
value: number;
|
|
11
|
+
/** Centre label above the value (e.g. "Active users"). */
|
|
12
|
+
label?: ReactNode;
|
|
13
|
+
/** Override the centre value text (default `${value}%`). */
|
|
14
|
+
valueText?: ReactNode;
|
|
15
|
+
shape?: ProgressCircleShape;
|
|
16
|
+
size?: ProgressCircleSize;
|
|
17
|
+
className?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface Spec {
|
|
21
|
+
d: number;
|
|
22
|
+
stroke: number;
|
|
23
|
+
valueClass: string;
|
|
24
|
+
labelClass: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const SPECS: Record<ProgressCircleSize, Spec> = {
|
|
28
|
+
xxs: { d: 64, stroke: 6, valueClass: "text-sm", labelClass: "text-xs" },
|
|
29
|
+
xs: { d: 160, stroke: 8, valueClass: "text-display-xs", labelClass: "text-xs" },
|
|
30
|
+
sm: { d: 200, stroke: 10, valueClass: "text-display-sm", labelClass: "text-xs" },
|
|
31
|
+
md: { d: 240, stroke: 12, valueClass: "text-display-md", labelClass: "text-sm" },
|
|
32
|
+
lg: { d: 280, stroke: 14, valueClass: "text-display-lg", labelClass: "text-sm" },
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Circular / half-circle progress gauge. Pure SVG + CSS (no chart lib) — the ring
|
|
37
|
+
* geometry comes from the shared `ringGeometry`/`RingPair` helper (also used by
|
|
38
|
+
* `ActivityGauge`). `shape="half"` renders a top-semicircle gauge.
|
|
39
|
+
*
|
|
40
|
+
* NOTE: the half-circle is an approximation — it crops a full ring to the top
|
|
41
|
+
* semicircle (rotate 180 + arcFraction 0.5); the centre content is positioned
|
|
42
|
+
* for the cropped box.
|
|
43
|
+
*/
|
|
44
|
+
export function ProgressCircle({
|
|
45
|
+
value,
|
|
46
|
+
label,
|
|
47
|
+
valueText,
|
|
48
|
+
shape = "circle",
|
|
49
|
+
size = "xxs",
|
|
50
|
+
className,
|
|
51
|
+
}: ProgressCircleProps) {
|
|
52
|
+
const { d, stroke, valueClass, labelClass } = SPECS[size];
|
|
53
|
+
const isHalf = shape === "half";
|
|
54
|
+
const geo = ringGeometry(d, stroke, value, { arcFraction: isHalf ? 0.5 : 1 });
|
|
55
|
+
|
|
56
|
+
const center = (
|
|
57
|
+
<div className="flex flex-col items-center text-center">
|
|
58
|
+
{label && <span className={clsx("font-medium text-text-tertiary", labelClass)}>{label}</span>}
|
|
59
|
+
<span className={clsx("font-display font-semibold text-text-primary", valueClass)}>
|
|
60
|
+
{valueText ?? `${value}%`}
|
|
61
|
+
</span>
|
|
62
|
+
</div>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
if (isHalf) {
|
|
66
|
+
const h = geo.center + stroke; // top half + stroke
|
|
67
|
+
return (
|
|
68
|
+
<div className={clsx("relative", className)} style={{ width: d, height: h }}>
|
|
69
|
+
<svg width={d} height={d} viewBox={`0 0 ${d} ${d}`} className="absolute left-0 top-0" role="img" aria-label={`${value}%`}>
|
|
70
|
+
<g transform={`rotate(180 ${geo.center} ${geo.center})`}>
|
|
71
|
+
<RingPair geo={geo} stroke={stroke} trackClass="stroke-bg-quaternary" progressClass="stroke-fg-brand-primary" />
|
|
72
|
+
</g>
|
|
73
|
+
</svg>
|
|
74
|
+
<div className="absolute bottom-0 left-1/2 -translate-x-1/2">{center}</div>
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<div className={clsx("relative", className)} style={{ width: d, height: d }}>
|
|
81
|
+
<svg width={d} height={d} viewBox={`0 0 ${d} ${d}`} className="block" role="img" aria-label={`${value}%`}>
|
|
82
|
+
<g transform={`rotate(-90 ${geo.center} ${geo.center})`}>
|
|
83
|
+
<RingPair geo={geo} stroke={stroke} trackClass="stroke-bg-quaternary" progressClass="stroke-fg-brand-primary" />
|
|
84
|
+
</g>
|
|
85
|
+
</svg>
|
|
86
|
+
<div className="absolute inset-0 flex items-center justify-center">{center}</div>
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ResponsiveContainer,
|
|
3
|
+
RadarChart as ReRadarChart,
|
|
4
|
+
Radar,
|
|
5
|
+
PolarGrid,
|
|
6
|
+
PolarAngleAxis,
|
|
7
|
+
} from "recharts";
|
|
8
|
+
import { ChartLegend } from "../ChartLegend";
|
|
9
|
+
import { chartColor, CHART_GRID, tickStyle } from "../../internal/chartTheme";
|
|
10
|
+
import type { ChartSeries } from "../LineAndBarChart";
|
|
11
|
+
|
|
12
|
+
export type RadarLegendPosition = "none" | "right" | "bottom";
|
|
13
|
+
|
|
14
|
+
export interface RadarChartProps {
|
|
15
|
+
data: Record<string, number | string>[];
|
|
16
|
+
series: ChartSeries[];
|
|
17
|
+
/** Key for the polar-angle axis category. */
|
|
18
|
+
axisKey: string;
|
|
19
|
+
legend?: RadarLegendPosition;
|
|
20
|
+
size?: number;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Radar chart — a themed Recharts `RadarChart` wrapper. Each `series` becomes a
|
|
26
|
+
* filled `<Radar>` colored from the shared palette; `legend` positions a
|
|
27
|
+
* `ChartLegend` to the right or bottom. Recharts is an optional peer dependency.
|
|
28
|
+
*/
|
|
29
|
+
export function RadarChart({
|
|
30
|
+
data,
|
|
31
|
+
series,
|
|
32
|
+
axisKey,
|
|
33
|
+
legend = "bottom",
|
|
34
|
+
size = 280,
|
|
35
|
+
className,
|
|
36
|
+
}: RadarChartProps) {
|
|
37
|
+
const colored = series.map((s, i) => ({ ...s, color: s.color ?? chartColor(i) }));
|
|
38
|
+
const legendEl =
|
|
39
|
+
legend !== "none" ? (
|
|
40
|
+
<ChartLegend
|
|
41
|
+
orientation={legend === "right" ? "vertical" : "horizontal"}
|
|
42
|
+
items={colored.map((s) => ({ label: s.name ?? s.dataKey, color: s.color! }))}
|
|
43
|
+
/>
|
|
44
|
+
) : null;
|
|
45
|
+
|
|
46
|
+
return (
|
|
47
|
+
<div className={`flex gap-2xl ${legend === "right" ? "flex-row items-center" : "flex-col items-center"} ${className ?? ""}`}>
|
|
48
|
+
<div style={{ width: size, height: size }} className="shrink-0">
|
|
49
|
+
<ResponsiveContainer width="100%" height="100%">
|
|
50
|
+
<ReRadarChart data={data}>
|
|
51
|
+
<PolarGrid stroke={CHART_GRID} />
|
|
52
|
+
<PolarAngleAxis dataKey={axisKey} tick={tickStyle} />
|
|
53
|
+
{colored.map((s) => (
|
|
54
|
+
<Radar key={s.dataKey} dataKey={s.dataKey} name={s.name ?? s.dataKey} stroke={s.color} fill={s.color} fillOpacity={0.2} />
|
|
55
|
+
))}
|
|
56
|
+
</ReRadarChart>
|
|
57
|
+
</ResponsiveContainer>
|
|
58
|
+
</div>
|
|
59
|
+
{legendEl}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { CheckControlVisual, type CheckControlSize } from "../Checkbox/checkboxBase";
|
|
4
|
+
|
|
5
|
+
export interface RadioProps
|
|
6
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
|
|
7
|
+
size?: CheckControlSize;
|
|
8
|
+
label?: string;
|
|
9
|
+
supportingText?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const textSize: Record<CheckControlSize, string> = { sm: "text-sm", md: "text-md" };
|
|
13
|
+
|
|
14
|
+
export function Radio({
|
|
15
|
+
size = "sm",
|
|
16
|
+
checked,
|
|
17
|
+
disabled,
|
|
18
|
+
label,
|
|
19
|
+
supportingText,
|
|
20
|
+
className,
|
|
21
|
+
...rest
|
|
22
|
+
}: RadioProps) {
|
|
23
|
+
return (
|
|
24
|
+
<label
|
|
25
|
+
className={clsx(
|
|
26
|
+
"inline-flex items-start gap-md font-body",
|
|
27
|
+
disabled ? "cursor-not-allowed opacity-70" : "cursor-pointer",
|
|
28
|
+
className,
|
|
29
|
+
)}
|
|
30
|
+
>
|
|
31
|
+
<input
|
|
32
|
+
type="radio"
|
|
33
|
+
checked={checked}
|
|
34
|
+
disabled={disabled}
|
|
35
|
+
className="peer sr-only"
|
|
36
|
+
{...rest}
|
|
37
|
+
/>
|
|
38
|
+
<CheckControlVisual type="radio" checked={checked} size={size} disabled={disabled} />
|
|
39
|
+
{label || supportingText ? (
|
|
40
|
+
<span className="flex flex-col">
|
|
41
|
+
{label ? (
|
|
42
|
+
<span className={clsx("font-medium text-text-secondary", textSize[size])}>
|
|
43
|
+
{label}
|
|
44
|
+
</span>
|
|
45
|
+
) : null}
|
|
46
|
+
{supportingText ? (
|
|
47
|
+
<span className={clsx("font-normal text-text-tertiary", textSize[size])}>
|
|
48
|
+
{supportingText}
|
|
49
|
+
</span>
|
|
50
|
+
) : null}
|
|
51
|
+
</span>
|
|
52
|
+
) : null}
|
|
53
|
+
</label>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Children,
|
|
3
|
+
cloneElement,
|
|
4
|
+
isValidElement,
|
|
5
|
+
type HTMLAttributes,
|
|
6
|
+
type MouseEvent,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
9
|
+
import clsx from "clsx";
|
|
10
|
+
|
|
11
|
+
export interface RadioGroupProps
|
|
12
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
13
|
+
/** Selected item value (controlled). */
|
|
14
|
+
value?: string;
|
|
15
|
+
onValueChange?: (value: string) => void;
|
|
16
|
+
/** `RadioGroupItem` elements, each with a `value` prop. */
|
|
17
|
+
children: ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ItemProps {
|
|
21
|
+
value?: string;
|
|
22
|
+
selected?: boolean;
|
|
23
|
+
onClick?: (e: MouseEvent<HTMLButtonElement>) => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Single-select container for `RadioGroupItem`s. Clones each child to inject
|
|
28
|
+
* `selected` (matching `value`) and an `onClick` that fires `onValueChange` with
|
|
29
|
+
* the child's `value`. Children control their own `type`/content.
|
|
30
|
+
*/
|
|
31
|
+
export function RadioGroup({
|
|
32
|
+
value,
|
|
33
|
+
onValueChange,
|
|
34
|
+
children,
|
|
35
|
+
className,
|
|
36
|
+
...rest
|
|
37
|
+
}: RadioGroupProps) {
|
|
38
|
+
return (
|
|
39
|
+
<div role="radiogroup" className={clsx("flex flex-col gap-lg", className)} {...rest}>
|
|
40
|
+
{Children.map(children, (child) => {
|
|
41
|
+
if (!isValidElement(child)) return child;
|
|
42
|
+
const props = child.props as ItemProps;
|
|
43
|
+
const v = props.value;
|
|
44
|
+
return cloneElement(child as React.ReactElement<ItemProps>, {
|
|
45
|
+
selected: v !== undefined ? v === value : props.selected,
|
|
46
|
+
onClick: (e: MouseEvent<HTMLButtonElement>) => {
|
|
47
|
+
props.onClick?.(e);
|
|
48
|
+
if (v !== undefined) onValueChange?.(v);
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
})}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type RadioGroupItemType =
|
|
5
|
+
| "radioButton"
|
|
6
|
+
| "checkbox"
|
|
7
|
+
| "iconSimple"
|
|
8
|
+
| "iconCard"
|
|
9
|
+
| "avatar"
|
|
10
|
+
| "paymentIcon";
|
|
11
|
+
export type RadioGroupItemSize = "sm" | "md";
|
|
12
|
+
|
|
13
|
+
export interface RadioGroupItemProps
|
|
14
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type" | "title"> {
|
|
15
|
+
type?: RadioGroupItemType;
|
|
16
|
+
selected?: boolean;
|
|
17
|
+
size?: RadioGroupItemSize;
|
|
18
|
+
title?: ReactNode;
|
|
19
|
+
/** Inline secondary text beside the title (e.g. "$10/month"). */
|
|
20
|
+
subtext?: ReactNode;
|
|
21
|
+
description?: ReactNode;
|
|
22
|
+
/** Leading visual — featured icon / avatar / payment-method icon. */
|
|
23
|
+
leading?: ReactNode;
|
|
24
|
+
/** iconCard badge (top-right). */
|
|
25
|
+
badge?: ReactNode;
|
|
26
|
+
/** Trailing actions row (paymentIcon). */
|
|
27
|
+
actions?: ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function Control({ kind, selected, size }: { kind: "radio" | "checkbox"; selected: boolean; size: RadioGroupItemSize }) {
|
|
31
|
+
const dim = size === "md" ? "size-5" : "size-4";
|
|
32
|
+
return (
|
|
33
|
+
<span
|
|
34
|
+
className={clsx(
|
|
35
|
+
"flex shrink-0 items-center justify-center border",
|
|
36
|
+
dim,
|
|
37
|
+
kind === "radio" ? "rounded-full" : "rounded-xs",
|
|
38
|
+
selected ? "border-transparent bg-bg-brand-solid" : "border-border-primary bg-bg-primary",
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
{selected && kind === "radio" && <span className="size-1.5 rounded-full bg-fg-white" />}
|
|
42
|
+
{selected && kind === "checkbox" && (
|
|
43
|
+
<svg viewBox="0 0 16 16" fill="none" className="size-3 text-fg-white" aria-hidden>
|
|
44
|
+
<path d="M13.33 4 6 11.33 2.67 8" stroke="currentColor" strokeWidth="1.67" strokeLinecap="round" strokeLinejoin="round" />
|
|
45
|
+
</svg>
|
|
46
|
+
)}
|
|
47
|
+
</span>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Selectable radio/checkbox card. One component over the 6 Figma `type`s — the
|
|
53
|
+
* shared shell (border, padding, `radius-xl`, selected `border-brand`) is
|
|
54
|
+
* constant; only the leading element and control placement change. `radioButton`
|
|
55
|
+
* uses a radio (leading); `checkbox` a checkbox (leading); `iconSimple`/`avatar`/
|
|
56
|
+
* `paymentIcon` put a `leading` visual on the left and the checkbox trailing;
|
|
57
|
+
* `iconCard` is a vertical card (header + body + `badge`).
|
|
58
|
+
*/
|
|
59
|
+
export function RadioGroupItem({
|
|
60
|
+
type = "radioButton",
|
|
61
|
+
selected = false,
|
|
62
|
+
size = "sm",
|
|
63
|
+
title,
|
|
64
|
+
subtext,
|
|
65
|
+
description,
|
|
66
|
+
leading,
|
|
67
|
+
badge,
|
|
68
|
+
actions,
|
|
69
|
+
className,
|
|
70
|
+
...rest
|
|
71
|
+
}: RadioGroupItemProps) {
|
|
72
|
+
const kind = type === "radioButton" ? "radio" : "checkbox";
|
|
73
|
+
const leadingControl = type === "radioButton" || type === "checkbox";
|
|
74
|
+
const shell = clsx(
|
|
75
|
+
"w-full bg-bg-primary text-left transition-colors",
|
|
76
|
+
selected ? "border-2 border-border-brand" : "border border-border-secondary",
|
|
77
|
+
className,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
if (type === "iconCard") {
|
|
81
|
+
return (
|
|
82
|
+
<button type="button" aria-pressed={selected} className={clsx(shell, "flex flex-col overflow-hidden rounded-xl")} {...rest}>
|
|
83
|
+
<div className="flex w-full items-center gap-lg border-b border-border-secondary py-lg pl-lg pr-2xl">
|
|
84
|
+
{leading && <span className="shrink-0">{leading}</span>}
|
|
85
|
+
<span className="min-w-0 flex-1 text-md font-semibold text-text-secondary">{title}</span>
|
|
86
|
+
<Control kind="checkbox" selected={selected} size={size} />
|
|
87
|
+
</div>
|
|
88
|
+
<div className="relative flex w-full flex-col gap-xs p-xl">
|
|
89
|
+
{(title || subtext) && (
|
|
90
|
+
<div className="flex items-end gap-xs">
|
|
91
|
+
{subtext && <span className="font-display text-display-sm font-semibold text-text-secondary">{subtext}</span>}
|
|
92
|
+
</div>
|
|
93
|
+
)}
|
|
94
|
+
{description && <p className="text-sm text-text-tertiary">{description}</p>}
|
|
95
|
+
{badge && <div className="absolute right-4 top-4">{badge}</div>}
|
|
96
|
+
</div>
|
|
97
|
+
</button>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<button type="button" aria-pressed={selected} className={clsx(shell, "flex items-start gap-md rounded-xl p-xl")} {...rest}>
|
|
103
|
+
{leadingControl && <span className="pt-xxs"><Control kind={kind} selected={selected} size={size} /></span>}
|
|
104
|
+
{leading && <span className="shrink-0">{leading}</span>}
|
|
105
|
+
<div className="flex min-w-0 flex-1 flex-col gap-md">
|
|
106
|
+
<div className="flex flex-col gap-xxs">
|
|
107
|
+
<div className="flex items-center gap-xs">
|
|
108
|
+
<span className="text-sm font-medium text-text-secondary">{title}</span>
|
|
109
|
+
{subtext && <span className="text-sm text-text-tertiary">{subtext}</span>}
|
|
110
|
+
</div>
|
|
111
|
+
{description && <p className="text-sm text-text-tertiary">{description}</p>}
|
|
112
|
+
</div>
|
|
113
|
+
{actions && <div className="flex items-center gap-lg">{actions}</div>}
|
|
114
|
+
</div>
|
|
115
|
+
{!leadingControl && <Control kind="checkbox" selected={selected} size={size} />}
|
|
116
|
+
</button>
|
|
117
|
+
);
|
|
118
|
+
}
|