@altruist03/ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (207) hide show
  1. package/assets/images/noData.webp +0 -0
  2. package/dist/index.js +15237 -650
  3. package/dist/index.js.map +1 -1
  4. package/dist/noData-XPRHKMFR.webp +0 -0
  5. package/package.json +21 -7
  6. package/src/ThemeProvider/ThemeContext.tsx +75 -0
  7. package/src/ThemeProvider/index.ts +7 -0
  8. package/src/assets/images/noData.webp +0 -0
  9. package/src/assets.d.ts +10 -0
  10. package/src/components/Accordion/Accordion.stories.tsx +64 -0
  11. package/src/components/Accordion/Accordion.tsx +219 -0
  12. package/src/components/Accordion/Accordion.types.ts +16 -0
  13. package/src/components/Avatar/Avatar.stories.tsx +304 -0
  14. package/src/components/Avatar/Avatar.tsx +211 -0
  15. package/src/components/Avatar/Avatar.types.ts +33 -0
  16. package/src/components/Badge/Badge.stories.tsx +143 -0
  17. package/src/components/Badge/Badge.tsx +138 -0
  18. package/src/components/Badge/Badge.types.ts +24 -0
  19. package/src/components/Breadcrumb/Breadcrumb.stories.tsx +114 -0
  20. package/src/components/Breadcrumb/Breadcrumb.tsx +120 -0
  21. package/src/components/Breadcrumb/Breadcrumb.types.ts +22 -0
  22. package/src/components/Button/Button.stories.tsx +457 -0
  23. package/src/components/Button/Button.tsx +202 -0
  24. package/src/components/Button/Button.types.ts +18 -0
  25. package/src/components/Checkbox/Checkbox.stories.tsx +254 -0
  26. package/src/components/Checkbox/Checkbox.tsx +352 -0
  27. package/src/components/Checkbox/Checkbox.types.ts +123 -0
  28. package/src/components/CloseButton/CloseButton.stories.tsx +100 -0
  29. package/src/components/CloseButton/CloseButton.tsx +117 -0
  30. package/src/components/CloseButton/CloseButton.types.ts +15 -0
  31. package/src/components/CollapsibleSearch/CollapsibleSearch.tsx +120 -0
  32. package/src/components/DataTable/DataTable.stories.tsx +233 -0
  33. package/src/components/DataTable/DataTable.tsx +968 -0
  34. package/src/components/DataTable/DataTable.types.ts +49 -0
  35. package/src/components/DatePicker/DatePicker.stories.tsx +341 -0
  36. package/src/components/DatePicker/DatePicker.tsx +669 -0
  37. package/src/components/DatePicker/DatePicker.types.ts +105 -0
  38. package/src/components/DatePicker/DateTimePicker.tsx +35 -0
  39. package/src/components/DateRangePicker/DateRangePicker.tsx +179 -0
  40. package/src/components/DateRangePicker/DateRangePicker.types.ts +34 -0
  41. package/src/components/Drawer/Drawer.stories.tsx +243 -0
  42. package/src/components/Drawer/Drawer.tsx +196 -0
  43. package/src/components/Drawer/Drawer.types.ts +60 -0
  44. package/src/components/FileUpload/FileUpload.stories.tsx +162 -0
  45. package/src/components/FileUpload/FileUpload.tsx +216 -0
  46. package/src/components/FileUpload/FileUpload.types.ts +80 -0
  47. package/src/components/FilterPopover/FilterPopover.stories.tsx +173 -0
  48. package/src/components/FilterPopover/FilterPopover.tsx +310 -0
  49. package/src/components/FilterPopover/FilterPopover.types.ts +26 -0
  50. package/src/components/ImageViewer/ImageViewerToolbar.jsx +81 -0
  51. package/src/components/ImageViewer/useImagePanRotate.js +102 -0
  52. package/src/components/IpWhitelistField/IpWhitelistField.stories.tsx +138 -0
  53. package/src/components/IpWhitelistField/IpWhitelistField.tsx +164 -0
  54. package/src/components/IpWhitelistField/IpWhitelistField.types.ts +42 -0
  55. package/src/components/Loader/Loader.stories.tsx +95 -0
  56. package/src/components/Loader/Loader.tsx +132 -0
  57. package/src/components/Loader/Loader.types.ts +6 -0
  58. package/src/components/Menu/Menu.tsx +143 -0
  59. package/src/components/Menu/Menu.types.ts +28 -0
  60. package/src/components/MenuButton/MenuButton.stories.tsx +140 -0
  61. package/src/components/MenuButton/MenuButton.tsx +45 -0
  62. package/src/components/MenuButton/MenuButton.types.ts +21 -0
  63. package/src/components/Modal/Modal.stories.tsx +266 -0
  64. package/src/components/Modal/Modal.tsx +399 -0
  65. package/src/components/Modal/index.ts +2 -0
  66. package/src/components/Modal/types.ts +126 -0
  67. package/src/components/Pagination/Pagination.stories.tsx +167 -0
  68. package/src/components/Pagination/Pagination.tsx +266 -0
  69. package/src/components/Pagination/Pagination.types.ts +20 -0
  70. package/src/components/PeriodSelector/PeriodSelector.stories.tsx +50 -0
  71. package/src/components/PeriodSelector/PeriodSelector.tsx +47 -0
  72. package/src/components/PeriodSelector/PeriodSelector.types.ts +10 -0
  73. package/src/components/PhoneField/PhoneField.stories.tsx +141 -0
  74. package/src/components/PhoneField/PhoneField.tsx +284 -0
  75. package/src/components/PhoneField/PhoneField.types.ts +89 -0
  76. package/src/components/PhoneField/countryCodes.ts +236 -0
  77. package/src/components/Progress/Progress.stories.tsx +83 -0
  78. package/src/components/Progress/Progress.tsx +91 -0
  79. package/src/components/Progress/Progress.types.ts +22 -0
  80. package/src/components/RadioButton/RadioButton.stories.tsx +332 -0
  81. package/src/components/RadioButton/RadioButton.tsx +427 -0
  82. package/src/components/RadioButton/RadioButton.types.ts +117 -0
  83. package/src/components/ReportCard/ReportCard.tsx +183 -0
  84. package/src/components/ReportCard/ReportCard.types.ts +17 -0
  85. package/src/components/SectionIcon/SectionIcon.tsx +87 -0
  86. package/src/components/SectionIcon/SectionIcon.types.ts +23 -0
  87. package/src/components/SegmentedInputField/SegmentedInputField.stories.tsx +165 -0
  88. package/src/components/SegmentedInputField/SegmentedInputField.tsx +174 -0
  89. package/src/components/SegmentedInputField/SegmentedInputField.types.ts +51 -0
  90. package/src/components/SelectScroll/SelectScroll.stories.tsx +336 -0
  91. package/src/components/SelectScroll/SelectScroll.tsx +1119 -0
  92. package/src/components/SelectScroll/SelectScroll.types.ts +60 -0
  93. package/src/components/Selector/Selector.stories.tsx +382 -0
  94. package/src/components/Selector/Selector.tsx +1310 -0
  95. package/src/components/Selector/Selector.types.ts +71 -0
  96. package/src/components/Skeleton/Skeleton.stories.tsx +133 -0
  97. package/src/components/Skeleton/Skeleton.tsx +20 -0
  98. package/src/components/Skeleton/Skeleton.types.ts +3 -0
  99. package/src/components/Spinner/Spinner.stories.tsx +84 -0
  100. package/src/components/Spinner/Spinner.tsx +79 -0
  101. package/src/components/Spinner/Spinner.types.ts +8 -0
  102. package/src/components/StatCard/StatCard.stories.tsx +125 -0
  103. package/src/components/StatCard/StatCard.tsx +167 -0
  104. package/src/components/StatCard/StatCard.types.ts +20 -0
  105. package/src/components/StatusBadge/StatusBadge.stories.tsx +179 -0
  106. package/src/components/StatusBadge/StatusBadge.tsx +74 -0
  107. package/src/components/StatusBadge/StatusBadge.types.ts +23 -0
  108. package/src/components/Stepper/Stepper.stories.tsx +85 -0
  109. package/src/components/Stepper/Stepper.tsx +138 -0
  110. package/src/components/Stepper/Stepper.types.ts +16 -0
  111. package/src/components/SubTabs/SubTabs.stories.tsx +128 -0
  112. package/src/components/SubTabs/SubTabs.tsx +279 -0
  113. package/src/components/SubTabs/SubTabs.types.ts +44 -0
  114. package/src/components/SummaryCard/SummaryCard.stories.tsx +100 -0
  115. package/src/components/SummaryCard/SummaryCard.tsx +141 -0
  116. package/src/components/SummaryCard/SummaryCard.types.ts +22 -0
  117. package/src/components/Switch/Switch.stories.tsx +59 -0
  118. package/src/components/Switch/Switch.tsx +217 -0
  119. package/src/components/Switch/Switch.types.ts +22 -0
  120. package/src/components/Tabs/Tabs.stories.tsx +178 -0
  121. package/src/components/Tabs/Tabs.tsx +197 -0
  122. package/src/components/Tabs/Tabs.types.ts +34 -0
  123. package/src/components/TextField/TextField.stories.tsx +249 -0
  124. package/src/components/TextField/TextField.tsx +462 -0
  125. package/src/components/TextField/TextField.types.ts +141 -0
  126. package/src/components/Toast/Toast.stories.tsx +237 -0
  127. package/src/components/Toast/Toast.tsx +246 -0
  128. package/src/components/Toast/Toast.types.ts +31 -0
  129. package/src/components/ToggleButton/ToggleButton.stories.tsx +105 -0
  130. package/src/components/ToggleButton/ToggleButton.tsx +267 -0
  131. package/src/components/ToggleButton/ToggleButton.types.ts +19 -0
  132. package/src/components/UploadDropzone/UploadDropzone.jsx +78 -0
  133. package/src/components/UploadDropzone2/UploadDropzone.stories.tsx +94 -0
  134. package/src/components/UploadDropzone2/UploadDropzone.tsx +222 -0
  135. package/src/components/UploadDropzone2/UploadDropzone.types.ts +54 -0
  136. package/src/components/ViewEditToggle/ViewEditToggle.stories.tsx +40 -0
  137. package/src/components/ViewEditToggle/ViewEditToggle.tsx +239 -0
  138. package/src/components/ViewEditToggle/ViewEditToggle.types.ts +27 -0
  139. package/src/components/index.ts +157 -0
  140. package/src/icons/ReportDollarIcon.tsx +71 -0
  141. package/src/icons/index.ts +27 -0
  142. package/src/index.ts +214 -0
  143. package/src/storybook/preview.tsx +64 -0
  144. package/src/theme/index.ts +4 -0
  145. package/src/theme/types.d.ts +1 -0
  146. package/src/themes/cpc.ts +4 -0
  147. package/src/themes/firebird.ts +4 -0
  148. package/src/tokens/brands/cpc/themes/dark.json +82 -0
  149. package/src/tokens/brands/cpc/themes/light.json +1 -0
  150. package/src/tokens/brands/default/brand.json +20 -0
  151. package/src/tokens/brands/firebird/themes/dark.json +28 -0
  152. package/src/tokens/brands/firebird/themes/light.json +20 -0
  153. package/src/tokens/button.ts +24 -0
  154. package/src/tokens/components/accordion.json +52 -0
  155. package/src/tokens/components/avatar.json +40 -0
  156. package/src/tokens/components/badge.json +41 -0
  157. package/src/tokens/components/breadcrumb.json +13 -0
  158. package/src/tokens/components/button.json +67 -0
  159. package/src/tokens/components/card.json +39 -0
  160. package/src/tokens/components/checkbox.json +64 -0
  161. package/src/tokens/components/closebutton.json +55 -0
  162. package/src/tokens/components/datatable.json +40 -0
  163. package/src/tokens/components/datepicker.json +99 -0
  164. package/src/tokens/components/drawer.json +23 -0
  165. package/src/tokens/components/field.json +29 -0
  166. package/src/tokens/components/fileupload.json +57 -0
  167. package/src/tokens/components/filterpopover.json +29 -0
  168. package/src/tokens/components/input.json +31 -0
  169. package/src/tokens/components/ipwhitelistfield.json +16 -0
  170. package/src/tokens/components/loader.json +15 -0
  171. package/src/tokens/components/menu.json +21 -0
  172. package/src/tokens/components/modal.json +25 -0
  173. package/src/tokens/components/pagination.json +45 -0
  174. package/src/tokens/components/phonefield.json +41 -0
  175. package/src/tokens/components/progress.json +12 -0
  176. package/src/tokens/components/radio.json +87 -0
  177. package/src/tokens/components/reportcard.json +17 -0
  178. package/src/tokens/components/selector.json +87 -0
  179. package/src/tokens/components/selectscroll.json +65 -0
  180. package/src/tokens/components/skeleton.json +6 -0
  181. package/src/tokens/components/stepper.json +38 -0
  182. package/src/tokens/components/subtabs.json +45 -0
  183. package/src/tokens/components/switch.json +55 -0
  184. package/src/tokens/components/tabs.json +38 -0
  185. package/src/tokens/components/textfield.json +59 -0
  186. package/src/tokens/components/togglebutton.json +48 -0
  187. package/src/tokens/components/uploaddropzone.json +61 -0
  188. package/src/tokens/drawer.ts +37 -0
  189. package/src/tokens/foundation/breakpoints.json +10 -0
  190. package/src/tokens/foundation/colors.json +122 -0
  191. package/src/tokens/foundation/opacity.json +16 -0
  192. package/src/tokens/foundation/radius.json +14 -0
  193. package/src/tokens/foundation/size.json +38 -0
  194. package/src/tokens/foundation/spacing.json +19 -0
  195. package/src/tokens/foundation/typography.json +43 -0
  196. package/src/tokens/index.ts +3665 -0
  197. package/src/tokens/modal.ts +42 -0
  198. package/src/tokens/semantic/color.json +148 -0
  199. package/src/tokens/semantic/field.json +28 -0
  200. package/src/tokens/semantic/opacity.json +5 -0
  201. package/src/tokens/semantic/size.json +722 -0
  202. package/src/tokens/semantic/spacing.json +27 -0
  203. package/src/tokens/semantic/typography.json +87 -0
  204. package/src/utils/scrollLock.ts +44 -0
  205. package/dist/index.css +0 -514
  206. package/dist/index.css.map +0 -1
  207. package/dist/index.d.ts +0 -289
@@ -0,0 +1,138 @@
1
+ import React, { useState, useCallback } from 'react';
2
+ import { useTokens } from '../../ThemeProvider/ThemeContext';
3
+ import { getBadgeTokensFromResolved } from '../../tokens';
4
+ import type { BadgeProps } from './Badge.types';
5
+
6
+ function CloseIcon({ size }: { size: number }) {
7
+ return (
8
+ <svg
9
+ width={size}
10
+ height={size}
11
+ viewBox="0 0 12 12"
12
+ fill="none"
13
+ xmlns="http://www.w3.org/2000/svg"
14
+ aria-hidden
15
+ >
16
+ <path
17
+ d="M1 1L11 11M11 1L1 11"
18
+ stroke="currentColor"
19
+ strokeWidth="1"
20
+ strokeLinecap="round"
21
+ strokeLinejoin="round"
22
+ />
23
+ </svg>
24
+ );
25
+ }
26
+
27
+ export const Badge = ({
28
+ children,
29
+ variant = 'default',
30
+ size = 'md',
31
+ color = 'grey',
32
+ onRemove,
33
+ disabled = false,
34
+ removeLabel,
35
+ style,
36
+ className,
37
+ }: BadgeProps) => {
38
+ const [removeHovered, setRemoveHovered] = useState(false);
39
+ const resolvedTokens = useTokens();
40
+ const tokens = getBadgeTokensFromResolved(resolvedTokens);
41
+
42
+ const height =
43
+ size === 'xs' ? tokens.heightXs
44
+ : size === 'sm' ? tokens.heightSm
45
+ : size === 'lg' ? tokens.heightLg
46
+ : tokens.heightMd;
47
+ const isCircle = variant === 'circle';
48
+ const padding =
49
+ size === 'xs' ? tokens.paddingXs
50
+ : size === 'sm' ? tokens.paddingSm
51
+ : size === 'lg' ? tokens.paddingLg
52
+ : tokens.paddingMd;
53
+ const fontSize =
54
+ size === 'xs' ? tokens.fontSizeXs
55
+ : size === 'sm' ? tokens.fontSizeSm
56
+ : size === 'lg' ? tokens.fontSizeLg
57
+ : tokens.fontSizeMd;
58
+ const borderRadius =
59
+ size === 'xs' ? tokens.borderRadiusXs
60
+ : size === 'sm' ? tokens.borderRadiusSm
61
+ : size === 'lg' ? tokens.borderRadiusLg
62
+ : tokens.borderRadiusMd;
63
+ const iconSize =
64
+ size === 'xs' ? tokens.removeIconSizeXs
65
+ : size === 'sm' ? tokens.removeIconSizeSm
66
+ : size === 'lg' ? tokens.removeIconSizeLg
67
+ : tokens.removeIconSizeMd;
68
+
69
+ const handleRemoveClick = useCallback(
70
+ (e: React.MouseEvent) => {
71
+ e.preventDefault();
72
+ e.stopPropagation();
73
+ if (!disabled && onRemove) onRemove(e);
74
+ },
75
+ [disabled, onRemove]
76
+ );
77
+
78
+ const removeButtonColor =
79
+ disabled ? tokens.removeButtonColor
80
+ : removeHovered ? tokens.removeButtonHoverColor
81
+ : tokens.removeButtonColor;
82
+
83
+ const backgroundColor = color === 'white' ? tokens.backgroundWhite : tokens.background;
84
+
85
+ return (
86
+ <span
87
+ className={className}
88
+ style={{
89
+ display: 'inline-flex',
90
+ alignItems: 'center',
91
+ justifyContent: 'center',
92
+ gap: isCircle ? 0 : 6,
93
+ boxSizing: 'border-box',
94
+ padding: isCircle ? 0 : padding,
95
+ fontSize,
96
+ fontWeight: 400,
97
+ backgroundColor,
98
+ color: tokens.color,
99
+ borderRadius: isCircle ? '50%' : borderRadius,
100
+ border: isCircle ? `1px solid ${tokens.borderColor}` : undefined,
101
+ width: isCircle ? height : undefined,
102
+ height: isCircle ? height : undefined,
103
+ minWidth: isCircle ? height : undefined,
104
+ boxShadow: isCircle ? 'none' : tokens.boxShadow,
105
+ opacity: disabled ? 0.6 : 1,
106
+ ...style,
107
+ }}
108
+ >
109
+ <span style={{ lineHeight: 1.4 }}>{children}</span>
110
+ {onRemove != null && (
111
+ <button
112
+ type="button"
113
+ onClick={handleRemoveClick}
114
+ onMouseDown={(e) => e.preventDefault()}
115
+ onMouseEnter={() => setRemoveHovered(true)}
116
+ onMouseLeave={() => setRemoveHovered(false)}
117
+ disabled={disabled}
118
+ aria-label={removeLabel ?? `Remove ${typeof children === 'string' ? children : 'item'}`}
119
+ style={{
120
+ display: 'flex',
121
+ alignItems: 'center',
122
+ justifyContent: 'center',
123
+ width: iconSize + 4,
124
+ height: iconSize + 4,
125
+ padding: 0,
126
+ border: 'none',
127
+ background: 'transparent',
128
+ cursor: disabled ? 'not-allowed' : 'pointer',
129
+ color: removeButtonColor,
130
+ marginLeft: 2,
131
+ }}
132
+ >
133
+ <CloseIcon size={iconSize} />
134
+ </button>
135
+ )}
136
+ </span>
137
+ );
138
+ };
@@ -0,0 +1,24 @@
1
+ import type { BadgeSize } from '../../tokens';
2
+
3
+ export type BadgeVariant = 'default' | 'circle';
4
+
5
+ export interface BadgeProps {
6
+ /** Badge content (label) */
7
+ children: React.ReactNode;
8
+ /** Shape variant */
9
+ variant?: BadgeVariant;
10
+ /** Size variant */
11
+ size?: BadgeSize;
12
+ /** Background color variant */
13
+ color?: 'grey' | 'white';
14
+ /** Callback when remove button is clicked. When provided, shows the remove (X) button. */
15
+ onRemove?: (e: React.MouseEvent) => void;
16
+ /** Disables the badge and remove button */
17
+ disabled?: boolean;
18
+ /** Accessible label for the remove button (e.g. "Remove Verified") */
19
+ removeLabel?: string;
20
+ /** Additional CSS styles */
21
+ style?: React.CSSProperties;
22
+ /** Additional CSS class name */
23
+ className?: string;
24
+ }
@@ -0,0 +1,114 @@
1
+ import type { Meta, StoryObj } from "@storybook/react";
2
+ import { useState } from "react";
3
+ import { Breadcrumb } from "./Breadcrumb";
4
+ import type { BreadcrumbProps, BreadcrumbOption } from "./Breadcrumb.types";
5
+
6
+ const defaultOptions: BreadcrumbOption[] = [
7
+ { label: "Campaign", value: "campaign" },
8
+ { label: "Schedule", value: "schedule" },
9
+ ];
10
+
11
+ const meta: Meta<typeof Breadcrumb> = {
12
+ title: "Design System/Breadcrumb",
13
+ component: Breadcrumb,
14
+ tags: [],
15
+ decorators: [
16
+ (Story) => (
17
+ <div style={{ padding: "24px", maxWidth: "420px" }}>
18
+ <Story />
19
+ </div>
20
+ ),
21
+ ],
22
+ args: {
23
+ options: defaultOptions,
24
+ value: "schedule",
25
+ separator: " > ",
26
+ } as BreadcrumbProps,
27
+ argTypes: {
28
+ value: {
29
+ control: { type: "radio" },
30
+ options: ["campaign", "schedule"],
31
+ },
32
+ separator: {
33
+ control: { type: "text" },
34
+ },
35
+ onChange: { action: "changed" },
36
+ },
37
+ };
38
+
39
+ export default meta;
40
+
41
+ type Story = StoryObj<typeof Breadcrumb>;
42
+
43
+ export const Playground: Story = {
44
+ args: {
45
+ options: defaultOptions,
46
+ value: "schedule",
47
+ },
48
+ render: function PlaygroundRender(args) {
49
+ const [value, setValue] = useState(args.value);
50
+ return (
51
+ <Breadcrumb
52
+ {...args}
53
+ value={value}
54
+ onChange={(v) => {
55
+ setValue(v);
56
+ args.onChange?.(v);
57
+ }}
58
+ />
59
+ );
60
+ },
61
+ };
62
+
63
+ export const Showcase: Story = {
64
+ render: function ShowcaseRender() {
65
+ const [value1, setValue1] = useState("schedule");
66
+ const [value2, setValue2] = useState("schedule");
67
+ const [value3, setValue3] = useState("schedule");
68
+
69
+ return (
70
+ <div style={{ display: "flex", flexDirection: "column", gap: "24px" }}>
71
+ {/* Campaign > Schedule (2 levels) */}
72
+ <Breadcrumb
73
+ options={[
74
+ { label: "Campaign", value: "campaign" },
75
+ { label: "Schedule", value: "schedule" },
76
+ ]}
77
+ value={value1}
78
+ onChange={setValue1}
79
+ />
80
+
81
+ {/* Home > Campaigns > Schedule (3 levels) */}
82
+ <Breadcrumb
83
+ options={[
84
+ { label: "Home", value: "home" },
85
+ { label: "Campaigns", value: "campaigns" },
86
+ { label: "Schedule", value: "schedule" },
87
+ ]}
88
+ value={value2}
89
+ onChange={setValue2}
90
+ />
91
+
92
+ {/* Using optionArray prop */}
93
+ <Breadcrumb
94
+ optionArray={[
95
+ { label: "Campaign", value: "campaign" },
96
+ { label: "Schedule", value: "schedule" },
97
+ ]}
98
+ value={value3}
99
+ onChange={setValue3}
100
+ />
101
+
102
+ {/* Custom separator */}
103
+ <Breadcrumb
104
+ options={[
105
+ { label: "Campaign", value: "campaign" },
106
+ { label: "Schedule", value: "schedule" },
107
+ ]}
108
+ value="schedule"
109
+ separator=" / "
110
+ />
111
+ </div>
112
+ );
113
+ },
114
+ };
@@ -0,0 +1,120 @@
1
+ import React from "react";
2
+ import { getBreadcrumbTokensFromResolved } from "../../tokens";
3
+ import { useTokens } from "../../ThemeProvider/ThemeContext";
4
+ import type { BreadcrumbProps, BreadcrumbOption } from "./Breadcrumb.types";
5
+
6
+ function toColor(value: string | undefined, fallback: string): string {
7
+ if (!value || typeof value !== "string") return fallback;
8
+ const s = value.trim();
9
+ if (s.includes("{")) return fallback;
10
+ if (s.startsWith("#") || s.startsWith("rgb") || s.startsWith("hsl")) return s;
11
+ if (/^[0-9A-Fa-f]{6}$/.test(s)) return `#${s}`;
12
+ return fallback;
13
+ }
14
+
15
+ export const Breadcrumb = ({
16
+ options,
17
+ optionArray,
18
+ value,
19
+ onChange,
20
+ separator = " > ",
21
+ style,
22
+ ...rest
23
+ }: BreadcrumbProps) => {
24
+ const resolvedTokens = useTokens();
25
+ const t = getBreadcrumbTokensFromResolved(resolvedTokens);
26
+
27
+ const items: BreadcrumbOption[] = optionArray ?? options ?? [];
28
+
29
+ const itemColor = toColor(t.itemColor, "#1f1f1f");
30
+ const currentColor = toColor(t.currentColor, "#1f1f1f");
31
+ const separatorColor = toColor(t.separatorColor, "#1f1f1f");
32
+
33
+ if (items.length === 0) {
34
+ return null;
35
+ }
36
+
37
+ return (
38
+ <nav
39
+ role="navigation"
40
+ aria-label="Breadcrumb"
41
+ style={{
42
+ display: "flex",
43
+ alignItems: "center",
44
+ flexWrap: "wrap",
45
+ gap: t.gap,
46
+ fontFamily: t.fontFamily,
47
+ fontSize: t.fontSize,
48
+ lineHeight: t.lineHeight,
49
+ color: itemColor,
50
+ ...style,
51
+ }}
52
+ {...rest}
53
+ >
54
+ {items.map((option, index) => {
55
+ const isLast = index === items.length - 1;
56
+ const isCurrent = option.value === value || isLast;
57
+ const isClickable = !isCurrent && !!onChange;
58
+
59
+ return (
60
+ <React.Fragment key={option.value}>
61
+ {index > 0 && (
62
+ <span
63
+ aria-hidden
64
+ style={{
65
+ color: separatorColor,
66
+ fontWeight: t.fontWeight,
67
+ fontFamily: t.fontFamily,
68
+ fontSize: t.fontSize,
69
+ lineHeight: t.lineHeight,
70
+ userSelect: "none",
71
+ }}
72
+ >
73
+ {separator}
74
+ </span>
75
+ )}
76
+ {isClickable ? (
77
+ <button
78
+ type="button"
79
+ onClick={() => onChange?.(option.value)}
80
+ style={{
81
+ background: "none",
82
+ border: "none",
83
+ padding: 0,
84
+ margin: 0,
85
+ cursor: "pointer",
86
+ color: itemColor,
87
+ fontWeight: t.itemFontWeight,
88
+ fontFamily: t.fontFamily,
89
+ fontSize: t.fontSize,
90
+ lineHeight: t.lineHeight,
91
+ textDecoration: "none",
92
+ }}
93
+ onMouseEnter={(e) => {
94
+ e.currentTarget.style.textDecoration = "underline";
95
+ }}
96
+ onMouseLeave={(e) => {
97
+ e.currentTarget.style.textDecoration = "none";
98
+ }}
99
+ >
100
+ {option.label}
101
+ </button>
102
+ ) : (
103
+ <span
104
+ style={{
105
+ color: currentColor,
106
+ fontWeight: t.fontWeight,
107
+ fontFamily: t.fontFamily,
108
+ fontSize: t.fontSize,
109
+ lineHeight: t.lineHeight,
110
+ }}
111
+ >
112
+ {option.label}
113
+ </span>
114
+ )}
115
+ </React.Fragment>
116
+ );
117
+ })}
118
+ </nav>
119
+ );
120
+ };
@@ -0,0 +1,22 @@
1
+ export interface BreadcrumbOption {
2
+ /** Display label for the breadcrumb item */
3
+ label: string;
4
+ /** Value used for selection and onChange */
5
+ value: string;
6
+ }
7
+
8
+ export interface BreadcrumbProps extends Omit<React.HTMLAttributes<HTMLElement>, 'onChange'> {
9
+ /**
10
+ * Array of options to display as breadcrumb path.
11
+ * Each option has `label` (display text) and `value` (for selection).
12
+ */
13
+ options: BreadcrumbOption[];
14
+ /** Alias for `options` - use either `options` or `optionArray` */
15
+ optionArray?: BreadcrumbOption[];
16
+ /** Currently selected/active value. The matching item is styled as current (non-clickable). */
17
+ value: string;
18
+ /** Called when user clicks a parent breadcrumb item (not the current one). */
19
+ onChange?: (value: string) => void;
20
+ /** Separator between breadcrumb items. Defaults to " > " */
21
+ separator?: string;
22
+ }