@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,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* StatCard component styles – vanilla-extract with semantic design tokens
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { style } from '@vanilla-extract/css';
|
|
6
|
+
|
|
7
|
+
import { tokens } from '../../theme/themeContract.css';
|
|
8
|
+
|
|
9
|
+
export const variantDefault = style({
|
|
10
|
+
backgroundColor: tokens.color.background.default,
|
|
11
|
+
borderColor: tokens.color.stroke.default,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
export const variantInformation = style({
|
|
15
|
+
backgroundColor: tokens.color.background.informationLight,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const variantSuccess = style({
|
|
19
|
+
backgroundColor: tokens.color.background.successLight,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const variantDanger = style({
|
|
23
|
+
backgroundColor: tokens.color.background.dangerLight,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const variantWarning = style({
|
|
27
|
+
backgroundColor: tokens.color.background.warningLight,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const variantSubdued = style({
|
|
31
|
+
backgroundColor: tokens.color.background.subduedLight,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
export const iconContainer = style({
|
|
35
|
+
borderRadius: tokens.radius.full,
|
|
36
|
+
display: 'flex',
|
|
37
|
+
alignItems: 'center',
|
|
38
|
+
justifyContent: 'center',
|
|
39
|
+
flexShrink: 0,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export const iconDefault = style({
|
|
43
|
+
backgroundColor: tokens.color.background.subduedLight,
|
|
44
|
+
color: tokens.color.icon.default,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
export const iconInformation = style({
|
|
48
|
+
backgroundColor: tokens.color.background.informationLightHover,
|
|
49
|
+
color: tokens.color.icon.informationStrong,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const iconSuccess = style({
|
|
53
|
+
backgroundColor: tokens.color.background.successLightHover,
|
|
54
|
+
color: tokens.color.icon.successStrong,
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const iconDanger = style({
|
|
58
|
+
backgroundColor: tokens.color.background.dangerLightHover,
|
|
59
|
+
color: tokens.color.icon.dangerStrong,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
export const iconWarning = style({
|
|
63
|
+
backgroundColor: tokens.color.background.warningLightHover,
|
|
64
|
+
color: tokens.color.icon.warningStrong,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const iconSubdued = style({
|
|
68
|
+
backgroundColor: tokens.color.background.subduedLightHover,
|
|
69
|
+
color: tokens.color.icon.default,
|
|
70
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { clsx } from 'clsx';
|
|
4
|
+
|
|
5
|
+
import { Box, Card, Group, Stack } from '../DesignSystemPrimitives';
|
|
6
|
+
import { Text, Title } from '../Typography';
|
|
7
|
+
import * as classes from './StatCard.css';
|
|
8
|
+
|
|
9
|
+
type StatCardVariant =
|
|
10
|
+
| 'default'
|
|
11
|
+
| 'information'
|
|
12
|
+
| 'success'
|
|
13
|
+
| 'danger'
|
|
14
|
+
| 'warning'
|
|
15
|
+
| 'subdued'
|
|
16
|
+
| 'destructive';
|
|
17
|
+
type StatCardLayout = 'left' | 'centered';
|
|
18
|
+
type StatCardIconSize = 'lg' | 'md' | 'sm';
|
|
19
|
+
type SemanticStatCardVariant =
|
|
20
|
+
| 'default'
|
|
21
|
+
| 'information'
|
|
22
|
+
| 'success'
|
|
23
|
+
| 'danger'
|
|
24
|
+
| 'warning'
|
|
25
|
+
| 'subdued';
|
|
26
|
+
type StatCardIconComponent = ComponentType<{ size?: number | string }>;
|
|
27
|
+
|
|
28
|
+
const cardVariantClassNames: Record<SemanticStatCardVariant, string> = {
|
|
29
|
+
default: classes.variantDefault,
|
|
30
|
+
information: classes.variantInformation,
|
|
31
|
+
success: classes.variantSuccess,
|
|
32
|
+
danger: classes.variantDanger,
|
|
33
|
+
warning: classes.variantWarning,
|
|
34
|
+
subdued: classes.variantSubdued,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const iconVariantClassNames: Record<SemanticStatCardVariant, string> = {
|
|
38
|
+
default: classes.iconDefault,
|
|
39
|
+
information: classes.iconInformation,
|
|
40
|
+
success: classes.iconSuccess,
|
|
41
|
+
danger: classes.iconDanger,
|
|
42
|
+
warning: classes.iconWarning,
|
|
43
|
+
subdued: classes.iconSubdued,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const toSemanticVariant = (
|
|
47
|
+
variant: StatCardVariant,
|
|
48
|
+
): SemanticStatCardVariant => {
|
|
49
|
+
if (variant === 'destructive') {
|
|
50
|
+
return 'danger';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return variant;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const iconSizePx: Record<StatCardIconSize, number> = {
|
|
57
|
+
lg: 44,
|
|
58
|
+
md: 36,
|
|
59
|
+
sm: 24,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const iconGlyphSizePx: Record<StatCardIconSize, number> = {
|
|
63
|
+
lg: 24,
|
|
64
|
+
md: 20,
|
|
65
|
+
sm: 14,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export interface StatCardProps {
|
|
69
|
+
/**
|
|
70
|
+
* The title of the statistic
|
|
71
|
+
*/
|
|
72
|
+
title: string;
|
|
73
|
+
/**
|
|
74
|
+
* The main value/number to display
|
|
75
|
+
*/
|
|
76
|
+
value: string | number;
|
|
77
|
+
/**
|
|
78
|
+
* Optional subtitle or additional information
|
|
79
|
+
*/
|
|
80
|
+
subtitle?: string;
|
|
81
|
+
/**
|
|
82
|
+
* Optional footer content rendered beneath the subtitle.
|
|
83
|
+
* Use for trend pills, sparklines, or other supporting UI.
|
|
84
|
+
*/
|
|
85
|
+
children?: ReactNode;
|
|
86
|
+
/**
|
|
87
|
+
* Visual variant of the card
|
|
88
|
+
* @default 'default'
|
|
89
|
+
*/
|
|
90
|
+
variant?: StatCardVariant;
|
|
91
|
+
/**
|
|
92
|
+
* Card content alignment
|
|
93
|
+
* @default 'left'
|
|
94
|
+
*/
|
|
95
|
+
layout?: StatCardLayout;
|
|
96
|
+
/**
|
|
97
|
+
* Icon component to display (for example: icon={Monitor})
|
|
98
|
+
*/
|
|
99
|
+
icon?: StatCardIconComponent;
|
|
100
|
+
/**
|
|
101
|
+
* Icon container size
|
|
102
|
+
* @default 'md'
|
|
103
|
+
*/
|
|
104
|
+
iconSize?: StatCardIconSize;
|
|
105
|
+
/**
|
|
106
|
+
* Optional small rounded-square swatch rendered before the title. Takes any
|
|
107
|
+
* CSS color value — pass a design-system token value for consistency.
|
|
108
|
+
* Ignored when `icon` is also provided (the icon slot wins).
|
|
109
|
+
*/
|
|
110
|
+
accentColor?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* StatCard component
|
|
115
|
+
* Displays a statistic with semantic variants and layout options
|
|
116
|
+
*/
|
|
117
|
+
export function StatCard({
|
|
118
|
+
title,
|
|
119
|
+
value,
|
|
120
|
+
subtitle,
|
|
121
|
+
children,
|
|
122
|
+
variant = 'default',
|
|
123
|
+
layout = 'left',
|
|
124
|
+
icon,
|
|
125
|
+
iconSize = 'md',
|
|
126
|
+
accentColor,
|
|
127
|
+
}: StatCardProps) {
|
|
128
|
+
const semanticVariant = toSemanticVariant(variant);
|
|
129
|
+
const cardClassName = cardVariantClassNames[semanticVariant];
|
|
130
|
+
const iconClassName = iconVariantClassNames[semanticVariant];
|
|
131
|
+
const iconContainerSize = iconSizePx[iconSize];
|
|
132
|
+
const iconGlyphSize = iconGlyphSizePx[iconSize];
|
|
133
|
+
const Icon = icon;
|
|
134
|
+
|
|
135
|
+
let withBorder = false;
|
|
136
|
+
if (semanticVariant === 'default') {
|
|
137
|
+
withBorder = true;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let align: 'flex-start' | 'center' = 'flex-start';
|
|
141
|
+
let titleGroupJustify: 'flex-start' | 'center' = 'flex-start';
|
|
142
|
+
let textAlign: 'left' | 'center' = 'left';
|
|
143
|
+
if (layout === 'centered') {
|
|
144
|
+
align = 'center';
|
|
145
|
+
titleGroupJustify = 'center';
|
|
146
|
+
textAlign = 'center';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<Card
|
|
151
|
+
padding="lg"
|
|
152
|
+
radius="lg"
|
|
153
|
+
withBorder={withBorder}
|
|
154
|
+
className={cardClassName}
|
|
155
|
+
>
|
|
156
|
+
<Stack gap="sm" align={align}>
|
|
157
|
+
<Group
|
|
158
|
+
gap="sm"
|
|
159
|
+
align="center"
|
|
160
|
+
justify={titleGroupJustify}
|
|
161
|
+
wrap="nowrap"
|
|
162
|
+
>
|
|
163
|
+
{Icon ? (
|
|
164
|
+
<Box
|
|
165
|
+
className={clsx(classes.iconContainer, iconClassName)}
|
|
166
|
+
style={{
|
|
167
|
+
width: `${iconContainerSize}px`,
|
|
168
|
+
height: `${iconContainerSize}px`,
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
<Icon size={iconGlyphSize} />
|
|
172
|
+
</Box>
|
|
173
|
+
) : accentColor ? (
|
|
174
|
+
<Box
|
|
175
|
+
aria-hidden
|
|
176
|
+
style={{
|
|
177
|
+
width: 10,
|
|
178
|
+
height: 10,
|
|
179
|
+
borderRadius: 2,
|
|
180
|
+
backgroundColor: accentColor,
|
|
181
|
+
flexShrink: 0,
|
|
182
|
+
}}
|
|
183
|
+
/>
|
|
184
|
+
) : null}
|
|
185
|
+
<Text variant="caption1" c="text.subdued.strong" ta={textAlign}>
|
|
186
|
+
{title}
|
|
187
|
+
</Text>
|
|
188
|
+
</Group>
|
|
189
|
+
<Title variant="heading2" order={2} ta={textAlign}>
|
|
190
|
+
{value}
|
|
191
|
+
</Title>
|
|
192
|
+
{subtitle && (
|
|
193
|
+
<Text variant="caption1" c="text.subdued.default" ta={textAlign}>
|
|
194
|
+
{subtitle}
|
|
195
|
+
</Text>
|
|
196
|
+
)}
|
|
197
|
+
{children}
|
|
198
|
+
</Stack>
|
|
199
|
+
</Card>
|
|
200
|
+
);
|
|
201
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { StatCard, type StatCardProps } from './StatCard';
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Badge, type BadgeProps, type BadgeSize } from '../Badge';
|
|
4
|
+
|
|
5
|
+
export type StatusBadgeTone =
|
|
6
|
+
| 'success'
|
|
7
|
+
| 'warning'
|
|
8
|
+
| 'info'
|
|
9
|
+
| 'destructive'
|
|
10
|
+
| 'neutral';
|
|
11
|
+
|
|
12
|
+
export interface StatusBadgeProps {
|
|
13
|
+
/**
|
|
14
|
+
* Semantic tone of the status. Drives both the Badge variant and the dot color.
|
|
15
|
+
*/
|
|
16
|
+
tone: StatusBadgeTone;
|
|
17
|
+
/**
|
|
18
|
+
* Status label to render (e.g. "Active", "In progress").
|
|
19
|
+
*/
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
/**
|
|
22
|
+
* Badge size
|
|
23
|
+
* @default 'sm'
|
|
24
|
+
*/
|
|
25
|
+
size?: BadgeSize;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const toneToVariant: Record<StatusBadgeTone, BadgeProps['variant']> = {
|
|
29
|
+
success: 'success',
|
|
30
|
+
warning: 'warning',
|
|
31
|
+
info: 'info',
|
|
32
|
+
destructive: 'destructive',
|
|
33
|
+
neutral: 'secondary',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function StatusDot() {
|
|
37
|
+
return (
|
|
38
|
+
<span
|
|
39
|
+
data-testid="statusbadge-dot"
|
|
40
|
+
aria-hidden="true"
|
|
41
|
+
style={{
|
|
42
|
+
display: 'inline-block',
|
|
43
|
+
width: 6,
|
|
44
|
+
height: 6,
|
|
45
|
+
borderRadius: '50%',
|
|
46
|
+
backgroundColor: 'currentColor',
|
|
47
|
+
}}
|
|
48
|
+
/>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* StatusBadge renders a tone-coded pill with a leading dot. Thin wrapper around
|
|
54
|
+
* Badge for the Budgets-style line-item status column (Active / In progress /
|
|
55
|
+
* Planned / Expired / …).
|
|
56
|
+
*/
|
|
57
|
+
export function StatusBadge({ tone, children, size = 'sm' }: StatusBadgeProps) {
|
|
58
|
+
return (
|
|
59
|
+
<Badge
|
|
60
|
+
data-testid="statusbadge-root"
|
|
61
|
+
data-variant={toneToVariant[tone]}
|
|
62
|
+
variant={toneToVariant[tone]}
|
|
63
|
+
roundness="round"
|
|
64
|
+
size={size}
|
|
65
|
+
leftSection={<StatusDot />}
|
|
66
|
+
>
|
|
67
|
+
{children}
|
|
68
|
+
</Badge>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Box } from '@mantine/core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Common status types used across the application
|
|
5
|
+
*/
|
|
6
|
+
export type DeviceStatus = 'online' | 'offline' | 'unknown';
|
|
7
|
+
export type DeploymentStatus = 'success' | 'failed';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Maps common status values to StatusIndicator variants
|
|
11
|
+
* - 'online' | 'success' → 'default'
|
|
12
|
+
* - 'offline' | 'failed' | 'unknown' → 'error'
|
|
13
|
+
*/
|
|
14
|
+
export function getStatusVariant(
|
|
15
|
+
status: DeviceStatus | DeploymentStatus,
|
|
16
|
+
): 'default' | 'error' {
|
|
17
|
+
return status === 'online' || status === 'success' ? 'default' : 'error';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface StatusIndicatorProps {
|
|
21
|
+
/**
|
|
22
|
+
* The visual variant to display
|
|
23
|
+
* - 'default': Green indicator with light border (positive states)
|
|
24
|
+
* - 'error': Red indicator with light border (negative states)
|
|
25
|
+
*/
|
|
26
|
+
variant: 'default' | 'error';
|
|
27
|
+
/**
|
|
28
|
+
* Size of the status indicator in pixels
|
|
29
|
+
* @default 16
|
|
30
|
+
*/
|
|
31
|
+
size?: number;
|
|
32
|
+
/**
|
|
33
|
+
* Accessible label for screen readers
|
|
34
|
+
* Required to ensure semantic accuracy in different contexts
|
|
35
|
+
* Examples: 'Online', 'Success', 'Active', 'Healthy', 'Offline', 'Failed', 'Error'
|
|
36
|
+
*/
|
|
37
|
+
label: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* StatusIndicator component
|
|
42
|
+
* Displays a circular status indicator with color-coded states
|
|
43
|
+
* Matches Figma design with Default and Error variants
|
|
44
|
+
*/
|
|
45
|
+
export function StatusIndicator({
|
|
46
|
+
variant,
|
|
47
|
+
size = 16,
|
|
48
|
+
label,
|
|
49
|
+
}: StatusIndicatorProps) {
|
|
50
|
+
// Map variant to colors (matching Figma design tokens)
|
|
51
|
+
const isDefault = variant === 'default';
|
|
52
|
+
const bgColor = isDefault ? 'green.6' : 'red.7';
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<Box
|
|
56
|
+
w={size}
|
|
57
|
+
h={size}
|
|
58
|
+
bg={bgColor}
|
|
59
|
+
role="status"
|
|
60
|
+
aria-label={label}
|
|
61
|
+
style={{
|
|
62
|
+
borderRadius: '50%',
|
|
63
|
+
border: '4px solid var(--color-stroke-default)',
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { style } from '@vanilla-extract/css';
|
|
2
|
+
|
|
3
|
+
import { tokens } from '../../theme/themeContract.css';
|
|
4
|
+
import { textStyleVariants } from '../../tokens/text-styles';
|
|
5
|
+
|
|
6
|
+
export const root = style({
|
|
7
|
+
display: 'flex',
|
|
8
|
+
alignItems: 'center',
|
|
9
|
+
justifyContent: 'space-between',
|
|
10
|
+
paddingInline: tokens.spacing.sm,
|
|
11
|
+
paddingBlock: tokens.spacing.xs,
|
|
12
|
+
borderBottom: `1px solid ${tokens.color.stroke.default}`,
|
|
13
|
+
gap: tokens.spacing.md,
|
|
14
|
+
flexShrink: 0,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export const items = style({
|
|
18
|
+
display: 'flex',
|
|
19
|
+
alignItems: 'center',
|
|
20
|
+
gap: tokens.spacing.md,
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
export const actions = style({
|
|
24
|
+
display: 'flex',
|
|
25
|
+
alignItems: 'center',
|
|
26
|
+
gap: tokens.spacing.xs,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const itemBase = style({
|
|
30
|
+
display: 'inline-flex',
|
|
31
|
+
alignItems: 'center',
|
|
32
|
+
gap: tokens.spacing.xs,
|
|
33
|
+
paddingInline: tokens.spacing.xs,
|
|
34
|
+
paddingBlock: tokens.spacing['3xs'],
|
|
35
|
+
borderRadius: tokens.radius.md,
|
|
36
|
+
cursor: 'pointer',
|
|
37
|
+
textDecoration: 'none',
|
|
38
|
+
border: 'none',
|
|
39
|
+
background: 'transparent',
|
|
40
|
+
color: tokens.color.text.subduedDefault,
|
|
41
|
+
...textStyleVariants['body1.strong'],
|
|
42
|
+
fontFamily: 'inherit',
|
|
43
|
+
transition: 'background-color 150ms ease, color 150ms ease',
|
|
44
|
+
selectors: {
|
|
45
|
+
'&:hover': {
|
|
46
|
+
backgroundColor: tokens.color.background.subduedUltralight,
|
|
47
|
+
color: tokens.color.text.subduedStrong,
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
export const item = itemBase;
|
|
53
|
+
|
|
54
|
+
export const itemActive = style({
|
|
55
|
+
backgroundColor: tokens.color.background.primaryLight,
|
|
56
|
+
color: tokens.color.text.primaryDefault,
|
|
57
|
+
selectors: {
|
|
58
|
+
'&:hover': {
|
|
59
|
+
backgroundColor: tokens.color.background.primaryLightHover,
|
|
60
|
+
color: tokens.color.text.primaryDefault,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
export const itemIcon = style({
|
|
66
|
+
display: 'inline-flex',
|
|
67
|
+
alignItems: 'center',
|
|
68
|
+
justifyContent: 'center',
|
|
69
|
+
flexShrink: 0,
|
|
70
|
+
width: 20,
|
|
71
|
+
height: 20,
|
|
72
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type ComponentPropsWithoutRef,
|
|
3
|
+
type ElementType,
|
|
4
|
+
type ReactNode,
|
|
5
|
+
} from 'react';
|
|
6
|
+
|
|
7
|
+
import { Text } from '../Typography';
|
|
8
|
+
import * as classes from './SubNavigation.css';
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// SubNavigation root
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
export interface SubNavigationProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
className?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function SubNavigationRoot({ children, className }: SubNavigationProps) {
|
|
20
|
+
return (
|
|
21
|
+
<nav className={`${classes.root}${className ? ` ${className}` : ''}`}>
|
|
22
|
+
{children}
|
|
23
|
+
</nav>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// SubNavigation.Items – wraps the left-aligned navigation items
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
function SubNavigationItems({ children }: { children: ReactNode }) {
|
|
32
|
+
return <div className={classes.items}>{children}</div>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// SubNavigation.Actions – wraps trailing actions on the right side
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
function SubNavigationActions({ children }: { children: ReactNode }) {
|
|
40
|
+
return <div className={classes.actions}>{children}</div>;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
// SubNavigation.Item – polymorphic (renders as <button> by default,
|
|
45
|
+
// accepts `component` to render as a TanStack Router Link etc.)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
type SubNavigationItemOwnProps = {
|
|
49
|
+
icon?: ReactNode;
|
|
50
|
+
label: string;
|
|
51
|
+
active?: boolean;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type PolymorphicProps<C extends ElementType> = SubNavigationItemOwnProps &
|
|
55
|
+
Omit<
|
|
56
|
+
ComponentPropsWithoutRef<C>,
|
|
57
|
+
keyof SubNavigationItemOwnProps | 'component'
|
|
58
|
+
> & {
|
|
59
|
+
component?: C;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
function SubNavigationItemInner<C extends ElementType = 'button'>({
|
|
63
|
+
component,
|
|
64
|
+
icon,
|
|
65
|
+
label,
|
|
66
|
+
active = false,
|
|
67
|
+
className,
|
|
68
|
+
...rest
|
|
69
|
+
}: PolymorphicProps<C>) {
|
|
70
|
+
const Component: ElementType = component ?? 'button';
|
|
71
|
+
const combinedClassName = [
|
|
72
|
+
classes.item,
|
|
73
|
+
active ? classes.itemActive : '',
|
|
74
|
+
className ?? '',
|
|
75
|
+
]
|
|
76
|
+
.filter(Boolean)
|
|
77
|
+
.join(' ');
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Component className={combinedClassName} {...rest}>
|
|
81
|
+
{icon && <span className={classes.itemIcon}>{icon}</span>}
|
|
82
|
+
<Text variant="body1.strong" c="inherit">
|
|
83
|
+
{label}
|
|
84
|
+
</Text>
|
|
85
|
+
</Component>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Typed as a generic function component so callers get inference on `component`
|
|
90
|
+
const SubNavigationItem = SubNavigationItemInner as <
|
|
91
|
+
C extends ElementType = 'button',
|
|
92
|
+
>(
|
|
93
|
+
props: PolymorphicProps<C>,
|
|
94
|
+
) => ReactNode;
|
|
95
|
+
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
// Compound export
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
|
|
100
|
+
export const SubNavigation = Object.assign(SubNavigationRoot, {
|
|
101
|
+
Items: SubNavigationItems,
|
|
102
|
+
Item: SubNavigationItem,
|
|
103
|
+
Actions: SubNavigationActions,
|
|
104
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Center, Loader } from '@mantine/core';
|
|
2
|
+
|
|
3
|
+
import { zIndex } from '../tokens';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fixed-position loader for Suspense boundaries
|
|
7
|
+
* Always centers on screen regardless of where the Suspense boundary is
|
|
8
|
+
*/
|
|
9
|
+
export function SuspenseLoader() {
|
|
10
|
+
return (
|
|
11
|
+
<Center
|
|
12
|
+
pos="fixed"
|
|
13
|
+
top={0}
|
|
14
|
+
left={0}
|
|
15
|
+
right={0}
|
|
16
|
+
bottom={0}
|
|
17
|
+
style={{ zIndex: zIndex.loading }}
|
|
18
|
+
>
|
|
19
|
+
<Loader size="lg" />
|
|
20
|
+
</Center>
|
|
21
|
+
);
|
|
22
|
+
}
|