@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,108 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type LoadingIndicatorStyle = "line-simple" | "line-spinner" | "dot-circle";
|
|
5
|
+
export type LoadingIndicatorSize = "sm" | "md" | "lg" | "xl";
|
|
6
|
+
|
|
7
|
+
export interface LoadingIndicatorProps {
|
|
8
|
+
/** Spinner style. */
|
|
9
|
+
variant?: LoadingIndicatorStyle;
|
|
10
|
+
/** Size scale. md = 48px (verified); sm/lg/xl inferred. */
|
|
11
|
+
size?: LoadingIndicatorSize;
|
|
12
|
+
/** Optional supporting text below the spinner (e.g. "Loading..."). */
|
|
13
|
+
label?: ReactNode;
|
|
14
|
+
className?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// md = 48px verified from Figma; sm/lg/xl inferred from the size ramp.
|
|
18
|
+
const sizePx: Record<LoadingIndicatorSize, number> = { sm: 32, md: 48, lg: 64, xl: 80 };
|
|
19
|
+
const borderPx: Record<LoadingIndicatorSize, number> = { sm: 3, md: 4, lg: 5, xl: 6 };
|
|
20
|
+
|
|
21
|
+
function DotCircle({ px }: { px: number }) {
|
|
22
|
+
const dot = Math.max(3, Math.round(px / 8));
|
|
23
|
+
return (
|
|
24
|
+
<span
|
|
25
|
+
className="relative inline-block animate-spin"
|
|
26
|
+
style={{ width: px, height: px, animationDuration: "1.2s" }}
|
|
27
|
+
>
|
|
28
|
+
{Array.from({ length: 12 }).map((_, i) => (
|
|
29
|
+
<span
|
|
30
|
+
key={i}
|
|
31
|
+
className="absolute left-1/2 top-0 rounded-full bg-fg-brand-primary"
|
|
32
|
+
style={{
|
|
33
|
+
width: dot,
|
|
34
|
+
height: dot,
|
|
35
|
+
marginLeft: -dot / 2,
|
|
36
|
+
transformOrigin: `${dot / 2}px ${px / 2}px`,
|
|
37
|
+
transform: `rotate(${i * 30}deg)`,
|
|
38
|
+
opacity: (i + 1) / 12,
|
|
39
|
+
}}
|
|
40
|
+
/>
|
|
41
|
+
))}
|
|
42
|
+
</span>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Spinner / loading indicator. `line-simple` and `line-spinner` are stroked
|
|
48
|
+
* rings (spinner uses a round cap); `dot-circle` is a ring of fading dots.
|
|
49
|
+
* Rendered with CSS animation rather than the Figma image asset.
|
|
50
|
+
*/
|
|
51
|
+
export function LoadingIndicator({
|
|
52
|
+
variant = "line-simple",
|
|
53
|
+
size = "md",
|
|
54
|
+
label,
|
|
55
|
+
className,
|
|
56
|
+
}: LoadingIndicatorProps) {
|
|
57
|
+
const px = sizePx[size];
|
|
58
|
+
|
|
59
|
+
let spinner: ReactNode;
|
|
60
|
+
if (variant === "dot-circle") {
|
|
61
|
+
spinner = <DotCircle px={px} />;
|
|
62
|
+
} else {
|
|
63
|
+
const stroke = borderPx[size];
|
|
64
|
+
const r = (px - stroke) / 2;
|
|
65
|
+
const c = 2 * Math.PI * r;
|
|
66
|
+
spinner = (
|
|
67
|
+
<svg
|
|
68
|
+
className="animate-spin"
|
|
69
|
+
style={{ width: px, height: px, animationDuration: "0.9s" }}
|
|
70
|
+
viewBox={`0 0 ${px} ${px}`}
|
|
71
|
+
role="presentation"
|
|
72
|
+
>
|
|
73
|
+
<circle
|
|
74
|
+
cx={px / 2}
|
|
75
|
+
cy={px / 2}
|
|
76
|
+
r={r}
|
|
77
|
+
fill="none"
|
|
78
|
+
className="stroke-bg-quaternary"
|
|
79
|
+
strokeWidth={stroke}
|
|
80
|
+
/>
|
|
81
|
+
<circle
|
|
82
|
+
cx={px / 2}
|
|
83
|
+
cy={px / 2}
|
|
84
|
+
r={r}
|
|
85
|
+
fill="none"
|
|
86
|
+
className="stroke-fg-brand-primary"
|
|
87
|
+
strokeWidth={stroke}
|
|
88
|
+
strokeDasharray={c}
|
|
89
|
+
strokeDashoffset={c * 0.75}
|
|
90
|
+
strokeLinecap={variant === "line-spinner" ? "round" : "butt"}
|
|
91
|
+
/>
|
|
92
|
+
</svg>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return (
|
|
97
|
+
<div
|
|
98
|
+
role="status"
|
|
99
|
+
aria-live="polite"
|
|
100
|
+
className={clsx("inline-flex flex-col items-center gap-xl font-body", className)}
|
|
101
|
+
>
|
|
102
|
+
{spinner}
|
|
103
|
+
{label ? (
|
|
104
|
+
<span className="text-sm font-medium text-text-secondary">{label}</span>
|
|
105
|
+
) : null}
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { MessageShell, bubbleClasses } from "../Message/messageShared";
|
|
4
|
+
|
|
5
|
+
export type MediaMessageType = "file" | "audio" | "video" | "image";
|
|
6
|
+
|
|
7
|
+
export interface MediaMessageProps {
|
|
8
|
+
/** Attachment kind. */
|
|
9
|
+
type?: MediaMessageType;
|
|
10
|
+
/** File name (file/audio/video). */
|
|
11
|
+
fileName?: ReactNode;
|
|
12
|
+
/** Supporting line — size / duration. */
|
|
13
|
+
meta?: ReactNode;
|
|
14
|
+
/** Short extension label for the file-type icon (file type). */
|
|
15
|
+
fileExt?: string;
|
|
16
|
+
/** Image/video poster source. */
|
|
17
|
+
src?: string;
|
|
18
|
+
author?: ReactNode;
|
|
19
|
+
time?: ReactNode;
|
|
20
|
+
avatar?: ReactNode;
|
|
21
|
+
sent?: boolean;
|
|
22
|
+
reactions?: ReactNode;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function FileTypeIcon({ ext }: { ext: string }) {
|
|
27
|
+
return (
|
|
28
|
+
<span className="relative flex h-10 w-8 shrink-0 items-end justify-center rounded-[2px] border border-border-secondary bg-bg-primary">
|
|
29
|
+
<span className="mb-1 rounded-[2px] bg-utility-brand-600 px-1 py-[2px] text-[10px] font-bold text-text-white">
|
|
30
|
+
{ext}
|
|
31
|
+
</span>
|
|
32
|
+
</span>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function PlayButton() {
|
|
37
|
+
return (
|
|
38
|
+
<span className="flex size-10 shrink-0 items-center justify-center rounded-full bg-bg-brand-solid text-text-white">
|
|
39
|
+
<svg viewBox="0 0 16 16" fill="currentColor" className="size-4" aria-hidden>
|
|
40
|
+
<path d="M4 2.5v11l9-5.5-9-5.5Z" />
|
|
41
|
+
</svg>
|
|
42
|
+
</span>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A chat message carrying an attachment — file, audio, video, or image. */
|
|
47
|
+
export function MediaMessage({
|
|
48
|
+
type = "file",
|
|
49
|
+
fileName,
|
|
50
|
+
meta,
|
|
51
|
+
fileExt = "JPG",
|
|
52
|
+
src,
|
|
53
|
+
author,
|
|
54
|
+
time,
|
|
55
|
+
avatar,
|
|
56
|
+
sent = false,
|
|
57
|
+
reactions,
|
|
58
|
+
className,
|
|
59
|
+
}: MediaMessageProps) {
|
|
60
|
+
let bubble: ReactNode;
|
|
61
|
+
if (type === "image") {
|
|
62
|
+
bubble = (
|
|
63
|
+
<div className={clsx(bubbleClasses(sent, "white"))}>
|
|
64
|
+
{src ? <img src={src} alt={typeof fileName === "string" ? fileName : ""} className="block w-full object-cover" /> : null}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
} else if (type === "video") {
|
|
68
|
+
bubble = (
|
|
69
|
+
<div className={clsx(bubbleClasses(sent, "white"), "relative")}>
|
|
70
|
+
{src ? <img src={src} alt="" className="block w-full object-cover" /> : null}
|
|
71
|
+
<span className="absolute inset-0 flex items-center justify-center">
|
|
72
|
+
<PlayButton />
|
|
73
|
+
</span>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
} else if (type === "audio") {
|
|
77
|
+
bubble = (
|
|
78
|
+
<div className={clsx(bubbleClasses(sent, "white"), "flex items-center gap-md p-lg")}>
|
|
79
|
+
<PlayButton />
|
|
80
|
+
<span className="flex h-6 flex-1 items-center gap-[2px]">
|
|
81
|
+
{Array.from({ length: 24 }).map((_, i) => (
|
|
82
|
+
<span
|
|
83
|
+
key={i}
|
|
84
|
+
className="w-[2px] rounded-full bg-bg-quaternary"
|
|
85
|
+
style={{ height: `${30 + Math.abs(Math.sin(i)) * 70}%` }}
|
|
86
|
+
/>
|
|
87
|
+
))}
|
|
88
|
+
</span>
|
|
89
|
+
{meta ? <span className="shrink-0 text-sm text-text-tertiary">{meta}</span> : null}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
bubble = (
|
|
94
|
+
<div className={clsx(bubbleClasses(sent, "white"), "flex items-start gap-lg p-lg")}>
|
|
95
|
+
<FileTypeIcon ext={fileExt} />
|
|
96
|
+
<span className="flex min-w-0 flex-col">
|
|
97
|
+
<span className="truncate text-sm font-medium text-text-secondary">{fileName}</span>
|
|
98
|
+
{meta ? <span className="text-sm font-normal text-text-tertiary">{meta}</span> : null}
|
|
99
|
+
</span>
|
|
100
|
+
</div>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<MessageShell author={author} time={time} avatar={avatar} sent={sent} reactions={reactions} className={className}>
|
|
106
|
+
{bubble}
|
|
107
|
+
</MessageShell>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { InputHTMLAttributes } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
export type MegaInputFieldBaseSize = "sm" | "md" | "lg";
|
|
5
|
+
|
|
6
|
+
export interface MegaInputFieldBaseProps
|
|
7
|
+
extends Omit<InputHTMLAttributes<HTMLInputElement>, "size"> {
|
|
8
|
+
size?: MegaInputFieldBaseSize;
|
|
9
|
+
/** Error styling. */
|
|
10
|
+
destructive?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const sizeClasses: Record<MegaInputFieldBaseSize, string> = {
|
|
14
|
+
sm: "size-16 rounded-lg text-display-lg",
|
|
15
|
+
md: "size-20 rounded-xl text-display-lg",
|
|
16
|
+
lg: "size-24 rounded-xl text-display-xl",
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A single large character/code-entry cell (one digit per box) used to compose
|
|
21
|
+
* OTP / verification inputs. `Size` (sm/md/lg) × states (placeholder/focused/
|
|
22
|
+
* filled/disabled/error) — focus & fill are native; pass `destructive` for error.
|
|
23
|
+
*/
|
|
24
|
+
export function MegaInputFieldBase({
|
|
25
|
+
size = "md",
|
|
26
|
+
destructive,
|
|
27
|
+
disabled,
|
|
28
|
+
className,
|
|
29
|
+
...rest
|
|
30
|
+
}: MegaInputFieldBaseProps) {
|
|
31
|
+
return (
|
|
32
|
+
<input
|
|
33
|
+
inputMode="numeric"
|
|
34
|
+
maxLength={1}
|
|
35
|
+
disabled={disabled}
|
|
36
|
+
className={clsx(
|
|
37
|
+
"border bg-bg-primary text-center font-display font-medium shadow-xs outline-none",
|
|
38
|
+
"placeholder:text-text-placeholder placeholder:opacity-40",
|
|
39
|
+
sizeClasses[size],
|
|
40
|
+
destructive
|
|
41
|
+
? "border-border-error-subtle text-text-error-primary"
|
|
42
|
+
: "border-border-primary text-text-brand-tertiary-alt focus:border-border-brand focus:ring-2 focus:ring-border-brand focus:ring-offset-2 focus:ring-offset-bg-primary",
|
|
43
|
+
disabled && "pointer-events-none opacity-50",
|
|
44
|
+
className,
|
|
45
|
+
)}
|
|
46
|
+
{...rest}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
import { MessageShell, bubbleClasses } from "./messageShared";
|
|
4
|
+
|
|
5
|
+
export type MessageType = "message" | "reply" | "writing";
|
|
6
|
+
|
|
7
|
+
export interface MessageProps {
|
|
8
|
+
/** Bubble variant. */
|
|
9
|
+
type?: MessageType;
|
|
10
|
+
/** Message body text. */
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
/** Quoted text shown above the body for `type="reply"`. */
|
|
13
|
+
replyTo?: ReactNode;
|
|
14
|
+
author?: ReactNode;
|
|
15
|
+
time?: ReactNode;
|
|
16
|
+
avatar?: ReactNode;
|
|
17
|
+
sent?: boolean;
|
|
18
|
+
/** Reaction chips (compose MessageReaction). */
|
|
19
|
+
reactions?: ReactNode;
|
|
20
|
+
className?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function WritingDots() {
|
|
24
|
+
return (
|
|
25
|
+
<span className="flex items-center gap-xs px-xs py-1">
|
|
26
|
+
{[0, 1, 2].map((i) => (
|
|
27
|
+
<span
|
|
28
|
+
key={i}
|
|
29
|
+
className="size-1 animate-bounce rounded-full bg-fg-quaternary"
|
|
30
|
+
style={{ animationDelay: `${i * 0.15}s` }}
|
|
31
|
+
/>
|
|
32
|
+
))}
|
|
33
|
+
</span>
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A text chat bubble. `type` covers a plain message, a reply (with a quoted
|
|
39
|
+
* line), and the writing/typing indicator. Use `sent` for own messages
|
|
40
|
+
* (right-aligned, brand bubble).
|
|
41
|
+
*/
|
|
42
|
+
export function Message({
|
|
43
|
+
type = "message",
|
|
44
|
+
children,
|
|
45
|
+
replyTo,
|
|
46
|
+
author,
|
|
47
|
+
time,
|
|
48
|
+
avatar,
|
|
49
|
+
sent = false,
|
|
50
|
+
reactions,
|
|
51
|
+
className,
|
|
52
|
+
}: MessageProps) {
|
|
53
|
+
const tone = sent ? "brand" : "secondary";
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<MessageShell
|
|
57
|
+
author={author}
|
|
58
|
+
time={time}
|
|
59
|
+
avatar={avatar}
|
|
60
|
+
sent={sent}
|
|
61
|
+
reactions={type === "writing" ? undefined : reactions}
|
|
62
|
+
className={className}
|
|
63
|
+
>
|
|
64
|
+
{type === "writing" ? (
|
|
65
|
+
<div className={clsx(bubbleClasses(sent, "secondary"), "px-md py-md")}>
|
|
66
|
+
<WritingDots />
|
|
67
|
+
</div>
|
|
68
|
+
) : (
|
|
69
|
+
<div className={clsx(bubbleClasses(sent, tone), "flex flex-col gap-xs px-lg py-md text-md")}>
|
|
70
|
+
{type === "reply" && replyTo ? (
|
|
71
|
+
<span
|
|
72
|
+
className={clsx(
|
|
73
|
+
"border-l-2 pl-md text-sm",
|
|
74
|
+
sent ? "border-white/40 text-text-white/80" : "border-border-secondary text-text-tertiary",
|
|
75
|
+
)}
|
|
76
|
+
>
|
|
77
|
+
{replyTo}
|
|
78
|
+
</span>
|
|
79
|
+
) : null}
|
|
80
|
+
<span className="font-normal">{children}</span>
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</MessageShell>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import clsx from "clsx";
|
|
3
|
+
|
|
4
|
+
// Shared layout for the Message family (Message / MediaMessage / LinkMessage).
|
|
5
|
+
// PRIVATE — not exported from the barrel.
|
|
6
|
+
|
|
7
|
+
export type MessageBubbleTone = "secondary" | "white" | "brand";
|
|
8
|
+
|
|
9
|
+
export interface MessageShellProps {
|
|
10
|
+
/** Author name. */
|
|
11
|
+
author?: ReactNode;
|
|
12
|
+
/** Timestamp. */
|
|
13
|
+
time?: ReactNode;
|
|
14
|
+
/** Avatar node (32px). */
|
|
15
|
+
avatar?: ReactNode;
|
|
16
|
+
/** Sent (own) message → right-aligned. */
|
|
17
|
+
sent?: boolean;
|
|
18
|
+
/** Reaction chips row (compose MessageReaction). */
|
|
19
|
+
reactions?: ReactNode;
|
|
20
|
+
/** The bubble content. */
|
|
21
|
+
children: ReactNode;
|
|
22
|
+
className?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Bubble container classes — tone + sent-aware corner. */
|
|
26
|
+
export function bubbleClasses(sent: boolean, tone: MessageBubbleTone) {
|
|
27
|
+
return clsx(
|
|
28
|
+
"overflow-hidden rounded-md border",
|
|
29
|
+
sent ? "rounded-tr-none" : "rounded-tl-none",
|
|
30
|
+
tone === "brand" && "border-transparent bg-bg-brand-solid text-text-white",
|
|
31
|
+
tone === "secondary" && "border-border-secondary bg-bg-secondary text-text-primary",
|
|
32
|
+
tone === "white" && "border-border-secondary bg-bg-primary text-text-primary",
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Avatar + name/time header + bubble + reactions, with sent alignment. */
|
|
37
|
+
export function MessageShell({
|
|
38
|
+
author,
|
|
39
|
+
time,
|
|
40
|
+
avatar,
|
|
41
|
+
sent = false,
|
|
42
|
+
reactions,
|
|
43
|
+
children,
|
|
44
|
+
className,
|
|
45
|
+
}: MessageShellProps) {
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
className={clsx(
|
|
49
|
+
"flex w-[360px] max-w-full items-start gap-lg font-body",
|
|
50
|
+
sent && "flex-row-reverse",
|
|
51
|
+
className,
|
|
52
|
+
)}
|
|
53
|
+
>
|
|
54
|
+
{avatar ? <div className="shrink-0">{avatar}</div> : null}
|
|
55
|
+
<div className={clsx("flex min-w-0 flex-1 flex-col gap-sm", sent && "items-end")}>
|
|
56
|
+
{author || time ? (
|
|
57
|
+
<div className={clsx("flex w-full items-center gap-md", sent && "flex-row-reverse")}>
|
|
58
|
+
<span className="min-w-0 flex-1 truncate text-sm font-medium text-text-secondary">
|
|
59
|
+
{author}
|
|
60
|
+
</span>
|
|
61
|
+
{time ? (
|
|
62
|
+
<span className="shrink-0 text-xs font-normal text-text-tertiary">{time}</span>
|
|
63
|
+
) : null}
|
|
64
|
+
</div>
|
|
65
|
+
) : null}
|
|
66
|
+
{children}
|
|
67
|
+
{reactions ? (
|
|
68
|
+
<div className="flex w-full items-center justify-end gap-xs">{reactions}</div>
|
|
69
|
+
) : null}
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type HTMLAttributes,
|
|
3
|
+
type ReactNode,
|
|
4
|
+
type TextareaHTMLAttributes,
|
|
5
|
+
} from "react";
|
|
6
|
+
import clsx from "clsx";
|
|
7
|
+
|
|
8
|
+
export type MessageActionType = "minimal" | "textarea" | "advanced";
|
|
9
|
+
|
|
10
|
+
export interface MessageActionProps
|
|
11
|
+
extends Omit<HTMLAttributes<HTMLDivElement>, "onChange"> {
|
|
12
|
+
/** minimal = single-line + send · textarea = multiline composer · advanced = AI prompt box */
|
|
13
|
+
type?: MessageActionType;
|
|
14
|
+
/** Controlled value of the input/textarea. */
|
|
15
|
+
value?: string;
|
|
16
|
+
onValueChange?: (value: string) => void;
|
|
17
|
+
/** Fired when the send affordance is activated. */
|
|
18
|
+
onSend?: () => void;
|
|
19
|
+
placeholder?: string;
|
|
20
|
+
/** Advanced only — left-side author chip (avatar + name). */
|
|
21
|
+
author?: ReactNode;
|
|
22
|
+
textareaProps?: TextareaHTMLAttributes<HTMLTextAreaElement>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* --- minimal inline icons (currentColor, 16/20px) ------------------------- */
|
|
26
|
+
const Send = ({ className }: { className?: string }) => (
|
|
27
|
+
<svg className={className} viewBox="0 0 20 20" fill="none" aria-hidden>
|
|
28
|
+
<path
|
|
29
|
+
d="M17.5 2.5 9.17 10.83M17.5 2.5l-5.3 15-3.03-6.67L2.5 7.8l15-5.3Z"
|
|
30
|
+
stroke="currentColor"
|
|
31
|
+
strokeWidth="1.67"
|
|
32
|
+
strokeLinecap="round"
|
|
33
|
+
strokeLinejoin="round"
|
|
34
|
+
/>
|
|
35
|
+
</svg>
|
|
36
|
+
);
|
|
37
|
+
const Attach = ({ className }: { className?: string }) => (
|
|
38
|
+
<svg className={className} viewBox="0 0 16 16" fill="none" aria-hidden>
|
|
39
|
+
<path
|
|
40
|
+
d="M14 7.33 8.07 13.26a3.33 3.33 0 0 1-4.71-4.71l5.93-5.93a2.22 2.22 0 1 1 3.14 3.14l-5.94 5.93a1.11 1.11 0 0 1-1.57-1.57l5.48-5.47"
|
|
41
|
+
stroke="currentColor"
|
|
42
|
+
strokeWidth="1.33"
|
|
43
|
+
strokeLinecap="round"
|
|
44
|
+
strokeLinejoin="round"
|
|
45
|
+
/>
|
|
46
|
+
</svg>
|
|
47
|
+
);
|
|
48
|
+
const Smile = ({ className }: { className?: string }) => (
|
|
49
|
+
<svg className={className} viewBox="0 0 16 16" fill="none" aria-hidden>
|
|
50
|
+
<path
|
|
51
|
+
d="M5.33 9.33s1 1.34 2.67 1.34S10.67 9.33 10.67 9.33M6 6h.01M10 6h.01M14.67 8A6.67 6.67 0 1 1 1.33 8a6.67 6.67 0 0 1 13.34 0Z"
|
|
52
|
+
stroke="currentColor"
|
|
53
|
+
strokeWidth="1.33"
|
|
54
|
+
strokeLinecap="round"
|
|
55
|
+
strokeLinejoin="round"
|
|
56
|
+
/>
|
|
57
|
+
</svg>
|
|
58
|
+
);
|
|
59
|
+
const Mic = ({ className }: { className?: string }) => (
|
|
60
|
+
<svg className={className} viewBox="0 0 16 16" fill="none" aria-hidden>
|
|
61
|
+
<path
|
|
62
|
+
d="M8 1.33A2 2 0 0 0 6 3.33V8a2 2 0 1 0 4 0V3.33a2 2 0 0 0-2-2ZM12.67 6.67V8A4.67 4.67 0 0 1 3.33 8V6.67M8 12.67v2"
|
|
63
|
+
stroke="currentColor"
|
|
64
|
+
strokeWidth="1.33"
|
|
65
|
+
strokeLinecap="round"
|
|
66
|
+
strokeLinejoin="round"
|
|
67
|
+
/>
|
|
68
|
+
</svg>
|
|
69
|
+
);
|
|
70
|
+
const Italic = ({ className }: { className?: string }) => (
|
|
71
|
+
<svg className={className} viewBox="0 0 16 16" fill="none" aria-hidden>
|
|
72
|
+
<path
|
|
73
|
+
d="M9.33 3.33H6.67M9.33 12.67H6.67M10 3.33 6 12.67"
|
|
74
|
+
stroke="currentColor"
|
|
75
|
+
strokeWidth="1.33"
|
|
76
|
+
strokeLinecap="round"
|
|
77
|
+
strokeLinejoin="round"
|
|
78
|
+
/>
|
|
79
|
+
</svg>
|
|
80
|
+
);
|
|
81
|
+
const Chevron = ({ className }: { className?: string }) => (
|
|
82
|
+
<svg className={className} viewBox="0 0 12 12" fill="none" aria-hidden>
|
|
83
|
+
<path
|
|
84
|
+
d="m3 4.5 3 3 3-3"
|
|
85
|
+
stroke="currentColor"
|
|
86
|
+
strokeWidth="1.5"
|
|
87
|
+
strokeLinecap="round"
|
|
88
|
+
strokeLinejoin="round"
|
|
89
|
+
/>
|
|
90
|
+
</svg>
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const utilityBtn =
|
|
94
|
+
"flex items-center justify-center rounded-sm p-sm text-fg-quaternary transition-colors hover:text-fg-quaternary-hover";
|
|
95
|
+
const inputBox =
|
|
96
|
+
"w-full border border-border-primary bg-bg-primary text-text-placeholder shadow-xs outline-none placeholder:text-text-placeholder";
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Message composer. Three layouts off `type`:
|
|
100
|
+
* - **minimal** — single-line field + square send button (row).
|
|
101
|
+
* - **textarea** — multiline box, attach/emoji + brand Send pinned bottom-right,
|
|
102
|
+
* record button top-right.
|
|
103
|
+
* - **advanced** — AI prompt box on `bg-secondary`; author chip + Shortcuts/Attach
|
|
104
|
+
* footer, mic top-right, resize handle.
|
|
105
|
+
*
|
|
106
|
+
* Renders real `<input>`/`<textarea>` (controlled via `value`/`onValueChange`);
|
|
107
|
+
* icons are inline SVG so the package ships no remote asset deps.
|
|
108
|
+
*/
|
|
109
|
+
export function MessageAction({
|
|
110
|
+
type = "minimal",
|
|
111
|
+
value,
|
|
112
|
+
onValueChange,
|
|
113
|
+
onSend,
|
|
114
|
+
placeholder,
|
|
115
|
+
author,
|
|
116
|
+
textareaProps,
|
|
117
|
+
className,
|
|
118
|
+
...rest
|
|
119
|
+
}: MessageActionProps) {
|
|
120
|
+
const ph = placeholder ?? (type === "advanced" ? "Ask me anything..." : "Message");
|
|
121
|
+
|
|
122
|
+
if (type === "minimal") {
|
|
123
|
+
return (
|
|
124
|
+
<div className={clsx("flex w-[360px] items-start gap-lg", className)} {...rest}>
|
|
125
|
+
<input
|
|
126
|
+
value={value}
|
|
127
|
+
onChange={(e) => onValueChange?.(e.target.value)}
|
|
128
|
+
placeholder={ph}
|
|
129
|
+
className={clsx(inputBox, "rounded-md px-3.5 py-2.5 text-md")}
|
|
130
|
+
/>
|
|
131
|
+
<button
|
|
132
|
+
type="button"
|
|
133
|
+
onClick={onSend}
|
|
134
|
+
aria-label="Send"
|
|
135
|
+
className="flex items-center justify-center rounded-md border border-border-primary bg-bg-primary p-lg text-fg-quaternary shadow-xs transition-colors hover:text-fg-quaternary-hover"
|
|
136
|
+
>
|
|
137
|
+
<Send className="size-5" />
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (type === "textarea") {
|
|
144
|
+
return (
|
|
145
|
+
<div className={clsx("relative flex h-32 w-[360px] flex-col", className)} {...rest}>
|
|
146
|
+
<textarea
|
|
147
|
+
value={value}
|
|
148
|
+
onChange={(e) => onValueChange?.(e.target.value)}
|
|
149
|
+
placeholder={ph}
|
|
150
|
+
{...textareaProps}
|
|
151
|
+
className={clsx(inputBox, "h-full resize-none rounded-md px-3.5 py-3 text-md")}
|
|
152
|
+
/>
|
|
153
|
+
<button type="button" aria-label="Record" className={clsx(utilityBtn, "absolute right-2 top-2")}>
|
|
154
|
+
<Mic className="size-4" />
|
|
155
|
+
</button>
|
|
156
|
+
<div className="absolute bottom-2 right-3.5 flex items-center gap-md">
|
|
157
|
+
<div className="flex items-center gap-xxs">
|
|
158
|
+
<button type="button" aria-label="Attach" className={utilityBtn}>
|
|
159
|
+
<Attach className="size-4" />
|
|
160
|
+
</button>
|
|
161
|
+
<button type="button" aria-label="Emoji" className={utilityBtn}>
|
|
162
|
+
<Smile className="size-4" />
|
|
163
|
+
</button>
|
|
164
|
+
</div>
|
|
165
|
+
<button
|
|
166
|
+
type="button"
|
|
167
|
+
onClick={onSend}
|
|
168
|
+
className="text-sm font-semibold text-text-brand-secondary"
|
|
169
|
+
>
|
|
170
|
+
Send
|
|
171
|
+
</button>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// advanced
|
|
178
|
+
return (
|
|
179
|
+
<div
|
|
180
|
+
className={clsx(
|
|
181
|
+
"relative flex h-40 w-[360px] flex-col rounded-xl border border-border-secondary bg-bg-secondary",
|
|
182
|
+
className,
|
|
183
|
+
)}
|
|
184
|
+
{...rest}
|
|
185
|
+
>
|
|
186
|
+
<textarea
|
|
187
|
+
value={value}
|
|
188
|
+
onChange={(e) => onValueChange?.(e.target.value)}
|
|
189
|
+
placeholder={ph}
|
|
190
|
+
{...textareaProps}
|
|
191
|
+
className={clsx(inputBox, "flex-1 resize-none rounded-xl px-3.5 py-3 text-md")}
|
|
192
|
+
/>
|
|
193
|
+
<button type="button" aria-label="Record" className={clsx(utilityBtn, "absolute right-[7px] top-[7px]")}>
|
|
194
|
+
<Mic className="size-4" />
|
|
195
|
+
</button>
|
|
196
|
+
<div className="flex items-center gap-lg px-lg py-md">
|
|
197
|
+
<div className="flex flex-1 items-center">
|
|
198
|
+
{author ?? (
|
|
199
|
+
<div className="flex items-center gap-xs">
|
|
200
|
+
<span className="size-4 rounded-full bg-bg-tertiary" />
|
|
201
|
+
<span className="flex items-center gap-xxs text-xs font-semibold text-text-tertiary">
|
|
202
|
+
Olivia
|
|
203
|
+
<Chevron className="size-3" />
|
|
204
|
+
</span>
|
|
205
|
+
</div>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
<div className="flex items-center gap-lg text-text-tertiary">
|
|
209
|
+
<span className="flex items-center gap-xs text-xs font-semibold">
|
|
210
|
+
<Italic className="size-4" />
|
|
211
|
+
Shortcuts
|
|
212
|
+
</span>
|
|
213
|
+
<span className="flex items-center gap-xs text-xs font-semibold">
|
|
214
|
+
<Attach className="size-4" />
|
|
215
|
+
Attach
|
|
216
|
+
</span>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
);
|
|
221
|
+
}
|