@scalepad/ui 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/.ai/rules/date-handling.md +39 -0
- package/.ai/rules/figma-design-system.md +372 -0
- package/.ai/rules/figma-lm-design-system-keys.md +680 -0
- package/.ai/rules/file-extensions.md +13 -0
- package/.ai/rules/modal-confirmation-mutation.md +56 -0
- package/.ai/rules/react-hooks.md +29 -0
- package/.ai/rules/styling.md +83 -0
- package/AGENTS.md +37 -0
- package/README.md +125 -0
- package/figma.config.json +9 -0
- package/package.json +127 -0
- package/scripts/install-ai-rules.mjs +136 -0
- package/src/ThemeProvider.tsx +57 -0
- package/src/charts.ts +32 -0
- package/src/components/ActionCard/ActionCard.css.ts +60 -0
- package/src/components/ActionCard/ActionCard.tsx +154 -0
- package/src/components/ActionCard/index.ts +2 -0
- package/src/components/Anchor/Anchor.tsx +47 -0
- package/src/components/Anchor/index.ts +2 -0
- package/src/components/AppliedFiltersManagerBar/AppliedFiltersManagerBar.tsx +105 -0
- package/src/components/AppliedFiltersManagerBar/FilterBadge.css.ts +23 -0
- package/src/components/AppliedFiltersManagerBar/FilterBadge.tsx +50 -0
- package/src/components/AppliedFiltersManagerBar/index.ts +5 -0
- package/src/components/Badge/Badge.css.ts +72 -0
- package/src/components/Badge/Badge.figma.tsx +43 -0
- package/src/components/Badge/Badge.tsx +159 -0
- package/src/components/Badge/index.ts +2 -0
- package/src/components/BreadCrumb/BreadCrumb.tsx +62 -0
- package/src/components/BreadCrumb/index.ts +2 -0
- package/src/components/BulkActionBar/BulkActionBar.css.ts +26 -0
- package/src/components/BulkActionBar/BulkActionBar.tsx +164 -0
- package/src/components/BulkActionBar/index.ts +2 -0
- package/src/components/Button/Button.css.ts +272 -0
- package/src/components/Button/Button.figma.tsx +74 -0
- package/src/components/Button/Button.tsx +84 -0
- package/src/components/Button/index.ts +2 -0
- package/src/components/Charts/ChartTooltip.figma.tsx +33 -0
- package/src/components/Charts/ChartTooltip.tsx +101 -0
- package/src/components/Charts/MiniBarSparkline.tsx +75 -0
- package/src/components/Charts/StackedPatternBarChart.tsx +494 -0
- package/src/components/Charts/TrendAreaChart.css.ts +23 -0
- package/src/components/Charts/TrendAreaChart.tsx +210 -0
- package/src/components/Charts/index.ts +12 -0
- package/src/components/CodePanel/CodePanel.css.ts +113 -0
- package/src/components/CodePanel/CodePanel.tsx +121 -0
- package/src/components/CodePanel/index.ts +2 -0
- package/src/components/CommentComposer/CommentComposer.css.ts +60 -0
- package/src/components/CommentComposer/CommentComposer.tsx +181 -0
- package/src/components/CommentComposer/index.ts +2 -0
- package/src/components/ConfirmationModal/ConfirmationModal.tsx +149 -0
- package/src/components/ConfirmationModal/index.ts +2 -0
- package/src/components/ConfirmationTooltip/ConfirmationTooltip.tsx +132 -0
- package/src/components/ConfirmationTooltip/index.ts +2 -0
- package/src/components/DataDialog.figma.tsx +33 -0
- package/src/components/DataDialog.tsx +46 -0
- package/src/components/DataTable/DataTable.tsx +1042 -0
- package/src/components/DataTable/RowExpandToggle.tsx +105 -0
- package/src/components/DataTable/RowGroupHeader.tsx +190 -0
- package/src/components/DataTable/createActionsColumn.tsx +86 -0
- package/src/components/DataTable/index.ts +25 -0
- package/src/components/DatePicker/CustomRangePicker.tsx +59 -0
- package/src/components/DatePicker/DateInput.tsx +329 -0
- package/src/components/DatePicker/DateNavigator.tsx +486 -0
- package/src/components/DatePicker/DatePicker.tsx +242 -0
- package/src/components/DatePicker/MonthlyRangePicker.tsx +231 -0
- package/src/components/DatePicker/QuarterlyRangePicker.tsx +224 -0
- package/src/components/DatePicker/QuickPicksSidebar.tsx +242 -0
- package/src/components/DatePicker/YearlyRangePicker.tsx +171 -0
- package/src/components/DatePicker/index.ts +7 -0
- package/src/components/DatePicker/types.ts +12 -0
- package/src/components/DesignSystemPrimitives/FluidGrid.tsx +44 -0
- package/src/components/DesignSystemPrimitives/InteractivePrimitives.tsx +177 -0
- package/src/components/DesignSystemPrimitives/LayoutPrimitives.tsx +220 -0
- package/src/components/DesignSystemPrimitives/LayoutPrimitives.types.tsx +15 -0
- package/src/components/DesignSystemPrimitives/SurfacePrimitives.tsx +46 -0
- package/src/components/DesignSystemPrimitives/index.ts +55 -0
- package/src/components/Details/Details.css.ts +74 -0
- package/src/components/Details/Details.tsx +140 -0
- package/src/components/Details/index.ts +2 -0
- package/src/components/DownloadCard/DownloadCard.css.ts +22 -0
- package/src/components/DownloadCard/DownloadCard.tsx +63 -0
- package/src/components/DownloadCard/index.ts +2 -0
- package/src/components/Drawer/Drawer.css.ts +32 -0
- package/src/components/Drawer/Drawer.tsx +236 -0
- package/src/components/Drawer/hooks/useDetailDrawer.ts +61 -0
- package/src/components/Drawer/hooks/useDetailDrawerNavigation.ts +125 -0
- package/src/components/Drawer/hooks/useDetailDrawerNavigationContext.ts +66 -0
- package/src/components/EditableRichText/EditableRichText.css.ts +72 -0
- package/src/components/EditableRichText/EditableRichText.tsx +324 -0
- package/src/components/EditableRichText/index.ts +2 -0
- package/src/components/EditableSelect/EditableSelect.css.ts +62 -0
- package/src/components/EditableSelect/EditableSelect.tsx +224 -0
- package/src/components/EditableSelect/index.ts +2 -0
- package/src/components/EditableText/EditableText.tsx +377 -0
- package/src/components/EditableText/index.ts +2 -0
- package/src/components/EmptyState/EmptyState.figma.tsx +33 -0
- package/src/components/EmptyState/EmptyState.tsx +230 -0
- package/src/components/EmptyState/index.ts +2 -0
- package/src/components/ErrorBoundary.tsx +135 -0
- package/src/components/ErrorState/ErrorState.tsx +197 -0
- package/src/components/ErrorState/index.ts +2 -0
- package/src/components/FeatureCard.tsx +42 -0
- package/src/components/FilterMenu/FilterMenu.figma.tsx +30 -0
- package/src/components/FilterMenu/FilterMenu.tsx +198 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/BooleanFilterSubmenu.tsx +46 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/SearchableFilterSubmenu.tsx +239 -0
- package/src/components/FilterMenu/FilterSubMenuTypes/index.ts +8 -0
- package/src/components/FilterMenu/defaultFilterSchemas.ts +63 -0
- package/src/components/FilterMenu/helpers.ts +115 -0
- package/src/components/FilterMenu/index.ts +35 -0
- package/src/components/FilterMenu/types.ts +101 -0
- package/src/components/IconButton/IconButton.css.ts +272 -0
- package/src/components/IconButton/IconButton.figma.tsx +47 -0
- package/src/components/IconButton/IconButton.tsx +72 -0
- package/src/components/IconButton/README.md +230 -0
- package/src/components/IconButton/index.ts +2 -0
- package/src/components/InfiniteScrollSentinel.tsx +86 -0
- package/src/components/InfiniteScrollTrigger.tsx +78 -0
- package/src/components/InfoCard.figma.tsx +47 -0
- package/src/components/InfoCard.tsx +216 -0
- package/src/components/KbdHint/KbdHint.tsx +23 -0
- package/src/components/KbdHint/index.ts +2 -0
- package/src/components/LabeledField/LabeledField.tsx +21 -0
- package/src/components/LabeledField/index.ts +2 -0
- package/src/components/LookupSelect/LookupSelect.css.ts +149 -0
- package/src/components/LookupSelect/LookupSelect.tsx +325 -0
- package/src/components/LookupSelect/index.ts +2 -0
- package/src/components/Menu/Menu.css.ts +89 -0
- package/src/components/Menu/Menu.tsx +105 -0
- package/src/components/Menu/index.ts +2 -0
- package/src/components/MessageBox/MessageBox.tsx +168 -0
- package/src/components/MessageBox/index.ts +2 -0
- package/src/components/MetricDisplay/MetricDisplay.tsx +55 -0
- package/src/components/MetricDisplay/index.ts +1 -0
- package/src/components/MultiSelect/MultiSelect.tsx +278 -0
- package/src/components/MultiSelect/index.ts +2 -0
- package/src/components/Notifications/Notifications.tsx +12 -0
- package/src/components/Notifications/README.md +93 -0
- package/src/components/Notifications/index.ts +4 -0
- package/src/components/Notifications/showToast.tsx +100 -0
- package/src/components/PropertyRow/PropertyRow.tsx +96 -0
- package/src/components/PropertyRow/index.ts +2 -0
- package/src/components/RadioTile/RadioTile.tsx +253 -0
- package/src/components/RadioTile/index.ts +2 -0
- package/src/components/RichText/FormattingToolbar.css.ts +69 -0
- package/src/components/RichText/FormattingToolbar.tsx +112 -0
- package/src/components/RichText/RichTextInline.css.ts +54 -0
- package/src/components/RichText/RichTextInline.tsx +318 -0
- package/src/components/RichText/formattingCommands.ts +181 -0
- package/src/components/RichText/formattingTypes.ts +34 -0
- package/src/components/RichText/index.ts +49 -0
- package/src/components/RichText/richTextExtensions.ts +111 -0
- package/src/components/RichText/richTextHelpers.ts +65 -0
- package/src/components/RichText/richTextImage.ts +253 -0
- package/src/components/RichText/richTextImageHandlers.ts +244 -0
- package/src/components/RichText/richTextProse.css.ts +261 -0
- package/src/components/RichTextEditor/RichTextEditor.css.ts +82 -0
- package/src/components/RichTextEditor/RichTextEditor.tsx +204 -0
- package/src/components/RichTextEditor/index.ts +2 -0
- package/src/components/RichTextView/RichTextView.css.ts +11 -0
- package/src/components/RichTextView/RichTextView.tsx +114 -0
- package/src/components/RichTextView/index.ts +2 -0
- package/src/components/Schedule/Schedule.tsx +35 -0
- package/src/components/SchedulePicker/SchedulePicker.css.ts +42 -0
- package/src/components/SchedulePicker/SchedulePicker.tsx +130 -0
- package/src/components/SchedulePicker/index.ts +2 -0
- package/src/components/SearchableList/types.ts +30 -0
- package/src/components/SearchableSubMenu/SearchableSubMenu.css.ts +25 -0
- package/src/components/SearchableSubMenu/SearchableSubMenu.tsx +139 -0
- package/src/components/SearchableSubMenu/index.ts +2 -0
- package/src/components/Select/README.md +114 -0
- package/src/components/Select/Select.css.ts +110 -0
- package/src/components/Select/Select.tsx +133 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/SelectCreatable/SelectCreatable.css.ts +16 -0
- package/src/components/SelectCreatable/SelectCreatable.tsx +203 -0
- package/src/components/SelectCreatable/index.ts +2 -0
- package/src/components/SettingsCard/SettingsCard.tsx +98 -0
- package/src/components/SettingsCard/index.ts +2 -0
- package/src/components/Sidebar/Sidebar.css.ts +91 -0
- package/src/components/Sidebar/Sidebar.tsx +129 -0
- package/src/components/Sidebar/index.ts +5 -0
- package/src/components/SimpleList/SimpleList.css.ts +12 -0
- package/src/components/SimpleList/SimpleList.tsx +44 -0
- package/src/components/SimpleList/index.ts +2 -0
- package/src/components/SimpleTable/SimpleTable.tsx +296 -0
- package/src/components/SimpleTable/index.ts +2 -0
- package/src/components/SlashRichTextEditor/SelectionBubbleMenu.css.ts +62 -0
- package/src/components/SlashRichTextEditor/SelectionBubbleMenu.tsx +85 -0
- package/src/components/SlashRichTextEditor/SlashCommandMenu.css.ts +124 -0
- package/src/components/SlashRichTextEditor/SlashCommandMenu.tsx +168 -0
- package/src/components/SlashRichTextEditor/SlashRichTextEditor.css.ts +81 -0
- package/src/components/SlashRichTextEditor/SlashRichTextEditor.tsx +538 -0
- package/src/components/SlashRichTextEditor/SlashSuggestionExtension.ts +48 -0
- package/src/components/SlashRichTextEditor/index.ts +13 -0
- package/src/components/SlashRichTextEditor/types.ts +48 -0
- package/src/components/StatCard/StatCard.css.ts +70 -0
- package/src/components/StatCard/StatCard.tsx +201 -0
- package/src/components/StatCard/index.ts +1 -0
- package/src/components/StatusBadge/StatusBadge.tsx +70 -0
- package/src/components/StatusBadge/index.ts +2 -0
- package/src/components/StatusIndicator/StatusIndicator.tsx +67 -0
- package/src/components/StatusIndicator/index.ts +6 -0
- package/src/components/SubNavigation/SubNavigation.css.ts +72 -0
- package/src/components/SubNavigation/SubNavigation.tsx +104 -0
- package/src/components/SubNavigation/index.ts +2 -0
- package/src/components/SuspenseLoader.tsx +22 -0
- package/src/components/Table/SortableColumnHeader.tsx +99 -0
- package/src/components/Table/TableSkeletonRows.figma.tsx +22 -0
- package/src/components/Table/TableSkeletonRows.tsx +113 -0
- package/src/components/Table/index.ts +9 -0
- package/src/components/TableActionsMenu.tsx +58 -0
- package/src/components/TableCard.tsx +29 -0
- package/src/components/TableContainer/TableContainer.tsx +86 -0
- package/src/components/TableContainer/index.ts +2 -0
- package/src/components/TableControlBar/TableControlBar.tsx +156 -0
- package/src/components/TableControlBar/TableSelectionButton.tsx +57 -0
- package/src/components/TableControlBar/index.ts +13 -0
- package/src/components/TableControlBar/useTableControlBar.tsx +314 -0
- package/src/components/TableSelection/TableSelection.tsx +43 -0
- package/src/components/TableSelection/index.ts +5 -0
- package/src/components/Tabs/README.md +76 -0
- package/src/components/Tabs/Tabs.css.ts +54 -0
- package/src/components/Tabs/Tabs.figma.tsx +47 -0
- package/src/components/Tabs/Tabs.tsx +96 -0
- package/src/components/Tabs/index.ts +8 -0
- package/src/components/TextInput/README.md +98 -0
- package/src/components/TextInput/SearchTextInput.figma.tsx +22 -0
- package/src/components/TextInput/SearchTextInput.tsx +150 -0
- package/src/components/TextInput/TextInput.figma.tsx +44 -0
- package/src/components/TextInput/TextInput.tsx +42 -0
- package/src/components/TextInput/index.ts +4 -0
- package/src/components/ThemeSwitcher.figma.tsx +28 -0
- package/src/components/ThemeSwitcher.tsx +69 -0
- package/src/components/TrendBadge/TrendBadge.tsx +76 -0
- package/src/components/TrendBadge/index.ts +2 -0
- package/src/components/TruncatedText.tsx +115 -0
- package/src/components/Typography/Text.tsx +74 -0
- package/src/components/Typography/Title.tsx +100 -0
- package/src/components/Typography/index.ts +4 -0
- package/src/geist-fonts.ts +48 -0
- package/src/hooks/index.ts +31 -0
- package/src/hooks/useFilters.ts +152 -0
- package/src/hooks/useInfiniteScroll.ts +62 -0
- package/src/hooks/usePlatform.ts +33 -0
- package/src/hooks/useServerTable.ts +495 -0
- package/src/hooks/useTableSelection.ts +102 -0
- package/src/hooks/useTableSort.ts +259 -0
- package/src/index.ts +483 -0
- package/src/mantine.ts +25 -0
- package/src/theme/mantineVars.ts +12 -0
- package/src/theme/themeContract.css.ts +131 -0
- package/src/theme/themeVars.ts +31 -0
- package/src/theme.ts +168 -0
- package/src/tokens/color-types.ts +107 -0
- package/src/tokens/colors.ts +243 -0
- package/src/tokens/index.ts +14 -0
- package/src/tokens/radius.ts +17 -0
- package/src/tokens/semantic-colors.ts +224 -0
- package/src/tokens/semantic-tokens-css.ts +53 -0
- package/src/tokens/shadows.ts +11 -0
- package/src/tokens/spacing.ts +20 -0
- package/src/tokens/text-styles.ts +179 -0
- package/src/tokens/typography.ts +40 -0
- package/src/tokens/zIndex.ts +27 -0
- package/src/types/mantine-theme.d.ts +17 -0
- package/src/types/tanstack-table.d.ts +22 -0
- package/src/utils/avatar.ts +150 -0
- package/src/utils/chartHelpers.ts +53 -0
- package/src/utils/color-props.ts +77 -0
- package/src/utils/createDesignComponent.tsx +104 -0
- package/src/utils/nestFlatRows.ts +111 -0
- package/src/utils/withStaticComponents.ts +6 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Flex } from '@mantine/core';
|
|
4
|
+
|
|
5
|
+
import { Text } from '../Typography/Text';
|
|
6
|
+
import { Title } from '../Typography/Title';
|
|
7
|
+
|
|
8
|
+
export interface MetricDisplayProps {
|
|
9
|
+
/**
|
|
10
|
+
* Label text to display above the metric value
|
|
11
|
+
*/
|
|
12
|
+
label: string;
|
|
13
|
+
/**
|
|
14
|
+
* Metric value to display (can be string or React node)
|
|
15
|
+
*/
|
|
16
|
+
value: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Size variant for the metric display
|
|
19
|
+
* - 'sm': heading4 variant with order 5 (smaller)
|
|
20
|
+
* - 'md': heading4 variant (default)
|
|
21
|
+
* - 'lg': heading3 variant (larger)
|
|
22
|
+
*/
|
|
23
|
+
size?: 'sm' | 'md' | 'lg';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* MetricDisplay component
|
|
28
|
+
* Displays a metric with a label and value in a consistent format
|
|
29
|
+
* Used for KPIs, statistics, and other key metrics throughout the app
|
|
30
|
+
*/
|
|
31
|
+
export function MetricDisplay({
|
|
32
|
+
label,
|
|
33
|
+
value,
|
|
34
|
+
size = 'md',
|
|
35
|
+
}: MetricDisplayProps) {
|
|
36
|
+
const titleVariantMap = {
|
|
37
|
+
sm: 'heading4',
|
|
38
|
+
md: 'heading4',
|
|
39
|
+
lg: 'heading3',
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Flex direction="column" gap="xs">
|
|
44
|
+
<Text variant="caption1.strong" c="text.subdued.default">
|
|
45
|
+
{label}
|
|
46
|
+
</Text>
|
|
47
|
+
<Title
|
|
48
|
+
variant={titleVariantMap[size]}
|
|
49
|
+
{...(size === 'sm' ? { order: 5 } : {})}
|
|
50
|
+
>
|
|
51
|
+
{value}
|
|
52
|
+
</Title>
|
|
53
|
+
</Flex>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MetricDisplay, type MetricDisplayProps } from './MetricDisplay';
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { forwardRef, useMemo } from 'react';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
CheckIcon,
|
|
5
|
+
Combobox,
|
|
6
|
+
Group,
|
|
7
|
+
Input,
|
|
8
|
+
MultiSelect as MantineMultiSelect,
|
|
9
|
+
Pill,
|
|
10
|
+
PillsInput,
|
|
11
|
+
type MultiSelectProps as MantineMultiSelectProps,
|
|
12
|
+
useCombobox,
|
|
13
|
+
} from '@mantine/core';
|
|
14
|
+
import { useUncontrolled } from '@mantine/hooks';
|
|
15
|
+
|
|
16
|
+
type ParsedOption = {
|
|
17
|
+
value: string;
|
|
18
|
+
label: string;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export interface MultiSelectProps extends MantineMultiSelectProps {
|
|
23
|
+
maxDisplayedItems?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface LimitedPillMultiSelectProps extends Omit<
|
|
27
|
+
MultiSelectProps,
|
|
28
|
+
'maxDisplayedItems'
|
|
29
|
+
> {
|
|
30
|
+
maxDisplayedItems: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseOption(
|
|
34
|
+
item: string | { value: string; label?: string; disabled?: boolean },
|
|
35
|
+
) {
|
|
36
|
+
if (typeof item === 'string') {
|
|
37
|
+
return { value: item, label: item };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
value: item.value,
|
|
42
|
+
label: item.label ?? item.value,
|
|
43
|
+
disabled: item.disabled,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseOptions(data: MantineMultiSelectProps['data']): ParsedOption[] {
|
|
48
|
+
if (!data) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return data.flatMap(item => {
|
|
53
|
+
if (
|
|
54
|
+
typeof item === 'string' ||
|
|
55
|
+
('value' in item && typeof item.value === 'string')
|
|
56
|
+
) {
|
|
57
|
+
return [parseOption(item)];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if ('items' in item && Array.isArray(item.items)) {
|
|
61
|
+
return item.items.map(groupItem => parseOption(groupItem));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return [];
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function getDisplayedValues(values: string[], maxDisplayedItems?: number) {
|
|
69
|
+
if (maxDisplayedItems == null) {
|
|
70
|
+
return values;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const normalizedMaxDisplayedItems = Math.max(1, maxDisplayedItems);
|
|
74
|
+
|
|
75
|
+
if (values.length <= normalizedMaxDisplayedItems) {
|
|
76
|
+
return values;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return values.slice(0, Math.max(0, normalizedMaxDisplayedItems - 1));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const MultiSelect = forwardRef<HTMLInputElement, MultiSelectProps>(
|
|
83
|
+
({ maxDisplayedItems, ...props }, ref) => {
|
|
84
|
+
if (maxDisplayedItems == null) {
|
|
85
|
+
return <MantineMultiSelect ref={ref} {...props} />;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<LimitedPillMultiSelect
|
|
90
|
+
ref={ref}
|
|
91
|
+
maxDisplayedItems={maxDisplayedItems}
|
|
92
|
+
{...props}
|
|
93
|
+
/>
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
MultiSelect.displayName = 'MultiSelect';
|
|
99
|
+
|
|
100
|
+
const LimitedPillMultiSelect = forwardRef<
|
|
101
|
+
HTMLInputElement,
|
|
102
|
+
LimitedPillMultiSelectProps
|
|
103
|
+
>(
|
|
104
|
+
(
|
|
105
|
+
{
|
|
106
|
+
maxDisplayedItems,
|
|
107
|
+
data,
|
|
108
|
+
value,
|
|
109
|
+
defaultValue,
|
|
110
|
+
onChange,
|
|
111
|
+
placeholder,
|
|
112
|
+
comboboxProps,
|
|
113
|
+
hidePickedOptions,
|
|
114
|
+
nothingFoundMessage = 'Nothing found',
|
|
115
|
+
disabled,
|
|
116
|
+
readOnly,
|
|
117
|
+
label,
|
|
118
|
+
description,
|
|
119
|
+
error,
|
|
120
|
+
required,
|
|
121
|
+
withAsterisk,
|
|
122
|
+
name,
|
|
123
|
+
id,
|
|
124
|
+
'aria-label': ariaLabel,
|
|
125
|
+
},
|
|
126
|
+
ref,
|
|
127
|
+
) => {
|
|
128
|
+
const parsedOptions = useMemo(() => parseOptions(data), [data]);
|
|
129
|
+
const [selectedValues, setSelectedValues] = useUncontrolled({
|
|
130
|
+
value,
|
|
131
|
+
defaultValue,
|
|
132
|
+
finalValue: [],
|
|
133
|
+
onChange,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const combobox = useCombobox({
|
|
137
|
+
onDropdownClose: () => combobox.resetSelectedOption(),
|
|
138
|
+
onDropdownOpen: () => combobox.updateSelectedOptionIndex('active'),
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const selectedLabelsByValue = useMemo(
|
|
142
|
+
() => new Map(parsedOptions.map(option => [option.value, option.label])),
|
|
143
|
+
[parsedOptions],
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
const availableOptions = hidePickedOptions
|
|
147
|
+
? parsedOptions.filter(option => !selectedValues.includes(option.value))
|
|
148
|
+
: parsedOptions;
|
|
149
|
+
|
|
150
|
+
const displayedValues = getDisplayedValues(
|
|
151
|
+
selectedValues,
|
|
152
|
+
maxDisplayedItems,
|
|
153
|
+
);
|
|
154
|
+
const hiddenValuesCount = selectedValues.length - displayedValues.length;
|
|
155
|
+
|
|
156
|
+
function handleOptionSubmit(optionValue: string) {
|
|
157
|
+
if (disabled || readOnly) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
setSelectedValues(
|
|
162
|
+
selectedValues.includes(optionValue)
|
|
163
|
+
? selectedValues.filter(
|
|
164
|
+
selectedValue => selectedValue !== optionValue,
|
|
165
|
+
)
|
|
166
|
+
: [...selectedValues, optionValue],
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const options = availableOptions.map(option => {
|
|
171
|
+
const isSelected = selectedValues.includes(option.value);
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<Combobox.Option
|
|
175
|
+
value={option.value}
|
|
176
|
+
key={option.value}
|
|
177
|
+
active={isSelected}
|
|
178
|
+
disabled={option.disabled}
|
|
179
|
+
>
|
|
180
|
+
<Group gap="sm">
|
|
181
|
+
{isSelected ? <CheckIcon size={12} /> : null}
|
|
182
|
+
<span>{option.label}</span>
|
|
183
|
+
</Group>
|
|
184
|
+
</Combobox.Option>
|
|
185
|
+
);
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<Input.Wrapper
|
|
190
|
+
label={label}
|
|
191
|
+
description={description}
|
|
192
|
+
error={error}
|
|
193
|
+
required={required}
|
|
194
|
+
withAsterisk={withAsterisk}
|
|
195
|
+
>
|
|
196
|
+
<Combobox
|
|
197
|
+
store={combobox}
|
|
198
|
+
onOptionSubmit={handleOptionSubmit}
|
|
199
|
+
{...comboboxProps}
|
|
200
|
+
>
|
|
201
|
+
<Combobox.DropdownTarget>
|
|
202
|
+
<PillsInput
|
|
203
|
+
pointer={!disabled && !readOnly}
|
|
204
|
+
disabled={disabled}
|
|
205
|
+
onClick={() => {
|
|
206
|
+
if (!disabled && !readOnly) {
|
|
207
|
+
combobox.toggleDropdown();
|
|
208
|
+
}
|
|
209
|
+
}}
|
|
210
|
+
>
|
|
211
|
+
<Pill.Group>
|
|
212
|
+
{selectedValues.length > 0 ? (
|
|
213
|
+
<>
|
|
214
|
+
{displayedValues.map(selectedValue => (
|
|
215
|
+
<Pill
|
|
216
|
+
key={selectedValue}
|
|
217
|
+
withRemoveButton={!disabled && !readOnly}
|
|
218
|
+
onRemove={() =>
|
|
219
|
+
setSelectedValues(
|
|
220
|
+
selectedValues.filter(
|
|
221
|
+
valueItem => valueItem !== selectedValue,
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
>
|
|
226
|
+
{selectedLabelsByValue.get(selectedValue) ??
|
|
227
|
+
selectedValue}
|
|
228
|
+
</Pill>
|
|
229
|
+
))}
|
|
230
|
+
{hiddenValuesCount > 0 ? (
|
|
231
|
+
<Pill>+ {hiddenValuesCount} More</Pill>
|
|
232
|
+
) : null}
|
|
233
|
+
</>
|
|
234
|
+
) : (
|
|
235
|
+
<Input.Placeholder>{placeholder}</Input.Placeholder>
|
|
236
|
+
)}
|
|
237
|
+
|
|
238
|
+
<Combobox.EventsTarget>
|
|
239
|
+
<PillsInput.Field
|
|
240
|
+
ref={ref}
|
|
241
|
+
type="hidden"
|
|
242
|
+
name={name}
|
|
243
|
+
id={id}
|
|
244
|
+
required={required}
|
|
245
|
+
aria-label={
|
|
246
|
+
ariaLabel ??
|
|
247
|
+
(typeof label === 'string' ? label : undefined)
|
|
248
|
+
}
|
|
249
|
+
onBlur={() => combobox.closeDropdown()}
|
|
250
|
+
onKeyDown={event => {
|
|
251
|
+
if (
|
|
252
|
+
event.key === 'Backspace' &&
|
|
253
|
+
selectedValues.length > 0
|
|
254
|
+
) {
|
|
255
|
+
event.preventDefault();
|
|
256
|
+
setSelectedValues(selectedValues.slice(0, -1));
|
|
257
|
+
}
|
|
258
|
+
}}
|
|
259
|
+
/>
|
|
260
|
+
</Combobox.EventsTarget>
|
|
261
|
+
</Pill.Group>
|
|
262
|
+
</PillsInput>
|
|
263
|
+
</Combobox.DropdownTarget>
|
|
264
|
+
|
|
265
|
+
<Combobox.Dropdown>
|
|
266
|
+
{options.length > 0 ? (
|
|
267
|
+
<Combobox.Options>{options}</Combobox.Options>
|
|
268
|
+
) : (
|
|
269
|
+
<div>{nothingFoundMessage}</div>
|
|
270
|
+
)}
|
|
271
|
+
</Combobox.Dropdown>
|
|
272
|
+
</Combobox>
|
|
273
|
+
</Input.Wrapper>
|
|
274
|
+
);
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
LimitedPillMultiSelect.displayName = 'LimitedPillMultiSelect';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Notifications as MantineNotifications } from '@mantine/notifications';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Notifications container for toast messages.
|
|
5
|
+
* Renders the Mantine notifications provider; must be mounted inside MantineProvider.
|
|
6
|
+
* Typically included inside ThemeProvider so toasts can display app-wide.
|
|
7
|
+
*/
|
|
8
|
+
export function Notifications() {
|
|
9
|
+
return (
|
|
10
|
+
<MantineNotifications position="bottom-right" autoClose={4000} limit={5} />
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Notifications
|
|
2
|
+
|
|
3
|
+
Toast notifications via `showSuccessToast` / `showErrorToast` helpers, plus the `<Notifications />` provider that renders them.
|
|
4
|
+
|
|
5
|
+
The helpers wrap `@mantine/notifications` with design-system defaults: green / red color, auto-close timing, and a styled-caption message body. The raw `notifications` object from Mantine is re-exported for cases the helpers don't cover, but new code should reach for the helpers.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { showSuccessToast, showErrorToast } from '@scalepad/ui';
|
|
11
|
+
|
|
12
|
+
// Plain success / error
|
|
13
|
+
showSuccessToast('User moved to department');
|
|
14
|
+
showErrorToast('Failed to update approval status.');
|
|
15
|
+
|
|
16
|
+
// With a custom title
|
|
17
|
+
showSuccessToast('Foo was removed.', { title: 'Initiative deleted' });
|
|
18
|
+
|
|
19
|
+
// With a retry action (auto-close disables so the user can click)
|
|
20
|
+
showErrorToast('Could not update completion status.', {
|
|
21
|
+
title: 'Could not update completion status',
|
|
22
|
+
action: { label: 'Retry', onClick: retry },
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Override auto-close
|
|
26
|
+
showSuccessToast('Saved.', { autoClose: 8000 });
|
|
27
|
+
showSuccessToast('Saved.', { autoClose: false }); // persist until dismissed
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## API
|
|
31
|
+
|
|
32
|
+
### `showSuccessToast(message, options?)` / `showErrorToast(message, options?)`
|
|
33
|
+
|
|
34
|
+
| Param | Type | Description |
|
|
35
|
+
| --------- | ----------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
36
|
+
| `message` | `string` | Body text rendered as a subdued caption. |
|
|
37
|
+
| `options` | `string \| ShowToastOptions` | Optional. Pass a string to set just the title (legacy form), or an options bag for richer toasts. |
|
|
38
|
+
|
|
39
|
+
### `ShowToastOptions`
|
|
40
|
+
|
|
41
|
+
| Field | Type | Default | Description |
|
|
42
|
+
| ----------- | ------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------- |
|
|
43
|
+
| `title` | `string` | `'Success'` / `'Error'` | Heading text. |
|
|
44
|
+
| `action` | `ShowToastAction` | — | Optional action button rendered below the body. |
|
|
45
|
+
| `autoClose` | `number \| false` | 4000 (success) / 5000 (error); `false` when `action` is present and not overridden | Override auto-close. `false` keeps the toast up. |
|
|
46
|
+
|
|
47
|
+
### `ShowToastAction`
|
|
48
|
+
|
|
49
|
+
| Field | Type | Description |
|
|
50
|
+
| --------- | -------------- | ------------------------------------ |
|
|
51
|
+
| `label` | `string` | Button label, e.g. `'Retry'`. |
|
|
52
|
+
| `onClick` | `() => void` | Click handler. |
|
|
53
|
+
|
|
54
|
+
## Defaults
|
|
55
|
+
|
|
56
|
+
- **Auto-close**: 4s for success, 5s for error.
|
|
57
|
+
- **Action toasts**: when `options.action` is present and `autoClose` is unspecified, the toast persists until dismissed so the user can click the action. Pass `autoClose: 8000` (or any number) to opt back into auto-close.
|
|
58
|
+
- **Color**: green (success) / red (error) — encoded in the function name. Never pass color manually.
|
|
59
|
+
|
|
60
|
+
## When to use what
|
|
61
|
+
|
|
62
|
+
- **Plain string** — simple confirmation, single line. `showSuccessToast('Saved.')`.
|
|
63
|
+
- **With `title`** — when heading and body convey different information ("Initiative deleted" / "Foo was removed.").
|
|
64
|
+
- **With `action`** — recoverable errors where a one-click retry is meaningful (transient API failures). Persists by default.
|
|
65
|
+
- **Raw `notifications.show()`** — escape hatch for cases the helpers don't cover (custom icons, loading states, multi-step UI). Prefer extending the helpers instead so call sites stay consistent.
|
|
66
|
+
|
|
67
|
+
## Don't
|
|
68
|
+
|
|
69
|
+
- Don't call `showSuccessToast` / `showErrorToast` inside a hook — toasting is a UI concern with no view context. See `apps/lm-web/.ai/rules/mutations.md` and `apps/lm-web/.ai/rules/error-handling.md` for the canonical pattern (hooks expose `error: Error | null`; components decide UX).
|
|
70
|
+
- Don't bypass the helpers to pass `color` directly via raw `notifications.show()` for semantics — the function-name choice is the source of truth.
|
|
71
|
+
|
|
72
|
+
## Provider
|
|
73
|
+
|
|
74
|
+
Mount `<Notifications />` once at the app root (or in a shared overlays component):
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
import { Notifications } from '@scalepad/ui';
|
|
78
|
+
|
|
79
|
+
<App>
|
|
80
|
+
<Notifications />
|
|
81
|
+
{/* ... */}
|
|
82
|
+
</App>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## More Examples
|
|
86
|
+
|
|
87
|
+
See Storybook for interactive demos: `SuccessToast`, `ErrorToast`, `CustomTitles`, `WithRetryAction`.
|
|
88
|
+
|
|
89
|
+
## Real-World Usage
|
|
90
|
+
|
|
91
|
+
- `apps/lm-web/src/features/assessments/components/AssessmentDetail.tsx` — error toast with retry action.
|
|
92
|
+
- `apps/lm-web/src/features/initiatives/components/initiativeOverflowMenu/InitiativeOverflowMenu.tsx` — success / error toasts in mutation callbacks.
|
|
93
|
+
- `apps/amm-web/src/features/catalog/components/CatalogAppActionsCell.tsx` — string-only success / error toasts.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { notifications } from '@mantine/notifications';
|
|
4
|
+
|
|
5
|
+
import { Button } from '../Button';
|
|
6
|
+
import { Stack } from '../DesignSystemPrimitives';
|
|
7
|
+
import { Text } from '../Typography';
|
|
8
|
+
|
|
9
|
+
export interface ShowToastAction {
|
|
10
|
+
label: string;
|
|
11
|
+
onClick: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ShowToastOptions {
|
|
15
|
+
title?: string;
|
|
16
|
+
action?: ShowToastAction;
|
|
17
|
+
/** Override auto-close. Pass `false` to keep the toast until dismissed. */
|
|
18
|
+
autoClose?: number | false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type SecondArg = string | ShowToastOptions;
|
|
22
|
+
|
|
23
|
+
const SUCCESS_AUTO_CLOSE_MS = 4000;
|
|
24
|
+
const ERROR_AUTO_CLOSE_MS = 5000;
|
|
25
|
+
|
|
26
|
+
function normalizeOptions(secondArg?: SecondArg): ShowToastOptions {
|
|
27
|
+
if (secondArg == null) return {};
|
|
28
|
+
return typeof secondArg === 'string' ? { title: secondArg } : secondArg;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildBody(message: string, action?: ShowToastAction): ReactNode {
|
|
32
|
+
const caption = (
|
|
33
|
+
<Text variant="caption1" c="text.subdued.default">
|
|
34
|
+
{message}
|
|
35
|
+
</Text>
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (!action) {
|
|
39
|
+
return caption;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Stack gap="xs">
|
|
44
|
+
{caption}
|
|
45
|
+
<Button
|
|
46
|
+
type="button"
|
|
47
|
+
size="xs"
|
|
48
|
+
variant="outline"
|
|
49
|
+
onClick={action.onClick}
|
|
50
|
+
>
|
|
51
|
+
{action.label}
|
|
52
|
+
</Button>
|
|
53
|
+
</Stack>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveAutoClose(
|
|
58
|
+
options: ShowToastOptions,
|
|
59
|
+
defaultMs: number,
|
|
60
|
+
): number | false {
|
|
61
|
+
if (options.autoClose !== undefined) return options.autoClose;
|
|
62
|
+
// When an action is present, persist by default so the user can click it.
|
|
63
|
+
return options.action ? false : defaultMs;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Show a brief success toast (e.g. after create/update/delete).
|
|
68
|
+
* Pass a string as the second arg to set just the title, or an options bag
|
|
69
|
+
* for `action` / `autoClose` overrides.
|
|
70
|
+
*/
|
|
71
|
+
export function showSuccessToast(
|
|
72
|
+
message: string,
|
|
73
|
+
optionsOrTitle?: SecondArg,
|
|
74
|
+
): void {
|
|
75
|
+
const options = normalizeOptions(optionsOrTitle);
|
|
76
|
+
notifications.show({
|
|
77
|
+
title: options.title ?? 'Success',
|
|
78
|
+
message: buildBody(message, options.action),
|
|
79
|
+
color: 'green',
|
|
80
|
+
autoClose: resolveAutoClose(options, SUCCESS_AUTO_CLOSE_MS),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Show a brief error toast.
|
|
86
|
+
* Pass a string as the second arg to set just the title, or an options bag
|
|
87
|
+
* for `action` (e.g. a Retry button) / `autoClose` overrides.
|
|
88
|
+
*/
|
|
89
|
+
export function showErrorToast(
|
|
90
|
+
message: string,
|
|
91
|
+
optionsOrTitle?: SecondArg,
|
|
92
|
+
): void {
|
|
93
|
+
const options = normalizeOptions(optionsOrTitle);
|
|
94
|
+
notifications.show({
|
|
95
|
+
title: options.title ?? 'Error',
|
|
96
|
+
message: buildBody(message, options.action),
|
|
97
|
+
color: 'red',
|
|
98
|
+
autoClose: resolveAutoClose(options, ERROR_AUTO_CLOSE_MS),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Box, Group } from '../DesignSystemPrimitives';
|
|
4
|
+
import { Text } from '../Typography';
|
|
5
|
+
|
|
6
|
+
export interface PropertyRowProps {
|
|
7
|
+
/** Small icon (typically a 14px lucide glyph) rendered to the left of the label. */
|
|
8
|
+
icon: ReactNode;
|
|
9
|
+
/** Plain label text — e.g. "Client", "Due date", "Status". */
|
|
10
|
+
label: string;
|
|
11
|
+
/**
|
|
12
|
+
* Right-hand value content. Free-form so each property can render its
|
|
13
|
+
* own widget (chip, picker, plain text, link, etc.) without the row
|
|
14
|
+
* imposing structure.
|
|
15
|
+
*/
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
/**
|
|
18
|
+
* Override the fixed width of the label column. Defaults to 120 — the
|
|
19
|
+
* value the task drawer was designed against. Bump it for surfaces
|
|
20
|
+
* with longer label text so labels and values stay vertically aligned
|
|
21
|
+
* across rows.
|
|
22
|
+
*/
|
|
23
|
+
labelWidth?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Cross-axis alignment of the label column against the value content.
|
|
26
|
+
* Defaults to `'center'` for single-line values; pass `'flex-start'`
|
|
27
|
+
* when the value can grow vertically (e.g. a stack of assignees) so
|
|
28
|
+
* the label stays anchored to the first row of the value instead of
|
|
29
|
+
* floating in the middle.
|
|
30
|
+
*/
|
|
31
|
+
align?: 'center' | 'flex-start';
|
|
32
|
+
/**
|
|
33
|
+
* When true, blocks interaction (e.g. while a mutation is in-flight).
|
|
34
|
+
*/
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const DEFAULT_LABEL_COLUMN_WIDTH = 120;
|
|
39
|
+
const ROW_MIN_HEIGHT = 28;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Two-column property row used by detail-drawer metadata stacks. Renders
|
|
43
|
+
* a subdued icon + label on the left and the consumer-provided value on
|
|
44
|
+
* the right. The label column is fixed-width (overridable via
|
|
45
|
+
* `labelWidth`) so labels stay vertically aligned across rows even when
|
|
46
|
+
* value widths vary — that lock-step alignment is what makes a stack of
|
|
47
|
+
* these rows scan as a coherent property table rather than a jagged list.
|
|
48
|
+
*
|
|
49
|
+
* Designed to be domain-agnostic: drawers in tasks, initiatives,
|
|
50
|
+
* playbooks, etc. should all reach for this primitive instead of
|
|
51
|
+
* re-implementing the same two-column layout.
|
|
52
|
+
*/
|
|
53
|
+
export function PropertyRow({
|
|
54
|
+
icon,
|
|
55
|
+
label,
|
|
56
|
+
children,
|
|
57
|
+
labelWidth = DEFAULT_LABEL_COLUMN_WIDTH,
|
|
58
|
+
align = 'center',
|
|
59
|
+
disabled = false,
|
|
60
|
+
}: PropertyRowProps) {
|
|
61
|
+
return (
|
|
62
|
+
<Group
|
|
63
|
+
gap="sm"
|
|
64
|
+
wrap="nowrap"
|
|
65
|
+
align={align}
|
|
66
|
+
mih={ROW_MIN_HEIGHT}
|
|
67
|
+
opacity={disabled ? 0.5 : 1}
|
|
68
|
+
aria-disabled={disabled || undefined}
|
|
69
|
+
// Block clicks / focus through to children when disabled so a
|
|
70
|
+
// mutation-in-flight row can't fire a second mutation. The visual
|
|
71
|
+
// opacity above is intentionally kept so the row still reads as
|
|
72
|
+
// "soft" rather than removed entirely.
|
|
73
|
+
style={
|
|
74
|
+
disabled ? { pointerEvents: 'none', userSelect: 'none' } : undefined
|
|
75
|
+
}
|
|
76
|
+
>
|
|
77
|
+
<Group
|
|
78
|
+
gap="2xs"
|
|
79
|
+
wrap="nowrap"
|
|
80
|
+
align="center"
|
|
81
|
+
w={labelWidth}
|
|
82
|
+
miw={labelWidth}
|
|
83
|
+
mih={ROW_MIN_HEIGHT}
|
|
84
|
+
c="text.subdued.default"
|
|
85
|
+
>
|
|
86
|
+
{icon}
|
|
87
|
+
<Text variant="body1" c="text.subdued.default">
|
|
88
|
+
{label}
|
|
89
|
+
</Text>
|
|
90
|
+
</Group>
|
|
91
|
+
<Box flex={1} miw={0}>
|
|
92
|
+
{children}
|
|
93
|
+
</Box>
|
|
94
|
+
</Group>
|
|
95
|
+
);
|
|
96
|
+
}
|