@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,50 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type DropdownMenuFooterType = "text" | "button";
|
|
5
|
+
|
|
6
|
+
export interface DropdownMenuFooterProps {
|
|
7
|
+
/** `text` shows a left/right caption pair; `button` hosts an action. */
|
|
8
|
+
type?: DropdownMenuFooterType;
|
|
9
|
+
/** Left caption for `type="text"`. */
|
|
10
|
+
left?: ReactNode;
|
|
11
|
+
/** Right caption for `type="text"`. */
|
|
12
|
+
right?: ReactNode;
|
|
13
|
+
/** Action content for `type="button"` — compose a Button. */
|
|
14
|
+
children?: ReactNode;
|
|
15
|
+
className?: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The bottom section of a dropdown menu panel. */
|
|
19
|
+
export function DropdownMenuFooter({
|
|
20
|
+
type = "button",
|
|
21
|
+
left,
|
|
22
|
+
right,
|
|
23
|
+
children,
|
|
24
|
+
className,
|
|
25
|
+
}: DropdownMenuFooterProps) {
|
|
26
|
+
if (type === "text") {
|
|
27
|
+
return (
|
|
28
|
+
<div
|
|
29
|
+
className={clsx(
|
|
30
|
+
"flex items-center justify-between border-t border-border-secondary px-xl py-lg font-body text-sm font-normal text-text-quaternary",
|
|
31
|
+
className,
|
|
32
|
+
)}
|
|
33
|
+
>
|
|
34
|
+
<span>{left}</span>
|
|
35
|
+
<span>{right}</span>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<div
|
|
42
|
+
className={clsx(
|
|
43
|
+
"flex items-center border-t border-border-secondary p-lg font-body [&>*]:flex-1",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
>
|
|
47
|
+
{children}
|
|
48
|
+
</div>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type DropdownMenuHeaderType =
|
|
5
|
+
| "avatar-group"
|
|
6
|
+
| "header"
|
|
7
|
+
| "subheading"
|
|
8
|
+
| "search";
|
|
9
|
+
|
|
10
|
+
export interface DropdownMenuHeaderProps {
|
|
11
|
+
/** Header layout. */
|
|
12
|
+
type?: DropdownMenuHeaderType;
|
|
13
|
+
/** Primary text (name / heading / subheading label). */
|
|
14
|
+
title?: ReactNode;
|
|
15
|
+
/** Secondary line (email / supporting text). */
|
|
16
|
+
supportingText?: ReactNode;
|
|
17
|
+
/** Avatar node for `avatar-group`. */
|
|
18
|
+
avatar?: ReactNode;
|
|
19
|
+
/** Search control for `search` (compose your own input). */
|
|
20
|
+
search?: ReactNode;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** The top section of a dropdown menu panel. */
|
|
25
|
+
export function DropdownMenuHeader({
|
|
26
|
+
type = "header",
|
|
27
|
+
title,
|
|
28
|
+
supportingText,
|
|
29
|
+
avatar,
|
|
30
|
+
search,
|
|
31
|
+
className,
|
|
32
|
+
}: DropdownMenuHeaderProps) {
|
|
33
|
+
if (type === "subheading") {
|
|
34
|
+
return (
|
|
35
|
+
<div className={clsx("px-xl pb-xxs pt-sm font-body", className)}>
|
|
36
|
+
<p className="text-xs font-semibold text-text-brand-secondary">{title}</p>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (type === "search") {
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className={clsx(
|
|
45
|
+
"border-b border-border-secondary p-lg font-body",
|
|
46
|
+
className,
|
|
47
|
+
)}
|
|
48
|
+
>
|
|
49
|
+
{search}
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (type === "avatar-group") {
|
|
55
|
+
return (
|
|
56
|
+
<div
|
|
57
|
+
className={clsx(
|
|
58
|
+
"flex items-center gap-md border-b border-border-secondary p-lg font-body",
|
|
59
|
+
className,
|
|
60
|
+
)}
|
|
61
|
+
>
|
|
62
|
+
{avatar ? <div className="shrink-0">{avatar}</div> : null}
|
|
63
|
+
<div className="flex min-w-0 flex-1 flex-col">
|
|
64
|
+
<p className="truncate text-sm font-semibold text-text-primary">
|
|
65
|
+
{title}
|
|
66
|
+
</p>
|
|
67
|
+
{supportingText ? (
|
|
68
|
+
<p className="truncate text-sm font-normal text-text-tertiary">
|
|
69
|
+
{supportingText}
|
|
70
|
+
</p>
|
|
71
|
+
) : null}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// header
|
|
78
|
+
return (
|
|
79
|
+
<div
|
|
80
|
+
className={clsx(
|
|
81
|
+
"flex flex-col border-b border-border-secondary px-xl py-lg font-body",
|
|
82
|
+
className,
|
|
83
|
+
)}
|
|
84
|
+
>
|
|
85
|
+
<p className="truncate text-sm font-semibold text-text-primary">{title}</p>
|
|
86
|
+
{supportingText ? (
|
|
87
|
+
<p className="truncate text-sm font-normal text-text-tertiary">
|
|
88
|
+
{supportingText}
|
|
89
|
+
</p>
|
|
90
|
+
) : null}
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type DropdownMenuItemInsetIconType =
|
|
5
|
+
| "spacer"
|
|
6
|
+
| "icon"
|
|
7
|
+
| "check"
|
|
8
|
+
| "checkbox"
|
|
9
|
+
| "dot"
|
|
10
|
+
| "avatar"
|
|
11
|
+
| "integration-icon";
|
|
12
|
+
|
|
13
|
+
export interface DropdownMenuItemInsetIconProps {
|
|
14
|
+
/** Which leading visual to render in the 16px slot. */
|
|
15
|
+
type?: DropdownMenuItemInsetIconType;
|
|
16
|
+
/** Glyph/avatar/integration content for `icon`/`avatar`/`integration-icon`. */
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
/** Checked state for `checkbox`. */
|
|
19
|
+
checked?: boolean;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* The 16px leading-icon slot used inside a dropdown menu row. Renders one of
|
|
25
|
+
* several visuals (icon, check, checkbox, radio dot, avatar, integration icon)
|
|
26
|
+
* or an empty spacer to keep labels aligned.
|
|
27
|
+
*/
|
|
28
|
+
export function DropdownMenuItemInsetIcon({
|
|
29
|
+
type = "spacer",
|
|
30
|
+
children,
|
|
31
|
+
checked = false,
|
|
32
|
+
className,
|
|
33
|
+
}: DropdownMenuItemInsetIconProps) {
|
|
34
|
+
return (
|
|
35
|
+
<span
|
|
36
|
+
className={clsx(
|
|
37
|
+
"relative flex size-4 shrink-0 items-center justify-center",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
>
|
|
41
|
+
{type === "icon" ? (
|
|
42
|
+
<span className="flex size-4 items-center justify-center text-fg-quaternary">
|
|
43
|
+
{children}
|
|
44
|
+
</span>
|
|
45
|
+
) : null}
|
|
46
|
+
{type === "check" ? (
|
|
47
|
+
<svg viewBox="0 0 16 16" fill="none" className="size-4 text-fg-brand-primary" aria-hidden>
|
|
48
|
+
<path
|
|
49
|
+
d="M13.333 4 6 11.333 2.667 8"
|
|
50
|
+
stroke="currentColor"
|
|
51
|
+
strokeWidth="1.5"
|
|
52
|
+
strokeLinecap="round"
|
|
53
|
+
strokeLinejoin="round"
|
|
54
|
+
/>
|
|
55
|
+
</svg>
|
|
56
|
+
) : null}
|
|
57
|
+
{type === "checkbox" ? (
|
|
58
|
+
<span
|
|
59
|
+
className={clsx(
|
|
60
|
+
"flex size-4 items-center justify-center rounded-xs border",
|
|
61
|
+
checked
|
|
62
|
+
? "border-border-brand bg-bg-brand-solid text-text-white"
|
|
63
|
+
: "border-border-primary bg-bg-primary",
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
{checked ? (
|
|
67
|
+
<svg viewBox="0 0 16 16" fill="none" className="size-3" aria-hidden>
|
|
68
|
+
<path
|
|
69
|
+
d="M13.333 4 6 11.333 2.667 8"
|
|
70
|
+
stroke="currentColor"
|
|
71
|
+
strokeWidth="1.667"
|
|
72
|
+
strokeLinecap="round"
|
|
73
|
+
strokeLinejoin="round"
|
|
74
|
+
/>
|
|
75
|
+
</svg>
|
|
76
|
+
) : null}
|
|
77
|
+
</span>
|
|
78
|
+
) : null}
|
|
79
|
+
{type === "dot" ? (
|
|
80
|
+
<span className="size-[6px] rounded-full bg-fg-brand-primary" aria-hidden />
|
|
81
|
+
) : null}
|
|
82
|
+
{type === "avatar" || type === "integration-icon" ? (
|
|
83
|
+
<span className="flex size-5 items-center justify-center overflow-hidden rounded-full">
|
|
84
|
+
{children}
|
|
85
|
+
</span>
|
|
86
|
+
) : null}
|
|
87
|
+
</span>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { ButtonHTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export interface DropdownMenuListItemProps
|
|
5
|
+
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "type"> {
|
|
6
|
+
/** Leading visual — typically a DropdownMenuItemInsetIcon. */
|
|
7
|
+
leadingIcon?: ReactNode;
|
|
8
|
+
/** Trailing keyboard-shortcut hint. */
|
|
9
|
+
shortcut?: ReactNode;
|
|
10
|
+
/** Shows the trailing chevron (submenu affordance). */
|
|
11
|
+
chevron?: boolean;
|
|
12
|
+
/** Renders a divider rule instead of an interactive row. */
|
|
13
|
+
divider?: boolean;
|
|
14
|
+
/** Highlights the row as currently active/open. */
|
|
15
|
+
selected?: boolean;
|
|
16
|
+
children?: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function ChevronRight() {
|
|
20
|
+
return (
|
|
21
|
+
<svg viewBox="0 0 16 16" fill="none" className="size-4 shrink-0 text-fg-quaternary" aria-hidden>
|
|
22
|
+
<path
|
|
23
|
+
d="M6 12l4-4-4-4"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
strokeWidth="1.333"
|
|
26
|
+
strokeLinecap="round"
|
|
27
|
+
strokeLinejoin="round"
|
|
28
|
+
/>
|
|
29
|
+
</svg>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* A single row in a dropdown menu — leading icon, label, optional shortcut and
|
|
35
|
+
* submenu chevron. Pass `divider` to render a separator rule instead.
|
|
36
|
+
*/
|
|
37
|
+
export function DropdownMenuListItem({
|
|
38
|
+
leadingIcon,
|
|
39
|
+
shortcut,
|
|
40
|
+
chevron = false,
|
|
41
|
+
divider = false,
|
|
42
|
+
selected = false,
|
|
43
|
+
children,
|
|
44
|
+
className,
|
|
45
|
+
disabled,
|
|
46
|
+
...rest
|
|
47
|
+
}: DropdownMenuListItemProps) {
|
|
48
|
+
if (divider) {
|
|
49
|
+
return (
|
|
50
|
+
<div className="flex w-full items-center py-xs" role="separator">
|
|
51
|
+
<span className="h-px flex-1 bg-border-secondary" />
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="flex w-full items-center px-sm py-px">
|
|
58
|
+
<button
|
|
59
|
+
type="button"
|
|
60
|
+
disabled={disabled}
|
|
61
|
+
className={clsx(
|
|
62
|
+
"flex flex-1 items-center gap-md overflow-hidden rounded-sm py-md pl-[10px] pr-sm font-body text-sm font-semibold",
|
|
63
|
+
"outline-none transition-colors",
|
|
64
|
+
"focus-visible:ring-2 focus-visible:ring-border-brand",
|
|
65
|
+
selected
|
|
66
|
+
? "bg-bg-primary-hover text-text-secondary-hover"
|
|
67
|
+
: "text-text-secondary hover:bg-bg-primary-hover hover:text-text-secondary-hover",
|
|
68
|
+
disabled && "pointer-events-none opacity-50",
|
|
69
|
+
className,
|
|
70
|
+
)}
|
|
71
|
+
{...rest}
|
|
72
|
+
>
|
|
73
|
+
{leadingIcon ? <span className="shrink-0">{leadingIcon}</span> : null}
|
|
74
|
+
<span className="min-w-0 flex-1 truncate text-left">{children}</span>
|
|
75
|
+
{shortcut ? (
|
|
76
|
+
<span className="shrink-0 pr-xs text-xs font-medium text-text-quaternary">
|
|
77
|
+
{shortcut}
|
|
78
|
+
</span>
|
|
79
|
+
) : null}
|
|
80
|
+
{chevron ? <ChevronRight /> : null}
|
|
81
|
+
</button>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type EmptyStateSize = "sm" | "md" | "lg";
|
|
5
|
+
|
|
6
|
+
export interface EmptyStateProps {
|
|
7
|
+
/**
|
|
8
|
+
* The icon/illustration area. Pass any of the 8 Figma options (featured
|
|
9
|
+
* icon, folder, file-type, illustration, users, integrations) as a node —
|
|
10
|
+
* EmptyState doesn't prescribe which.
|
|
11
|
+
*/
|
|
12
|
+
icon?: ReactNode;
|
|
13
|
+
/** Headline. */
|
|
14
|
+
title: ReactNode;
|
|
15
|
+
/** Supporting copy under the title. */
|
|
16
|
+
description?: ReactNode;
|
|
17
|
+
/** Action buttons row — compose Button(s). */
|
|
18
|
+
actions?: ReactNode;
|
|
19
|
+
/** Size scale (affects title size + max text width). */
|
|
20
|
+
size?: EmptyStateSize;
|
|
21
|
+
className?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// md verified: title text-lg. sm/lg inferred one step each.
|
|
25
|
+
const titleSize: Record<EmptyStateSize, string> = {
|
|
26
|
+
sm: "text-md",
|
|
27
|
+
md: "text-lg",
|
|
28
|
+
lg: "text-xl",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Centred empty/zero state — icon, title, supporting text, and actions.
|
|
33
|
+
* The icon area is a single slot (`icon`) per the resolved structural
|
|
34
|
+
* decision (one component, icon slot — not split per icon type).
|
|
35
|
+
*/
|
|
36
|
+
export function EmptyState({
|
|
37
|
+
icon,
|
|
38
|
+
title,
|
|
39
|
+
description,
|
|
40
|
+
actions,
|
|
41
|
+
size = "md",
|
|
42
|
+
className,
|
|
43
|
+
}: EmptyStateProps) {
|
|
44
|
+
return (
|
|
45
|
+
<div
|
|
46
|
+
className={clsx(
|
|
47
|
+
"flex flex-col items-center gap-4xl font-body text-center",
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
>
|
|
51
|
+
<div className="flex flex-col items-center gap-2xl">
|
|
52
|
+
{icon ? <div className="shrink-0">{icon}</div> : null}
|
|
53
|
+
<div className="flex max-w-[352px] flex-col gap-md">
|
|
54
|
+
<p className={clsx("font-semibold text-text-primary", titleSize[size])}>
|
|
55
|
+
{title}
|
|
56
|
+
</p>
|
|
57
|
+
{description ? (
|
|
58
|
+
<p className="text-sm font-normal text-text-tertiary">{description}</p>
|
|
59
|
+
) : null}
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
{actions ? <div className="flex items-start gap-lg">{actions}</div> : null}
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type FeedItemSize = "sm" | "md";
|
|
5
|
+
|
|
6
|
+
export interface FeedItemBaseProps {
|
|
7
|
+
/** Display name of the actor (e.g. "Olivia Rhye"). */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Relative timestamp shown next to the name (e.g. "2 mins ago"). */
|
|
10
|
+
timestamp: string;
|
|
11
|
+
/**
|
|
12
|
+
* The activity line beneath the name — e.g. "Invited Lana Steiner to the
|
|
13
|
+
* team". Pass a ReactNode so embedded links can use
|
|
14
|
+
* `text-text-brand-secondary font-medium`.
|
|
15
|
+
*/
|
|
16
|
+
action?: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Optional supporting content rendered below the action line. In Figma this
|
|
19
|
+
* is the `Supporting item` variant (File / Labels / Text). It's modelled as a
|
|
20
|
+
* slot rather than a `type` prop so consumers compose the actual content —
|
|
21
|
+
* which keeps the File sample's `Colors/Red/600` and the Labels sample's
|
|
22
|
+
* `utility-*` color families (both flagged in figma-map.md, neither in
|
|
23
|
+
* tailwind-preset.js) out of this base component.
|
|
24
|
+
*/
|
|
25
|
+
supporting?: ReactNode;
|
|
26
|
+
/** Avatar image URL. Falls back to a neutral placeholder when omitted. */
|
|
27
|
+
avatarSrc?: string;
|
|
28
|
+
/** Alt text for the avatar image. */
|
|
29
|
+
avatarAlt?: string;
|
|
30
|
+
/** Show the online status dot on the avatar. */
|
|
31
|
+
hasStatus?: boolean;
|
|
32
|
+
/** Render the vertical connector line below the avatar (for stacked feeds). */
|
|
33
|
+
connector?: boolean;
|
|
34
|
+
/** Show the unread "new" dot in the top-right corner. */
|
|
35
|
+
isNew?: boolean;
|
|
36
|
+
size?: FeedItemSize;
|
|
37
|
+
className?: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const avatarSize: Record<FeedItemSize, string> = {
|
|
41
|
+
sm: "size-8", // 32px
|
|
42
|
+
md: "size-10", // 40px
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const statusSize: Record<FeedItemSize, string> = {
|
|
46
|
+
sm: "size-3", // 12px
|
|
47
|
+
md: "size-3.5", // 14px
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function FeedItemBase({
|
|
51
|
+
name,
|
|
52
|
+
timestamp,
|
|
53
|
+
action,
|
|
54
|
+
supporting,
|
|
55
|
+
avatarSrc,
|
|
56
|
+
avatarAlt = "",
|
|
57
|
+
hasStatus = false,
|
|
58
|
+
connector = false,
|
|
59
|
+
isNew = false,
|
|
60
|
+
size = "sm",
|
|
61
|
+
className,
|
|
62
|
+
}: FeedItemBaseProps) {
|
|
63
|
+
return (
|
|
64
|
+
<div
|
|
65
|
+
className={clsx(
|
|
66
|
+
"relative flex w-full items-start gap-lg font-body",
|
|
67
|
+
className,
|
|
68
|
+
)}
|
|
69
|
+
>
|
|
70
|
+
{/* Avatar column (+ connector line) */}
|
|
71
|
+
<div
|
|
72
|
+
className={clsx(
|
|
73
|
+
"flex shrink-0 flex-col items-center gap-sm",
|
|
74
|
+
connector && "self-stretch pb-sm",
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
<div className={clsx("relative shrink-0", avatarSize[size])}>
|
|
78
|
+
<div className="size-full overflow-hidden rounded-full border-[0.5px] border-black/[0.16] bg-bg-quaternary">
|
|
79
|
+
{avatarSrc ? (
|
|
80
|
+
<img
|
|
81
|
+
src={avatarSrc}
|
|
82
|
+
alt={avatarAlt}
|
|
83
|
+
className="size-full object-cover"
|
|
84
|
+
/>
|
|
85
|
+
) : null}
|
|
86
|
+
</div>
|
|
87
|
+
{hasStatus ? (
|
|
88
|
+
<span
|
|
89
|
+
className={clsx(
|
|
90
|
+
"absolute -bottom-0.5 -right-0.5 rounded-full border-[1.5px] border-bg-primary bg-fg-success-secondary",
|
|
91
|
+
statusSize[size],
|
|
92
|
+
)}
|
|
93
|
+
/>
|
|
94
|
+
) : null}
|
|
95
|
+
</div>
|
|
96
|
+
{connector ? (
|
|
97
|
+
<div className="w-px flex-1 bg-border-primary" aria-hidden />
|
|
98
|
+
) : null}
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{/* Content column */}
|
|
102
|
+
<div
|
|
103
|
+
className={clsx(
|
|
104
|
+
"flex min-w-0 flex-1 flex-col",
|
|
105
|
+
supporting ? "gap-lg" : "gap-none",
|
|
106
|
+
connector && "pb-4xl",
|
|
107
|
+
)}
|
|
108
|
+
>
|
|
109
|
+
<div className="flex flex-col items-start">
|
|
110
|
+
<div className="flex w-full items-center gap-md">
|
|
111
|
+
<span className="text-sm font-medium text-text-secondary">
|
|
112
|
+
{name}
|
|
113
|
+
</span>
|
|
114
|
+
<span className="text-xs font-normal text-text-tertiary">
|
|
115
|
+
{timestamp}
|
|
116
|
+
</span>
|
|
117
|
+
</div>
|
|
118
|
+
{action ? (
|
|
119
|
+
<div className="text-sm font-normal text-text-tertiary">
|
|
120
|
+
{action}
|
|
121
|
+
</div>
|
|
122
|
+
) : null}
|
|
123
|
+
</div>
|
|
124
|
+
{supporting ? <div>{supporting}</div> : null}
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
{isNew ? (
|
|
128
|
+
<span
|
|
129
|
+
className="absolute right-0 top-0 size-2 rounded-full bg-fg-brand-primary"
|
|
130
|
+
aria-label="Unread"
|
|
131
|
+
/>
|
|
132
|
+
) : null}
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useRef, useState, type ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { FileUploadBase } from "../FileUploadBase";
|
|
4
|
+
import { FileUploadItemBase } from "../FileUploadItemBase";
|
|
5
|
+
|
|
6
|
+
export interface FileUploadProps {
|
|
7
|
+
/** Selected files (controlled). */
|
|
8
|
+
files?: File[];
|
|
9
|
+
onFilesChange?: (files: File[]) => void;
|
|
10
|
+
/** Max number of files kept; extra picks are dropped. */
|
|
11
|
+
maxFiles?: number;
|
|
12
|
+
/** Accepted MIME types / extensions for the native picker. */
|
|
13
|
+
accept?: string;
|
|
14
|
+
disabled?: boolean;
|
|
15
|
+
actionText?: ReactNode;
|
|
16
|
+
promptText?: ReactNode;
|
|
17
|
+
hint?: ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const formatBytes = (n: number) => {
|
|
22
|
+
if (n < 1024) return `${n} B`;
|
|
23
|
+
if (n < 1024 * 1024) return `${(n / 1024).toFixed(0)} KB`;
|
|
24
|
+
return `${(n / 1024 / 1024).toFixed(1)} MB`;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* File upload — composes the `FileUploadBase` dropzone (click + drag/drop, via a
|
|
29
|
+
* hidden native input) with a `FileUploadItemBase` list. Controlled through
|
|
30
|
+
* `files`/`onFilesChange`; respects `maxFiles` and `accept`. The item rows here
|
|
31
|
+
* are simple "complete" entries — wire `progress`/`status` per item if you need
|
|
32
|
+
* live upload state.
|
|
33
|
+
*/
|
|
34
|
+
export function FileUpload({
|
|
35
|
+
files = [],
|
|
36
|
+
onFilesChange,
|
|
37
|
+
maxFiles = Infinity,
|
|
38
|
+
accept,
|
|
39
|
+
disabled = false,
|
|
40
|
+
actionText,
|
|
41
|
+
promptText,
|
|
42
|
+
hint,
|
|
43
|
+
className,
|
|
44
|
+
}: FileUploadProps) {
|
|
45
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
46
|
+
const [dragActive, setDragActive] = useState(false);
|
|
47
|
+
|
|
48
|
+
const addFiles = (incoming: FileList | null) => {
|
|
49
|
+
if (!incoming?.length) return;
|
|
50
|
+
const merged = [...files, ...Array.from(incoming)].slice(0, maxFiles);
|
|
51
|
+
onFilesChange?.(merged);
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const removeAt = (i: number) => onFilesChange?.(files.filter((_, idx) => idx !== i));
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className={clsx("flex flex-col gap-lg font-body", className)}>
|
|
58
|
+
<label
|
|
59
|
+
className={clsx("block", disabled ? "cursor-not-allowed" : "cursor-pointer")}
|
|
60
|
+
onDragOver={(e) => {
|
|
61
|
+
if (disabled) return;
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
setDragActive(true);
|
|
64
|
+
}}
|
|
65
|
+
onDragLeave={() => setDragActive(false)}
|
|
66
|
+
onDrop={(e) => {
|
|
67
|
+
if (disabled) return;
|
|
68
|
+
e.preventDefault();
|
|
69
|
+
setDragActive(false);
|
|
70
|
+
addFiles(e.dataTransfer.files);
|
|
71
|
+
}}
|
|
72
|
+
>
|
|
73
|
+
<input
|
|
74
|
+
ref={inputRef}
|
|
75
|
+
type="file"
|
|
76
|
+
multiple={maxFiles !== 1}
|
|
77
|
+
accept={accept}
|
|
78
|
+
disabled={disabled}
|
|
79
|
+
className="sr-only"
|
|
80
|
+
onChange={(e) => {
|
|
81
|
+
addFiles(e.target.files);
|
|
82
|
+
e.target.value = "";
|
|
83
|
+
}}
|
|
84
|
+
/>
|
|
85
|
+
<FileUploadBase
|
|
86
|
+
actionText={actionText}
|
|
87
|
+
promptText={promptText}
|
|
88
|
+
hint={hint}
|
|
89
|
+
dragActive={dragActive}
|
|
90
|
+
disabled={disabled}
|
|
91
|
+
/>
|
|
92
|
+
</label>
|
|
93
|
+
|
|
94
|
+
{files.length > 0 && (
|
|
95
|
+
<ul className="flex flex-col gap-md">
|
|
96
|
+
{files.map((f, i) => (
|
|
97
|
+
<li key={`${f.name}-${i}`}>
|
|
98
|
+
<FileUploadItemBase
|
|
99
|
+
fileName={f.name}
|
|
100
|
+
meta={formatBytes(f.size)}
|
|
101
|
+
status="complete"
|
|
102
|
+
progress={100}
|
|
103
|
+
fileExt={f.name.split(".").pop()?.toUpperCase()}
|
|
104
|
+
onDelete={() => removeAt(i)}
|
|
105
|
+
/>
|
|
106
|
+
</li>
|
|
107
|
+
))}
|
|
108
|
+
</ul>
|
|
109
|
+
)}
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|