@trackunit/react-components 1.17.22 → 1.17.24
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/index.cjs.js +1321 -195
- package/index.esm.js +1321 -195
- package/package.json +1 -1
- package/src/components/Badge/Badge.d.ts +13 -6
- package/src/components/Card/CardBody.d.ts +38 -3
- package/src/components/Card/CardFooter.d.ts +44 -3
- package/src/components/Card/CardHeader.d.ts +43 -2
- package/src/components/CompletionStatusIndicator/CompletionStatusIndicator.d.ts +19 -10
- package/src/components/CopyableText/CopyableText.d.ts +28 -2
- package/src/components/DetailsList/DetailsList.d.ts +28 -7
- package/src/components/EmptyState/EmptyState.d.ts +24 -0
- package/src/components/EmptyValue/EmptyValue.d.ts +19 -1
- package/src/components/ExternalLink/ExternalLink.d.ts +35 -4
- package/src/components/Heading/Heading.d.ts +22 -1
- package/src/components/Highlight/Highlight.d.ts +32 -7
- package/src/components/HorizontalOverflowScroller/HorizontalOverflowScroller.d.ts +24 -9
- package/src/components/Icon/Icon.d.ts +19 -2
- package/src/components/KPI/KPISkeleton.d.ts +23 -2
- package/src/components/KPICard/KPICardSkeleton.d.ts +23 -2
- package/src/components/ListItem/ListItem.d.ts +25 -3
- package/src/components/ListItem/ListItemSkeleton.d.ts +24 -2
- package/src/components/Menu/MenuDivider/MenuDivider.d.ts +22 -1
- package/src/components/Menu/MenuItem/MenuItem.d.ts +30 -1
- package/src/components/Menu/MoreMenu/MoreMenu.d.ts +56 -2
- package/src/components/Notice/Notice.d.ts +33 -3
- package/src/components/Page/Page.d.ts +38 -2
- package/src/components/Page/PageContent.d.ts +34 -5
- package/src/components/PageHeader/PageHeader.d.ts +8 -4
- package/src/components/Pagination/Pagination.d.ts +31 -2
- package/src/components/Polygon/Polygon.d.ts +38 -3
- package/src/components/Popover/Popover.d.ts +8 -6
- package/src/components/Popover/PopoverContent.d.ts +29 -3
- package/src/components/Popover/PopoverTitle.d.ts +36 -2
- package/src/components/Popover/PopoverTrigger.d.ts +35 -4
- package/src/components/Portal/Portal.d.ts +35 -4
- package/src/components/PreferenceCard/PreferenceCard.d.ts +25 -3
- package/src/components/PreferenceCard/PreferenceCardSkeleton.d.ts +23 -2
- package/src/components/Prompt/Prompt.d.ts +42 -8
- package/src/components/SectionHeader/SectionHeader.d.ts +23 -3
- package/src/components/Sidebar/Sidebar.d.ts +12 -5
- package/src/components/Skeleton/SkeletonBlock/SkeletonBlock.d.ts +21 -5
- package/src/components/Skeleton/SkeletonLabel/SkeletonLabel.d.ts +21 -5
- package/src/components/SkeletonLines/SkeletonLines.d.ts +8 -6
- package/src/components/Spacer/Spacer.d.ts +17 -10
- package/src/components/Spinner/Spinner.d.ts +27 -6
- package/src/components/Tabs/Tab.d.ts +29 -2
- package/src/components/Tabs/TabContent.d.ts +46 -1
- package/src/components/Tabs/TabList.d.ts +29 -1
- package/src/components/Tabs/Tabs.d.ts +14 -1
- package/src/components/Tag/Tag.d.ts +13 -6
- package/src/components/Text/Text.d.ts +28 -3
- package/src/components/ToggleGroup/ToggleGroup.d.ts +30 -1
- package/src/components/ValueBar/ValueBar.d.ts +38 -2
- package/src/components/ValueBar/ValueBar.shared.d.ts +4 -0
- package/src/components/ValueBar/ValueBar.variants.d.ts +0 -3
- package/src/components/ZStack/ZStack.d.ts +23 -4
- package/src/components/buttons/Button/Button.d.ts +1 -1
- package/src/components/buttons/StarButton/StarButton.d.ts +24 -1
package/index.esm.js
CHANGED
|
@@ -118,10 +118,27 @@ const isSafari = () => {
|
|
|
118
118
|
return ua.includes("safari") && !ua.includes("chrome");
|
|
119
119
|
};
|
|
120
120
|
/**
|
|
121
|
-
*
|
|
121
|
+
* Icon renders SVG icons from the icon sprite sheets. All [HeroIcons](https://heroicons.com/) as well as custom Trackunit icons are available.
|
|
122
|
+
* Icons support three sizes (small=16px, medium=20px, large=24px) and theme-based colors.
|
|
122
123
|
*
|
|
123
|
-
*
|
|
124
|
+
* ### When to use
|
|
125
|
+
* Use Icon to provide visual cues for actions, statuses, or navigation. Icons can be used standalone or paired with text (e.g., inside Buttons, MenuItems, Tags).
|
|
126
|
+
*
|
|
127
|
+
* ### When not to use
|
|
128
|
+
* Do not use Icon alone for critical actions without an accessible label. Always provide `ariaLabel` or pair with visible text.
|
|
129
|
+
*
|
|
130
|
+
* @example Icons at different sizes
|
|
131
|
+
* ```tsx
|
|
132
|
+
* import { Icon } from "@trackunit/react-components";
|
|
124
133
|
*
|
|
134
|
+
* const IconExamples = () => (
|
|
135
|
+
* <div className="flex items-center gap-4">
|
|
136
|
+
* <Icon name="MapPin" size="small" color="danger" />
|
|
137
|
+
* <Icon name="Truck" size="medium" color="primary" />
|
|
138
|
+
* <Icon name="Cog6Tooth" size="large" type="outline" />
|
|
139
|
+
* </div>
|
|
140
|
+
* );
|
|
141
|
+
* ```
|
|
125
142
|
* @param {IconProps} props - The props for the Icon component
|
|
126
143
|
* @returns {ReactElement} Icon component
|
|
127
144
|
*/
|
|
@@ -245,14 +262,21 @@ const cvaTagIcon = cvaMerge(["cursor-pointer", "transition-opacity", "hover:opac
|
|
|
245
262
|
|
|
246
263
|
const TAG_TEXT_MIN_WIDTH_PX = parseTailwindArbitraryValue(TAG_TEXT_MIN_WIDTH_CLASS);
|
|
247
264
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
265
|
+
* Tag is used for labeling or categorizing items in the UI. Common use cases include indicating asset status,
|
|
266
|
+
* marking features as Beta, or displaying selected options in multi-select inputs.
|
|
267
|
+
* Tags support dismissal (close button), icons, and multiple color variants.
|
|
251
268
|
*
|
|
252
|
-
*
|
|
269
|
+
* ### When to use
|
|
270
|
+
* Use Tag to label statuses, categories, or user selections. It supports colors for intent (success, warning, danger) and activity states.
|
|
271
|
+
*
|
|
272
|
+
* ### When not to use
|
|
273
|
+
* Do not use Tag for numeric counts — use `Badge`.
|
|
274
|
+
* Do not use Tag for highlighting data values — use `Highlight`.
|
|
275
|
+
*
|
|
276
|
+
* How to choose between Tag, `Badge` and `Highlight`?
|
|
253
277
|
* - Use a Tag for labeling statuses, categories, or selections.
|
|
254
|
-
* - Use a
|
|
255
|
-
* - Use a
|
|
278
|
+
* - Use a `Badge` to indicate notifications or counts of applied elements, such as filters.
|
|
279
|
+
* - Use a `Highlight` to draw attention to values in plain text that require special attention or have crossed a threshold.
|
|
256
280
|
*
|
|
257
281
|
* @example Status tags with different colors
|
|
258
282
|
* ```tsx
|
|
@@ -499,12 +523,37 @@ const cvaText = cvaMerge(["text-black", "m-0", "relative", "text-sm", "font-norm
|
|
|
499
523
|
});
|
|
500
524
|
|
|
501
525
|
/**
|
|
502
|
-
*
|
|
526
|
+
* Text applies Trackunit default typography styles to body text. It renders as a `<p>`, `<span>`, or `<div>` element
|
|
527
|
+
* and supports size, weight, alignment, and style variants (subtle, inverted, uppercase, etc.).
|
|
528
|
+
*
|
|
529
|
+
* ### When to use
|
|
530
|
+
* Use Text for body content, descriptions, labels, and any non-heading text. It ensures consistent typography across the application.
|
|
531
|
+
*
|
|
532
|
+
* ### When not to use
|
|
533
|
+
* Do not use Text for page or section headings — use `Heading` instead.
|
|
503
534
|
*
|
|
504
|
-
*
|
|
535
|
+
* @example Text with different sizes and weights
|
|
536
|
+
* ```tsx
|
|
537
|
+
* import { Text } from "@trackunit/react-components";
|
|
505
538
|
*
|
|
506
|
-
*
|
|
539
|
+
* const TextExamples = () => (
|
|
540
|
+
* <div>
|
|
541
|
+
* <Text size="large" weight="bold">Large bold text</Text>
|
|
542
|
+
* <Text size="medium">Default body text</Text>
|
|
543
|
+
* <Text size="small" subtle>Small subtle caption</Text>
|
|
544
|
+
* </div>
|
|
545
|
+
* );
|
|
546
|
+
* ```
|
|
547
|
+
* @example Inline text using span
|
|
548
|
+
* ```tsx
|
|
549
|
+
* import { Text } from "@trackunit/react-components";
|
|
507
550
|
*
|
|
551
|
+
* const InlineExample = () => (
|
|
552
|
+
* <Text>
|
|
553
|
+
* Asset status: <Text type="span" weight="bold">Active</Text>
|
|
554
|
+
* </Text>
|
|
555
|
+
* );
|
|
556
|
+
* ```
|
|
508
557
|
* @param {TextProps} props - The props for the Text component
|
|
509
558
|
* @returns {ReactElement} Text component
|
|
510
559
|
*/
|
|
@@ -567,16 +616,37 @@ const cvaSpinnerContainer = cvaMerge(["box-border", "p-0.5", "overflow-hidden"],
|
|
|
567
616
|
const cvaSpinnerLabel = cvaMerge(["self-center", "text-center", "text-current"]);
|
|
568
617
|
|
|
569
618
|
/**
|
|
570
|
-
*
|
|
619
|
+
* Spinner provides visual feedback that data is being processed. It reassures users that their action is being handled
|
|
620
|
+
* during short operations (1-5 seconds) such as saving, loading, or refreshing data.
|
|
621
|
+
*
|
|
622
|
+
* ### When to use
|
|
623
|
+
* Use Spinner for short-duration loading states: button actions, table refreshes, inline data fetches, or modal content loading.
|
|
571
624
|
*
|
|
572
|
-
*
|
|
625
|
+
* ### When not to use
|
|
626
|
+
* Do not use Spinner for long loading states where content structure is known — use `SkeletonBlock` or `SkeletonLabel` instead.
|
|
627
|
+
* Do not use Spinner for full-page loading — use `EmptyState` with `loading` prop.
|
|
573
628
|
*
|
|
574
|
-
*
|
|
629
|
+
* @example Centered spinner during data load
|
|
630
|
+
* ```tsx
|
|
631
|
+
* import { Spinner } from "@trackunit/react-components";
|
|
575
632
|
*
|
|
576
|
-
*
|
|
633
|
+
* const LoadingContent = () => (
|
|
634
|
+
* <div className="h-64">
|
|
635
|
+
* <Spinner centering="centered" label="Loading assets..." />
|
|
636
|
+
* </div>
|
|
637
|
+
* );
|
|
638
|
+
* ```
|
|
639
|
+
* @example Small inline spinner
|
|
640
|
+
* ```tsx
|
|
641
|
+
* import { Spinner } from "@trackunit/react-components";
|
|
577
642
|
*
|
|
578
|
-
*
|
|
579
|
-
|
|
643
|
+
* const InlineLoading = () => (
|
|
644
|
+
* <div className="flex items-center gap-2">
|
|
645
|
+
* <Spinner size="small" centering="vertically" />
|
|
646
|
+
* <span>Saving...</span>
|
|
647
|
+
* </div>
|
|
648
|
+
* );
|
|
649
|
+
* ```
|
|
580
650
|
* @param {SpinnerProps} props - The props for the Spinner component
|
|
581
651
|
* @returns {ReactElement} Spinner component
|
|
582
652
|
*/
|
|
@@ -862,7 +932,7 @@ const cvaIconButton = cvaMerge([], {
|
|
|
862
932
|
* Use buttons to communicate actions users can take and to allow users to interact with the page. Each page should have one primary button, and any remaining calls to action should be represented as lower emphasis buttons.
|
|
863
933
|
*
|
|
864
934
|
* ### When not to use
|
|
865
|
-
* Do not use buttons as navigational elements. Instead, use
|
|
935
|
+
* Do not use buttons as navigational elements. Instead, use `Links` when the desired action is to take the user to a new page.
|
|
866
936
|
*
|
|
867
937
|
* @example Basic button with click handler
|
|
868
938
|
* ```tsx
|
|
@@ -1212,14 +1282,21 @@ const cvaBadge = cvaMerge([
|
|
|
1212
1282
|
});
|
|
1213
1283
|
|
|
1214
1284
|
/**
|
|
1215
|
-
*
|
|
1216
|
-
* It
|
|
1285
|
+
* Badge displays a numeric count or a small colored dot to indicate notifications or applied elements.
|
|
1286
|
+
* It is typically placed on buttons, tabs, or filters to draw attention to new or pending items.
|
|
1287
|
+
*
|
|
1288
|
+
* ### When to use
|
|
1289
|
+
* Use Badge to indicate a count (e.g., unread notifications, active filters) or as a compact status dot.
|
|
1290
|
+
*
|
|
1291
|
+
* ### When not to use
|
|
1292
|
+
* Do not use Badge for labeling statuses or categories — use `Tag` instead.
|
|
1293
|
+
* Do not use Badge for highlighting data values — use `Highlight`.
|
|
1217
1294
|
*
|
|
1218
|
-
* How to choose between Badge and Tag
|
|
1295
|
+
* How to choose between Badge and `Tag`?
|
|
1219
1296
|
* - Use a Badge to indicate notifications or counts of applied elements.
|
|
1220
|
-
* - Use a
|
|
1297
|
+
* - Use a `Tag` for labeling statuses, categories, or selections.
|
|
1221
1298
|
*
|
|
1222
|
-
* @example Badge with count and max
|
|
1299
|
+
* @example Badge with count and max — Use `count` and `max` to show a capped notification count. Values above `max` display as "9+".
|
|
1223
1300
|
* ```tsx
|
|
1224
1301
|
* import { Badge, Button } from "@trackunit/react-components";
|
|
1225
1302
|
*
|
|
@@ -1229,7 +1306,7 @@ const cvaBadge = cvaMerge([
|
|
|
1229
1306
|
* </Button>
|
|
1230
1307
|
* );
|
|
1231
1308
|
* ```
|
|
1232
|
-
* @example Compact
|
|
1309
|
+
* @example Compact status dot — Set `compact` to render a small colored dot without any count, useful for online/offline indicators.
|
|
1233
1310
|
* ```tsx
|
|
1234
1311
|
* import { Badge } from "@trackunit/react-components";
|
|
1235
1312
|
*
|
|
@@ -1478,8 +1555,25 @@ const BreadcrumbContainer = ({ "data-testid": dataTestId, breadcrumbItems, }) =>
|
|
|
1478
1555
|
};
|
|
1479
1556
|
|
|
1480
1557
|
/**
|
|
1481
|
-
*
|
|
1558
|
+
* StarButton renders a clickable star icon for toggling favorite/starred state.
|
|
1559
|
+
* The star appears in the primary color when starred and neutral when unstarred.
|
|
1482
1560
|
*
|
|
1561
|
+
* ### When to use
|
|
1562
|
+
* Use StarButton for favorite or bookmark actions on list items, cards, or tables.
|
|
1563
|
+
*
|
|
1564
|
+
* ### When not to use
|
|
1565
|
+
* Do not use StarButton for primary actions — use `Button` or `IconButton`.
|
|
1566
|
+
*
|
|
1567
|
+
* @example Star button for favoriting an asset
|
|
1568
|
+
* ```tsx
|
|
1569
|
+
* import { StarButton } from "@trackunit/react-components";
|
|
1570
|
+
* import { useState } from "react";
|
|
1571
|
+
*
|
|
1572
|
+
* const FavoriteToggle = () => {
|
|
1573
|
+
* const [starred, setStarred] = useState(false);
|
|
1574
|
+
* return <StarButton starred={starred} onClick={() => setStarred(s => !s)} />;
|
|
1575
|
+
* };
|
|
1576
|
+
* ```
|
|
1483
1577
|
* @param {StarButtonProps} props - The props for the StarButton component
|
|
1484
1578
|
* @returns {ReactElement} StarButton component
|
|
1485
1579
|
*/
|
|
@@ -1627,10 +1721,45 @@ const Card = ({ children, onClick, fullHeight = false, onMouseEnter, onMouseLeav
|
|
|
1627
1721
|
Card.displayName = "Card";
|
|
1628
1722
|
|
|
1629
1723
|
/**
|
|
1630
|
-
*
|
|
1724
|
+
* CardBody is the main content area of a Card. It provides consistent padding and gap between child elements.
|
|
1725
|
+
* Padding is applied here rather than on the Card itself to avoid insetting the scrollbar when content overflows.
|
|
1726
|
+
*
|
|
1727
|
+
* Use CardBody inside a Card to wrap the primary content.
|
|
1728
|
+
* It works alongside CardHeader and CardFooter.
|
|
1729
|
+
*
|
|
1730
|
+
* ### When to use
|
|
1731
|
+
* Use CardBody to wrap the main content area of a `Card`. It handles padding, gap, and flex direction so content is laid out consistently.
|
|
1631
1732
|
*
|
|
1632
|
-
*
|
|
1633
|
-
*
|
|
1733
|
+
* ### When not to use
|
|
1734
|
+
* Do not use CardBody outside of a `Card`. For standalone content layout, use standard flex or grid containers.
|
|
1735
|
+
*
|
|
1736
|
+
* @example Basic card body inside a card
|
|
1737
|
+
* ```tsx
|
|
1738
|
+
* import { Card, CardHeader, CardBody, Text } from "@trackunit/react-components";
|
|
1739
|
+
*
|
|
1740
|
+
* const AssetInfo = () => (
|
|
1741
|
+
* <Card>
|
|
1742
|
+
* <CardHeader heading="Asset Details" />
|
|
1743
|
+
* <CardBody>
|
|
1744
|
+
* <Text>Operating hours: 1,234</Text>
|
|
1745
|
+
* <Text>Status: Active</Text>
|
|
1746
|
+
* </CardBody>
|
|
1747
|
+
* </Card>
|
|
1748
|
+
* );
|
|
1749
|
+
* ```
|
|
1750
|
+
* @example Card body with custom direction and no gap
|
|
1751
|
+
* ```tsx
|
|
1752
|
+
* import { Card, CardBody } from "@trackunit/react-components";
|
|
1753
|
+
*
|
|
1754
|
+
* const HorizontalLayout = () => (
|
|
1755
|
+
* <Card>
|
|
1756
|
+
* <CardBody direction="row" gap="none" padding="none">
|
|
1757
|
+
* <div>Left section</div>
|
|
1758
|
+
* <div>Right section</div>
|
|
1759
|
+
* </CardBody>
|
|
1760
|
+
* </Card>
|
|
1761
|
+
* );
|
|
1762
|
+
* ```
|
|
1634
1763
|
* @param {CardBodyProps} props - The props for the CardBody component
|
|
1635
1764
|
* @returns {ReactElement} CardBody component
|
|
1636
1765
|
*/
|
|
@@ -1644,10 +1773,51 @@ const CardBody = ({ children, "data-testid": dataTestId, className, direction =
|
|
|
1644
1773
|
};
|
|
1645
1774
|
|
|
1646
1775
|
/**
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1649
|
-
*
|
|
1776
|
+
* CardFooter provides a consistent footer section for a Card, typically containing action buttons.
|
|
1777
|
+
* Buttons are right-justified by default. To push a button to the left, apply `margin-right: auto` to it.
|
|
1778
|
+
*
|
|
1779
|
+
* Use CardFooter as the last child inside a Card, below CardBody.
|
|
1780
|
+
*
|
|
1781
|
+
* ### When to use
|
|
1782
|
+
* Use CardFooter to add action buttons (e.g., Save, Cancel, View Details) at the bottom of a `Card` or Modal.
|
|
1783
|
+
*
|
|
1784
|
+
* ### When not to use
|
|
1785
|
+
* Do not use CardFooter for content display. It is designed specifically for action buttons and controls.
|
|
1650
1786
|
*
|
|
1787
|
+
* @example Card with footer actions
|
|
1788
|
+
* ```tsx
|
|
1789
|
+
* import { Card, CardHeader, CardBody, CardFooter, Button, Text } from "@trackunit/react-components";
|
|
1790
|
+
*
|
|
1791
|
+
* const ConfirmationCard = () => (
|
|
1792
|
+
* <Card>
|
|
1793
|
+
* <CardHeader heading="Confirm Action" />
|
|
1794
|
+
* <CardBody>
|
|
1795
|
+
* <Text>Are you sure you want to proceed?</Text>
|
|
1796
|
+
* </CardBody>
|
|
1797
|
+
* <CardFooter>
|
|
1798
|
+
* <Button variant="secondary">Cancel</Button>
|
|
1799
|
+
* <Button variant="primary">Confirm</Button>
|
|
1800
|
+
* </CardFooter>
|
|
1801
|
+
* </Card>
|
|
1802
|
+
* );
|
|
1803
|
+
* ```
|
|
1804
|
+
* @example Footer with left-aligned and right-aligned buttons
|
|
1805
|
+
* ```tsx
|
|
1806
|
+
* import { Card, CardBody, CardFooter, Button, Text } from "@trackunit/react-components";
|
|
1807
|
+
*
|
|
1808
|
+
* const FormCard = () => (
|
|
1809
|
+
* <Card>
|
|
1810
|
+
* <CardBody>
|
|
1811
|
+
* <Text>Form content here</Text>
|
|
1812
|
+
* </CardBody>
|
|
1813
|
+
* <CardFooter>
|
|
1814
|
+
* <Button variant="secondary-danger" className="mr-auto">Delete</Button>
|
|
1815
|
+
* <Button variant="secondary">Cancel</Button>
|
|
1816
|
+
* <Button variant="primary">Save</Button>
|
|
1817
|
+
* </CardFooter>
|
|
1818
|
+
* </Card>
|
|
1819
|
+
* );
|
|
1820
|
+
* ```
|
|
1651
1821
|
* @param {CardFooterProps} props - The props for the CardFooter component
|
|
1652
1822
|
* @returns {ReactElement} CardFooter component
|
|
1653
1823
|
*/
|
|
@@ -1691,8 +1861,29 @@ const cvaHeading = cvaMerge(["m-0", "leading-normal", "text-black"], {
|
|
|
1691
1861
|
});
|
|
1692
1862
|
|
|
1693
1863
|
/**
|
|
1694
|
-
*
|
|
1864
|
+
* Heading renders semantic heading elements (h1, h2, h3, h4) with Trackunit typography styles.
|
|
1865
|
+
* The `variant` prop maps to the semantic heading level: "primary" = h1, "secondary" = h2, "tertiary" = h3, "subtitle" = h4.
|
|
1866
|
+
*
|
|
1867
|
+
* ### When to use
|
|
1868
|
+
* Use Heading for page titles, section titles, and any hierarchical heading. Choose the variant based on the semantic level of the heading.
|
|
1869
|
+
*
|
|
1870
|
+
* ### When not to use
|
|
1871
|
+
* Do not use Heading for body text — use `Text` instead.
|
|
1872
|
+
* Do not use Heading for page-level headers with actions — use `PageHeader` or `SectionHeader`.
|
|
1695
1873
|
*
|
|
1874
|
+
* @example Heading variants
|
|
1875
|
+
* ```tsx
|
|
1876
|
+
* import { Heading } from "@trackunit/react-components";
|
|
1877
|
+
*
|
|
1878
|
+
* const HeadingExamples = () => (
|
|
1879
|
+
* <div>
|
|
1880
|
+
* <Heading variant="primary">Page Title (h1)</Heading>
|
|
1881
|
+
* <Heading variant="secondary">Section Title (h2)</Heading>
|
|
1882
|
+
* <Heading variant="tertiary">Subsection (h3)</Heading>
|
|
1883
|
+
* <Heading variant="subtitle" subtle>Subtitle (h4)</Heading>
|
|
1884
|
+
* </div>
|
|
1885
|
+
* );
|
|
1886
|
+
* ```
|
|
1696
1887
|
* @param {HeadingProps} props - The props for the Heading component
|
|
1697
1888
|
* @returns {ReactElement} Heading component
|
|
1698
1889
|
*/
|
|
@@ -1712,8 +1903,49 @@ const Heading = ({ variant = "primary", inverted = false, subtle = false, classN
|
|
|
1712
1903
|
};
|
|
1713
1904
|
|
|
1714
1905
|
/**
|
|
1715
|
-
*
|
|
1906
|
+
* CardHeader provides a consistent header section for a Card, including a heading, optional subheading, accessories, and action buttons.
|
|
1907
|
+
* It is designed to be used as the first child inside a Card, above CardBody.
|
|
1908
|
+
*
|
|
1909
|
+
* ### When to use
|
|
1910
|
+
* Use CardHeader when a `Card` needs a title and optional actions (e.g., close button, edit button). It handles heading layout, separator lines, and action placement.
|
|
1911
|
+
*
|
|
1912
|
+
* ### When not to use
|
|
1913
|
+
* Do not use CardHeader outside of a `Card`. For standalone section titles, use `SectionHeader` or `Heading` instead.
|
|
1716
1914
|
*
|
|
1915
|
+
* @example Card header with heading and actions
|
|
1916
|
+
* ```tsx
|
|
1917
|
+
* import { Card, CardHeader, CardBody, Button, Text } from "@trackunit/react-components";
|
|
1918
|
+
*
|
|
1919
|
+
* const AssetCard = () => (
|
|
1920
|
+
* <Card>
|
|
1921
|
+
* <CardHeader
|
|
1922
|
+
* heading="Excavator #1234"
|
|
1923
|
+
* subHeading="Last updated: 2 hours ago"
|
|
1924
|
+
* actions={<Button variant="ghost" size="small">Edit</Button>}
|
|
1925
|
+
* />
|
|
1926
|
+
* <CardBody>
|
|
1927
|
+
* <Text>Operating hours: 1,234</Text>
|
|
1928
|
+
* </CardBody>
|
|
1929
|
+
* </Card>
|
|
1930
|
+
* );
|
|
1931
|
+
* ```
|
|
1932
|
+
* @example Card header with accessories and no separator
|
|
1933
|
+
* ```tsx
|
|
1934
|
+
* import { Card, CardHeader, CardBody, Badge, Text } from "@trackunit/react-components";
|
|
1935
|
+
*
|
|
1936
|
+
* const NotificationsCard = () => (
|
|
1937
|
+
* <Card>
|
|
1938
|
+
* <CardHeader
|
|
1939
|
+
* heading="Notifications"
|
|
1940
|
+
* accessories={<Badge count={5} color="primary" />}
|
|
1941
|
+
* hideSeparator
|
|
1942
|
+
* />
|
|
1943
|
+
* <CardBody>
|
|
1944
|
+
* <Text>Notification list here</Text>
|
|
1945
|
+
* </CardBody>
|
|
1946
|
+
* </Card>
|
|
1947
|
+
* );
|
|
1948
|
+
* ```
|
|
1717
1949
|
* @param {CardHeaderProps} props - The props for the CardHeader component
|
|
1718
1950
|
* @returns {ReactElement} CardHeader component
|
|
1719
1951
|
*/
|
|
@@ -2010,17 +2242,26 @@ const Collapsible = ({ children, expanded, id, variant, extraPadding }) => {
|
|
|
2010
2242
|
};
|
|
2011
2243
|
|
|
2012
2244
|
/**
|
|
2013
|
-
*
|
|
2245
|
+
* CompletionStatusIndicator displays a visual icon or spinner based on a process completion status:
|
|
2246
|
+
* loading (spinner), success (green check circle), or error (red X circle). Returns null if no status flag is set.
|
|
2014
2247
|
*
|
|
2015
|
-
*
|
|
2016
|
-
*
|
|
2017
|
-
*
|
|
2018
|
-
*
|
|
2019
|
-
*
|
|
2020
|
-
*
|
|
2021
|
-
*
|
|
2022
|
-
* @
|
|
2023
|
-
*
|
|
2248
|
+
* ### When to use
|
|
2249
|
+
* Use CompletionStatusIndicator to show inline feedback for async operations (e.g., form submissions, save actions, data syncs).
|
|
2250
|
+
*
|
|
2251
|
+
* ### When not to use
|
|
2252
|
+
* Do not use for page-level loading — use `Spinner`.
|
|
2253
|
+
* Do not use for persistent status labels — use `Tag` or `Notice`.
|
|
2254
|
+
*
|
|
2255
|
+
* @example Completion status for a save action
|
|
2256
|
+
* ```tsx
|
|
2257
|
+
* import { CompletionStatusIndicator } from "@trackunit/react-components";
|
|
2258
|
+
*
|
|
2259
|
+
* const SaveStatus = ({ saving, saved, failed }: { saving: boolean; saved: boolean; failed: boolean }) => (
|
|
2260
|
+
* <CompletionStatusIndicator loading={saving} success={saved} error={failed} />
|
|
2261
|
+
* );
|
|
2262
|
+
* ```
|
|
2263
|
+
* @param {CompletionStatusIndicatorProps} props - The props for the CompletionStatusIndicator component
|
|
2264
|
+
* @returns {ReactElement | null} CompletionStatusIndicator component, or null when no status flag is set
|
|
2024
2265
|
*/
|
|
2025
2266
|
const CompletionStatusIndicator = ({ loading = false, error, success, ...rest }) => {
|
|
2026
2267
|
if (loading) {
|
|
@@ -2129,8 +2370,34 @@ const cvaCopyableText = cvaMerge([
|
|
|
2129
2370
|
});
|
|
2130
2371
|
|
|
2131
2372
|
/**
|
|
2132
|
-
*
|
|
2133
|
-
|
|
2373
|
+
* CopyableText displays a text value that the user can click to copy to the clipboard.
|
|
2374
|
+
* It shows a brief animation on copy to provide visual feedback. The copied value can differ from the displayed text via `alternativeText`.
|
|
2375
|
+
*
|
|
2376
|
+
* ### When to use
|
|
2377
|
+
* Use CopyableText for identifiers, serial numbers, URLs, or any value the user may want to copy (e.g., asset IDs, error codes).
|
|
2378
|
+
*
|
|
2379
|
+
* ### When not to use
|
|
2380
|
+
* Do not use CopyableText for long paragraphs or content that doesn't need to be copied.
|
|
2381
|
+
*
|
|
2382
|
+
* @example Copyable serial number
|
|
2383
|
+
* ```tsx
|
|
2384
|
+
* import { CopyableText } from "@trackunit/react-components";
|
|
2385
|
+
*
|
|
2386
|
+
* const AssetSerial = () => (
|
|
2387
|
+
* <CopyableText text="SN-2024-00142" data-testid="serial-number" />
|
|
2388
|
+
* );
|
|
2389
|
+
* ```
|
|
2390
|
+
* @example Copyable text with alternative clipboard value
|
|
2391
|
+
* ```tsx
|
|
2392
|
+
* import { CopyableText } from "@trackunit/react-components";
|
|
2393
|
+
*
|
|
2394
|
+
* const AssetLink = () => (
|
|
2395
|
+
* <CopyableText
|
|
2396
|
+
* text="Excavator #1234"
|
|
2397
|
+
* alternativeText="https://app.trackunit.com/assets/1234"
|
|
2398
|
+
* />
|
|
2399
|
+
* );
|
|
2400
|
+
* ```
|
|
2134
2401
|
* @param {CopyableTextProps} props - The props for the CopyableText component
|
|
2135
2402
|
* @returns {ReactElement} CopyableText component
|
|
2136
2403
|
*/
|
|
@@ -2158,14 +2425,35 @@ const cvaDetailsList = cvaMerge(["flex", "w-full", "min-w-0", "items-center", "t
|
|
|
2158
2425
|
const cvaDetailsListItem = cvaMerge(["last:truncate"]);
|
|
2159
2426
|
|
|
2160
2427
|
/**
|
|
2161
|
-
*
|
|
2428
|
+
* DetailsList renders a one-line list of text values separated by slash icons.
|
|
2429
|
+
* It is used to display compact metadata such as model, serial number, or location in a single row.
|
|
2430
|
+
*
|
|
2431
|
+
* ### When to use
|
|
2432
|
+
* Use DetailsList to display a compact, horizontal list of metadata values (e.g., in a card subtitle or list item description).
|
|
2433
|
+
*
|
|
2434
|
+
* ### When not to use
|
|
2435
|
+
* Do not use DetailsList for key-value pairs — use a standard layout with labels. Do not use for long text — values should be short strings.
|
|
2436
|
+
*
|
|
2437
|
+
* @example Displaying asset metadata
|
|
2438
|
+
* ```tsx
|
|
2439
|
+
* import { DetailsList } from "@trackunit/react-components";
|
|
2440
|
+
*
|
|
2441
|
+
* const AssetMeta = () => (
|
|
2442
|
+
* <DetailsList details={["Excavator", "CAT 320", "SN-00142"]} />
|
|
2443
|
+
* );
|
|
2444
|
+
* ```
|
|
2445
|
+
* @example DetailsList inside a linked context
|
|
2446
|
+
* ```tsx
|
|
2447
|
+
* import { DetailsList } from "@trackunit/react-components";
|
|
2162
2448
|
*
|
|
2163
|
-
*
|
|
2164
|
-
*
|
|
2165
|
-
*
|
|
2166
|
-
*
|
|
2167
|
-
*
|
|
2168
|
-
*
|
|
2449
|
+
* const LinkedAssetMeta = () => (
|
|
2450
|
+
* <a href="/assets/123">
|
|
2451
|
+
* <DetailsList details={["Site: Oslo", "Group: Heavy"]} hasLink />
|
|
2452
|
+
* </a>
|
|
2453
|
+
* );
|
|
2454
|
+
* ```
|
|
2455
|
+
* @param {DetailsListProps} props - The props for the DetailsList component
|
|
2456
|
+
* @returns {ReactElement} DetailsList component
|
|
2169
2457
|
*/
|
|
2170
2458
|
const DetailsList = ({ details, className, hasLink = false, ref }) => {
|
|
2171
2459
|
return (jsx("div", { className: cvaDetailsList({ className, hasLink }), ref: ref, children: details.map((value, index, array) => (jsxs(Fragment, { children: [jsx("span", { className: cvaDetailsListItem({ className }), children: value }), index < array.length - 1 && (jsx("div", { className: "mx-0.5 flex items-center", children: jsx(Icon, { className: "w-4 text-neutral-300", color: "neutral", name: "Slash", size: "small" }) }))] }, index))) }));
|
|
@@ -2301,13 +2589,29 @@ const cvaSkeleton = cvaMerge([
|
|
|
2301
2589
|
});
|
|
2302
2590
|
|
|
2303
2591
|
/**
|
|
2304
|
-
*
|
|
2592
|
+
* SkeletonLabel renders a single animated placeholder line for text content. It uses text-size keys (text-xs, text-sm, text-base, etc.)
|
|
2593
|
+
* to match the visual cap-height of actual text, with appropriate vertical margins to maintain line-height alignment.
|
|
2594
|
+
*
|
|
2595
|
+
* ### When to use
|
|
2596
|
+
* Use SkeletonLabel as a loading placeholder for single text lines: labels, titles, descriptions, values.
|
|
2597
|
+
*
|
|
2598
|
+
* ### When not to use
|
|
2599
|
+
* For multiple text lines, use `SkeletonLines`.
|
|
2600
|
+
* For shape-based elements (images, icons, avatars), use `SkeletonBlock`.
|
|
2305
2601
|
*
|
|
2306
|
-
*
|
|
2307
|
-
*
|
|
2602
|
+
* @example Skeleton label for a title and description
|
|
2603
|
+
* ```tsx
|
|
2604
|
+
* import { SkeletonLabel } from "@trackunit/react-components";
|
|
2308
2605
|
*
|
|
2309
|
-
*
|
|
2310
|
-
*
|
|
2606
|
+
* const TextSkeleton = () => (
|
|
2607
|
+
* <div className="flex flex-col gap-1">
|
|
2608
|
+
* <SkeletonLabel textSize="text-lg" width={200} />
|
|
2609
|
+
* <SkeletonLabel textSize="text-sm" width="80%" />
|
|
2610
|
+
* </div>
|
|
2611
|
+
* );
|
|
2612
|
+
* ```
|
|
2613
|
+
* @param {SkeletonLabelProps} props - The props for the SkeletonLabel component
|
|
2614
|
+
* @returns {ReactElement} SkeletonLabel component
|
|
2311
2615
|
*/
|
|
2312
2616
|
const SkeletonLabel = memo((props) => {
|
|
2313
2617
|
const { width = "100%", textSize = "text-base", flexibleWidth = true, className, "data-testid": dataTestId, children, ref, } = props;
|
|
@@ -2457,8 +2761,26 @@ const EmptyState = ({ description, altText, image = "SEARCH_DOCUMENT", customIma
|
|
|
2457
2761
|
const cvaEmptyValue = cvaMerge(["text-neutral-400"]);
|
|
2458
2762
|
|
|
2459
2763
|
/**
|
|
2460
|
-
*
|
|
2764
|
+
* EmptyValue renders a consistent dash symbol ("–") to represent missing, null, undefined, or not applicable values.
|
|
2765
|
+
* It is primarily used in tables and detail lists to maintain visual consistency when data is absent.
|
|
2766
|
+
*
|
|
2767
|
+
* ### When to use
|
|
2768
|
+
* Use EmptyValue as a placeholder in table cells, detail lists, or any data display where values may be missing.
|
|
2769
|
+
*
|
|
2770
|
+
* ### When not to use
|
|
2771
|
+
* Do not use EmptyValue for empty pages or sections — use `EmptyState` instead.
|
|
2772
|
+
*
|
|
2773
|
+
* @example Empty value in a details row
|
|
2774
|
+
* ```tsx
|
|
2775
|
+
* import { EmptyValue, Text } from "@trackunit/react-components";
|
|
2461
2776
|
*
|
|
2777
|
+
* const AssetDetail = ({ value }: { value?: string }) => (
|
|
2778
|
+
* <div className="flex justify-between">
|
|
2779
|
+
* <Text weight="bold">Serial Number</Text>
|
|
2780
|
+
* {value ? <Text>{value}</Text> : <EmptyValue />}
|
|
2781
|
+
* </div>
|
|
2782
|
+
* );
|
|
2783
|
+
* ```
|
|
2462
2784
|
* @param {EmptyValueProps} props - The props for the EmptyValue component
|
|
2463
2785
|
* @returns {ReactElement} EmptyValue component
|
|
2464
2786
|
*/
|
|
@@ -2498,10 +2820,41 @@ const cvaExternalLink = cvaMerge(["underline", "decoration-[1.5px]", "underline-
|
|
|
2498
2820
|
});
|
|
2499
2821
|
|
|
2500
2822
|
/**
|
|
2501
|
-
*
|
|
2502
|
-
|
|
2503
|
-
*
|
|
2504
|
-
*
|
|
2823
|
+
* ExternalLink renders an anchor element for navigating to external URLs. It opens in a new tab by default with `rel="noreferrer"` for security.
|
|
2824
|
+
* If no children are provided, the href URL is displayed as the link text.
|
|
2825
|
+
*
|
|
2826
|
+
* ### When to use
|
|
2827
|
+
* Use ExternalLink for any links that navigate to external resources outside the application (e.g., documentation, support pages, vendor sites).
|
|
2828
|
+
*
|
|
2829
|
+
* ### When not to use
|
|
2830
|
+
* Do not use ExternalLink for in-app navigation. Use `Link` from `@tanstack/react-router` for internal routes.
|
|
2831
|
+
*
|
|
2832
|
+
* @example Basic external link
|
|
2833
|
+
* ```tsx
|
|
2834
|
+
* import { ExternalLink } from "@trackunit/react-components";
|
|
2835
|
+
*
|
|
2836
|
+
* const SupportLink = () => (
|
|
2837
|
+
* <ExternalLink href="https://support.trackunit.com">
|
|
2838
|
+
* Visit Support Center
|
|
2839
|
+
* </ExternalLink>
|
|
2840
|
+
* );
|
|
2841
|
+
* ```
|
|
2842
|
+
* @example Neutral colored link opening in same window
|
|
2843
|
+
* ```tsx
|
|
2844
|
+
* import { ExternalLink } from "@trackunit/react-components";
|
|
2845
|
+
*
|
|
2846
|
+
* const DocumentLink = () => (
|
|
2847
|
+
* <ExternalLink
|
|
2848
|
+
* href="https://docs.trackunit.com/api"
|
|
2849
|
+
* color="neutral"
|
|
2850
|
+
* target="_self"
|
|
2851
|
+
* >
|
|
2852
|
+
* API Documentation
|
|
2853
|
+
* </ExternalLink>
|
|
2854
|
+
* );
|
|
2855
|
+
* ```
|
|
2856
|
+
* @param {ExternalLinkProps} props - The props for the ExternalLink component
|
|
2857
|
+
* @returns {ReactElement} ExternalLink component
|
|
2505
2858
|
*/
|
|
2506
2859
|
const ExternalLink = ({ rel = "noreferrer", target = "_blank", href, className, children = href, title = href, "data-testid": dataTestId, onClick, color = "primary", ref, }) => {
|
|
2507
2860
|
return (jsx("a", { className: cvaExternalLink({ className, color }), "data-testid": dataTestId, href: href, onClick: onClick, ref: ref, rel: rel, target: target, title: title, children: children }));
|
|
@@ -3293,15 +3646,40 @@ const cvaHighlight = cvaMerge([
|
|
|
3293
3646
|
const cvaHighlightText = cvaMerge(["truncate"]);
|
|
3294
3647
|
|
|
3295
3648
|
/**
|
|
3296
|
-
*
|
|
3297
|
-
* It
|
|
3649
|
+
* Highlight draws visual attention to data values that may require user action, monitoring, or investigation.
|
|
3650
|
+
* It uses color cues (e.g., danger, warning, success) to emphasize out-of-range or critical values for quick scanning.
|
|
3651
|
+
*
|
|
3652
|
+
* ### When to use
|
|
3653
|
+
* Use Highlight to emphasize numeric or text values inline that have crossed a threshold or need attention (e.g., temperature warnings, low battery).
|
|
3654
|
+
*
|
|
3655
|
+
* ### When not to use
|
|
3656
|
+
* Do not use Highlight for labeling statuses or categories — use `Tag`.
|
|
3657
|
+
* Do not use Highlight for notification counts — use `Badge`.
|
|
3658
|
+
*
|
|
3659
|
+
* @example Highlighting a warning value
|
|
3660
|
+
* ```tsx
|
|
3661
|
+
* import { Highlight, Text } from "@trackunit/react-components";
|
|
3298
3662
|
*
|
|
3299
|
-
*
|
|
3300
|
-
*
|
|
3301
|
-
*
|
|
3663
|
+
* const TemperatureDisplay = () => (
|
|
3664
|
+
* <Text>
|
|
3665
|
+
* Engine temperature: <Highlight color="warning">92°C</Highlight>
|
|
3666
|
+
* </Text>
|
|
3667
|
+
* );
|
|
3668
|
+
* ```
|
|
3669
|
+
* @example Different highlight colors for thresholds
|
|
3670
|
+
* ```tsx
|
|
3671
|
+
* import { Highlight } from "@trackunit/react-components";
|
|
3302
3672
|
*
|
|
3303
|
-
*
|
|
3304
|
-
*
|
|
3673
|
+
* const BatteryStatus = () => (
|
|
3674
|
+
* <div className="flex gap-2">
|
|
3675
|
+
* <Highlight color="success">85%</Highlight>
|
|
3676
|
+
* <Highlight color="warning">32%</Highlight>
|
|
3677
|
+
* <Highlight color="danger">8%</Highlight>
|
|
3678
|
+
* </div>
|
|
3679
|
+
* );
|
|
3680
|
+
* ```
|
|
3681
|
+
* @param {HighlightProps} props - The props for the Highlight component
|
|
3682
|
+
* @returns {ReactElement} Highlight component
|
|
3305
3683
|
*/
|
|
3306
3684
|
const Highlight = ({ className, "data-testid": dataTestId, children, size = "small", color = "warning", ref, }) => {
|
|
3307
3685
|
return (jsx("div", { className: cvaHighlight({ className, size, color }), "data-testid": dataTestId, ref: ref, children: jsx("span", { className: cvaHighlightText(), children: children }) }));
|
|
@@ -3563,11 +3941,30 @@ const cvaZStackContainer = cvaMerge(["grid", "grid-cols-1", "grid-rows-1", "isol
|
|
|
3563
3941
|
const cvaZStackItem = cvaMerge(["col-start-1", "col-end-1", "row-start-1", "row-end-2"]);
|
|
3564
3942
|
|
|
3565
3943
|
/**
|
|
3566
|
-
* ZStack
|
|
3567
|
-
*
|
|
3944
|
+
* ZStack stacks its children on the z-axis (overlaying them on top of each other).
|
|
3945
|
+
* It is a CSS grid-based alternative to `position: absolute` that avoids side effects like elements being removed from the document flow.
|
|
3946
|
+
*
|
|
3947
|
+
* ### When to use
|
|
3948
|
+
* Use ZStack when you need to overlay elements on top of each other (e.g., an image with a badge overlay, or a scroll container with fade indicators).
|
|
3949
|
+
*
|
|
3950
|
+
* ### When not to use
|
|
3951
|
+
* Do not use ZStack for standard stacking/layout — use flex or grid containers instead.
|
|
3568
3952
|
*
|
|
3569
|
-
* @
|
|
3570
|
-
*
|
|
3953
|
+
* @example Overlaying a badge on a thumbnail
|
|
3954
|
+
* ```tsx
|
|
3955
|
+
* import { ZStack, Badge, Icon } from "@trackunit/react-components";
|
|
3956
|
+
*
|
|
3957
|
+
* const ThumbnailWithBadge = () => (
|
|
3958
|
+
* <ZStack>
|
|
3959
|
+
* <Icon name="Truck" size="large" />
|
|
3960
|
+
* <div className="self-start justify-self-end">
|
|
3961
|
+
* <Badge count={3} color="danger" />
|
|
3962
|
+
* </div>
|
|
3963
|
+
* </ZStack>
|
|
3964
|
+
* );
|
|
3965
|
+
* ```
|
|
3966
|
+
* @param {ZStackProps} props - The props for the ZStack component
|
|
3967
|
+
* @returns {ReactElement} ZStack component
|
|
3571
3968
|
*/
|
|
3572
3969
|
const ZStack = ({ children, className, "data-testid": dataTestId, ref }) => {
|
|
3573
3970
|
return (jsx("div", { className: cvaZStackContainer({ className }), "data-testid": dataTestId, ref: ref, children: Children.map(children, (child, index) => {
|
|
@@ -3633,16 +4030,31 @@ const OverflowIndicator = ({ className, "data-testid": dataTestId, direction, on
|
|
|
3633
4030
|
};
|
|
3634
4031
|
|
|
3635
4032
|
/**
|
|
3636
|
-
*
|
|
3637
|
-
*
|
|
4033
|
+
* HorizontalOverflowScroller displays child elements in a horizontal row with automatic overflow detection.
|
|
4034
|
+
* When content overflows, it shows left/right scroll indicators with click-to-scroll functionality.
|
|
3638
4035
|
*
|
|
3639
|
-
*
|
|
3640
|
-
*
|
|
3641
|
-
*
|
|
3642
|
-
*
|
|
3643
|
-
*
|
|
3644
|
-
*
|
|
3645
|
-
* @
|
|
4036
|
+
* ### When to use
|
|
4037
|
+
* Use HorizontalOverflowScroller to display a row of items (cards, tags, buttons) that may overflow on smaller screens, with visual cues that more content is available.
|
|
4038
|
+
*
|
|
4039
|
+
* ### When not to use
|
|
4040
|
+
* Do not use HorizontalOverflowScroller for tab navigation — use `TabList` which has its own scroll handling.
|
|
4041
|
+
*
|
|
4042
|
+
* @example Horizontal scroller for KPI cards
|
|
4043
|
+
* ```tsx
|
|
4044
|
+
* import { HorizontalOverflowScroller, Tag } from "@trackunit/react-components";
|
|
4045
|
+
*
|
|
4046
|
+
* const TagRow = () => (
|
|
4047
|
+
* <HorizontalOverflowScroller>
|
|
4048
|
+
* <Tag color="success">Active</Tag>
|
|
4049
|
+
* <Tag color="warning">Idle</Tag>
|
|
4050
|
+
* <Tag color="danger">Offline</Tag>
|
|
4051
|
+
* <Tag color="info">Maintenance</Tag>
|
|
4052
|
+
* <Tag color="neutral">Archived</Tag>
|
|
4053
|
+
* </HorizontalOverflowScroller>
|
|
4054
|
+
* );
|
|
4055
|
+
* ```
|
|
4056
|
+
* @param {HorizontalOverflowScrollerProps} props - The props for the HorizontalOverflowScroller component
|
|
4057
|
+
* @returns {ReactElement} HorizontalOverflowScroller component
|
|
3646
4058
|
*/
|
|
3647
4059
|
const HorizontalOverflowScroller = ({ className, "data-testid": dataTestId, children, onScrollStateChange, ref, }) => {
|
|
3648
4060
|
const childrenArray = Children.toArray(children);
|
|
@@ -3886,14 +4298,16 @@ const usePopoverContext = () => {
|
|
|
3886
4298
|
return context;
|
|
3887
4299
|
};
|
|
3888
4300
|
/**
|
|
3889
|
-
*
|
|
3890
|
-
*
|
|
3891
|
-
*
|
|
3892
|
-
* - The context is used to share the state of the popover, such as the open state and the reference element.
|
|
4301
|
+
* Popover is a floating overlay that appears relative to a trigger element. It provides a context for
|
|
4302
|
+
* PopoverTrigger and PopoverContent children,
|
|
4303
|
+
* managing open/close state, positioning, and focus management.
|
|
3893
4304
|
*
|
|
3894
|
-
*
|
|
4305
|
+
* ### When to use
|
|
4306
|
+
* Use Popover for contextual information, menus, or forms that should appear near a trigger element without navigating away.
|
|
3895
4307
|
*
|
|
3896
|
-
*
|
|
4308
|
+
* ### When not to use
|
|
4309
|
+
* Do not use Popover for simple text hints — use `Tooltip` instead.
|
|
4310
|
+
* For full-screen overlays or blocking dialogs, use a Modal.
|
|
3897
4311
|
*
|
|
3898
4312
|
* @example Basic popover with trigger
|
|
3899
4313
|
* ```tsx
|
|
@@ -3962,10 +4376,41 @@ const getDefaultPortalContainer = () => {
|
|
|
3962
4376
|
};
|
|
3963
4377
|
|
|
3964
4378
|
/**
|
|
3965
|
-
*
|
|
3966
|
-
* By default
|
|
3967
|
-
*
|
|
3968
|
-
*
|
|
4379
|
+
* Portal renders its children into a separate DOM node, outside the normal React tree hierarchy.
|
|
4380
|
+
* By default, content is portalled into a z-index-isolated `div#portal-container` in the document body.
|
|
4381
|
+
* This is used internally by Popover, Tooltip, and other overlay components.
|
|
4382
|
+
*
|
|
4383
|
+
* ### When to use
|
|
4384
|
+
* Use Portal when you need to render content (modals, popovers, tooltips) outside its parent's DOM hierarchy to avoid z-index or overflow clipping issues.
|
|
4385
|
+
*
|
|
4386
|
+
* ### When not to use
|
|
4387
|
+
* Do not use Portal for regular content rendering. Most overlay components (`Popover`, `Tooltip`) already use Portal internally.
|
|
4388
|
+
*
|
|
4389
|
+
* @example Rendering into a specific container
|
|
4390
|
+
* ```tsx
|
|
4391
|
+
* import { Portal } from "@trackunit/react-components";
|
|
4392
|
+
*
|
|
4393
|
+
* const PortalledContent = () => (
|
|
4394
|
+
* <Portal root={document.getElementById('sidebar-container')}>
|
|
4395
|
+
* <div className="p-4">This content renders in #sidebar-container</div>
|
|
4396
|
+
* </Portal>
|
|
4397
|
+
* );
|
|
4398
|
+
* ```
|
|
4399
|
+
* @example Default portal into document body
|
|
4400
|
+
* ```tsx
|
|
4401
|
+
* import { Portal } from "@trackunit/react-components";
|
|
4402
|
+
*
|
|
4403
|
+
* const FloatingContent = ({ isVisible }: { isVisible: boolean }) => (
|
|
4404
|
+
* isVisible ? (
|
|
4405
|
+
* <Portal>
|
|
4406
|
+
* <div className="fixed bottom-4 right-4 bg-white p-4 rounded-lg shadow-lg">
|
|
4407
|
+
* Toast notification content
|
|
4408
|
+
* </div>
|
|
4409
|
+
* </Portal>
|
|
4410
|
+
* ) : null
|
|
4411
|
+
* );
|
|
4412
|
+
* @param {PortalProps} props - The props for the Portal component
|
|
4413
|
+
* @returns {ReactElement} Portal component
|
|
3969
4414
|
*/
|
|
3970
4415
|
const Portal = (props) => {
|
|
3971
4416
|
return jsx(FloatingPortal, { ...props, root: props.root ?? getDefaultPortalContainer() });
|
|
@@ -3983,11 +4428,37 @@ const cvaPopoverTitleContainer = cvaMerge(["flex", "items-center", "px-2", "py-1
|
|
|
3983
4428
|
const cvaPopoverTitleText = cvaMerge(["flex-1", "text-neutral-500"]);
|
|
3984
4429
|
|
|
3985
4430
|
/**
|
|
3986
|
-
*
|
|
4431
|
+
* PopoverContent displays the floating content inside a Popover.
|
|
4432
|
+
* It renders in a portal and manages focus, positioning, and accessibility. Must be a child of a Popover.
|
|
3987
4433
|
*
|
|
3988
|
-
*
|
|
3989
|
-
* in a portal and manages focus, positioning, and accessibility features.
|
|
4434
|
+
* The `children` prop can be a ReactNode or a render function that receives a `close` callback to programmatically dismiss the popover.
|
|
3990
4435
|
*
|
|
4436
|
+
* ### When to use
|
|
4437
|
+
* Use PopoverContent inside a `Popover` to define what appears in the floating overlay.
|
|
4438
|
+
*
|
|
4439
|
+
* ### When not to use
|
|
4440
|
+
* Do not use PopoverContent outside of a `Popover` context. It relies on `Popover`'s context for positioning and state.
|
|
4441
|
+
*
|
|
4442
|
+
* @example PopoverContent with close callback
|
|
4443
|
+
* ```tsx
|
|
4444
|
+
* import { Popover, PopoverTrigger, PopoverContent, Button, Text } from "@trackunit/react-components";
|
|
4445
|
+
*
|
|
4446
|
+
* const DismissablePopover = () => (
|
|
4447
|
+
* <Popover>
|
|
4448
|
+
* <PopoverTrigger>
|
|
4449
|
+
* <Button variant="secondary">Open</Button>
|
|
4450
|
+
* </PopoverTrigger>
|
|
4451
|
+
* <PopoverContent>
|
|
4452
|
+
* {(close) => (
|
|
4453
|
+
* <div className="p-4">
|
|
4454
|
+
* <Text>Popover content here</Text>
|
|
4455
|
+
* <Button onClick={close} size="small">Done</Button>
|
|
4456
|
+
* </div>
|
|
4457
|
+
* )}
|
|
4458
|
+
* </PopoverContent>
|
|
4459
|
+
* </Popover>
|
|
4460
|
+
* );
|
|
4461
|
+
* ```
|
|
3991
4462
|
* @param {PopoverContentProps} props - The props for the PopoverContent component
|
|
3992
4463
|
* @returns {ReactElement} The popover content element
|
|
3993
4464
|
*/
|
|
@@ -4004,8 +4475,34 @@ const PopoverContent = function PopoverContent({ className, "data-testid": dataT
|
|
|
4004
4475
|
};
|
|
4005
4476
|
|
|
4006
4477
|
/**
|
|
4007
|
-
*
|
|
4478
|
+
* PopoverTrigger is the clickable element that opens or closes a Popover.
|
|
4479
|
+
* It must be used as a direct child of a Popover component. By default, it clones the child element and attaches popover behavior.
|
|
4480
|
+
* Set `renderButton` to true to wrap children in a default Button.
|
|
4481
|
+
*
|
|
4482
|
+
* ### When to use
|
|
4483
|
+
* Use PopoverTrigger inside a `Popover` to designate which element opens the popover overlay.
|
|
4484
|
+
*
|
|
4485
|
+
* ### When not to use
|
|
4486
|
+
* Do not use PopoverTrigger outside of a `Popover` context. It relies on `Popover`'s context for state management.
|
|
4487
|
+
*
|
|
4488
|
+
* @example PopoverTrigger with a custom button
|
|
4489
|
+
* ```tsx
|
|
4490
|
+
* import { Popover, PopoverTrigger, PopoverContent, IconButton, Icon } from "@trackunit/react-components";
|
|
4008
4491
|
*
|
|
4492
|
+
* const IconPopover = () => (
|
|
4493
|
+
* <Popover placement="bottom">
|
|
4494
|
+
* <PopoverTrigger>
|
|
4495
|
+
* <IconButton
|
|
4496
|
+
* icon={<Icon name="InformationCircle" size="small" />}
|
|
4497
|
+
* variant="ghost"
|
|
4498
|
+
* />
|
|
4499
|
+
* </PopoverTrigger>
|
|
4500
|
+
* <PopoverContent>
|
|
4501
|
+
* <div className="p-3">Helpful information</div>
|
|
4502
|
+
* </PopoverContent>
|
|
4503
|
+
* </Popover>
|
|
4504
|
+
* );
|
|
4505
|
+
* ```
|
|
4009
4506
|
* @param {PopoverTriggerProps} props - The props for the PopoverTrigger component
|
|
4010
4507
|
* @returns {ReactElement} PopoverTrigger component
|
|
4011
4508
|
*/
|
|
@@ -4462,8 +4959,29 @@ const KPI = ({ title, value, unit, className, "data-testid": dataTestId, tooltip
|
|
|
4462
4959
|
};
|
|
4463
4960
|
|
|
4464
4961
|
/**
|
|
4465
|
-
*
|
|
4466
|
-
*
|
|
4962
|
+
* KPISkeleton is a loading placeholder that mimics the layout of a KPI component.
|
|
4963
|
+
* It renders skeleton lines for the title and value with randomized widths for a natural appearance.
|
|
4964
|
+
*
|
|
4965
|
+
* ### When to use
|
|
4966
|
+
* Use KPISkeleton while `KPI` data is loading to maintain layout stability and reduce perceived load time.
|
|
4967
|
+
*
|
|
4968
|
+
* ### When not to use
|
|
4969
|
+
* Do not use KPISkeleton for generic text loading — use `SkeletonLabel`.
|
|
4970
|
+
*
|
|
4971
|
+
* @example KPI skeleton in a dashboard
|
|
4972
|
+
* ```tsx
|
|
4973
|
+
* import { KPISkeleton } from "@trackunit/react-components";
|
|
4974
|
+
*
|
|
4975
|
+
* const LoadingDashboard = () => (
|
|
4976
|
+
* <div className="flex gap-4">
|
|
4977
|
+
* <KPISkeleton variant="default" />
|
|
4978
|
+
* <KPISkeleton variant="default" />
|
|
4979
|
+
* <KPISkeleton variant="small" />
|
|
4980
|
+
* </div>
|
|
4981
|
+
* );
|
|
4982
|
+
* ```
|
|
4983
|
+
* @param {KPISkeletonProps} props - The props for the KPISkeleton component
|
|
4984
|
+
* @returns {ReactElement} KPISkeleton component
|
|
4467
4985
|
*/
|
|
4468
4986
|
const KPISkeleton = ({ variant = "default", className, "data-testid": dataTestId, style, ref, ...rest }) => {
|
|
4469
4987
|
const isSmallVariant = variant === "small";
|
|
@@ -4492,32 +5010,34 @@ const TrendIndicators = ({ trends, "data-testid": dataTestId, className, ref, })
|
|
|
4492
5010
|
return (jsx("span", { className: twMerge("flex flex-row items-center gap-1", className), "data-testid": dataTestId, ref: ref, children: trends.map((trend, index) => (jsx(TrendIndicator, { "data-testid": dataTestId ? `${dataTestId}-trend-indicator-${index}` : undefined, ...trend }, index))) }));
|
|
4493
5011
|
};
|
|
4494
5012
|
|
|
4495
|
-
const
|
|
4496
|
-
|
|
4497
|
-
"overflow-hidden",
|
|
4498
|
-
"rounded",
|
|
4499
|
-
"bg-neutral-100",
|
|
4500
|
-
"appearance-none",
|
|
4501
|
-
"[&::-webkit-progress-bar]:bg-transparent",
|
|
4502
|
-
"[&::-webkit-progress-value]:bg-current",
|
|
4503
|
-
"[&::-moz-progress-bar]:bg-current",
|
|
4504
|
-
], {
|
|
5013
|
+
const valueBarContainerClassName = "relative flex w-full items-center gap-2";
|
|
5014
|
+
const cvaValueBarText = cvaMerge(["whitespace-nowrap"], {
|
|
4505
5015
|
variants: {
|
|
4506
5016
|
size: {
|
|
4507
|
-
|
|
4508
|
-
|
|
4509
|
-
large: "h-9",
|
|
5017
|
+
small: "leading-xs text-xs font-medium text-neutral-600",
|
|
5018
|
+
large: "absolute pl-3 text-base text-white drop-shadow-lg",
|
|
4510
5019
|
},
|
|
4511
5020
|
},
|
|
4512
5021
|
defaultVariants: {
|
|
4513
5022
|
size: "small",
|
|
4514
5023
|
},
|
|
4515
5024
|
});
|
|
4516
|
-
|
|
5025
|
+
|
|
5026
|
+
const cvaValueBar = cvaMerge([
|
|
5027
|
+
"w-full",
|
|
5028
|
+
"overflow-hidden",
|
|
5029
|
+
"rounded",
|
|
5030
|
+
"bg-neutral-100",
|
|
5031
|
+
"appearance-none",
|
|
5032
|
+
"[&::-webkit-progress-bar]:bg-transparent",
|
|
5033
|
+
"[&::-webkit-progress-value]:bg-current",
|
|
5034
|
+
"[&::-moz-progress-bar]:bg-current",
|
|
5035
|
+
], {
|
|
4517
5036
|
variants: {
|
|
4518
5037
|
size: {
|
|
4519
|
-
|
|
4520
|
-
|
|
5038
|
+
extraSmall: "h-1",
|
|
5039
|
+
small: "h-3",
|
|
5040
|
+
large: "h-9",
|
|
4521
5041
|
},
|
|
4522
5042
|
},
|
|
4523
5043
|
defaultVariants: {
|
|
@@ -4593,8 +5113,44 @@ const getValueBarColorByValue = (value, min, max, levelColors) => {
|
|
|
4593
5113
|
};
|
|
4594
5114
|
|
|
4595
5115
|
/**
|
|
4596
|
-
* ValueBar
|
|
4597
|
-
|
|
5116
|
+
* ValueBar displays a numeric value as a colored progress bar within a defined range.
|
|
5117
|
+
* The bar color changes based on the score (value relative to min/max) using either default or custom level colors.
|
|
5118
|
+
* It can optionally display the numeric value and unit alongside the bar.
|
|
5119
|
+
*
|
|
5120
|
+
* ### When to use
|
|
5121
|
+
* Use ValueBar to visualize a metric relative to a range (e.g., battery level, fuel percentage, utilization rate, temperature).
|
|
5122
|
+
*
|
|
5123
|
+
* ### When not to use
|
|
5124
|
+
* Do not use ValueBar for progress through a multi-step process. Use a stepper or progress indicator instead.
|
|
5125
|
+
*
|
|
5126
|
+
* @example Basic value bar with percentage
|
|
5127
|
+
* ```tsx
|
|
5128
|
+
* import { ValueBar } from "@trackunit/react-components";
|
|
5129
|
+
*
|
|
5130
|
+
* const BatteryLevel = () => (
|
|
5131
|
+
* <ValueBar value={72} min={0} max={100} unit="%" showValue />
|
|
5132
|
+
* );
|
|
5133
|
+
* ```
|
|
5134
|
+
* @example Value bar with custom level colors
|
|
5135
|
+
* ```tsx
|
|
5136
|
+
* import { ValueBar } from "@trackunit/react-components";
|
|
5137
|
+
*
|
|
5138
|
+
* const TemperatureBar = () => (
|
|
5139
|
+
* <ValueBar
|
|
5140
|
+
* value={85}
|
|
5141
|
+
* min={0}
|
|
5142
|
+
* max={120}
|
|
5143
|
+
* unit="°C"
|
|
5144
|
+
* showValue
|
|
5145
|
+
* size="large"
|
|
5146
|
+
* levelColors={[
|
|
5147
|
+
* { min: 0, max: 0.5, color: "#22c55e" },
|
|
5148
|
+
* { min: 0.5, max: 0.75, color: "#f59e0b" },
|
|
5149
|
+
* { min: 0.75, max: 1, color: "#ef4444" },
|
|
5150
|
+
* ]}
|
|
5151
|
+
* />
|
|
5152
|
+
* );
|
|
5153
|
+
* ```
|
|
4598
5154
|
* @param {ValueBarProps} props - The props for the ValueBar component
|
|
4599
5155
|
* @returns {ReactElement} ValueBar component
|
|
4600
5156
|
*/
|
|
@@ -4602,7 +5158,7 @@ const ValueBar = ({ value, min = 0, max = 100, unit, size = "small", levelColors
|
|
|
4602
5158
|
const score = getScore(value, min, max, zeroScoreAllowed);
|
|
4603
5159
|
const barFillColor = levelColors ? getFillColor(score, levelColors) : getDefaultFillColor(score);
|
|
4604
5160
|
const valueText = `${Number(value.toFixed(1))}${nonNullable(unit) ? unit : ""}`;
|
|
4605
|
-
return (jsxs("span", { className:
|
|
5161
|
+
return (jsxs("span", { className: valueBarContainerClassName, "data-testid": dataTestId, ref: ref, children: [jsx("progress", { "aria-label": valueText, className: cvaValueBar({ className, size }), max: 100, style: { color: barFillColor }, value: score * 100 }), showValue && (size === "small" || size === "large") ? (jsx(Text, { className: cvaValueBarText({ size }), "data-testid": dataTestId ? `${dataTestId}-value` : undefined, children: jsx("span", { style: valueColor ? { color: valueColor } : undefined, children: valueText }) })) : null] }));
|
|
4606
5162
|
};
|
|
4607
5163
|
|
|
4608
5164
|
const cvaKPICard = cvaMerge([
|
|
@@ -4703,13 +5259,29 @@ const KPICard = ({ isActive = false, onClick, className, "data-testid": dataTest
|
|
|
4703
5259
|
};
|
|
4704
5260
|
|
|
4705
5261
|
/**
|
|
4706
|
-
*
|
|
5262
|
+
* SkeletonBlock renders a single animated placeholder block for shape-based elements (images, icons, buttons, avatars)
|
|
5263
|
+
* before data is loaded. It uses exact height and width values to match the element it replaces.
|
|
4707
5264
|
*
|
|
4708
|
-
*
|
|
4709
|
-
*
|
|
5265
|
+
* ### When to use
|
|
5266
|
+
* Use SkeletonBlock for loading placeholders of shape-based UI elements: images, icons, badges, buttons, avatars, thumbnails.
|
|
5267
|
+
*
|
|
5268
|
+
* ### When not to use
|
|
5269
|
+
* For text content, use `SkeletonLabel` which accounts for text line-height margins.
|
|
5270
|
+
* For multiple text lines, use `SkeletonLines`.
|
|
5271
|
+
*
|
|
5272
|
+
* @example Skeleton block for an avatar and button
|
|
5273
|
+
* ```tsx
|
|
5274
|
+
* import { SkeletonBlock } from "@trackunit/react-components";
|
|
4710
5275
|
*
|
|
4711
|
-
*
|
|
4712
|
-
*
|
|
5276
|
+
* const AvatarSkeleton = () => (
|
|
5277
|
+
* <div className="flex items-center gap-3">
|
|
5278
|
+
* <SkeletonBlock height={40} width={40} className="rounded-full" />
|
|
5279
|
+
* <SkeletonBlock height={32} width={120} className="rounded-md" />
|
|
5280
|
+
* </div>
|
|
5281
|
+
* );
|
|
5282
|
+
* ```
|
|
5283
|
+
* @param {SkeletonBlockProps} props - The props for the SkeletonBlock component
|
|
5284
|
+
* @returns {ReactElement} SkeletonBlock component
|
|
4713
5285
|
*/
|
|
4714
5286
|
const SkeletonBlock = memo((props) => {
|
|
4715
5287
|
const { width = "100%", height = 16, flexibleWidth = false, className, "data-testid": dataTestId, children, ref, } = props;
|
|
@@ -4724,8 +5296,29 @@ const SkeletonBlock = memo((props) => {
|
|
|
4724
5296
|
SkeletonBlock.displayName = "SkeletonBlock";
|
|
4725
5297
|
|
|
4726
5298
|
/**
|
|
4727
|
-
*
|
|
4728
|
-
*
|
|
5299
|
+
* KPICardSkeleton is a loading placeholder that mimics the layout of a KPICard.
|
|
5300
|
+
* It renders skeleton lines for the KPI title/value, and optional icon, trends, value bar, and notice slots.
|
|
5301
|
+
*
|
|
5302
|
+
* ### When to use
|
|
5303
|
+
* Use KPICardSkeleton while `KPICard` data is loading to maintain layout stability in dashboards and metric displays.
|
|
5304
|
+
*
|
|
5305
|
+
* ### When not to use
|
|
5306
|
+
* Do not use KPICardSkeleton for generic card loading — use a `Card` with `SkeletonLabel` inside.
|
|
5307
|
+
*
|
|
5308
|
+
* @example KPICard skeletons in a dashboard row
|
|
5309
|
+
* ```tsx
|
|
5310
|
+
* import { KPICardSkeleton } from "@trackunit/react-components";
|
|
5311
|
+
*
|
|
5312
|
+
* const LoadingMetrics = () => (
|
|
5313
|
+
* <div className="grid grid-cols-3 gap-4">
|
|
5314
|
+
* <KPICardSkeleton hasIcon hasTrends />
|
|
5315
|
+
* <KPICardSkeleton hasIcon hasValueBar />
|
|
5316
|
+
* <KPICardSkeleton hasNotice />
|
|
5317
|
+
* </div>
|
|
5318
|
+
* );
|
|
5319
|
+
* ```
|
|
5320
|
+
* @param {KPICardSkeletonProps} props - The props for the KPICardSkeleton component
|
|
5321
|
+
* @returns {ReactElement} KPICardSkeleton component
|
|
4729
5322
|
*/
|
|
4730
5323
|
const KPICardSkeleton = ({ hasIcon = false, hasTrends = false, hasValueBar = false, hasNotice = false, children, className, "data-testid": dataTestId, style, ref, ...rest }) => {
|
|
4731
5324
|
return (jsx(Card, { className: cvaKPICard({ className }), "data-testid": dataTestId, ref: ref, style: style, ...rest, children: jsxs(CardBody, { className: cvaKPICardBody(), gap: "none", padding: "none", children: [jsxs("div", { className: cvaKPICardHeader(), children: [jsx(KPISkeleton, { className: "p-0", "data-testid": dataTestId ? `${dataTestId}-kpi` : undefined }), hasIcon ? (jsx(SkeletonBlock, { "data-testid": dataTestId ? `${dataTestId}-icon-loading` : undefined, height: 28, width: 28 })) : null] }), hasTrends ? (jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-trend-indicator-loading` : undefined, textSize: "text-xs", width: "100%" })) : null, hasValueBar ? (jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-value-bar-loading` : undefined, textSize: "text-xs", width: "100%" })) : null, hasNotice ? (jsx(SkeletonLabel, { "data-testid": dataTestId ? `${dataTestId}-notice-loading` : undefined, textSize: "text-xs", width: "100%" })) : null, children] }) }));
|
|
@@ -4805,8 +5398,30 @@ const DEFAULT_SKELETON_LIST_ITEM_PROPS = {
|
|
|
4805
5398
|
hasDetails: false,
|
|
4806
5399
|
};
|
|
4807
5400
|
/**
|
|
4808
|
-
*
|
|
4809
|
-
*
|
|
5401
|
+
* ListItemSkeleton is a loading placeholder that mimics the layout of a ListItem.
|
|
5402
|
+
* It renders skeleton lines for the title, description, meta, thumbnail, and details slots.
|
|
5403
|
+
* Use it inside a List to indicate that list data is loading.
|
|
5404
|
+
*
|
|
5405
|
+
* ### When to use
|
|
5406
|
+
* Use ListItemSkeleton as the loading state inside a `List` when data is being fetched. Configure it to match the shape of your actual ListItems.
|
|
5407
|
+
*
|
|
5408
|
+
* ### When not to use
|
|
5409
|
+
* Do not use ListItemSkeleton for non-list loading states. Use `SkeletonBlock` or `SkeletonLabel` for generic loading placeholders.
|
|
5410
|
+
*
|
|
5411
|
+
* @example ListItemSkeleton matching a typical asset list item
|
|
5412
|
+
* ```tsx
|
|
5413
|
+
* import { ListItemSkeleton } from "@trackunit/react-components";
|
|
5414
|
+
*
|
|
5415
|
+
* const LoadingList = () => (
|
|
5416
|
+
* <div>
|
|
5417
|
+
* <ListItemSkeleton hasThumbnail hasDescription hasMeta={false} />
|
|
5418
|
+
* <ListItemSkeleton hasThumbnail hasDescription hasMeta={false} />
|
|
5419
|
+
* <ListItemSkeleton hasThumbnail hasDescription hasMeta={false} />
|
|
5420
|
+
* </div>
|
|
5421
|
+
* );
|
|
5422
|
+
* ```
|
|
5423
|
+
* @param {ListItemSkeletonProps} props - The props for the ListItemSkeleton component
|
|
5424
|
+
* @returns {ReactElement} ListItemSkeleton component
|
|
4810
5425
|
*/
|
|
4811
5426
|
const ListItemSkeleton = ({ hasThumbnail = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasThumbnail, thumbnailShape = "circle", hasDescription = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasDescription, hasMeta = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasMeta, hasDetails = DEFAULT_SKELETON_LIST_ITEM_PROPS.hasDetails, }) => {
|
|
4812
5427
|
// Generate stable random widths once and never change them
|
|
@@ -5292,10 +5907,32 @@ const getResolvedLoadingIndicator = (loadingIndicator) => {
|
|
|
5292
5907
|
};
|
|
5293
5908
|
|
|
5294
5909
|
/**
|
|
5295
|
-
*
|
|
5910
|
+
* ListItem presents a concise row of information for quick scanning and navigation. It supports a title, description, meta text, thumbnail,
|
|
5911
|
+
* and a details slot. When `onClick` is provided, the item becomes interactive with a chevron indicator.
|
|
5296
5912
|
*
|
|
5297
|
-
*
|
|
5298
|
-
*
|
|
5913
|
+
* ### When to use
|
|
5914
|
+
* Use ListItem inside a `List` to display items such as assets, events, users, or notifications with consistent layout.
|
|
5915
|
+
*
|
|
5916
|
+
* ### When not to use
|
|
5917
|
+
* Do not use ListItem outside of a `List` component for standalone clickable elements — use `Card` or `Button`.
|
|
5918
|
+
*
|
|
5919
|
+
* @example ListItem with thumbnail and details
|
|
5920
|
+
* ```tsx
|
|
5921
|
+
* import { ListItem, Icon, Tag } from "@trackunit/react-components";
|
|
5922
|
+
*
|
|
5923
|
+
* const AssetListItem = () => (
|
|
5924
|
+
* <ListItem
|
|
5925
|
+
* title="Excavator CAT 320"
|
|
5926
|
+
* description="Serial: SN-2024-00142"
|
|
5927
|
+
* meta="Last seen: 2 hours ago"
|
|
5928
|
+
* thumbnail={<Icon name="Truck" size="small" />}
|
|
5929
|
+
* details={<Tag color="success" size="small">Active</Tag>}
|
|
5930
|
+
* onClick={() => console.log("Navigate to asset")}
|
|
5931
|
+
* />
|
|
5932
|
+
* );
|
|
5933
|
+
* ```
|
|
5934
|
+
* @param {ListItemProps} props - The props for the ListItem component
|
|
5935
|
+
* @returns {ReactElement} ListItem component
|
|
5299
5936
|
*/
|
|
5300
5937
|
const ListItem = ({ className, "data-testid": dataTestId, onClick, details, title, description, meta, thumbnail, thumbnailColor = "info-600", thumbnailBackground = "info-100", ...rest }) => {
|
|
5301
5938
|
const baseClass = cvaListItem({ className });
|
|
@@ -5399,7 +6036,28 @@ const cvaMenuListMultiSelect = cvaMerge([
|
|
|
5399
6036
|
const cvaMenuListItem = cvaMerge("max-w-full");
|
|
5400
6037
|
|
|
5401
6038
|
/**
|
|
5402
|
-
*
|
|
6039
|
+
* MenuDivider renders a horizontal line to visually separate groups of items within a MenuList.
|
|
6040
|
+
*
|
|
6041
|
+
* ### When to use
|
|
6042
|
+
* Use MenuDivider between groups of related `MenuItem` elements to create logical sections within a menu.
|
|
6043
|
+
*
|
|
6044
|
+
* ### When not to use
|
|
6045
|
+
* Do not use MenuDivider outside of a `MenuList`. For general-purpose dividers, use `Spacer` with `border`.
|
|
6046
|
+
*
|
|
6047
|
+
* @example Menu with grouped sections
|
|
6048
|
+
* ```tsx
|
|
6049
|
+
* import { MenuList, MenuItem, MenuDivider, Icon } from "@trackunit/react-components";
|
|
6050
|
+
*
|
|
6051
|
+
* const GroupedMenu = () => (
|
|
6052
|
+
* <MenuList>
|
|
6053
|
+
* <MenuItem id="edit" label="Edit" prefix={<Icon name="PencilSquare" size="small" />} />
|
|
6054
|
+
* <MenuItem id="duplicate" label="Duplicate" prefix={<Icon name="DocumentDuplicate" size="small" />} />
|
|
6055
|
+
* <MenuDivider />
|
|
6056
|
+
* <MenuItem id="delete" label="Delete" variant="danger" prefix={<Icon name="Trash" size="small" />} />
|
|
6057
|
+
* </MenuList>
|
|
6058
|
+
* );
|
|
6059
|
+
* ```
|
|
6060
|
+
* @returns {ReactElement} MenuDivider component
|
|
5403
6061
|
*/
|
|
5404
6062
|
const MenuDivider = () => {
|
|
5405
6063
|
return jsx("div", { className: cvaMenuListDivider(), "data-testid": "menu-divider" });
|
|
@@ -5525,8 +6183,37 @@ const cvaMenuItemSuffix = cvaMerge(["text-neutral-400", "text-sm", "flex", "item
|
|
|
5525
6183
|
});
|
|
5526
6184
|
|
|
5527
6185
|
/**
|
|
5528
|
-
*
|
|
6186
|
+
* MenuItem represents a single actionable item within a MenuList.
|
|
6187
|
+
* It supports labels, icons (prefix/suffix), selected and focused states, and danger variants.
|
|
6188
|
+
*
|
|
6189
|
+
* ### When to use
|
|
6190
|
+
* Use MenuItem inside a `MenuList` for individual actions (edit, delete, duplicate) or selectable options.
|
|
6191
|
+
*
|
|
6192
|
+
* ### When not to use
|
|
6193
|
+
* Do not use MenuItem outside of a `MenuList` context. For standalone clickable items, use `Button` or `ListItem`.
|
|
5529
6194
|
*
|
|
6195
|
+
* @example MenuItem with icon prefix
|
|
6196
|
+
* ```tsx
|
|
6197
|
+
* import { MenuList, MenuItem, Icon } from "@trackunit/react-components";
|
|
6198
|
+
*
|
|
6199
|
+
* const ActionMenu = () => (
|
|
6200
|
+
* <MenuList>
|
|
6201
|
+
* <MenuItem
|
|
6202
|
+
* id="edit"
|
|
6203
|
+
* label="Edit asset"
|
|
6204
|
+
* prefix={<Icon name="PencilSquare" size="small" />}
|
|
6205
|
+
* onClick={() => console.log("Edit clicked")}
|
|
6206
|
+
* />
|
|
6207
|
+
* <MenuItem
|
|
6208
|
+
* id="delete"
|
|
6209
|
+
* label="Delete asset"
|
|
6210
|
+
* prefix={<Icon name="Trash" size="small" />}
|
|
6211
|
+
* variant="danger"
|
|
6212
|
+
* onClick={() => console.log("Delete clicked")}
|
|
6213
|
+
* />
|
|
6214
|
+
* </MenuList>
|
|
6215
|
+
* );
|
|
6216
|
+
* ```
|
|
5530
6217
|
* @param {MenuItemProps} props - The props for the MenuItem component
|
|
5531
6218
|
* @returns {ReactElement} MenuItem component
|
|
5532
6219
|
*/
|
|
@@ -5656,9 +6343,45 @@ const MenuList = ({ "data-testid": dataTestId, className, children, isMulti = fa
|
|
|
5656
6343
|
const cvaMoreMenu = cvaMerge(["p-0"]);
|
|
5657
6344
|
|
|
5658
6345
|
/**
|
|
5659
|
-
*
|
|
5660
|
-
*
|
|
6346
|
+
* MoreMenu (kebab menu) renders a three-dot button that opens a popover with a list of actions.
|
|
6347
|
+
* It is typically filled with a MenuList containing MenuItem elements.
|
|
6348
|
+
*
|
|
6349
|
+
* ### When to use
|
|
6350
|
+
* Use MoreMenu when you have overflow actions that don't fit in the main UI. Common for row-level actions in tables, card headers, or list items.
|
|
6351
|
+
*
|
|
6352
|
+
* ### When not to use
|
|
6353
|
+
* Do not use MoreMenu for primary actions that should always be visible. Use `Button` instead.
|
|
5661
6354
|
*
|
|
6355
|
+
* @example Action items with render prop — Pass a function as `children` to receive the `close` callback and dismiss the menu after an action.
|
|
6356
|
+
* ```tsx
|
|
6357
|
+
* import { MoreMenu, MenuList, MenuItem, Icon } from "@trackunit/react-components";
|
|
6358
|
+
*
|
|
6359
|
+
* const AssetActions = () => (
|
|
6360
|
+
* <MoreMenu>
|
|
6361
|
+
* {(close) => (
|
|
6362
|
+
* <MenuList onClick={close}>
|
|
6363
|
+
* <MenuItem id="edit" label="Edit" prefix={<Icon name="PencilSquare" size="small" />} />
|
|
6364
|
+
* <MenuItem id="delete" label="Delete" variant="danger" prefix={<Icon name="Trash" size="small" />} />
|
|
6365
|
+
* </MenuList>
|
|
6366
|
+
* )}
|
|
6367
|
+
* </MoreMenu>
|
|
6368
|
+
* );
|
|
6369
|
+
* ```
|
|
6370
|
+
* @example Custom trigger button — Use `customButton` to replace the default kebab icon with any element, like a labeled Button.
|
|
6371
|
+
* ```tsx
|
|
6372
|
+
* import { MoreMenu, MenuList, MenuItem, Button } from "@trackunit/react-components";
|
|
6373
|
+
*
|
|
6374
|
+
* const CustomTriggerMenu = () => (
|
|
6375
|
+
* <MoreMenu customButton={<Button variant="secondary" size="small">Actions</Button>}>
|
|
6376
|
+
* {(close) => (
|
|
6377
|
+
* <MenuList onClick={close}>
|
|
6378
|
+
* <MenuItem id="export" label="Export" />
|
|
6379
|
+
* <MenuItem id="archive" label="Archive" />
|
|
6380
|
+
* </MenuList>
|
|
6381
|
+
* )}
|
|
6382
|
+
* </MoreMenu>
|
|
6383
|
+
* );
|
|
6384
|
+
* ```
|
|
5662
6385
|
* @param {MoreMenuProps} props - The props for the MoreMenu component
|
|
5663
6386
|
* @returns {ReactElement} MoreMenu component
|
|
5664
6387
|
*/
|
|
@@ -5711,12 +6434,42 @@ const cvaNoticeIcon = cvaMerge(["rounded-full", "items-center", "justify-center"
|
|
|
5711
6434
|
});
|
|
5712
6435
|
|
|
5713
6436
|
/**
|
|
5714
|
-
*
|
|
6437
|
+
* Notice is a non-interactive element that communicates informational messages the user should see, without prompting an action.
|
|
6438
|
+
* It displays an optional icon and text label, with optional tooltip support.
|
|
5715
6439
|
*
|
|
5716
|
-
*
|
|
6440
|
+
* ### When to use
|
|
6441
|
+
* Use Notice to communicate non-essential, contextual information that does not require action (e.g., status notes, informational hints).
|
|
5717
6442
|
*
|
|
5718
|
-
*
|
|
6443
|
+
* ### When not to use
|
|
6444
|
+
* Do not use Notice for essential information that requires user action — use `Alert` instead.
|
|
6445
|
+
* Do not use Notice for asset state indicators — use `Indicator` instead.
|
|
5719
6446
|
*
|
|
6447
|
+
* @example Notice with icon and label
|
|
6448
|
+
* ```tsx
|
|
6449
|
+
* import { Notice } from "@trackunit/react-components";
|
|
6450
|
+
*
|
|
6451
|
+
* const InfoNotice = () => (
|
|
6452
|
+
* <Notice
|
|
6453
|
+
* iconName="InformationCircle"
|
|
6454
|
+
* label="Last synced 5 minutes ago"
|
|
6455
|
+
* color="info"
|
|
6456
|
+
* />
|
|
6457
|
+
* );
|
|
6458
|
+
* ```
|
|
6459
|
+
* @example Warning notice with tooltip
|
|
6460
|
+
* ```tsx
|
|
6461
|
+
* import { Notice } from "@trackunit/react-components";
|
|
6462
|
+
*
|
|
6463
|
+
* const WarningNotice = () => (
|
|
6464
|
+
* <Notice
|
|
6465
|
+
* iconName="ExclamationTriangle"
|
|
6466
|
+
* label="Low battery"
|
|
6467
|
+
* color="warning"
|
|
6468
|
+
* withTooltip
|
|
6469
|
+
* tooltipLabel="Battery level is below 20%"
|
|
6470
|
+
* />
|
|
6471
|
+
* );
|
|
6472
|
+
* ```
|
|
5720
6473
|
* @param {NoticeProps} props - The props for the Notice component
|
|
5721
6474
|
* @returns {ReactElement} Notice component
|
|
5722
6475
|
*/
|
|
@@ -5751,18 +6504,68 @@ const cvaPageContent = cvaMerge(["overflow-auto", "page-content", "grid", "gap-r
|
|
|
5751
6504
|
});
|
|
5752
6505
|
|
|
5753
6506
|
/**
|
|
5754
|
-
*
|
|
6507
|
+
* Page is the top-level layout container that applies consistent padding and layout to page content.
|
|
6508
|
+
* Use it in combination with PageContent and PageHeader.
|
|
6509
|
+
*
|
|
6510
|
+
* ### When to use
|
|
6511
|
+
* Use Page as the outermost wrapper for a page view. It provides the base layout structure (padding, spacing) for the page.
|
|
6512
|
+
*
|
|
6513
|
+
* ### When not to use
|
|
6514
|
+
* Do not use Page for card-level or section-level layout. Use `Card` or standard flex/grid containers instead.
|
|
6515
|
+
*
|
|
6516
|
+
* @example Page with header and content
|
|
6517
|
+
* ```tsx
|
|
6518
|
+
* import { Page, PageContent, PageHeader } from "@trackunit/react-components";
|
|
6519
|
+
*
|
|
6520
|
+
* const AssetListPage = () => (
|
|
6521
|
+
* <Page layout="default">
|
|
6522
|
+
* <PageHeader
|
|
6523
|
+
* title="Assets"
|
|
6524
|
+
* accessoryType="actions"
|
|
6525
|
+
* primaryAction={{ actionText: "Add Asset", actionCallback: () => {} }}
|
|
6526
|
+
* />
|
|
6527
|
+
* <PageContent layout="default">
|
|
6528
|
+
* <p>Asset list content goes here</p>
|
|
6529
|
+
* </PageContent>
|
|
6530
|
+
* </Page>
|
|
6531
|
+
* );
|
|
6532
|
+
* ```
|
|
6533
|
+
* @param {PageProps} props - The props for the Page component
|
|
6534
|
+
* @returns {ReactElement} Page component
|
|
5755
6535
|
*/
|
|
5756
6536
|
const Page = ({ layout, className, children, "data-testid": dataTestId, ref }) => {
|
|
5757
6537
|
return (jsx("div", { className: cvaPage({ className, layout }), "data-testid": dataTestId ? dataTestId : "page", ref: ref, children: children }));
|
|
5758
6538
|
};
|
|
5759
6539
|
|
|
5760
6540
|
/**
|
|
5761
|
-
*
|
|
5762
|
-
*
|
|
6541
|
+
* PageContent is the main content area inside a Page.
|
|
6542
|
+
* It applies consistent padding to the content section. Must be used within a Page component.
|
|
6543
|
+
*
|
|
6544
|
+
* ### When to use
|
|
6545
|
+
* Use PageContent to wrap the main content of a page, below a `PageHeader`. It provides consistent padding and layout.
|
|
6546
|
+
*
|
|
6547
|
+
* ### When not to use
|
|
6548
|
+
* Do not use PageContent outside of a `Page`. For standalone content wrappers, use `Card` or `CardBody`.
|
|
5763
6549
|
*
|
|
5764
|
-
* @
|
|
5765
|
-
*
|
|
6550
|
+
* @example PageContent inside a Page
|
|
6551
|
+
* ```tsx
|
|
6552
|
+
* import { Page, PageContent, PageHeader, Card, CardBody, Text } from "@trackunit/react-components";
|
|
6553
|
+
*
|
|
6554
|
+
* const DetailPage = () => (
|
|
6555
|
+
* <Page layout="default">
|
|
6556
|
+
* <PageHeader title="Asset Details" />
|
|
6557
|
+
* <PageContent layout="default">
|
|
6558
|
+
* <Card>
|
|
6559
|
+
* <CardBody>
|
|
6560
|
+
* <Text>Asset information goes here</Text>
|
|
6561
|
+
* </CardBody>
|
|
6562
|
+
* </Card>
|
|
6563
|
+
* </PageContent>
|
|
6564
|
+
* </Page>
|
|
6565
|
+
* );
|
|
6566
|
+
* ```
|
|
6567
|
+
* @param {PageContentProps} props - The props for the PageContent component
|
|
6568
|
+
* @returns {ReactElement} PageContent component
|
|
5766
6569
|
*/
|
|
5767
6570
|
const PageContent = ({ className, children, "data-testid": dataTestId, layout, ref, }) => {
|
|
5768
6571
|
return (jsx("div", { className: cvaPageContent({ className, layout }), "data-testid": dataTestId ? dataTestId : "page-content", ref: ref, children: children }));
|
|
@@ -5846,14 +6649,16 @@ cvaMerge([
|
|
|
5846
6649
|
]);
|
|
5847
6650
|
|
|
5848
6651
|
/**
|
|
5849
|
-
*
|
|
6652
|
+
* SkeletonLines renders multiple animated placeholder text lines before data loads.
|
|
6653
|
+
* It supports three modes: uniform (identical lines), custom (per-line config), and preset (common patterns like paragraphs or articles).
|
|
6654
|
+
* Built on top of SkeletonLabel for text-specific margins and sizing.
|
|
5850
6655
|
*
|
|
5851
|
-
*
|
|
5852
|
-
* -
|
|
5853
|
-
* - **Custom mode**: Display customized lines with per-line configuration using `variant="custom"` and `lines` prop
|
|
5854
|
-
* - **Preset mode**: Display common patterns using `variant="preset"` and `preset` name
|
|
6656
|
+
* ### When to use
|
|
6657
|
+
* Use SkeletonLines for loading placeholders of multi-line text content: paragraphs, descriptions, articles, or any text block.
|
|
5855
6658
|
*
|
|
5856
|
-
*
|
|
6659
|
+
* ### When not to use
|
|
6660
|
+
* For single text lines, use `SkeletonLabel`.
|
|
6661
|
+
* For shape-based elements, use `SkeletonBlock`.
|
|
5857
6662
|
*
|
|
5858
6663
|
* @example
|
|
5859
6664
|
* // Uniform lines (simple mode)
|
|
@@ -6062,11 +6867,15 @@ const PageHeaderTitle = ({ title, "data-testid": dataTestId, className, ref: for
|
|
|
6062
6867
|
};
|
|
6063
6868
|
|
|
6064
6869
|
/**
|
|
6065
|
-
*
|
|
6870
|
+
* PageHeader displays the header of a page, providing context about the current location within the application.
|
|
6871
|
+
* It supports a title, optional description tooltip, tag, action buttons, KPI metrics, and tabs for in-page navigation.
|
|
6066
6872
|
*
|
|
6067
|
-
*
|
|
6068
|
-
* PageHeader
|
|
6069
|
-
*
|
|
6873
|
+
* ### When to use
|
|
6874
|
+
* Use PageHeader at the top of a `Page` to display the page title, description, and primary/secondary actions.
|
|
6875
|
+
*
|
|
6876
|
+
* ### When not to use
|
|
6877
|
+
* Do not use PageHeader for section-level headings within a page. Use `SectionHeader` instead.
|
|
6878
|
+
* Do not use for card-level headers — use `CardHeader`.
|
|
6070
6879
|
*
|
|
6071
6880
|
* @example Page header with actions
|
|
6072
6881
|
* ```tsx
|
|
@@ -6135,8 +6944,37 @@ const cvaPagination = cvaMerge(["flex", "items-center", "gap-1"]);
|
|
|
6135
6944
|
const cvaPaginationText = cvaMerge("whitespace-nowrap");
|
|
6136
6945
|
|
|
6137
6946
|
/**
|
|
6138
|
-
* Pagination
|
|
6139
|
-
|
|
6947
|
+
* Pagination provides previous/next page navigation with an optional page counter and jump-to-page input.
|
|
6948
|
+
* It supports both offset-based pagination (with `pageIndex` and `pageCount`) and cursor-based pagination (with `cursorBase`).
|
|
6949
|
+
*
|
|
6950
|
+
* ### When to use
|
|
6951
|
+
* Use Pagination below a table or list when data is split across multiple pages and the user needs to navigate between them.
|
|
6952
|
+
*
|
|
6953
|
+
* ### When not to use
|
|
6954
|
+
* Do not use Pagination for infinite scroll patterns. Use the `useInfiniteScroll` hook instead.
|
|
6955
|
+
*
|
|
6956
|
+
* @example Basic offset-based pagination
|
|
6957
|
+
* ```tsx
|
|
6958
|
+
* import { Pagination } from "@trackunit/react-components";
|
|
6959
|
+
* import { useState } from "react";
|
|
6960
|
+
*
|
|
6961
|
+
* const PaginatedList = () => {
|
|
6962
|
+
* const [page, setPage] = useState(0);
|
|
6963
|
+
* const totalPages = 10;
|
|
6964
|
+
*
|
|
6965
|
+
* return (
|
|
6966
|
+
* <Pagination
|
|
6967
|
+
* pageIndex={page}
|
|
6968
|
+
* pageCount={totalPages}
|
|
6969
|
+
* canPreviousPage={page > 0}
|
|
6970
|
+
* canNextPage={page < totalPages - 1}
|
|
6971
|
+
* previousPage={() => setPage(p => p - 1)}
|
|
6972
|
+
* nextPage={() => setPage(p => p + 1)}
|
|
6973
|
+
* getTranslatedCount={(count) => `of ${count}`}
|
|
6974
|
+
* />
|
|
6975
|
+
* );
|
|
6976
|
+
* };
|
|
6977
|
+
* ```
|
|
6140
6978
|
* @param {PaginationProps} props - The props for the Pagination component
|
|
6141
6979
|
* @returns {ReactElement} Pagination component
|
|
6142
6980
|
*/
|
|
@@ -6192,10 +7030,29 @@ const Pagination = ({ previousPage, nextPage, canPreviousPage = false, canNextPa
|
|
|
6192
7030
|
|
|
6193
7031
|
const STROKE_WIDTH_THRESHOLD = 32;
|
|
6194
7032
|
/**
|
|
6195
|
-
*
|
|
7033
|
+
* Polygon renders an SVG polygon from a set of coordinate points. Points are automatically normalized to fit within the specified size.
|
|
7034
|
+
* It supports fill/stroke color options and optional opacity for overlay effects.
|
|
7035
|
+
*
|
|
7036
|
+
* ### When to use
|
|
7037
|
+
* Use Polygon for rendering geofence boundaries, map overlays, or any custom shape defined by coordinate points.
|
|
7038
|
+
*
|
|
7039
|
+
* ### When not to use
|
|
7040
|
+
* Do not use Polygon for standard UI elements — use the design system components instead.
|
|
7041
|
+
*
|
|
7042
|
+
* @example Rendering a triangle
|
|
7043
|
+
* ```tsx
|
|
7044
|
+
* import { Polygon } from "@trackunit/react-components";
|
|
6196
7045
|
*
|
|
6197
|
-
*
|
|
6198
|
-
*
|
|
7046
|
+
* const Triangle = () => (
|
|
7047
|
+
* <Polygon
|
|
7048
|
+
* points={[[0, 100], [50, 0], [100, 100]]}
|
|
7049
|
+
* size={64}
|
|
7050
|
+
* color="black"
|
|
7051
|
+
* />
|
|
7052
|
+
* );
|
|
7053
|
+
* ```
|
|
7054
|
+
* @param {PolygonProps} props - The props for the Polygon component
|
|
7055
|
+
* @returns {ReactElement} Polygon component
|
|
6199
7056
|
*/
|
|
6200
7057
|
const Polygon = ({ points, size, color = "black", opaque = true, className, "data-testid": dataTestId, ref, }) => {
|
|
6201
7058
|
// Calculate the bounds of the points
|
|
@@ -6225,8 +7082,39 @@ const Polygon = ({ points, size, color = "black", opaque = true, className, "dat
|
|
|
6225
7082
|
const normalize = ({ value, min, max, size }) => ((value - min) / (max - min)) * size;
|
|
6226
7083
|
|
|
6227
7084
|
/**
|
|
6228
|
-
*
|
|
7085
|
+
* PopoverTitle renders a styled title bar at the top of a PopoverContent panel.
|
|
7086
|
+
* It displays uppercase bold text with an optional action element and divider.
|
|
7087
|
+
*
|
|
7088
|
+
* ### When to use
|
|
7089
|
+
* Use PopoverTitle inside `PopoverContent` when the popover needs a labeled header, optionally with an action (e.g., a close button).
|
|
6229
7090
|
*
|
|
7091
|
+
* ### When not to use
|
|
7092
|
+
* Do not use PopoverTitle outside of a `Popover` context. For standalone headings, use `Heading`.
|
|
7093
|
+
*
|
|
7094
|
+
* @example Popover with titled content
|
|
7095
|
+
* ```tsx
|
|
7096
|
+
* import { Popover, PopoverTrigger, PopoverContent, PopoverTitle, Button, Text } from "@trackunit/react-components";
|
|
7097
|
+
*
|
|
7098
|
+
* const TitledPopover = () => (
|
|
7099
|
+
* <Popover placement="bottom-start">
|
|
7100
|
+
* <PopoverTrigger>
|
|
7101
|
+
* <Button variant="secondary">Details</Button>
|
|
7102
|
+
* </PopoverTrigger>
|
|
7103
|
+
* <PopoverContent>
|
|
7104
|
+
* {(close) => (
|
|
7105
|
+
* <div className="w-64">
|
|
7106
|
+
* <PopoverTitle divider action={<Button variant="ghost" size="small" onClick={close}>Close</Button>}>
|
|
7107
|
+
* Asset Info
|
|
7108
|
+
* </PopoverTitle>
|
|
7109
|
+
* <div className="p-3">
|
|
7110
|
+
* <Text size="small">Additional information about this asset.</Text>
|
|
7111
|
+
* </div>
|
|
7112
|
+
* </div>
|
|
7113
|
+
* )}
|
|
7114
|
+
* </PopoverContent>
|
|
7115
|
+
* </Popover>
|
|
7116
|
+
* );
|
|
7117
|
+
* ```
|
|
6230
7118
|
* @param {PopoverTitleProps} props - The props for the PopoverTitle component
|
|
6231
7119
|
* @returns {ReactElement} PopoverTitle component
|
|
6232
7120
|
*/
|
|
@@ -6369,9 +7257,31 @@ const preferenceCardGrid = createGrid()
|
|
|
6369
7257
|
})
|
|
6370
7258
|
.build();
|
|
6371
7259
|
/**
|
|
6372
|
-
* PreferenceCard is a flexible component for displaying add-
|
|
6373
|
-
* with
|
|
6374
|
-
*
|
|
7260
|
+
* PreferenceCard is a flexible component for displaying add-on options or settings with an optional input control (checkbox, radio, toggle).
|
|
7261
|
+
* It supports icons/images, titles with tags, descriptions, and custom child content for advanced layouts.
|
|
7262
|
+
*
|
|
7263
|
+
* ### When to use
|
|
7264
|
+
* Use PreferenceCard for settings pages, feature toggles, or plan selection where each option needs a title, description, and an input control.
|
|
7265
|
+
*
|
|
7266
|
+
* ### When not to use
|
|
7267
|
+
* Do not use PreferenceCard for data display — use `Card`.
|
|
7268
|
+
* Do not use for simple toggle settings — use a standalone toggle switch.
|
|
7269
|
+
*
|
|
7270
|
+
* @example PreferenceCard with checkbox input
|
|
7271
|
+
* ```tsx
|
|
7272
|
+
* import { PreferenceCard } from "@trackunit/react-components";
|
|
7273
|
+
*
|
|
7274
|
+
* const FeatureToggle = () => (
|
|
7275
|
+
* <PreferenceCard
|
|
7276
|
+
* title="Email Notifications"
|
|
7277
|
+
* description="Receive email alerts for asset status changes"
|
|
7278
|
+
* icon={{ type: "icon", name: "EnvelopeOpen", containerClassName: "bg-primary-100" }}
|
|
7279
|
+
* input={<input type="checkbox" />}
|
|
7280
|
+
* />
|
|
7281
|
+
* );
|
|
7282
|
+
* ```
|
|
7283
|
+
* @param {PreferenceCardProps} props - The props for the PreferenceCard component
|
|
7284
|
+
* @returns {ReactNode} PreferenceCard component
|
|
6375
7285
|
*/
|
|
6376
7286
|
const PreferenceCard = ({ title, description, icon, input, titleTag, cardTag, disabled = false, className, "data-testid": dataTestId, children, ref: forwardedRef, }) => {
|
|
6377
7287
|
const { ref: measureRef, geometry } = useMeasure();
|
|
@@ -6421,8 +7331,29 @@ const getRandomWidth = (min, max) => {
|
|
|
6421
7331
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
6422
7332
|
};
|
|
6423
7333
|
/**
|
|
6424
|
-
*
|
|
6425
|
-
*
|
|
7334
|
+
* PreferenceCardSkeleton is a loading placeholder that mimics the layout of a PreferenceCard.
|
|
7335
|
+
* It renders skeleton lines for the title, description, and optional slots (icon, tags, input).
|
|
7336
|
+
* Configure the boolean flags to match the shape of your actual PreferenceCards.
|
|
7337
|
+
*
|
|
7338
|
+
* ### When to use
|
|
7339
|
+
* Use PreferenceCardSkeleton while preference/settings data is loading to maintain layout stability and reduce perceived load time.
|
|
7340
|
+
*
|
|
7341
|
+
* ### When not to use
|
|
7342
|
+
* Do not use PreferenceCardSkeleton for generic loading — use `SkeletonBlock` or `SkeletonLabel`.
|
|
7343
|
+
*
|
|
7344
|
+
* @example PreferenceCard skeleton with icon and input
|
|
7345
|
+
* ```tsx
|
|
7346
|
+
* import { PreferenceCardSkeleton } from "@trackunit/react-components";
|
|
7347
|
+
*
|
|
7348
|
+
* const LoadingSettings = () => (
|
|
7349
|
+
* <div className="flex flex-col gap-3">
|
|
7350
|
+
* <PreferenceCardSkeleton hasIcon hasInput />
|
|
7351
|
+
* <PreferenceCardSkeleton hasIcon hasInput />
|
|
7352
|
+
* </div>
|
|
7353
|
+
* );
|
|
7354
|
+
* ```
|
|
7355
|
+
* @param {PreferenceCardSkeletonProps} props - The props for the PreferenceCardSkeleton component
|
|
7356
|
+
* @returns {ReactElement} PreferenceCardSkeleton component
|
|
6426
7357
|
*/
|
|
6427
7358
|
const PreferenceCardSkeleton = ({ hasIcon = DEFAULT_SKELETON_PREFERENCE_CARD_PROPS.hasIcon, hasTitleTag = DEFAULT_SKELETON_PREFERENCE_CARD_PROPS.hasTitleTag, hasCardTag = DEFAULT_SKELETON_PREFERENCE_CARD_PROPS.hasCardTag, hasInput = DEFAULT_SKELETON_PREFERENCE_CARD_PROPS.hasInput, ref, }) => {
|
|
6428
7359
|
const gridAreas = useGridAreas(preferenceCardGrid);
|
|
@@ -6449,11 +7380,27 @@ function useConfirmExit(confirmExit, when = true) {
|
|
|
6449
7380
|
useBlocker(confirmExit, when);
|
|
6450
7381
|
}
|
|
6451
7382
|
/**
|
|
6452
|
-
*
|
|
6453
|
-
|
|
6454
|
-
*
|
|
6455
|
-
*
|
|
6456
|
-
*
|
|
7383
|
+
* usePrompt displays a browser confirmation dialog when the user tries to navigate away from the current page.
|
|
7384
|
+
* It blocks both in-app navigation (via TanStack Router's `useBlocker`) and browser-level navigation (via `beforeunload`).
|
|
7385
|
+
*
|
|
7386
|
+
* ### When to use
|
|
7387
|
+
* Use usePrompt when the user has unsaved changes (e.g., in a form) and should be warned before navigating away.
|
|
7388
|
+
*
|
|
7389
|
+
* ### When not to use
|
|
7390
|
+
* Do not use usePrompt for non-destructive navigation. Only use when there is genuinely unsaved state that would be lost.
|
|
7391
|
+
*
|
|
7392
|
+
* @example Warn user about unsaved form changes
|
|
7393
|
+
* ```tsx
|
|
7394
|
+
* import { usePrompt } from "@trackunit/react-components";
|
|
7395
|
+
*
|
|
7396
|
+
* const EditForm = ({ isDirty }: { isDirty: boolean }) => {
|
|
7397
|
+
* usePrompt("You have unsaved changes. Are you sure you want to leave?", isDirty);
|
|
7398
|
+
* return <form>...</form>;
|
|
7399
|
+
* };
|
|
7400
|
+
* ```
|
|
7401
|
+
* @param {string} message - The confirmation message shown to the user
|
|
7402
|
+
* @param {boolean} when - Whether the prompt should be active. Defaults to true.
|
|
7403
|
+
* @returns {void}
|
|
6457
7404
|
*/
|
|
6458
7405
|
const usePrompt = (message, when = true) => {
|
|
6459
7406
|
useEffect(() => {
|
|
@@ -6476,10 +7423,28 @@ const usePrompt = (message, when = true) => {
|
|
|
6476
7423
|
useConfirmExit(confirmExit, when);
|
|
6477
7424
|
};
|
|
6478
7425
|
/**
|
|
6479
|
-
*
|
|
7426
|
+
* Prompt is a declarative component wrapper around the `usePrompt` hook. It displays a browser confirmation dialog
|
|
7427
|
+
* when the user tries to navigate away while `when` is true.
|
|
6480
7428
|
*
|
|
6481
|
-
*
|
|
6482
|
-
*
|
|
7429
|
+
* ### When to use
|
|
7430
|
+
* Use Prompt when you prefer a declarative approach to navigation blocking (instead of the `usePrompt` hook).
|
|
7431
|
+
*
|
|
7432
|
+
* ### When not to use
|
|
7433
|
+
* Do not use Prompt when `when` is always false — simply omit it.
|
|
7434
|
+
*
|
|
7435
|
+
* @example Declarative prompt for unsaved changes
|
|
7436
|
+
* ```tsx
|
|
7437
|
+
* import { Prompt } from "@trackunit/react-components";
|
|
7438
|
+
*
|
|
7439
|
+
* const EditPage = ({ hasUnsavedChanges }: { hasUnsavedChanges: boolean }) => (
|
|
7440
|
+
* <>
|
|
7441
|
+
* <Prompt when={hasUnsavedChanges} message="Discard unsaved changes?" />
|
|
7442
|
+
* <form>...</form>
|
|
7443
|
+
* </>
|
|
7444
|
+
* );
|
|
7445
|
+
* ```
|
|
7446
|
+
* @param {PromptProps} props - The props for the Prompt component
|
|
7447
|
+
* @returns {null} Renders nothing; only activates the navigation prompt
|
|
6483
7448
|
*/
|
|
6484
7449
|
const Prompt = ({ when, message }) => {
|
|
6485
7450
|
usePrompt(message, when);
|
|
@@ -6505,18 +7470,25 @@ const cvaSpacer = cvaMerge([], {
|
|
|
6505
7470
|
});
|
|
6506
7471
|
|
|
6507
7472
|
/**
|
|
6508
|
-
*
|
|
7473
|
+
* Spacer adds vertical whitespace between elements. It can optionally render a visible border line as a divider.
|
|
7474
|
+
*
|
|
7475
|
+
* ### When to use
|
|
7476
|
+
* Use Spacer to add consistent vertical gaps between sections or to render a horizontal divider line.
|
|
6509
7477
|
*
|
|
6510
|
-
*
|
|
7478
|
+
* ### When not to use
|
|
7479
|
+
* Do not use Spacer for horizontal gaps — use Tailwind flex/grid gap utilities. Do not use for structural layout — use CSS grid or flex.
|
|
7480
|
+
*
|
|
7481
|
+
* @example Spacer as a divider
|
|
6511
7482
|
* ```tsx
|
|
6512
|
-
* import { Spacer } from "@trackunit/react-components";
|
|
6513
|
-
*
|
|
6514
|
-
*
|
|
6515
|
-
*
|
|
6516
|
-
*
|
|
6517
|
-
*
|
|
6518
|
-
*
|
|
6519
|
-
*
|
|
7483
|
+
* import { Spacer, Text } from "@trackunit/react-components";
|
|
7484
|
+
*
|
|
7485
|
+
* const DividedSections = () => (
|
|
7486
|
+
* <div>
|
|
7487
|
+
* <Text>Section 1 content</Text>
|
|
7488
|
+
* <Spacer size="medium" border />
|
|
7489
|
+
* <Text>Section 2 content</Text>
|
|
7490
|
+
* </div>
|
|
7491
|
+
* );
|
|
6520
7492
|
* ```
|
|
6521
7493
|
* @param {SpacerProps} props - The props for the Spacer component
|
|
6522
7494
|
* @returns {ReactElement} Spacer component
|
|
@@ -6526,9 +7498,29 @@ const Spacer = ({ size = "medium", border = false, "data-testid": dataTestId, cl
|
|
|
6526
7498
|
};
|
|
6527
7499
|
|
|
6528
7500
|
/**
|
|
6529
|
-
*
|
|
6530
|
-
|
|
6531
|
-
*
|
|
7501
|
+
* SectionHeader renders a section title with an optional subtitle and addon elements (e.g., action buttons, status indicators).
|
|
7502
|
+
* It also sets the document title via React Helmet for SEO and browser tab labeling.
|
|
7503
|
+
*
|
|
7504
|
+
* ### When to use
|
|
7505
|
+
* Use SectionHeader to label a distinct section within a page, below a `PageHeader`. It provides consistent heading styles and an optional subtitle.
|
|
7506
|
+
*
|
|
7507
|
+
* ### When not to use
|
|
7508
|
+
* Do not use SectionHeader for the main page title — use `PageHeader`.
|
|
7509
|
+
* Do not use for card-level headers — use `CardHeader`.
|
|
7510
|
+
*
|
|
7511
|
+
* @example Section header with subtitle and addon
|
|
7512
|
+
* ```tsx
|
|
7513
|
+
* import { SectionHeader, Button } from "@trackunit/react-components";
|
|
7514
|
+
*
|
|
7515
|
+
* const AssetSection = () => (
|
|
7516
|
+
* <SectionHeader
|
|
7517
|
+
* title="Maintenance Schedule"
|
|
7518
|
+
* subtitle="Upcoming and past maintenance events"
|
|
7519
|
+
* addons={<Button variant="secondary" size="small">Add Event</Button>}
|
|
7520
|
+
* />
|
|
7521
|
+
* );
|
|
7522
|
+
* ```
|
|
7523
|
+
* @param {SectionHeaderProps} props - The props for the SectionHeader component
|
|
6532
7524
|
* @returns {ReactElement} SectionHeader component
|
|
6533
7525
|
*/
|
|
6534
7526
|
const SectionHeader = ({ title, subtitle, "data-testid": dataTestId, addons, ref, }) => {
|
|
@@ -6604,12 +7596,19 @@ const useOverflowItems = ({ threshold = 1, childUniqueIdentifierAttribute = "id"
|
|
|
6604
7596
|
};
|
|
6605
7597
|
|
|
6606
7598
|
/**
|
|
6607
|
-
*
|
|
6608
|
-
*
|
|
6609
|
-
*
|
|
6610
|
-
*
|
|
7599
|
+
* Sidebar renders a responsive horizontal/vertical navigation bar that automatically collapses overflowing items into a MoreMenu.
|
|
7600
|
+
* It is rendered horizontally until a given breakpoint, then stacks vertically.
|
|
7601
|
+
*
|
|
7602
|
+
* **Important:** The Sidebar is just a layout wrapper. You are responsible for styling the children.
|
|
7603
|
+
* For the overflow functionality, use `min-w-[*]` or `flex-shrink-0` on child elements.
|
|
7604
|
+
*
|
|
7605
|
+
* When testing, add `setupIntersectionObserver();` to your `jest.setup.ts` file.
|
|
7606
|
+
*
|
|
7607
|
+
* ### When to use
|
|
7608
|
+
* Use Sidebar for secondary in-page navigation (e.g., switching between views within a page). Works well with `Tabs` for page-level navigation.
|
|
6611
7609
|
*
|
|
6612
|
-
* When
|
|
7610
|
+
* ### When not to use
|
|
7611
|
+
* Do not use Sidebar for primary app navigation (the main menu). For top-level navigation, use the app shell navigation.
|
|
6613
7612
|
*
|
|
6614
7613
|
* @example Responsive sidebar with navigation items
|
|
6615
7614
|
* ```tsx
|
|
@@ -6715,8 +7714,35 @@ const cvaTab = cvaMerge([
|
|
|
6715
7714
|
});
|
|
6716
7715
|
|
|
6717
7716
|
/**
|
|
6718
|
-
*
|
|
6719
|
-
*
|
|
7717
|
+
* Tab is an individual tab trigger within a TabList.
|
|
7718
|
+
* Each Tab requires a unique `value` prop that corresponds to a TabContent with the same value.
|
|
7719
|
+
* Supports optional icons, suffixes (e.g., badges), and rendering as a custom child element via `asChild`.
|
|
7720
|
+
*
|
|
7721
|
+
* ### When to use
|
|
7722
|
+
* Use Tab inside a `TabList` to create clickable tab triggers. Each Tab maps to a `TabContent` panel.
|
|
7723
|
+
*
|
|
7724
|
+
* ### When not to use
|
|
7725
|
+
* Do not use Tab outside of a `TabList`/`Tabs` context. For standalone buttons, use `Button`.
|
|
7726
|
+
*
|
|
7727
|
+
* @example Tab with icon and badge suffix
|
|
7728
|
+
* ```tsx
|
|
7729
|
+
* import { Tabs, TabList, Tab, TabContent, Badge } from "@trackunit/react-components";
|
|
7730
|
+
*
|
|
7731
|
+
* const TabsWithBadge = () => (
|
|
7732
|
+
* <Tabs defaultValue="alerts">
|
|
7733
|
+
* <TabList>
|
|
7734
|
+
* <Tab value="alerts" iconName="Bell" suffix={<Badge count={3} color="danger" />}>
|
|
7735
|
+
* Alerts
|
|
7736
|
+
* </Tab>
|
|
7737
|
+
* <Tab value="history" iconName="Clock">History</Tab>
|
|
7738
|
+
* </TabList>
|
|
7739
|
+
* <TabContent value="alerts">Active alerts</TabContent>
|
|
7740
|
+
* <TabContent value="history">Event history</TabContent>
|
|
7741
|
+
* </Tabs>
|
|
7742
|
+
* );
|
|
7743
|
+
* ```
|
|
7744
|
+
* @param {TabProps} props - The props for the Tab component
|
|
7745
|
+
* @returns {ReactElement} Tab component
|
|
6720
7746
|
*/
|
|
6721
7747
|
const Tab = ({ value, isFullWidth = false, iconName = undefined, "data-testid": dataTestId, className, children, suffix, asChild = false, appendTabStylesToChildIfAsChild = true, ref, ...rest }) => {
|
|
6722
7748
|
const renderContent = () => (jsxs(Fragment$1, { children: [iconName !== undefined ? jsx(Icon, { name: iconName, size: "small" }) : null, isValidElement(children) ? children.props.children : children, suffix] }));
|
|
@@ -6731,14 +7757,72 @@ const Tab = ({ value, isFullWidth = false, iconName = undefined, "data-testid":
|
|
|
6731
7757
|
};
|
|
6732
7758
|
|
|
6733
7759
|
/**
|
|
6734
|
-
*
|
|
7760
|
+
* TabContent is the content panel displayed when its corresponding Tab is active.
|
|
7761
|
+
* Each TabContent must have a `value` prop that matches the `value` of its corresponding Tab trigger.
|
|
7762
|
+
* TabContent is only rendered when its tab is selected (unless `forceMount` is set).
|
|
7763
|
+
*
|
|
7764
|
+
* ### When to use
|
|
7765
|
+
* Use TabContent inside a `Tabs` component, one for each `Tab` trigger.
|
|
7766
|
+
*
|
|
7767
|
+
* ### When not to use
|
|
7768
|
+
* Do not use TabContent outside of a `Tabs` context. For conditionally shown content, use standard conditional rendering.
|
|
7769
|
+
*
|
|
7770
|
+
* @example Tabs with styled content panels
|
|
7771
|
+
* ```tsx
|
|
7772
|
+
* import { Tabs, TabList, Tab, TabContent, Text } from "@trackunit/react-components";
|
|
7773
|
+
*
|
|
7774
|
+
* const AssetTabs = () => (
|
|
7775
|
+
* <Tabs defaultValue="overview">
|
|
7776
|
+
* <TabList>
|
|
7777
|
+
* <Tab value="overview">Overview</Tab>
|
|
7778
|
+
* <Tab value="maintenance">Maintenance</Tab>
|
|
7779
|
+
* </TabList>
|
|
7780
|
+
* <TabContent value="overview">
|
|
7781
|
+
* <Text>Asset overview information</Text>
|
|
7782
|
+
* </TabContent>
|
|
7783
|
+
* <TabContent value="maintenance">
|
|
7784
|
+
* <Text>Maintenance schedule and history</Text>
|
|
7785
|
+
* </TabContent>
|
|
7786
|
+
* </Tabs>
|
|
7787
|
+
* );
|
|
7788
|
+
* ```
|
|
7789
|
+
* @param {TabContentProps} props - The props for the TabContent component
|
|
7790
|
+
* @returns {ReactElement} TabContent component
|
|
6735
7791
|
*/
|
|
6736
7792
|
const TabContent = ({ className, "data-testid": dataTestId, children, ref, ...rest }) => {
|
|
6737
7793
|
return (jsx(Content, { className: cvaTabContent({ className }), "data-testid": dataTestId ? `${dataTestId}-content` : undefined, ref: ref, ...rest, children: children }));
|
|
6738
7794
|
};
|
|
6739
7795
|
|
|
6740
7796
|
/**
|
|
6741
|
-
*
|
|
7797
|
+
* TabList is the container for Tab triggers inside a Tabs component.
|
|
7798
|
+
* It renders a horizontal row of tab triggers and handles horizontal scroll behavior when tabs overflow.
|
|
7799
|
+
* By default, it auto-scrolls the active tab into view.
|
|
7800
|
+
*
|
|
7801
|
+
* ### When to use
|
|
7802
|
+
* Use TabList as a direct child of `Tabs` to wrap the `Tab` triggers. It handles layout and scrolling.
|
|
7803
|
+
*
|
|
7804
|
+
* ### When not to use
|
|
7805
|
+
* Do not use TabList outside of a `Tabs` context. For horizontal navigation lists, use a standard nav element.
|
|
7806
|
+
*
|
|
7807
|
+
* @example TabList with auto-scroll disabled
|
|
7808
|
+
* ```tsx
|
|
7809
|
+
* import { Tabs, TabList, Tab, TabContent } from "@trackunit/react-components";
|
|
7810
|
+
*
|
|
7811
|
+
* const StaticTabs = () => (
|
|
7812
|
+
* <Tabs defaultValue="first">
|
|
7813
|
+
* <TabList autoScrollToActive={false}>
|
|
7814
|
+
* <Tab value="first">First</Tab>
|
|
7815
|
+
* <Tab value="second">Second</Tab>
|
|
7816
|
+
* <Tab value="third">Third</Tab>
|
|
7817
|
+
* </TabList>
|
|
7818
|
+
* <TabContent value="first">First panel</TabContent>
|
|
7819
|
+
* <TabContent value="second">Second panel</TabContent>
|
|
7820
|
+
* <TabContent value="third">Third panel</TabContent>
|
|
7821
|
+
* </Tabs>
|
|
7822
|
+
* );
|
|
7823
|
+
* ```
|
|
7824
|
+
* @param {TabListProps} props - The props for the TabList component
|
|
7825
|
+
* @returns {ReactElement} TabList component
|
|
6742
7826
|
*/
|
|
6743
7827
|
const TabList = ({ className, "data-testid": dataTestId, children, autoScrollToActive = true, ref, ...rest }) => {
|
|
6744
7828
|
const listRef = useRef(null);
|
|
@@ -6785,7 +7869,18 @@ const TabList = ({ className, "data-testid": dataTestId, children, autoScrollToA
|
|
|
6785
7869
|
};
|
|
6786
7870
|
|
|
6787
7871
|
/**
|
|
6788
|
-
* Tabs
|
|
7872
|
+
* Tabs group different but related content, allowing users to navigate views without leaving the page.
|
|
7873
|
+
* They always contain at least two items and one tab is active at a time.
|
|
7874
|
+
* Tabs can be used on full page layouts or in components such as modals or tables.
|
|
7875
|
+
*
|
|
7876
|
+
* Compose Tabs with TabList, Tab, and TabContent.
|
|
7877
|
+
*
|
|
7878
|
+
* ### When to use
|
|
7879
|
+
* Use tabs to switch between related content sections within the same context (e.g., different views of an asset, or settings categories).
|
|
7880
|
+
*
|
|
7881
|
+
* ### When not to use
|
|
7882
|
+
* Do not use tabs for primary navigation between unrelated pages. Use `Sidebar` or route-based navigation instead.
|
|
7883
|
+
* Avoid tabs when there is only one content panel — just show the content directly.
|
|
6789
7884
|
*
|
|
6790
7885
|
* @example Basic tabs with content panels
|
|
6791
7886
|
* ```tsx
|
|
@@ -6819,6 +7914,8 @@ const TabList = ({ className, "data-testid": dataTestId, children, autoScrollToA
|
|
|
6819
7914
|
* </Tabs>
|
|
6820
7915
|
* );
|
|
6821
7916
|
* ```
|
|
7917
|
+
* @param {TabsProps} props - The props for the Tabs component
|
|
7918
|
+
* @returns {ReactElement} Tabs component
|
|
6822
7919
|
*/
|
|
6823
7920
|
const Tabs = ({ children, forceRender, className, "data-testid": dataTestId, fullWidth, ref, ...rest }) => {
|
|
6824
7921
|
return (jsx(Root, { className: cvaTabsRoot({ className }), "data-testid": dataTestId, ref: ref, ...rest, children: children }));
|
|
@@ -6939,8 +8036,37 @@ const cvaToggleItemContent = cvaMerge([], {
|
|
|
6939
8036
|
});
|
|
6940
8037
|
|
|
6941
8038
|
/**
|
|
6942
|
-
*
|
|
8039
|
+
* ToggleGroup allows users to toggle between two or more closely related options and immediately apply the selection.
|
|
8040
|
+
* It renders a segmented control with a sliding background indicator. Supports text-only, icon-only, and text+icon modes.
|
|
6943
8041
|
*
|
|
8042
|
+
* ### When to use
|
|
8043
|
+
* Use ToggleGroup when the user needs to switch between 2-5 mutually exclusive options that take effect immediately (e.g., view modes, map/list toggle, time ranges).
|
|
8044
|
+
*
|
|
8045
|
+
* ### When not to use
|
|
8046
|
+
* Do not use ToggleGroup for navigation — use `Tabs`.
|
|
8047
|
+
* Do not use ToggleGroup for form selections with many options — use a Select or RadioGroup.
|
|
8048
|
+
*
|
|
8049
|
+
* @example ToggleGroup for view mode switching
|
|
8050
|
+
* ```tsx
|
|
8051
|
+
* import { ToggleGroup } from "@trackunit/react-components";
|
|
8052
|
+
* import { useState } from "react";
|
|
8053
|
+
*
|
|
8054
|
+
* const ViewToggle = () => {
|
|
8055
|
+
* const [selected, setSelected] = useState("list");
|
|
8056
|
+
*
|
|
8057
|
+
* return (
|
|
8058
|
+
* <ToggleGroup
|
|
8059
|
+
* selected={selected}
|
|
8060
|
+
* setSelected={setSelected}
|
|
8061
|
+
* onChange={(id) => console.log("Selected:", id)}
|
|
8062
|
+
* list={[
|
|
8063
|
+
* { id: "list", title: "List", iconName: "Bars3" },
|
|
8064
|
+
* { id: "map", title: "Map", iconName: "MapPin" },
|
|
8065
|
+
* ]}
|
|
8066
|
+
* />
|
|
8067
|
+
* );
|
|
8068
|
+
* };
|
|
8069
|
+
* ```
|
|
6944
8070
|
* @param {ToggleGroupProps} props - The props for the ToggleGroup component
|
|
6945
8071
|
* @returns {ReactElement} ToggleGroup component
|
|
6946
8072
|
*/
|
|
@@ -7057,7 +8183,7 @@ const SegmentedValueBar = ({ segments, total, size = "small", showValue = false,
|
|
|
7057
8183
|
const valueText = formatValue(sum, unit);
|
|
7058
8184
|
const canShowValue = showValue && size !== "extraSmall";
|
|
7059
8185
|
const valueTextClassName = cvaValueBarText({ size: getValueTextVariant(size, sum, segments, total) });
|
|
7060
|
-
return (jsxs("span", { className:
|
|
8186
|
+
return (jsxs("span", { className: valueBarContainerClassName, "data-testid": dataTestId, children: [jsx("div", { "aria-label": valueText, className: cvaSegmentedValueBar({ className, size }), "data-testid": dataTestId ? `${dataTestId}-track` : undefined, children: computedSegments.map((segment, index) => {
|
|
7061
8187
|
const tooltipLabel = segment.label
|
|
7062
8188
|
? `${segment.label}: ${formatValue(segment.value, unit)}`
|
|
7063
8189
|
: formatValue(segment.value, unit);
|