@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,102 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type NotificationVariant = "default" | "success" | "warning" | "error" | "gray";
|
|
5
|
+
|
|
6
|
+
export interface NotificationProps
|
|
7
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
|
|
8
|
+
/** Colours the default featured-icon ring (ignored if `leadingMedia` set). */
|
|
9
|
+
variant?: NotificationVariant;
|
|
10
|
+
title: ReactNode;
|
|
11
|
+
description?: ReactNode;
|
|
12
|
+
/** Left media slot (icon / avatar / progress). Overrides the default ring icon. */
|
|
13
|
+
leadingMedia?: ReactNode;
|
|
14
|
+
/** Full-bleed image (the "Image" type) rendered below the text. */
|
|
15
|
+
imageUrl?: string;
|
|
16
|
+
/** Action row — compose link/text buttons. */
|
|
17
|
+
actions?: ReactNode;
|
|
18
|
+
/** Renders an x-close button top-right. */
|
|
19
|
+
onDismiss?: () => void;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const RING: Record<NotificationVariant, string> = {
|
|
23
|
+
default: "border-fg-brand-primary text-fg-brand-primary",
|
|
24
|
+
success: "border-fg-success-primary text-fg-success-primary",
|
|
25
|
+
warning: "border-fg-warning-primary text-fg-warning-primary",
|
|
26
|
+
error: "border-fg-error-primary text-fg-error-primary",
|
|
27
|
+
gray: "border-fg-quaternary text-fg-quaternary",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const GLYPH: Record<NotificationVariant, string> = {
|
|
31
|
+
default: "M10 13.33V10M10 6.67h.01M18.33 10a8.33 8.33 0 1 1-16.66 0 8.33 8.33 0 0 1 16.66 0Z",
|
|
32
|
+
gray: "M10 13.33V10M10 6.67h.01M18.33 10a8.33 8.33 0 1 1-16.66 0 8.33 8.33 0 0 1 16.66 0Z",
|
|
33
|
+
success: "M6.25 10l2.5 2.5 5-5M18.33 10a8.33 8.33 0 1 1-16.66 0 8.33 8.33 0 0 1 16.66 0Z",
|
|
34
|
+
warning: "M10 6.67V10m0 3.33h.01M18.33 10a8.33 8.33 0 1 1-16.66 0 8.33 8.33 0 0 1 16.66 0Z",
|
|
35
|
+
error: "M10 6.67V10m0 3.33h.01M18.33 10a8.33 8.33 0 1 1-16.66 0 8.33 8.33 0 0 1 16.66 0Z",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const XClose = () => (
|
|
39
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
40
|
+
<path d="M15 5 5 15M5 5l10 10" stroke="currentColor" strokeWidth="1.67" strokeLinecap="round" strokeLinejoin="round" />
|
|
41
|
+
</svg>
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const FeaturedIcon = ({ variant }: { variant: NotificationVariant }) => (
|
|
45
|
+
<span className={clsx("relative inline-flex size-5 shrink-0 items-center justify-center", RING[variant].split(" ")[1])}>
|
|
46
|
+
<span className={clsx("absolute inset-[-20%] rounded-full border-2 opacity-30", RING[variant].split(" ")[0])} />
|
|
47
|
+
<span className={clsx("absolute inset-[-45%] rounded-full border-2 opacity-10", RING[variant].split(" ")[0])} />
|
|
48
|
+
<svg viewBox="0 0 20 20" fill="none" className="relative size-5" aria-hidden>
|
|
49
|
+
<path d={GLYPH[variant]} stroke="currentColor" strokeWidth="1.67" strokeLinecap="round" strokeLinejoin="round" />
|
|
50
|
+
</svg>
|
|
51
|
+
</span>
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Toast / inline notification. One component: `variant` colours the default
|
|
56
|
+
* featured-icon ring; `leadingMedia` overrides it with any node (avatar, custom
|
|
57
|
+
* icon, progress). `imageUrl` adds the full-bleed image type, `actions` the CTA
|
|
58
|
+
* row, `onDismiss` the x-close. `shadow-lg` approximates Figma's 3-layer lg.
|
|
59
|
+
*/
|
|
60
|
+
export function Notification({
|
|
61
|
+
variant = "default",
|
|
62
|
+
title,
|
|
63
|
+
description,
|
|
64
|
+
leadingMedia,
|
|
65
|
+
imageUrl,
|
|
66
|
+
actions,
|
|
67
|
+
onDismiss,
|
|
68
|
+
className,
|
|
69
|
+
...rest
|
|
70
|
+
}: NotificationProps) {
|
|
71
|
+
return (
|
|
72
|
+
<div
|
|
73
|
+
className={clsx(
|
|
74
|
+
"relative flex w-full items-start gap-xl rounded-xl border border-border-secondary-alt bg-bg-primary-alt p-xl shadow-lg",
|
|
75
|
+
className,
|
|
76
|
+
)}
|
|
77
|
+
{...rest}
|
|
78
|
+
>
|
|
79
|
+
{leadingMedia ?? <FeaturedIcon variant={variant} />}
|
|
80
|
+
|
|
81
|
+
<div className="flex min-w-0 flex-1 flex-col gap-lg pr-8">
|
|
82
|
+
<div className="flex flex-col gap-xs">
|
|
83
|
+
<p className="text-sm font-semibold text-fg-primary">{title}</p>
|
|
84
|
+
{description && <p className="text-sm text-fg-secondary">{description}</p>}
|
|
85
|
+
</div>
|
|
86
|
+
{imageUrl && <img src={imageUrl} alt="" className="h-40 w-full rounded-md object-cover" />}
|
|
87
|
+
{actions && <div className="flex items-center gap-lg">{actions}</div>}
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
{onDismiss && (
|
|
91
|
+
<button
|
|
92
|
+
type="button"
|
|
93
|
+
onClick={onDismiss}
|
|
94
|
+
aria-label="Dismiss"
|
|
95
|
+
className="absolute right-2 top-2 flex size-9 items-center justify-center rounded-md text-fg-quaternary transition-colors hover:text-fg-quaternary-hover"
|
|
96
|
+
>
|
|
97
|
+
<XClose />
|
|
98
|
+
</button>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { FieldWrapper, boxClasses } from "../InputField/inputFieldShared";
|
|
4
|
+
|
|
5
|
+
export type NumberInputLayout = "horizontal" | "vertical";
|
|
6
|
+
|
|
7
|
+
export interface NumberInputProps {
|
|
8
|
+
layout?: NumberInputLayout;
|
|
9
|
+
value?: number;
|
|
10
|
+
onChange?: (value: number) => void;
|
|
11
|
+
min?: number;
|
|
12
|
+
max?: number;
|
|
13
|
+
step?: number;
|
|
14
|
+
label?: ReactNode;
|
|
15
|
+
hint?: ReactNode;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
destructive?: boolean;
|
|
18
|
+
disabled?: boolean;
|
|
19
|
+
className?: string;
|
|
20
|
+
"aria-label"?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Standalone numeric counter (NOT an `InputField` type). `horizontal` flanks the
|
|
25
|
+
* value with −/+ buttons; `vertical` stacks up/down chevrons on the right. Reuses
|
|
26
|
+
* the input family's `FieldWrapper` + `boxClasses`. Controlled via
|
|
27
|
+
* `value`/`onChange`, clamped to `min`/`max` by `step`.
|
|
28
|
+
*/
|
|
29
|
+
export function NumberInput({
|
|
30
|
+
layout = "horizontal",
|
|
31
|
+
value = 0,
|
|
32
|
+
onChange,
|
|
33
|
+
min = -Infinity,
|
|
34
|
+
max = Infinity,
|
|
35
|
+
step = 1,
|
|
36
|
+
label,
|
|
37
|
+
hint,
|
|
38
|
+
required,
|
|
39
|
+
destructive,
|
|
40
|
+
disabled,
|
|
41
|
+
className,
|
|
42
|
+
"aria-label": ariaLabel,
|
|
43
|
+
}: NumberInputProps) {
|
|
44
|
+
const clamp = (n: number) => Math.min(max, Math.max(min, n));
|
|
45
|
+
const set = (n: number) => onChange?.(clamp(n));
|
|
46
|
+
const dec = () => set(value - step);
|
|
47
|
+
const inc = () => set(value + step);
|
|
48
|
+
|
|
49
|
+
const Minus = (
|
|
50
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
51
|
+
<path d="M4.167 10h11.666" stroke="currentColor" strokeWidth="1.67" strokeLinecap="round" />
|
|
52
|
+
</svg>
|
|
53
|
+
);
|
|
54
|
+
const Plus = (
|
|
55
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
56
|
+
<path d="M10 4.167v11.666M4.167 10h11.666" stroke="currentColor" strokeWidth="1.67" strokeLinecap="round" />
|
|
57
|
+
</svg>
|
|
58
|
+
);
|
|
59
|
+
const Chevron = ({ up }: { up: boolean }) => (
|
|
60
|
+
<svg viewBox="0 0 12 12" fill="none" className={clsx("size-3", up && "rotate-180")} aria-hidden>
|
|
61
|
+
<path d="M3 4.5 6 7.5 9 4.5" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round" />
|
|
62
|
+
</svg>
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const numberEl = (
|
|
66
|
+
<input
|
|
67
|
+
type="text"
|
|
68
|
+
inputMode="numeric"
|
|
69
|
+
aria-label={ariaLabel ?? (typeof label === "string" ? label : "Number")}
|
|
70
|
+
value={value}
|
|
71
|
+
disabled={disabled}
|
|
72
|
+
onChange={(e) => {
|
|
73
|
+
const n = Number(e.target.value.replace(/[^\d.-]/g, ""));
|
|
74
|
+
if (!Number.isNaN(n)) set(n);
|
|
75
|
+
}}
|
|
76
|
+
className={clsx(
|
|
77
|
+
"min-w-0 flex-1 bg-transparent py-md text-md text-text-primary outline-none",
|
|
78
|
+
layout === "horizontal" ? "px-lg text-center" : "px-lg text-left",
|
|
79
|
+
)}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
const box =
|
|
84
|
+
layout === "horizontal" ? (
|
|
85
|
+
<div className={clsx(boxClasses(destructive), "gap-0 overflow-hidden p-0")}>
|
|
86
|
+
<button type="button" onClick={dec} disabled={disabled} aria-label="Decrease" className="flex items-center justify-center border-r border-border-primary p-2.5 text-fg-quaternary hover:bg-bg-primary-hover disabled:opacity-60">
|
|
87
|
+
{Minus}
|
|
88
|
+
</button>
|
|
89
|
+
{numberEl}
|
|
90
|
+
<button type="button" onClick={inc} disabled={disabled} aria-label="Increase" className="flex items-center justify-center border-l border-border-primary p-2.5 text-fg-quaternary hover:bg-bg-primary-hover disabled:opacity-60">
|
|
91
|
+
{Plus}
|
|
92
|
+
</button>
|
|
93
|
+
</div>
|
|
94
|
+
) : (
|
|
95
|
+
<div className={clsx(boxClasses(destructive), "gap-0 overflow-hidden p-0")}>
|
|
96
|
+
{numberEl}
|
|
97
|
+
<div className="flex flex-col self-stretch border-l border-border-primary">
|
|
98
|
+
<button type="button" onClick={inc} disabled={disabled} aria-label="Increase" className="flex flex-1 items-center justify-center px-md text-fg-quaternary hover:bg-bg-primary-hover disabled:opacity-60">
|
|
99
|
+
<Chevron up />
|
|
100
|
+
</button>
|
|
101
|
+
<span className="h-px w-full bg-border-primary" />
|
|
102
|
+
<button type="button" onClick={dec} disabled={disabled} aria-label="Decrease" className="flex flex-1 items-center justify-center px-md text-fg-quaternary hover:bg-bg-primary-hover disabled:opacity-60">
|
|
103
|
+
<Chevron up={false} />
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
return (
|
|
110
|
+
<FieldWrapper label={label} required={required} hint={hint} destructive={destructive} className={className}>
|
|
111
|
+
{box}
|
|
112
|
+
</FieldWrapper>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type PageHeaderStyle =
|
|
5
|
+
| "simple"
|
|
6
|
+
| "avatar"
|
|
7
|
+
| "bannerAvatar"
|
|
8
|
+
| "bannerAvatarCentered"
|
|
9
|
+
| "bannerSimpleCentered"
|
|
10
|
+
| "bannerSimple";
|
|
11
|
+
|
|
12
|
+
export interface PageHeaderProps
|
|
13
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "title" | "style"> {
|
|
14
|
+
style?: PageHeaderStyle;
|
|
15
|
+
title: ReactNode;
|
|
16
|
+
description?: ReactNode;
|
|
17
|
+
/** Avatar / featured visual (avatar styles). */
|
|
18
|
+
avatar?: ReactNode;
|
|
19
|
+
/** Trailing actions row (buttons). */
|
|
20
|
+
actions?: ReactNode;
|
|
21
|
+
/** Optional breadcrumbs slot above the title. */
|
|
22
|
+
breadcrumbs?: ReactNode;
|
|
23
|
+
/** Banner background image (banner styles); falls back to `bg-tertiary`. */
|
|
24
|
+
bannerUrl?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Page header — one component over the 6 Figma `Style`s. `banner*` styles render
|
|
29
|
+
* a banner strip the content overlaps; `*Centered` styles centre the text;
|
|
30
|
+
* `avatar` styles show the `avatar` slot. Breakpoint handled with Tailwind
|
|
31
|
+
* responsive prefixes. Constrained to `container-max-width-desktop` (1280) with
|
|
32
|
+
* container padding.
|
|
33
|
+
*/
|
|
34
|
+
export function PageHeader({
|
|
35
|
+
style = "simple",
|
|
36
|
+
title,
|
|
37
|
+
description,
|
|
38
|
+
avatar,
|
|
39
|
+
actions,
|
|
40
|
+
breadcrumbs,
|
|
41
|
+
bannerUrl,
|
|
42
|
+
className,
|
|
43
|
+
...rest
|
|
44
|
+
}: PageHeaderProps) {
|
|
45
|
+
const isBanner = style.startsWith("banner");
|
|
46
|
+
const isCentered = style.endsWith("Centered");
|
|
47
|
+
const hasAvatar = style === "avatar" || style === "bannerAvatar" || style === "bannerAvatarCentered";
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
className={clsx(
|
|
52
|
+
"mx-auto w-full max-w-screen-xl px-container-padding-mobile md:px-container-padding-desktop",
|
|
53
|
+
className,
|
|
54
|
+
)}
|
|
55
|
+
{...rest}
|
|
56
|
+
>
|
|
57
|
+
{isBanner && (
|
|
58
|
+
<div
|
|
59
|
+
className="h-32 w-full rounded-2xl bg-bg-tertiary bg-cover bg-center md:h-40"
|
|
60
|
+
style={bannerUrl ? { backgroundImage: `url(${bannerUrl})` } : undefined}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
<div className={clsx("flex flex-col gap-xl", isCentered && "items-center text-center", isBanner && "px-md")}>
|
|
65
|
+
{breadcrumbs}
|
|
66
|
+
|
|
67
|
+
{hasAvatar && avatar && (
|
|
68
|
+
<div className={clsx("shrink-0", isBanner && "-mt-10", isCentered && "flex justify-center")}>
|
|
69
|
+
{avatar}
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
<div
|
|
74
|
+
className={clsx(
|
|
75
|
+
"flex w-full gap-xl",
|
|
76
|
+
isCentered ? "flex-col items-center" : "flex-col items-start justify-between md:flex-row md:items-center",
|
|
77
|
+
)}
|
|
78
|
+
>
|
|
79
|
+
<div className={clsx("flex flex-col gap-xxs", isCentered && "max-w-width-lg items-center")}>
|
|
80
|
+
<h1 className="text-xl font-semibold text-text-primary">{title}</h1>
|
|
81
|
+
{description && <p className="text-md text-text-tertiary">{description}</p>}
|
|
82
|
+
</div>
|
|
83
|
+
{actions && <div className="flex shrink-0 items-center gap-lg">{actions}</div>}
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { type HTMLAttributes } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { PaginationNumberBase, type PaginationNumberShape } from "../PaginationNumberBase";
|
|
4
|
+
import { PaginationButtonGroupBase } from "../PaginationButtonGroupBase";
|
|
5
|
+
|
|
6
|
+
export type PaginationType = "default" | "minimal" | "buttonGroup";
|
|
7
|
+
|
|
8
|
+
export interface PaginationProps extends HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
/** 1-based current page. */
|
|
10
|
+
page: number;
|
|
11
|
+
/** Total page count. */
|
|
12
|
+
total: number;
|
|
13
|
+
onPageChange?: (page: number) => void;
|
|
14
|
+
/** default = Prev / numbers / Next · minimal = Prev / "Page x of y" / Next · buttonGroup = bordered group. */
|
|
15
|
+
type?: PaginationType;
|
|
16
|
+
/** Number-cell shape (default/minimal). */
|
|
17
|
+
shape?: PaginationNumberShape;
|
|
18
|
+
/** Max page cells before collapsing the middle with ellipsis. Default 7. */
|
|
19
|
+
maxVisible?: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
type PageToken = number | "ellipsis";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build the visible page tokens: always first + last + current±2, with an
|
|
26
|
+
* `"ellipsis"` marker inserted wherever consecutive pages are skipped.
|
|
27
|
+
*/
|
|
28
|
+
function pageTokens(current: number, total: number, maxVisible: number): PageToken[] {
|
|
29
|
+
if (total <= maxVisible) {
|
|
30
|
+
return Array.from({ length: total }, (_, i) => i + 1);
|
|
31
|
+
}
|
|
32
|
+
const wanted = new Set<number>([1, total, current, current - 1, current - 2, current + 1, current + 2]);
|
|
33
|
+
const sorted = [...wanted].filter((p) => p >= 1 && p <= total).sort((a, b) => a - b);
|
|
34
|
+
const out: PageToken[] = [];
|
|
35
|
+
let prev = 0;
|
|
36
|
+
for (const p of sorted) {
|
|
37
|
+
if (p - prev > 1) out.push("ellipsis");
|
|
38
|
+
out.push(p);
|
|
39
|
+
prev = p;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const Arrow = ({ dir }: { dir: "left" | "right" }) => (
|
|
45
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
46
|
+
<path d={dir === "left" ? "M15.83 10H4.17M4.17 10l5 5M4.17 10l5-5" : "M4.17 10h11.66M15.83 10l-5 5M15.83 10l-5-5"} stroke="currentColor" strokeWidth="1.67" strokeLinecap="round" strokeLinejoin="round" />
|
|
47
|
+
</svg>
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const PrevNext = ({ dir, disabled, onClick }: { dir: "left" | "right"; disabled: boolean; onClick: () => void }) => (
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
disabled={disabled}
|
|
54
|
+
onClick={onClick}
|
|
55
|
+
className="flex items-center gap-xs text-sm font-semibold text-text-tertiary disabled:opacity-50"
|
|
56
|
+
>
|
|
57
|
+
{dir === "left" && <Arrow dir="left" />}
|
|
58
|
+
{dir === "left" ? "Previous" : "Next"}
|
|
59
|
+
{dir === "right" && <Arrow dir="right" />}
|
|
60
|
+
</button>
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Number/button-based pagination. `default` = Previous · number cells · Next;
|
|
65
|
+
* `minimal` = Previous · "Page x of y" · Next; `buttonGroup` = a bordered
|
|
66
|
+
* connected group. Composes `PaginationNumberBase` + `PaginationButtonGroupBase`.
|
|
67
|
+
* Collapses the middle with ellipsis past `maxVisible` (always shows first/last
|
|
68
|
+
* + current±2).
|
|
69
|
+
*/
|
|
70
|
+
export function Pagination({
|
|
71
|
+
page,
|
|
72
|
+
total,
|
|
73
|
+
onPageChange,
|
|
74
|
+
type = "default",
|
|
75
|
+
shape = "square",
|
|
76
|
+
maxVisible = 7,
|
|
77
|
+
className,
|
|
78
|
+
...rest
|
|
79
|
+
}: PaginationProps) {
|
|
80
|
+
const go = (p: number) => p >= 1 && p <= total && onPageChange?.(p);
|
|
81
|
+
const tokens = pageTokens(page, total, maxVisible);
|
|
82
|
+
|
|
83
|
+
if (type === "buttonGroup") {
|
|
84
|
+
return (
|
|
85
|
+
<div className={clsx("inline-flex overflow-hidden rounded-md border border-border-primary [&>*:last-child]:border-r-0", className)} {...rest}>
|
|
86
|
+
<PaginationButtonGroupBase cellType="leadingIcon" onClick={() => go(page - 1)} />
|
|
87
|
+
{tokens.map((t, i) =>
|
|
88
|
+
t === "ellipsis" ? (
|
|
89
|
+
<span key={`e${i}`} className="flex min-h-9 min-w-9 items-center justify-center border-r border-border-primary bg-bg-primary p-md text-sm font-semibold text-text-quaternary">
|
|
90
|
+
…
|
|
91
|
+
</span>
|
|
92
|
+
) : (
|
|
93
|
+
<PaginationButtonGroupBase key={t} cellType="number" active={t === page} onClick={() => go(t)}>
|
|
94
|
+
{t}
|
|
95
|
+
</PaginationButtonGroupBase>
|
|
96
|
+
),
|
|
97
|
+
)}
|
|
98
|
+
<PaginationButtonGroupBase cellType="trailingIcon" onClick={() => go(page + 1)} />
|
|
99
|
+
</div>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return (
|
|
104
|
+
<div className={clsx("flex w-full items-center justify-between gap-md", className)} {...rest}>
|
|
105
|
+
<PrevNext dir="left" disabled={page <= 1} onClick={() => go(page - 1)} />
|
|
106
|
+
{type === "minimal" ? (
|
|
107
|
+
<span className="text-sm font-medium text-text-secondary">Page {page} of {total}</span>
|
|
108
|
+
) : (
|
|
109
|
+
<div className="flex items-center gap-xxs">
|
|
110
|
+
{tokens.map((t, i) =>
|
|
111
|
+
t === "ellipsis" ? (
|
|
112
|
+
<span key={`e${i}`} className="flex size-9 items-center justify-center text-sm font-medium text-text-quaternary">…</span>
|
|
113
|
+
) : (
|
|
114
|
+
<PaginationNumberBase key={t} shape={shape} active={t === page} onClick={() => go(t)}>
|
|
115
|
+
{t}
|
|
116
|
+
</PaginationNumberBase>
|
|
117
|
+
),
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
<PrevNext dir="right" disabled={page >= total} onClick={() => go(page + 1)} />
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type PaginationButtonGroupType = "number" | "leadingIcon" | "trailingIcon" | "iconOnly";
|
|
5
|
+
|
|
6
|
+
export interface PaginationButtonGroupBaseProps
|
|
7
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type" | "children"> {
|
|
8
|
+
/** number cell · leading/trailing icon+label · icon-only arrow. */
|
|
9
|
+
cellType?: PaginationButtonGroupType;
|
|
10
|
+
/** Active/current cell — applies the hover background + darker text. */
|
|
11
|
+
active?: boolean;
|
|
12
|
+
/** Number or label text. */
|
|
13
|
+
children?: ReactNode;
|
|
14
|
+
/** Override the default arrow glyph. */
|
|
15
|
+
icon?: ReactNode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const ArrowLeft = () => (
|
|
19
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
20
|
+
<path d="M15.83 10H4.17M4.17 10l5 5M4.17 10l5-5" stroke="currentColor" strokeWidth="1.67" strokeLinecap="round" strokeLinejoin="round" />
|
|
21
|
+
</svg>
|
|
22
|
+
);
|
|
23
|
+
const ArrowRight = () => (
|
|
24
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
25
|
+
<path d="M4.17 10h11.66M15.83 10l-5 5M15.83 10l-5-5" stroke="currentColor" strokeWidth="1.67" strokeLinecap="round" strokeLinejoin="round" />
|
|
26
|
+
</svg>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* One cell of a button-group pagination bar (the bordered Page/Card-button-group
|
|
31
|
+
* style). `cellType` picks number / leading-icon (Previous) / trailing-icon
|
|
32
|
+
* (Next) / icon-only. Carries a right divider; `active` highlights it.
|
|
33
|
+
*/
|
|
34
|
+
export function PaginationButtonGroupBase({
|
|
35
|
+
cellType = "number",
|
|
36
|
+
active = false,
|
|
37
|
+
children,
|
|
38
|
+
icon,
|
|
39
|
+
className,
|
|
40
|
+
...rest
|
|
41
|
+
}: PaginationButtonGroupBaseProps) {
|
|
42
|
+
const isIcon = cellType === "iconOnly";
|
|
43
|
+
const isLeading = cellType === "leadingIcon";
|
|
44
|
+
const isTrailing = cellType === "trailingIcon";
|
|
45
|
+
const isNumber = cellType === "number";
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<button
|
|
49
|
+
type="button"
|
|
50
|
+
aria-current={active && isNumber ? "page" : undefined}
|
|
51
|
+
className={clsx(
|
|
52
|
+
"flex min-h-9 items-center justify-center border-r border-border-primary text-sm font-semibold outline-none transition-colors",
|
|
53
|
+
"focus-visible:relative focus-visible:z-10 focus-visible:ring-2 focus-visible:ring-utility-brand-500",
|
|
54
|
+
isNumber ? "min-w-9 p-md" : isIcon ? "p-md" : "gap-xs px-lg py-md",
|
|
55
|
+
active ? "bg-bg-primary-hover text-text-secondary-hover" : "bg-bg-primary hover:bg-bg-primary-hover",
|
|
56
|
+
isNumber && !active && "text-text-quaternary",
|
|
57
|
+
(isLeading || isTrailing) && "text-text-secondary",
|
|
58
|
+
className,
|
|
59
|
+
)}
|
|
60
|
+
{...rest}
|
|
61
|
+
>
|
|
62
|
+
{isLeading && (icon ?? <ArrowLeft />)}
|
|
63
|
+
{(isLeading || isTrailing) && <span className="px-xxs">{children ?? (isLeading ? "Previous" : "Next")}</span>}
|
|
64
|
+
{isTrailing && (icon ?? <ArrowRight />)}
|
|
65
|
+
{isNumber && children}
|
|
66
|
+
{isIcon && (icon ?? <ArrowRight />)}
|
|
67
|
+
</button>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type HTMLAttributes } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { PaginationDotGroup } from "../PaginationDotGroup";
|
|
4
|
+
|
|
5
|
+
export type PaginationCardsVariant = "default" | "minimal" | "advanced";
|
|
6
|
+
|
|
7
|
+
export interface PaginationCardsProps extends HTMLAttributes<HTMLDivElement> {
|
|
8
|
+
page: number;
|
|
9
|
+
total: number;
|
|
10
|
+
onPageChange?: (page: number) => void;
|
|
11
|
+
/** default = arrows + dots · minimal = dots + "Page x of y" · advanced = bordered card. */
|
|
12
|
+
variant?: PaginationCardsVariant;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const ArrowBtn = ({ dir, disabled, onClick }: { dir: "left" | "right"; disabled: boolean; onClick: () => void }) => (
|
|
16
|
+
<button
|
|
17
|
+
type="button"
|
|
18
|
+
aria-label={dir === "left" ? "Previous" : "Next"}
|
|
19
|
+
disabled={disabled}
|
|
20
|
+
onClick={onClick}
|
|
21
|
+
className="flex size-9 items-center justify-center rounded-md border border-border-primary bg-bg-primary text-fg-secondary shadow-xs disabled:opacity-50"
|
|
22
|
+
>
|
|
23
|
+
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
24
|
+
<path d={dir === "left" ? "M12.5 15l-5-5 5-5" : "M7.5 15l5-5-5-5"} stroke="currentColor" strokeWidth="1.67" strokeLinecap="round" strokeLinejoin="round" />
|
|
25
|
+
</svg>
|
|
26
|
+
</button>
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Card/dot-based pagination. `default` = arrow buttons flanking a dot pager;
|
|
31
|
+
* `minimal` = dots + "Page x of y"; `advanced` = the same in a bordered card.
|
|
32
|
+
* Composes `PaginationDotGroup`.
|
|
33
|
+
*/
|
|
34
|
+
export function PaginationCards({
|
|
35
|
+
page,
|
|
36
|
+
total,
|
|
37
|
+
onPageChange,
|
|
38
|
+
variant = "default",
|
|
39
|
+
className,
|
|
40
|
+
...rest
|
|
41
|
+
}: PaginationCardsProps) {
|
|
42
|
+
const go = (p: number) => p >= 1 && p <= total && onPageChange?.(p);
|
|
43
|
+
const dots = <PaginationDotGroup count={total} current={page - 1} />;
|
|
44
|
+
|
|
45
|
+
const body =
|
|
46
|
+
variant === "minimal" ? (
|
|
47
|
+
<>
|
|
48
|
+
{dots}
|
|
49
|
+
<span className="text-sm font-medium text-text-secondary">Page {page} of {total}</span>
|
|
50
|
+
</>
|
|
51
|
+
) : (
|
|
52
|
+
<>
|
|
53
|
+
<ArrowBtn dir="left" disabled={page <= 1} onClick={() => go(page - 1)} />
|
|
54
|
+
{dots}
|
|
55
|
+
<ArrowBtn dir="right" disabled={page >= total} onClick={() => go(page + 1)} />
|
|
56
|
+
</>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div
|
|
61
|
+
className={clsx(
|
|
62
|
+
"flex items-center gap-lg",
|
|
63
|
+
variant === "default" && "justify-between",
|
|
64
|
+
variant === "advanced" && "rounded-xl border border-border-secondary bg-bg-primary p-lg shadow-xs",
|
|
65
|
+
className,
|
|
66
|
+
)}
|
|
67
|
+
{...rest}
|
|
68
|
+
>
|
|
69
|
+
{body}
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { type HTMLAttributes } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import {
|
|
4
|
+
PaginationDotIndicator,
|
|
5
|
+
type PaginationDotSize,
|
|
6
|
+
type PaginationDotVariant,
|
|
7
|
+
} from "../PaginationDotIndicator";
|
|
8
|
+
|
|
9
|
+
export interface PaginationDotGroupProps
|
|
10
|
+
extends HTMLAttributes<HTMLDivElement> {
|
|
11
|
+
/** Total indicators. */
|
|
12
|
+
count?: number;
|
|
13
|
+
/** Active index. */
|
|
14
|
+
current?: number;
|
|
15
|
+
size?: PaginationDotSize;
|
|
16
|
+
variant?: PaginationDotVariant;
|
|
17
|
+
/** Floating pill on a translucent blurred background (carousel overlay). */
|
|
18
|
+
framed?: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Row of `PaginationDotIndicator`s (carousel pager). `count`/`current` drive the
|
|
23
|
+
* dots; `framed` wraps them in a blurred translucent pill for image overlays.
|
|
24
|
+
* Line variant stretches indicators to fill. framed uses the
|
|
25
|
+
* `alpha-white-90` token.
|
|
26
|
+
*/
|
|
27
|
+
export function PaginationDotGroup({
|
|
28
|
+
count = 3,
|
|
29
|
+
current = 0,
|
|
30
|
+
size = "md",
|
|
31
|
+
variant = "dot",
|
|
32
|
+
framed = false,
|
|
33
|
+
className,
|
|
34
|
+
...rest
|
|
35
|
+
}: PaginationDotGroupProps) {
|
|
36
|
+
const gap =
|
|
37
|
+
variant === "line"
|
|
38
|
+
? size === "lg"
|
|
39
|
+
? "gap-lg"
|
|
40
|
+
: "gap-md"
|
|
41
|
+
: size === "lg"
|
|
42
|
+
? "gap-xl"
|
|
43
|
+
: "gap-lg";
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div
|
|
47
|
+
className={clsx(
|
|
48
|
+
"flex items-center justify-center",
|
|
49
|
+
gap,
|
|
50
|
+
framed && "rounded-full bg-alpha-white-90 p-md backdrop-blur-sm",
|
|
51
|
+
className,
|
|
52
|
+
)}
|
|
53
|
+
{...rest}
|
|
54
|
+
>
|
|
55
|
+
{Array.from({ length: count }).map((_, i) => (
|
|
56
|
+
<PaginationDotIndicator
|
|
57
|
+
key={i}
|
|
58
|
+
current={i === current}
|
|
59
|
+
size={size}
|
|
60
|
+
variant={variant}
|
|
61
|
+
className={variant === "line" ? "h-1.5 min-w-0 flex-1" : undefined}
|
|
62
|
+
/>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
);
|
|
66
|
+
}
|