@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,99 @@
|
|
|
1
|
+
import { ChevronDown, ChevronsUpDown, ChevronUp } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
import { Group, UnstyledButton } from '@mantine/core';
|
|
4
|
+
|
|
5
|
+
import { Text } from '../Typography';
|
|
6
|
+
|
|
7
|
+
export interface SortableColumnHeaderProps {
|
|
8
|
+
/**
|
|
9
|
+
* Column label text
|
|
10
|
+
*/
|
|
11
|
+
label: string;
|
|
12
|
+
/**
|
|
13
|
+
* Props to spread on the header (from useTableSort.getSortProps)
|
|
14
|
+
*/
|
|
15
|
+
sortProps: {
|
|
16
|
+
onClick: () => void;
|
|
17
|
+
'aria-sort': 'ascending' | 'descending' | 'none';
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Text alignment
|
|
21
|
+
* @default 'left'
|
|
22
|
+
*/
|
|
23
|
+
align?: 'left' | 'right' | 'center';
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Reusable sortable column header component
|
|
28
|
+
*
|
|
29
|
+
* Displays a column label with a sort indicator icon that changes based on sort state:
|
|
30
|
+
* - No sort: ChevronsUpDown (dimmed)
|
|
31
|
+
* - Ascending: ChevronUp
|
|
32
|
+
* - Descending: ChevronDown
|
|
33
|
+
*
|
|
34
|
+
* Uses a semantic button element for full keyboard and screen reader accessibility.
|
|
35
|
+
* The button responds to Enter/Space key presses and announces sort state via aria-sort.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```tsx
|
|
39
|
+
* const sort = useTableSort({ ... });
|
|
40
|
+
*
|
|
41
|
+
* <Table.Th>
|
|
42
|
+
* <SortableColumnHeader
|
|
43
|
+
* label="Name"
|
|
44
|
+
* sortProps={sort.getSortProps('name')}
|
|
45
|
+
* />
|
|
46
|
+
* </Table.Th>
|
|
47
|
+
*
|
|
48
|
+
* <Table.Th>
|
|
49
|
+
* <SortableColumnHeader
|
|
50
|
+
* label="Count"
|
|
51
|
+
* sortProps={sort.getSortProps('count')}
|
|
52
|
+
* align="right"
|
|
53
|
+
* />
|
|
54
|
+
* </Table.Th>
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function SortableColumnHeader({
|
|
58
|
+
label,
|
|
59
|
+
sortProps,
|
|
60
|
+
align = 'left',
|
|
61
|
+
}: SortableColumnHeaderProps) {
|
|
62
|
+
const { onClick, 'aria-sort': ariaSort } = sortProps;
|
|
63
|
+
|
|
64
|
+
// Determine which icon to show based on sort state
|
|
65
|
+
const SortIcon =
|
|
66
|
+
ariaSort === 'ascending'
|
|
67
|
+
? ChevronUp
|
|
68
|
+
: ariaSort === 'descending'
|
|
69
|
+
? ChevronDown
|
|
70
|
+
: ChevronsUpDown;
|
|
71
|
+
|
|
72
|
+
// Dim the icon when no sort is applied
|
|
73
|
+
const iconOpacity = ariaSort === 'none' ? 0.5 : 1;
|
|
74
|
+
|
|
75
|
+
// Determine justification based on alignment
|
|
76
|
+
const justifyContent =
|
|
77
|
+
align === 'right'
|
|
78
|
+
? 'flex-end'
|
|
79
|
+
: align === 'center'
|
|
80
|
+
? 'center'
|
|
81
|
+
: 'flex-start';
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<UnstyledButton
|
|
85
|
+
onClick={onClick}
|
|
86
|
+
aria-sort={ariaSort}
|
|
87
|
+
aria-label={`Sort by ${label}`}
|
|
88
|
+
style={{
|
|
89
|
+
width: '100%',
|
|
90
|
+
userSelect: 'none',
|
|
91
|
+
}}
|
|
92
|
+
>
|
|
93
|
+
<Group gap="4px" justify={justifyContent} wrap="nowrap">
|
|
94
|
+
<Text variant="caption2.stronger">{label}</Text>
|
|
95
|
+
<SortIcon size={16} opacity={iconOpacity} aria-hidden="true" />
|
|
96
|
+
</Group>
|
|
97
|
+
</UnstyledButton>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import figma from '@figma/code-connect';
|
|
2
|
+
|
|
3
|
+
import { TableSkeletonRows } from './TableSkeletonRows';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* -- This file was auto-generated by Code Connect --
|
|
7
|
+
* None of your props could be automatically mapped to Figma properties.
|
|
8
|
+
* You should update the `props` object to include a mapping from your
|
|
9
|
+
* code props to Figma properties, and update the `example` function to
|
|
10
|
+
* return the code example you'd like to see in Figma
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
figma.connect(
|
|
14
|
+
TableSkeletonRows,
|
|
15
|
+
'https://www.figma.com/design/VCLfybgU3OaUUPrQdBaVmP/LM-Design-System?node-id=303%3A246698',
|
|
16
|
+
{
|
|
17
|
+
props: {},
|
|
18
|
+
example: _props => (
|
|
19
|
+
<TableSkeletonRows columns={[{ width: 160 }, { width: 80 }]} rowCount={3} />
|
|
20
|
+
),
|
|
21
|
+
}
|
|
22
|
+
);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { Flex, Skeleton, Table } from '@mantine/core';
|
|
2
|
+
|
|
3
|
+
export interface SkeletonColumnConfig {
|
|
4
|
+
/**
|
|
5
|
+
* Skeleton width (number in px or string like '100%')
|
|
6
|
+
*/
|
|
7
|
+
width: number | string;
|
|
8
|
+
/**
|
|
9
|
+
* Skeleton height in px (defaults to 16)
|
|
10
|
+
*/
|
|
11
|
+
height?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Whether this is a circular skeleton (e.g., for avatars)
|
|
14
|
+
*/
|
|
15
|
+
circle?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Alignment - adds marginLeft: auto for 'right', auto margins for 'center'
|
|
18
|
+
*/
|
|
19
|
+
align?: 'left' | 'right' | 'center';
|
|
20
|
+
/**
|
|
21
|
+
* Border radius for pill-shaped skeletons (e.g., badges)
|
|
22
|
+
*/
|
|
23
|
+
radius?: 'xl' | 'lg' | 'md' | 'sm' | 'xs';
|
|
24
|
+
/**
|
|
25
|
+
* Whether this is a flex cell with multiple items (e.g., avatar + name)
|
|
26
|
+
*/
|
|
27
|
+
flex?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Gap between flex items (only used when flex=true)
|
|
30
|
+
*/
|
|
31
|
+
gap?: 'xs' | 'sm' | 'md' | 'lg';
|
|
32
|
+
/**
|
|
33
|
+
* Cell padding override (use Mantine spacing tokens)
|
|
34
|
+
*/
|
|
35
|
+
px?: number | string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface TableSkeletonRowsProps {
|
|
39
|
+
/**
|
|
40
|
+
* Column configuration for skeleton cells
|
|
41
|
+
*/
|
|
42
|
+
columns: SkeletonColumnConfig[];
|
|
43
|
+
/**
|
|
44
|
+
* Number of rows to render
|
|
45
|
+
*/
|
|
46
|
+
rowCount: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* TableSkeletonRows component
|
|
51
|
+
* Renders skeleton loading rows for tables with configurable column layouts
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* const columns = [
|
|
56
|
+
* { width: 120, flex: true, circle: true }, // Avatar + name
|
|
57
|
+
* { width: 60, align: 'right' }, // Number column
|
|
58
|
+
* { width: 70, radius: 'xl', align: 'right' }, // Badge column
|
|
59
|
+
* ];
|
|
60
|
+
*
|
|
61
|
+
* <Table.Tbody>
|
|
62
|
+
* {loading ? (
|
|
63
|
+
* <TableSkeletonRows columns={columns} rowCount={5} />
|
|
64
|
+
* ) : (
|
|
65
|
+
* data.map(row => <Table.Tr key={row.id}>...</Table.Tr>)
|
|
66
|
+
* )}
|
|
67
|
+
* </Table.Tbody>
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export function TableSkeletonRows({
|
|
71
|
+
columns,
|
|
72
|
+
rowCount,
|
|
73
|
+
}: TableSkeletonRowsProps) {
|
|
74
|
+
// Helper to determine margin style based on alignment
|
|
75
|
+
const getAlignmentStyle = (align?: 'left' | 'right' | 'center') => {
|
|
76
|
+
if (!align || align === 'left') return undefined;
|
|
77
|
+
if (align === 'right') return { marginLeft: 'auto' };
|
|
78
|
+
if (align === 'center') return { marginLeft: 'auto', marginRight: 'auto' };
|
|
79
|
+
return undefined;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<>
|
|
84
|
+
{Array.from({ length: rowCount }).map((_, rowIndex) => (
|
|
85
|
+
<Table.Tr key={`skeleton-row-${rowIndex}`}>
|
|
86
|
+
{columns.map((col, colIndex) => (
|
|
87
|
+
<Table.Td key={`skeleton-cell-${rowIndex}-${colIndex}`} px={col.px}>
|
|
88
|
+
{col.flex ? (
|
|
89
|
+
// Flex cell with multiple items (e.g., avatar + name)
|
|
90
|
+
<Flex gap={col.gap ?? 'xs'} align="center">
|
|
91
|
+
{col.circle && <Skeleton circle height={26} width={26} />}
|
|
92
|
+
<Skeleton
|
|
93
|
+
height={col.height ?? 16}
|
|
94
|
+
width={col.width}
|
|
95
|
+
radius={col.radius}
|
|
96
|
+
/>
|
|
97
|
+
</Flex>
|
|
98
|
+
) : (
|
|
99
|
+
// Single skeleton cell
|
|
100
|
+
<Skeleton
|
|
101
|
+
height={col.height ?? 16}
|
|
102
|
+
width={col.width}
|
|
103
|
+
radius={col.radius}
|
|
104
|
+
style={getAlignmentStyle(col.align)}
|
|
105
|
+
/>
|
|
106
|
+
)}
|
|
107
|
+
</Table.Td>
|
|
108
|
+
))}
|
|
109
|
+
</Table.Tr>
|
|
110
|
+
))}
|
|
111
|
+
</>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { MoreVertical } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { IconButton } from './IconButton';
|
|
6
|
+
import { Menu } from './Menu';
|
|
7
|
+
|
|
8
|
+
export interface TableActionsMenuProps {
|
|
9
|
+
/**
|
|
10
|
+
* Menu items to display in the dropdown
|
|
11
|
+
*/
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* Aria label for the action button
|
|
15
|
+
* @default "Row actions"
|
|
16
|
+
*/
|
|
17
|
+
ariaLabel?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Reusable action menu for table rows with three-dot icon
|
|
22
|
+
*
|
|
23
|
+
* Handles all the boilerplate for rendering a consistent action menu:
|
|
24
|
+
* - Three-dot icon button
|
|
25
|
+
* - Menu dropdown with proper positioning
|
|
26
|
+
* - Consistent styling and accessibility
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```tsx
|
|
30
|
+
* createActionsColumn<UserData>({
|
|
31
|
+
* cell: ({ row }) => (
|
|
32
|
+
* <TableActionsMenu>
|
|
33
|
+
* <Menu.Item onClick={() => handleEdit(row.original)}>
|
|
34
|
+
* Edit
|
|
35
|
+
* </Menu.Item>
|
|
36
|
+
* <Menu.Item onClick={() => handleDelete(row.original)}>
|
|
37
|
+
* Delete
|
|
38
|
+
* </Menu.Item>
|
|
39
|
+
* </TableActionsMenu>
|
|
40
|
+
* ),
|
|
41
|
+
* })
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export function TableActionsMenu({
|
|
45
|
+
children,
|
|
46
|
+
ariaLabel = 'Row actions',
|
|
47
|
+
}: TableActionsMenuProps) {
|
|
48
|
+
return (
|
|
49
|
+
<Menu position="bottom-end" withArrow>
|
|
50
|
+
<Menu.Target>
|
|
51
|
+
<IconButton variant="ghost" color="gray" aria-label={ariaLabel}>
|
|
52
|
+
<MoreVertical size={16} />
|
|
53
|
+
</IconButton>
|
|
54
|
+
</Menu.Target>
|
|
55
|
+
<Menu.Dropdown miw={260}>{children}</Menu.Dropdown>
|
|
56
|
+
</Menu>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Paper, type PaperProps } from '@mantine/core';
|
|
4
|
+
|
|
5
|
+
export interface TableCardProps extends PaperProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
/**
|
|
8
|
+
* Whether to show a border around the table card
|
|
9
|
+
* @default true
|
|
10
|
+
*/
|
|
11
|
+
withBorder?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* TableCard Component
|
|
16
|
+
* Wraps tables with a consistent card-style background and border.
|
|
17
|
+
* Use this as the default wrapper for all Table components.
|
|
18
|
+
*/
|
|
19
|
+
export function TableCard({
|
|
20
|
+
children,
|
|
21
|
+
withBorder = true,
|
|
22
|
+
...props
|
|
23
|
+
}: TableCardProps) {
|
|
24
|
+
return (
|
|
25
|
+
<Paper withBorder={withBorder} radius="lg" shadow="0" p={0} {...props}>
|
|
26
|
+
{children}
|
|
27
|
+
</Paper>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { Box, Flex, Paper } from '@mantine/core';
|
|
4
|
+
|
|
5
|
+
export interface TableContainerProps {
|
|
6
|
+
/**
|
|
7
|
+
* The controls section (search, filters, buttons, etc.)
|
|
8
|
+
*/
|
|
9
|
+
controls?: ReactNode;
|
|
10
|
+
/**
|
|
11
|
+
* Optional filters bar (e.g., applied filter badges)
|
|
12
|
+
* Appears between controls and children
|
|
13
|
+
*/
|
|
14
|
+
filtersBar?: ReactNode;
|
|
15
|
+
/**
|
|
16
|
+
* Optional action bar (e.g., bulk actions when items are selected)
|
|
17
|
+
*/
|
|
18
|
+
actionBar?: ReactNode;
|
|
19
|
+
/**
|
|
20
|
+
* The table or empty state content
|
|
21
|
+
*/
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
/**
|
|
24
|
+
* Whether to show a border around the table container
|
|
25
|
+
* @default false
|
|
26
|
+
*/
|
|
27
|
+
withBorder?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* When true, hides controls and filters bar (e.g. during initial load)
|
|
30
|
+
* @default false
|
|
31
|
+
*/
|
|
32
|
+
isLoading?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* TableContainer Component
|
|
37
|
+
*
|
|
38
|
+
* A reusable container for tables with controls, optional filters bar, and optional action bar.
|
|
39
|
+
* Wraps table controls, filters bar, action bar, and the table itself in a bordered Paper container.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```tsx
|
|
43
|
+
* <TableContainer
|
|
44
|
+
* controls={
|
|
45
|
+
* <Flex justify="space-between" align="center" gap="md" wrap="wrap">
|
|
46
|
+
* <TextInput placeholder="Search..." />
|
|
47
|
+
* <Button>Export</Button>
|
|
48
|
+
* </Flex>
|
|
49
|
+
* }
|
|
50
|
+
* filtersBar={
|
|
51
|
+
* <AppliedFiltersManagerBar
|
|
52
|
+
* filterCategories={filters}
|
|
53
|
+
* onClearAll={clearFilters}
|
|
54
|
+
* />
|
|
55
|
+
* }
|
|
56
|
+
* actionBar={
|
|
57
|
+
* <BulkActionBar
|
|
58
|
+
* selectedCount={3}
|
|
59
|
+
* onDelete={handleDelete}
|
|
60
|
+
* onClose={handleClose}
|
|
61
|
+
* />
|
|
62
|
+
* }
|
|
63
|
+
* >
|
|
64
|
+
* <MyTable data={data} withBorder={false} />
|
|
65
|
+
* </TableContainer>
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export function TableContainer({
|
|
69
|
+
controls,
|
|
70
|
+
filtersBar,
|
|
71
|
+
actionBar,
|
|
72
|
+
children,
|
|
73
|
+
withBorder = true,
|
|
74
|
+
isLoading = false,
|
|
75
|
+
}: TableContainerProps) {
|
|
76
|
+
return (
|
|
77
|
+
<Paper radius="lg" p={0} py="md" withBorder={withBorder}>
|
|
78
|
+
<Flex direction="column" gap="lg">
|
|
79
|
+
{!isLoading && <Box px="md">{controls}</Box>}
|
|
80
|
+
{!isLoading && filtersBar && <Box px="md">{filtersBar}</Box>}
|
|
81
|
+
{children}
|
|
82
|
+
{actionBar}
|
|
83
|
+
</Flex>
|
|
84
|
+
</Paper>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
import { CloudDownload } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
import { Flex } from '@mantine/core';
|
|
6
|
+
|
|
7
|
+
import { IconButton } from '../IconButton';
|
|
8
|
+
import { SearchTextInput } from '../TextInput';
|
|
9
|
+
import { TableSelectionButton } from './TableSelectionButton';
|
|
10
|
+
|
|
11
|
+
export interface TableControlBarProps {
|
|
12
|
+
/**
|
|
13
|
+
* Placeholder text for the search input
|
|
14
|
+
*/
|
|
15
|
+
searchPlaceholder: string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Current search value
|
|
19
|
+
*/
|
|
20
|
+
searchValue: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Callback when search value changes
|
|
24
|
+
*/
|
|
25
|
+
onSearchChange: (value: string) => void;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Optional key to force reset/remount of search input
|
|
29
|
+
* Change this value to clear the search input state
|
|
30
|
+
*/
|
|
31
|
+
searchKey?: string | number;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Whether selection mode is active
|
|
35
|
+
*/
|
|
36
|
+
isSelectionMode: boolean;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Entity name for the selection button (e.g., "Apps", "Devices", "Users")
|
|
40
|
+
*/
|
|
41
|
+
entityName: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Callback to toggle selection mode
|
|
45
|
+
*/
|
|
46
|
+
onToggleSelection: () => void;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Optional custom filter controls to display between search and selection button
|
|
50
|
+
*/
|
|
51
|
+
customFilters?: ReactNode;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Optional custom actions to display on the right side instead of default Download CSV button
|
|
55
|
+
*/
|
|
56
|
+
rightActions?: ReactNode;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether to show the Download CSV button
|
|
60
|
+
* @default true
|
|
61
|
+
*/
|
|
62
|
+
showDownloadButton?: boolean;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Optional callback when Download CSV button is clicked
|
|
66
|
+
*/
|
|
67
|
+
onDownloadCSV?: () => void;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Whether to show the selection button
|
|
71
|
+
* @default true
|
|
72
|
+
*/
|
|
73
|
+
canSelectItems?: boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Whether right-side action controls are disabled (selection button and download CSV).
|
|
77
|
+
* Search input and custom filters remain enabled to allow filtering/searching.
|
|
78
|
+
* @default false
|
|
79
|
+
*/
|
|
80
|
+
disabled?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Main container component for standardized table controls
|
|
85
|
+
*
|
|
86
|
+
* Displays search, filters, selection button, and action buttons in a consistent layout
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```tsx
|
|
90
|
+
* <TableControlBar
|
|
91
|
+
* searchPlaceholder="Search by Name"
|
|
92
|
+
* searchValue={searchQuery}
|
|
93
|
+
* onSearchChange={setSearchQuery}
|
|
94
|
+
* isSelectionMode={selection.isSelectionMode}
|
|
95
|
+
* entityName="Devices"
|
|
96
|
+
* onToggleSelection={selection.toggleSelectionMode}
|
|
97
|
+
* customFilters={<FilterButton />}
|
|
98
|
+
* rightActions={<ExportButton />}
|
|
99
|
+
* />
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export function TableControlBar({
|
|
103
|
+
searchPlaceholder,
|
|
104
|
+
searchValue,
|
|
105
|
+
onSearchChange,
|
|
106
|
+
searchKey,
|
|
107
|
+
isSelectionMode,
|
|
108
|
+
entityName,
|
|
109
|
+
onToggleSelection,
|
|
110
|
+
customFilters,
|
|
111
|
+
rightActions,
|
|
112
|
+
showDownloadButton = true,
|
|
113
|
+
onDownloadCSV,
|
|
114
|
+
canSelectItems = true,
|
|
115
|
+
disabled = false,
|
|
116
|
+
}: TableControlBarProps) {
|
|
117
|
+
return (
|
|
118
|
+
<Flex justify="space-between" align="center" gap="md" wrap="wrap">
|
|
119
|
+
{/* Left side: Search and filters - always enabled to allow filtering/searching */}
|
|
120
|
+
<Flex gap="sm" align="center">
|
|
121
|
+
<SearchTextInput
|
|
122
|
+
key={searchKey}
|
|
123
|
+
placeholder={searchPlaceholder}
|
|
124
|
+
value={searchValue}
|
|
125
|
+
onChange={e => onSearchChange(e.currentTarget.value)}
|
|
126
|
+
debounceMs={300}
|
|
127
|
+
w="320px"
|
|
128
|
+
/>
|
|
129
|
+
{customFilters}
|
|
130
|
+
</Flex>
|
|
131
|
+
|
|
132
|
+
{/* Right side: Selection button and actions - disabled when no data */}
|
|
133
|
+
<Flex gap="sm" align="center">
|
|
134
|
+
{canSelectItems && (
|
|
135
|
+
<TableSelectionButton
|
|
136
|
+
isSelectionMode={isSelectionMode}
|
|
137
|
+
entityName={entityName}
|
|
138
|
+
onClick={onToggleSelection}
|
|
139
|
+
disabled={disabled}
|
|
140
|
+
/>
|
|
141
|
+
)}
|
|
142
|
+
{rightActions ||
|
|
143
|
+
(showDownloadButton && (
|
|
144
|
+
<IconButton
|
|
145
|
+
variant="outline"
|
|
146
|
+
size="sm"
|
|
147
|
+
onClick={onDownloadCSV}
|
|
148
|
+
disabled={disabled}
|
|
149
|
+
>
|
|
150
|
+
<CloudDownload size={16} />
|
|
151
|
+
</IconButton>
|
|
152
|
+
))}
|
|
153
|
+
</Flex>
|
|
154
|
+
</Flex>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { CheckCheck, X } from 'lucide-react';
|
|
2
|
+
|
|
3
|
+
import { Button } from '../Button';
|
|
4
|
+
|
|
5
|
+
export interface TableSelectionButtonProps {
|
|
6
|
+
/**
|
|
7
|
+
* Whether selection mode is active
|
|
8
|
+
*/
|
|
9
|
+
isSelectionMode: boolean;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Entity name for the button label (e.g., "Apps", "Devices", "Users")
|
|
13
|
+
*/
|
|
14
|
+
entityName: string;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Callback when button is clicked to toggle selection mode
|
|
18
|
+
*/
|
|
19
|
+
onClick: () => void;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Whether the button is disabled
|
|
23
|
+
* @default false
|
|
24
|
+
*/
|
|
25
|
+
disabled?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Standardized selection toggle button for tables
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```tsx
|
|
33
|
+
* <TableSelectionButton
|
|
34
|
+
* isSelectionMode={selection.isSelectionMode}
|
|
35
|
+
* entityName="Devices"
|
|
36
|
+
* onClick={selection.toggleSelectionMode}
|
|
37
|
+
* />
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function TableSelectionButton({
|
|
41
|
+
isSelectionMode,
|
|
42
|
+
entityName,
|
|
43
|
+
onClick,
|
|
44
|
+
disabled = false,
|
|
45
|
+
}: TableSelectionButtonProps) {
|
|
46
|
+
return (
|
|
47
|
+
<Button
|
|
48
|
+
variant={isSelectionMode ? 'primary' : 'outline'}
|
|
49
|
+
leftSection={isSelectionMode ? <X size={16} /> : <CheckCheck size={16} />}
|
|
50
|
+
size="sm"
|
|
51
|
+
onClick={onClick}
|
|
52
|
+
disabled={disabled}
|
|
53
|
+
>
|
|
54
|
+
{isSelectionMode ? 'Cancel Selection' : `Select ${entityName}`}
|
|
55
|
+
</Button>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Components
|
|
2
|
+
export { TableControlBar, type TableControlBarProps } from './TableControlBar';
|
|
3
|
+
export {
|
|
4
|
+
TableSelectionButton,
|
|
5
|
+
type TableSelectionButtonProps,
|
|
6
|
+
} from './TableSelectionButton';
|
|
7
|
+
|
|
8
|
+
// Hook
|
|
9
|
+
export {
|
|
10
|
+
useTableControlBar,
|
|
11
|
+
type UseTableControlBarProps,
|
|
12
|
+
type UseTableControlBarReturn,
|
|
13
|
+
} from './useTableControlBar';
|