@syscore/ui-library 1.27.3 → 2.0.0-beta-publish-workflow-beta.2

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 (169) hide show
  1. package/client/App.tsx +0 -2
  2. package/client/assets/alert/icon-clear.svg +4 -0
  3. package/client/assets/alert/icon-done.svg +3 -0
  4. package/client/assets/avatar-identity/photo.png +0 -0
  5. package/client/assets/avatar-identity/placeholder-icon.svg +8 -0
  6. package/client/assets/breadcrumb/icon-ellipsis.svg +5 -0
  7. package/client/assets/breadcrumb/icon-home.svg +3 -0
  8. package/client/assets/breadcrumb/icon-separator.svg +3 -0
  9. package/client/assets/button-v01/icon-back.svg +4 -0
  10. package/client/assets/button-v01/icon-check.svg +3 -0
  11. package/client/assets/button-v01/icon-close.svg +4 -0
  12. package/client/assets/button-v01/icon-delete.svg +3 -0
  13. package/client/assets/button-v01/icon-device.svg +3 -0
  14. package/client/assets/dropdown/icon-carrot-contract.svg +3 -0
  15. package/client/assets/dropdown/icon-carrot-expand.svg +3 -0
  16. package/client/assets/dropdown/icon-clear.svg +4 -0
  17. package/client/assets/dropdown/icon-enter.svg +3 -0
  18. package/client/components/icons/UtilityChangeType.tsx +21 -0
  19. package/client/components/ui/alert-dialog-panel.tsx +246 -0
  20. package/client/components/ui/alert-v02.tsx +214 -0
  21. package/client/components/ui/app-bar.tsx +299 -0
  22. package/client/components/ui/avatar-identity.tsx +122 -0
  23. package/client/components/ui/banner.tsx +108 -0
  24. package/client/components/ui/bottom-navigation.tsx +223 -0
  25. package/client/components/ui/breadcrumb-trail.tsx +170 -0
  26. package/client/components/ui/button-v01.tsx +238 -0
  27. package/client/components/ui/button.tsx +2 -0
  28. package/client/components/ui/card.tsx +108 -26
  29. package/client/components/ui/code-badge.tsx +62 -0
  30. package/client/components/ui/date-picker.tsx +188 -0
  31. package/client/components/ui/dropdown.tsx +448 -0
  32. package/client/components/ui/empty-state.tsx +197 -0
  33. package/client/components/ui/file-upload.tsx +214 -0
  34. package/client/components/ui/footer.tsx +179 -0
  35. package/client/components/ui/layout.tsx +1182 -0
  36. package/client/components/ui/pagination-v01.tsx +157 -0
  37. package/client/components/ui/password-field-v01.tsx +67 -0
  38. package/client/components/ui/search-field-v01.tsx +192 -0
  39. package/client/components/ui/section-heading.tsx +49 -0
  40. package/client/components/ui/sidebar.tsx +2 -3
  41. package/client/components/ui/standard-navigation.tsx +8 -2
  42. package/client/components/ui/stepper.tsx +148 -0
  43. package/client/components/ui/switch-v01.tsx +71 -0
  44. package/client/components/ui/system-bar.tsx +119 -0
  45. package/client/components/ui/table-v01.tsx +230 -0
  46. package/client/components/ui/tabs-v01.tsx +99 -0
  47. package/client/components/ui/tag-v02.tsx +113 -0
  48. package/client/components/ui/text-area-v01.tsx +33 -0
  49. package/client/components/ui/text-field-v01.tsx +108 -0
  50. package/client/components/ui/timeline.tsx +181 -0
  51. package/client/components/ui/toast-v01.tsx +206 -0
  52. package/client/components/ui/tooltip.tsx +264 -71
  53. package/client/global.css +9565 -4378
  54. package/client/storybook.css +125 -0
  55. package/client/ui/Accordion.stories.tsx +1 -1
  56. package/client/ui/AlertDialogPanel/AlertDialogPanel.stories.tsx +150 -0
  57. package/client/ui/AlertV02/AlertV02.stories.tsx +188 -0
  58. package/client/ui/AppBar/app-bar.stories.tsx +280 -0
  59. package/client/ui/AspectRatio.stories.tsx +1 -1
  60. package/client/ui/AvatarIdentity/AvatarIdentity.stories.tsx +77 -0
  61. package/client/ui/Banner/banner.stories.tsx +238 -0
  62. package/client/ui/BottomNavigation/bottom-navigation.stories.tsx +285 -0
  63. package/client/ui/BreadcrumbTrail/BreadcrumbTrail.stories.tsx +232 -0
  64. package/client/ui/Button.stories.tsx +1 -1
  65. package/client/ui/ButtonV01/ButtonV01.stories.tsx +417 -0
  66. package/client/ui/Calendar/Calendar.stories.tsx +1 -1
  67. package/client/ui/Card.stories.tsx +285 -168
  68. package/client/ui/Carousel/Carousel.stories.tsx +1 -1
  69. package/client/ui/Chart/Chart.stories.tsx +1 -1
  70. package/client/ui/Checkbox.stories.tsx +1 -1
  71. package/client/ui/Collapsible/Collapsible.stories.tsx +1 -1
  72. package/client/ui/Colors.stories.tsx +1 -1
  73. package/client/ui/Command/Command.stories.tsx +1 -1
  74. package/client/ui/ContextMenu/ContextMenu.stories.tsx +1 -1
  75. package/client/ui/DatePicker/DatePicker.stories.tsx +293 -0
  76. package/client/ui/Dialog.stories.tsx +1 -1
  77. package/client/ui/Drawer/Drawer.stories.tsx +1 -1
  78. package/client/ui/Dropdown/Dropdown.stories.tsx +205 -0
  79. package/client/ui/EmptyState/empty-state.stories.tsx +251 -0
  80. package/client/ui/FileUpload/FileUpload.stories.tsx +218 -0
  81. package/client/ui/Footer/footer.stories.tsx +194 -0
  82. package/client/ui/Form/Form.stories.tsx +1 -1
  83. package/client/ui/Hero.stories.tsx +1 -1
  84. package/client/ui/HoverCard/HoverCard.stories.tsx +1 -1
  85. package/client/ui/Icons.stories.tsx +1 -1
  86. package/client/ui/Input.stories.tsx +1 -1
  87. package/client/ui/InputOTP/InputOTP.stories.tsx +1 -1
  88. package/client/ui/Label.stories.tsx +1 -1
  89. package/client/ui/Layout/Center.stories.tsx +100 -0
  90. package/client/ui/Layout/Columns.stories.tsx +136 -0
  91. package/client/ui/Layout/Container.stories.tsx +104 -0
  92. package/client/ui/Layout/FormLayout.stories.tsx +144 -0
  93. package/client/ui/Layout/Grid.stories.tsx +158 -0
  94. package/client/ui/Layout/GridSpan.stories.tsx +75 -0
  95. package/client/ui/Layout/HStack.stories.tsx +128 -0
  96. package/client/ui/Layout/Layout.stories.tsx +287 -0
  97. package/client/ui/Layout/LayoutContent.stories.tsx +91 -0
  98. package/client/ui/Layout/LayoutFooter.stories.tsx +77 -0
  99. package/client/ui/Layout/LayoutHeader.stories.tsx +104 -0
  100. package/client/ui/Layout/LayoutPanel.stories.tsx +143 -0
  101. package/client/ui/Layout/Positioned.stories.tsx +113 -0
  102. package/client/ui/Layout/Section.stories.tsx +84 -0
  103. package/client/ui/Layout/ShowHide.stories.tsx +99 -0
  104. package/client/ui/Layout/Spacer.stories.tsx +149 -0
  105. package/client/ui/Layout/Stack.stories.tsx +69 -0
  106. package/client/ui/Layout/StackItem.stories.tsx +124 -0
  107. package/client/ui/Layout/VStack.stories.tsx +101 -0
  108. package/client/ui/Layout/controls.ts +33 -0
  109. package/client/ui/Layout/demo.tsx +37 -0
  110. package/client/ui/Menubar/Menubar.stories.tsx +1 -1
  111. package/client/ui/MobileNav.stories.tsx +1 -1
  112. package/client/ui/Navigation.stories.tsx +1 -1
  113. package/client/ui/NavigationMenu/NavigationMenu.stories.tsx +1 -1
  114. package/client/ui/PageHeader.stories.tsx +13 -13
  115. package/client/ui/Pagination/Pagination.stories.tsx +178 -98
  116. package/client/ui/Pagination/PaginationLegacy.stories.tsx +121 -0
  117. package/client/ui/Panel.stories.tsx +1 -1
  118. package/client/ui/Popover/Popover.stories.tsx +1 -1
  119. package/client/ui/Progress/Progress.stories.tsx +1 -1
  120. package/client/ui/RadioGroup/RadioGroup.stories.tsx +1 -1
  121. package/client/ui/Resizable/Resizable.stories.tsx +1 -1
  122. package/client/ui/ScrollArea/ScrollArea.stories.tsx +1 -1
  123. package/client/ui/SearchField/SearchField.stories.tsx +153 -0
  124. package/client/ui/{SearchField.stories.tsx → SearchField/SearchFieldLegacy.stories.tsx} +2 -2
  125. package/client/ui/SectionBadge.stories.tsx +1 -1
  126. package/client/ui/Select.stories.tsx +1 -1
  127. package/client/ui/Separator/Separator.stories.tsx +1 -1
  128. package/client/ui/Sheet/Sheet.stories.tsx +1 -1
  129. package/client/ui/Sidebar/Sidebar.stories.tsx +57 -8
  130. package/client/ui/Skeleton/Skeleton.stories.tsx +1 -1
  131. package/client/ui/Slider/Slider.stories.tsx +1 -1
  132. package/client/ui/StandardNavigation.stories.tsx +1 -1
  133. package/client/ui/Stepper/Stepper.stories.tsx +344 -0
  134. package/client/ui/Switch.stories.tsx +1 -1
  135. package/client/ui/SwitchV01/SwitchV01.stories.tsx +152 -0
  136. package/client/ui/SystemBar/system-bar.stories.tsx +179 -0
  137. package/client/ui/Table/TableV01.stories.tsx +256 -0
  138. package/client/ui/Tabs.stories.tsx +1 -1
  139. package/client/ui/TabsV01/TabsV01.stories.tsx +144 -0
  140. package/client/ui/Tag.stories.tsx +1 -1
  141. package/client/ui/TagV02/TagStatusV02.stories.tsx +132 -0
  142. package/client/ui/TagV02/TagV02.stories.tsx +142 -0
  143. package/client/ui/TextAreaV01/TextAreaV01.stories.tsx +146 -0
  144. package/client/ui/TextFieldV01/PasswordFieldV01.stories.tsx +161 -0
  145. package/client/ui/TextFieldV01/TextFieldV01.stories.tsx +160 -0
  146. package/client/ui/Textarea.stories.tsx +1 -1
  147. package/client/ui/Timeline/timeline.stories.tsx +404 -0
  148. package/client/ui/Toast/Toast.stories.tsx +1 -1
  149. package/client/ui/ToastV01/ToastV01.stories.tsx +201 -0
  150. package/client/ui/Toaster/Toaster.stories.tsx +1 -1
  151. package/client/ui/Toggle.stories.tsx +1 -1
  152. package/client/ui/Tooltip.stories.tsx +287 -114
  153. package/client/ui/Typography.stories.tsx +1 -1
  154. package/dist/index.cjs.js +1 -1
  155. package/dist/index.d.ts +649 -13
  156. package/dist/index.es.js +4405 -653
  157. package/package.json +1 -1
  158. package/client/components/ui/badge.tsx +0 -33
  159. package/client/components/ui/sonner.tsx +0 -26
  160. package/client/components/ui/toggle-group.tsx +0 -76
  161. package/client/ui/Alert/Alert.stories.tsx +0 -82
  162. package/client/ui/AlertDialog/AlertDialog.stories.tsx +0 -106
  163. package/client/ui/Avatar/Avatar.stories.tsx +0 -94
  164. package/client/ui/Badge/Badge.stories.tsx +0 -60
  165. package/client/ui/Breadcrumb/Breadcrumb.stories.tsx +0 -97
  166. package/client/ui/DropdownMenu/DropdownMenu.stories.tsx +0 -139
  167. package/client/ui/Sonner/Sonner.stories.tsx +0 -48
  168. package/client/ui/Table/Table.stories.tsx +0 -135
  169. package/client/ui/ToggleGroup/ToggleGroup.stories.tsx +0 -88
@@ -0,0 +1,157 @@
1
+ import * as React from "react";
2
+ import { MoreHorizontal } from "lucide-react";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ export type PaginationV01State = "default" | "active" | "disabled";
7
+
8
+ /**
9
+ * Figma draws the chevrons as filled paths on a 12x12 box (node 12223:4804 /
10
+ * 12223:4842), not as stroked icons — lucide's stroked chevron reads noticeably
11
+ * thinner at this size. Inlined verbatim; colour comes from `currentColor`,
12
+ * which the CSS sets independently of the label.
13
+ */
14
+ const ChevronLeftFilled = (props: React.SVGProps<SVGSVGElement>) => (
15
+ <svg viewBox="0 0 12 12" fill="none" aria-hidden {...props}>
16
+ <path
17
+ d="M7.18279 1.18276C7.42687 0.938681 7.82348 0.938681 8.06755 1.18276C8.31159 1.42684 8.31162 1.82346 8.06755 2.06752L4.50994 5.62514L8.06755 9.18276C8.31159 9.42684 8.31162 9.82346 8.06755 10.0675C7.82349 10.3116 7.42687 10.3116 7.18279 10.0675L3.18279 6.06752C2.93871 5.82345 2.93871 5.42684 3.18279 5.18276L7.18279 1.18276Z"
18
+ fill="currentColor"
19
+ />
20
+ </svg>
21
+ );
22
+
23
+ const ChevronRightFilled = (props: React.SVGProps<SVGSVGElement>) => (
24
+ <svg viewBox="0 0 12 12" fill="none" aria-hidden {...props}>
25
+ <path
26
+ d="M4.06774 10.0677C3.8238 10.3116 3.42708 10.3113 3.18297 10.0677C2.9389 9.82361 2.9389 9.427 3.18297 9.18292L6.74059 5.62531L3.18297 2.06769C2.9389 1.82361 2.9389 1.427 3.18297 1.18292C3.42705 0.938845 3.82366 0.938845 4.06774 1.18292L8.06774 5.18292C8.31139 5.42704 8.31167 5.82375 8.06774 6.06769L4.06774 10.0677Z"
27
+ fill="currentColor"
28
+ />
29
+ </svg>
30
+ );
31
+
32
+ const PaginationV01 = ({
33
+ className,
34
+ children,
35
+ ...props
36
+ }: React.ComponentProps<"nav">) => (
37
+ <nav
38
+ role="navigation"
39
+ aria-label="Pagination"
40
+ className={cn("pagination-v01", className)}
41
+ {...props}
42
+ >
43
+ {children}
44
+ </nav>
45
+ );
46
+ PaginationV01.displayName = "PaginationV01";
47
+
48
+ interface ControlProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
49
+ state?: PaginationV01State;
50
+ }
51
+
52
+ const PaginationV01Previous = React.forwardRef<HTMLButtonElement, ControlProps>(
53
+ (
54
+ { className, state = "default", disabled, children = "Previous", ...props },
55
+ ref,
56
+ ) => {
57
+ const isDisabled = disabled || state === "disabled";
58
+ return (
59
+ <button
60
+ ref={ref}
61
+ type="button"
62
+ data-state={state}
63
+ disabled={isDisabled}
64
+ aria-label="Go to previous page"
65
+ className={cn("pagination-v01-control font-mazzardSoft", className)}
66
+ {...props}
67
+ >
68
+ <ChevronLeftFilled className="pagination-v01-icon" />
69
+ <span className="pagination-v01-label body-small">{children}</span>
70
+ </button>
71
+ );
72
+ },
73
+ );
74
+ PaginationV01Previous.displayName = "PaginationV01Previous";
75
+
76
+ const PaginationV01Next = React.forwardRef<HTMLButtonElement, ControlProps>(
77
+ (
78
+ { className, state = "default", disabled, children = "Next", ...props },
79
+ ref,
80
+ ) => {
81
+ const isDisabled = disabled || state === "disabled";
82
+ return (
83
+ <button
84
+ ref={ref}
85
+ type="button"
86
+ data-state={state}
87
+ disabled={isDisabled}
88
+ aria-label="Go to next page"
89
+ className={cn("pagination-v01-control font-mazzardSoft", className)}
90
+ {...props}
91
+ >
92
+ <span className="pagination-v01-label body-small">{children}</span>
93
+ <ChevronRightFilled className="pagination-v01-icon" />
94
+ </button>
95
+ );
96
+ },
97
+ );
98
+ PaginationV01Next.displayName = "PaginationV01Next";
99
+
100
+ interface PageProps extends ControlProps {
101
+ current?: boolean;
102
+ }
103
+
104
+ const PaginationV01Page = React.forwardRef<HTMLButtonElement, PageProps>(
105
+ (
106
+ { className, state = "default", current, disabled, children, ...props },
107
+ ref,
108
+ ) => {
109
+ const resolved: PaginationV01State = current ? "active" : state;
110
+ const isDisabled = disabled || resolved === "disabled";
111
+ const isCurrent = resolved === "active";
112
+ return (
113
+ <button
114
+ ref={ref}
115
+ type="button"
116
+ data-state={resolved}
117
+ disabled={isDisabled}
118
+ aria-current={isCurrent ? "page" : undefined}
119
+ aria-label={typeof children === "string" ? `Page ${children}` : "Page"}
120
+ className={cn("pagination-v01-page", className)}
121
+ {...props}
122
+ >
123
+ {children}
124
+ </button>
125
+ );
126
+ },
127
+ );
128
+ PaginationV01Page.displayName = "PaginationV01Page";
129
+
130
+ interface EllipsisProps extends React.HTMLAttributes<HTMLSpanElement> {
131
+ state?: PaginationV01State;
132
+ }
133
+
134
+ const PaginationV01Ellipsis = ({
135
+ className,
136
+ state = "default",
137
+ ...props
138
+ }: EllipsisProps) => (
139
+ <span
140
+ aria-hidden
141
+ data-state={state}
142
+ className={cn("pagination-v01-ellipsis", className)}
143
+ {...props}
144
+ >
145
+ <MoreHorizontal className="pagination-v01-icon" strokeWidth={4} />
146
+ <span className="sr-only">More pages</span>
147
+ </span>
148
+ );
149
+ PaginationV01Ellipsis.displayName = "PaginationV01Ellipsis";
150
+
151
+ export {
152
+ PaginationV01,
153
+ PaginationV01Previous,
154
+ PaginationV01Next,
155
+ PaginationV01Page,
156
+ PaginationV01Ellipsis,
157
+ };
@@ -0,0 +1,67 @@
1
+ import * as React from "react";
2
+ import { Eye, EyeOff } from "lucide-react";
3
+
4
+ import {
5
+ TextFieldV01,
6
+ type TextFieldV01Props,
7
+ } from "@/components/ui/text-field-v01";
8
+
9
+ export interface PasswordFieldV01Props
10
+ extends Omit<TextFieldV01Props, "type" | "action"> {
11
+ visible?: boolean;
12
+ defaultVisible?: boolean;
13
+ onVisibleChange?: (visible: boolean) => void;
14
+ }
15
+
16
+ const PasswordFieldV01 = React.forwardRef<
17
+ HTMLInputElement,
18
+ PasswordFieldV01Props
19
+ >(
20
+ (
21
+ {
22
+ visible: controlledVisible,
23
+ defaultVisible = false,
24
+ onVisibleChange,
25
+ disabled,
26
+ ...props
27
+ },
28
+ ref,
29
+ ) => {
30
+ const isControlled = controlledVisible !== undefined;
31
+ const [internal, setInternal] = React.useState(defaultVisible);
32
+ const visible = isControlled ? controlledVisible : internal;
33
+
34
+ const toggle = () => {
35
+ if (!isControlled) setInternal(!visible);
36
+ onVisibleChange?.(!visible);
37
+ };
38
+
39
+ return (
40
+ <TextFieldV01
41
+ ref={ref}
42
+ type={visible ? "text" : "password"}
43
+ disabled={disabled}
44
+ action={
45
+ <button
46
+ type="button"
47
+ className="text-field-v01-action"
48
+ onClick={toggle}
49
+ disabled={disabled}
50
+ aria-label={visible ? "Hide password" : "Show password"}
51
+ aria-pressed={visible}
52
+ >
53
+ {visible ? (
54
+ <Eye size={16} strokeWidth={1.5} aria-hidden />
55
+ ) : (
56
+ <EyeOff size={16} strokeWidth={1.5} aria-hidden />
57
+ )}
58
+ </button>
59
+ }
60
+ {...props}
61
+ />
62
+ );
63
+ },
64
+ );
65
+ PasswordFieldV01.displayName = "PasswordFieldV01";
66
+
67
+ export { PasswordFieldV01 };
@@ -0,0 +1,192 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ /**
6
+ * Search field — built from the Figma component set `Search field`
7
+ * (node 12008:2262, "Search Field -v0.1").
8
+ *
9
+ * Figma models it as a single `State` property: Default, Focus, Active, Filled,
10
+ * Disable. Those map onto real interaction here — focus and value derive the
11
+ * state — and `state` can also be passed to pin one for specs and stories.
12
+ *
13
+ * Spacing and radius come from `--space-*` / `--radius-*`. The sizes Figma
14
+ * specifies that are not on a scale live as `--search-field-*` variables.
15
+ */
16
+ type SearchFieldV01State =
17
+ | "default"
18
+ | "focus"
19
+ | "active"
20
+ | "filled"
21
+ | "disabled";
22
+
23
+ /**
24
+ * Figma's icons are stroked at 1.5 on a 16 viewBox — lucide's own 24 viewBox
25
+ * renders a different weight at this size, so the paths are inlined verbatim.
26
+ * All three take `currentColor`.
27
+ */
28
+ const iconProps = {
29
+ fill: "none",
30
+ strokeWidth: 1.5,
31
+ strokeLinecap: "round" as const,
32
+ strokeLinejoin: "round" as const,
33
+ "aria-hidden": true,
34
+ };
35
+
36
+ const SearchIcon = (props: React.SVGProps<SVGSVGElement>) => (
37
+ <svg viewBox="0 0 16 16" {...iconProps} {...props}>
38
+ <path
39
+ d="M15 15L10.3333 10.3333M11.8889 6.44444C11.8889 9.45133 9.45133 11.8889 6.44444 11.8889C3.43756 11.8889 1 9.45133 1 6.44444C1 3.43756 3.43756 1 6.44444 1C9.45133 1 11.8889 3.43756 11.8889 6.44444Z"
40
+ stroke="currentColor"
41
+ />
42
+ </svg>
43
+ );
44
+
45
+ const EnterIcon = (props: React.SVGProps<SVGSVGElement>) => (
46
+ <svg viewBox="0 0 16 16" {...iconProps} {...props}>
47
+ <path d="M9.16667 13.6L15 8L9.16667 2.4M15 8L1 8" stroke="currentColor" />
48
+ </svg>
49
+ );
50
+
51
+ const ClearIcon = (props: React.SVGProps<SVGSVGElement>) => (
52
+ <svg viewBox="0 0 11.1 11.1" {...iconProps} {...props}>
53
+ <path d="M10.35 0.75L0.75 10.35" stroke="currentColor" />
54
+ <path d="M0.75 0.75L10.35 10.35" stroke="currentColor" />
55
+ </svg>
56
+ );
57
+
58
+ interface SearchFieldV01Props
59
+ extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "size"> {
60
+ /**
61
+ * Figma `State`. Omit to let focus and value drive it:
62
+ * empty + blurred = `default`, empty + focused = `focus`,
63
+ * has value + focused = `active`, has value + blurred = `filled`.
64
+ */
65
+ state?: SearchFieldV01State;
66
+ /**
67
+ * Inline typeahead remainder rendered after the value — Figma's `Active`
68
+ * state ("Entry" + "typeahead"). Also shows the Enter affordance.
69
+ */
70
+ suggestion?: string;
71
+ /** Renders the clear button once there is a value (Figma `Filled`). */
72
+ onClear?: () => void;
73
+ /** Class for the outer box; `className` goes to the input. */
74
+ containerClassName?: string;
75
+ }
76
+
77
+ const SearchFieldV01 = React.forwardRef<HTMLInputElement, SearchFieldV01Props>(
78
+ (
79
+ {
80
+ className,
81
+ containerClassName,
82
+ state,
83
+ suggestion,
84
+ onClear,
85
+ placeholder = "Placeholder",
86
+ disabled,
87
+ value,
88
+ defaultValue,
89
+ onFocus,
90
+ onBlur,
91
+ onChange,
92
+ ...props
93
+ },
94
+ ref,
95
+ ) => {
96
+ const [focused, setFocused] = React.useState(false);
97
+ const [internalValue, setInternalValue] = React.useState(
98
+ defaultValue != null ? String(defaultValue) : "",
99
+ );
100
+
101
+ const currentValue = value != null ? String(value) : internalValue;
102
+ const hasValue = currentValue.length > 0;
103
+
104
+ const resolved: SearchFieldV01State =
105
+ state ??
106
+ (disabled
107
+ ? "disabled"
108
+ : focused
109
+ ? hasValue
110
+ ? "active"
111
+ : "focus"
112
+ : hasValue
113
+ ? "filled"
114
+ : "default");
115
+
116
+ // Figma drops the search icon as soon as the field is in use.
117
+ const showSearchIcon = resolved === "default" || resolved === "disabled";
118
+ const showTypeahead = resolved === "active" && Boolean(suggestion);
119
+ const showEnter = resolved === "active";
120
+ const showClear = resolved === "filled" && hasValue && Boolean(onClear);
121
+
122
+ return (
123
+ <div
124
+ data-state={resolved}
125
+ className={cn(
126
+ "search-field-v01",
127
+ showTypeahead && "search-field-v01--typeahead",
128
+ containerClassName,
129
+ )}
130
+ >
131
+ {showSearchIcon && (
132
+ <span className="search-field-v01-icon-box">
133
+ <SearchIcon className="search-field-v01-icon" />
134
+ </span>
135
+ )}
136
+
137
+ <span className="search-field-v01-field">
138
+ <input
139
+ ref={ref}
140
+ type="search"
141
+ role="searchbox"
142
+ className={cn("search-field-v01-input", className)}
143
+ placeholder={placeholder}
144
+ disabled={disabled || resolved === "disabled"}
145
+ value={value != null ? value : internalValue}
146
+ onFocus={(event) => {
147
+ setFocused(true);
148
+ onFocus?.(event);
149
+ }}
150
+ onBlur={(event) => {
151
+ setFocused(false);
152
+ onBlur?.(event);
153
+ }}
154
+ onChange={(event) => {
155
+ if (value == null) setInternalValue(event.target.value);
156
+ onChange?.(event);
157
+ }}
158
+ {...props}
159
+ />
160
+ {showTypeahead && (
161
+ <span className="search-field-v01-suggestion" aria-hidden="true">
162
+ {suggestion}
163
+ </span>
164
+ )}
165
+ </span>
166
+
167
+ {(showEnter || showClear) && (
168
+ <span className="search-field-v01-actions">
169
+ {showClear ? (
170
+ <button
171
+ type="button"
172
+ className="search-field-v01-clear"
173
+ aria-label="Clear search"
174
+ onClick={onClear}
175
+ >
176
+ <ClearIcon className="search-field-v01-clear-glyph" />
177
+ </button>
178
+ ) : (
179
+ <span className="search-field-v01-enter-box">
180
+ <EnterIcon className="search-field-v01-icon" />
181
+ </span>
182
+ )}
183
+ </span>
184
+ )}
185
+ </div>
186
+ );
187
+ },
188
+ );
189
+ SearchFieldV01.displayName = "SearchFieldV01";
190
+
191
+ export { SearchFieldV01 };
192
+ export type { SearchFieldV01Props, SearchFieldV01State };
@@ -0,0 +1,49 @@
1
+ import * as React from "react";
2
+ import { Text } from "@syscore/ui-library";
3
+ import { Stack } from "./layout";
4
+
5
+ // ---------------------------------------------------------------------------
6
+ // SectionHeading — title + subtitle pair (RULE #-4B / ui-spacing-and-density.md)
7
+ //
8
+ // Owns the gap (floor gap-3, defaults to the gap-4 target) AND the line-height on
9
+ // each line, via DS_PENDING_CSS combined selectors that beat the DS typography
10
+ // class on specificity. No page should stack two Text lines and hand-roll this
11
+ // itself -- that's exactly how the heading/subtitle overlap bug shipped.
12
+ // ---------------------------------------------------------------------------
13
+ interface SectionHeadingProps {
14
+ title: React.ReactNode;
15
+ subtitle?: React.ReactNode;
16
+ titleAs?: React.ElementType;
17
+ titleVariant?: string;
18
+ subtitleVariant?: string;
19
+ gap?: number;
20
+ }
21
+
22
+ const SectionHeading = React.forwardRef<HTMLElement, SectionHeadingProps>(
23
+ (
24
+ {
25
+ title,
26
+ subtitle,
27
+ titleAs,
28
+ titleVariant = "heading-xxsmall",
29
+ subtitleVariant = "body-base",
30
+ gap = 4,
31
+ },
32
+ ref,
33
+ ) => (
34
+ <Stack ref={ref} gap={gap}>
35
+ <Text as={titleAs} variant={titleVariant} className="section-heading__title">
36
+ {title}
37
+ </Text>
38
+ {subtitle ? (
39
+ <Text variant={subtitleVariant} className="section-heading__subtitle">
40
+ {subtitle}
41
+ </Text>
42
+ ) : null}
43
+ </Stack>
44
+ ),
45
+ );
46
+ SectionHeading.displayName = "SectionHeading";
47
+
48
+ export { SectionHeading };
49
+ export type { SectionHeadingProps };
@@ -573,11 +573,10 @@ const SidebarMenuButton = React.forwardRef<
573
573
  }
574
574
 
575
575
  return (
576
- <Tooltip>
576
+ <Tooltip trigger="hover">
577
577
  <TooltipTrigger asChild>{button}</TooltipTrigger>
578
578
  <TooltipContent
579
- side="right"
580
- align="center"
579
+ direction="left"
581
580
  hidden={state !== "collapsed" || isMobile}
582
581
  {...tooltip}
583
582
  />
@@ -154,6 +154,10 @@ interface NavigationProps {
154
154
  beforeActions?: React.ReactNode; // Slot for custom items before user icon (e.g., language switcher)
155
155
  userDropdown?: React.ReactNode; // Dropdown content to show when user icon is clicked
156
156
  isStrategy?: boolean;
157
+ /** Show the small alpha (α) badge next to the logo. Default true. */
158
+ showAlpha?: boolean;
159
+ /** Show the back/close button in strategy mode. Default true. */
160
+ showClose?: boolean;
157
161
  Link?: LinkComponent; // Optional Link component from routing library
158
162
  onLinkClick?: (href: string) => void; // Fallback callback for navigation
159
163
  onClose?: () => void;
@@ -166,6 +170,8 @@ export const StandardNavigation: React.FC<NavigationProps> = ({
166
170
  beforeActions,
167
171
  userDropdown,
168
172
  isStrategy = false,
173
+ showAlpha = true,
174
+ showClose = true,
169
175
  Link: LinkComponent,
170
176
  onLinkClick,
171
177
  onClose,
@@ -393,7 +399,7 @@ export const StandardNavigation: React.FC<NavigationProps> = ({
393
399
 
394
400
  <div className="navigation-container">
395
401
  <div className="navigation-logo-container">
396
- {isStrategy ? (
402
+ {isStrategy && showClose ? (
397
403
  <Button
398
404
  className="navigation-close-button"
399
405
  size="icon"
@@ -425,7 +431,7 @@ export const StandardNavigation: React.FC<NavigationProps> = ({
425
431
 
426
432
  <NavLogo dark={isStrategy} />
427
433
  <StandardLogo className={cn(!isStrategy ? "text-white" : "text-black")} />
428
- <AlphaIcon dark={isStrategy} />
434
+ {showAlpha ? <AlphaIcon dark={isStrategy} /> : null}
429
435
  </>
430
436
 
431
437
  ) : null}
@@ -0,0 +1,148 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ // ---------------------------------------------------------------------------
5
+ // Types
6
+ // ---------------------------------------------------------------------------
7
+ export interface StepItem {
8
+ title: string;
9
+ description?: string;
10
+ }
11
+
12
+ type StepStatus = "complete" | "current" | "upcoming";
13
+ type StepperOrientation = "horizontal" | "vertical";
14
+ type StepperVariant = "default" | "outlined";
15
+ type StepperSize = "sm" | "md" | "lg";
16
+
17
+ export interface StepperProps {
18
+ steps: StepItem[];
19
+ currentStep: number; // 0-indexed
20
+ orientation?: StepperOrientation;
21
+ variant?: StepperVariant;
22
+ size?: StepperSize;
23
+ onStepClick?: (index: number) => void;
24
+ className?: string;
25
+ }
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Helpers
29
+ // ---------------------------------------------------------------------------
30
+ function getStatus(index: number, currentStep: number): StepStatus {
31
+ if (index < currentStep) return "complete";
32
+ if (index === currentStep) return "current";
33
+ return "upcoming";
34
+ }
35
+
36
+ const CheckIcon = () => (
37
+ <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
38
+ <path
39
+ d="M2.5 7L5.5 10L11.5 4"
40
+ stroke="currentColor"
41
+ strokeWidth="2"
42
+ strokeLinecap="round"
43
+ strokeLinejoin="round"
44
+ />
45
+ </svg>
46
+ );
47
+
48
+ // ---------------------------------------------------------------------------
49
+ // Stepper
50
+ // ---------------------------------------------------------------------------
51
+ const Stepper = React.forwardRef<HTMLDivElement, StepperProps>(
52
+ (
53
+ {
54
+ steps,
55
+ currentStep,
56
+ orientation = "horizontal",
57
+ variant = "default",
58
+ size = "md",
59
+ onStepClick,
60
+ className,
61
+ },
62
+ ref,
63
+ ) => {
64
+ return (
65
+ <div
66
+ ref={ref}
67
+ role="list"
68
+ aria-label="Progress steps"
69
+ className={cn(
70
+ "stepper",
71
+ `stepper--${orientation}`,
72
+ `stepper--${size}`,
73
+ className,
74
+ )}
75
+ >
76
+ {steps.map((step, index) => {
77
+ const status = getStatus(index, currentStep);
78
+ const isClickable = !!onStepClick && status === "complete";
79
+ const isLast = index === steps.length - 1;
80
+
81
+ return (
82
+ <div
83
+ key={index}
84
+ role="listitem"
85
+ aria-current={status === "current" ? "step" : undefined}
86
+ className={cn(
87
+ "stepper-item",
88
+ `stepper-item--${status}`,
89
+ orientation === "horizontal" && !isLast && "stepper-item--with-connector",
90
+ )}
91
+ >
92
+ {/* Indicator + connector row */}
93
+ <div className="stepper-indicator-row">
94
+ {/* Circle */}
95
+ <button
96
+ type="button"
97
+ disabled={!isClickable}
98
+ onClick={() => isClickable && onStepClick(index)}
99
+ className={cn(
100
+ "stepper-indicator",
101
+ `stepper-indicator--${status}`,
102
+ `stepper-indicator--${variant}`,
103
+ isClickable && "stepper-indicator--clickable",
104
+ )}
105
+ aria-label={`Step ${index + 1}: ${step.title} (${status})`}
106
+ >
107
+ {status === "complete" ? (
108
+ <CheckIcon />
109
+ ) : (
110
+ <span className="stepper-indicator-number">{index + 1}</span>
111
+ )}
112
+ </button>
113
+
114
+ {/* Connector line — horizontal: after indicator, vertical: below */}
115
+ {!isLast && (
116
+ <div
117
+ className={cn(
118
+ "stepper-connector",
119
+ `stepper-connector--${orientation}`,
120
+ index < currentStep && "stepper-connector--complete",
121
+ )}
122
+ aria-hidden="true"
123
+ />
124
+ )}
125
+ </div>
126
+
127
+ {/* Label */}
128
+ <div className="stepper-label">
129
+ <span className={cn("stepper-title", `stepper-title--${status}`)}>
130
+ {step.title}
131
+ </span>
132
+ {step.description && (
133
+ <span className={cn("stepper-description", `stepper-description--${status}`)}>
134
+ {step.description}
135
+ </span>
136
+ )}
137
+ </div>
138
+ </div>
139
+ );
140
+ })}
141
+ </div>
142
+ );
143
+ },
144
+ );
145
+
146
+ Stepper.displayName = "Stepper";
147
+
148
+ export { Stepper };