@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.
- package/client/App.tsx +0 -2
- package/client/assets/alert/icon-clear.svg +4 -0
- package/client/assets/alert/icon-done.svg +3 -0
- package/client/assets/avatar-identity/photo.png +0 -0
- package/client/assets/avatar-identity/placeholder-icon.svg +8 -0
- package/client/assets/breadcrumb/icon-ellipsis.svg +5 -0
- package/client/assets/breadcrumb/icon-home.svg +3 -0
- package/client/assets/breadcrumb/icon-separator.svg +3 -0
- package/client/assets/button-v01/icon-back.svg +4 -0
- package/client/assets/button-v01/icon-check.svg +3 -0
- package/client/assets/button-v01/icon-close.svg +4 -0
- package/client/assets/button-v01/icon-delete.svg +3 -0
- package/client/assets/button-v01/icon-device.svg +3 -0
- package/client/assets/dropdown/icon-carrot-contract.svg +3 -0
- package/client/assets/dropdown/icon-carrot-expand.svg +3 -0
- package/client/assets/dropdown/icon-clear.svg +4 -0
- package/client/assets/dropdown/icon-enter.svg +3 -0
- package/client/components/icons/UtilityChangeType.tsx +21 -0
- package/client/components/ui/alert-dialog-panel.tsx +246 -0
- package/client/components/ui/alert-v02.tsx +214 -0
- package/client/components/ui/app-bar.tsx +299 -0
- package/client/components/ui/avatar-identity.tsx +122 -0
- package/client/components/ui/banner.tsx +108 -0
- package/client/components/ui/bottom-navigation.tsx +223 -0
- package/client/components/ui/breadcrumb-trail.tsx +170 -0
- package/client/components/ui/button-v01.tsx +238 -0
- package/client/components/ui/button.tsx +2 -0
- package/client/components/ui/card.tsx +108 -26
- package/client/components/ui/code-badge.tsx +62 -0
- package/client/components/ui/date-picker.tsx +188 -0
- package/client/components/ui/dropdown.tsx +448 -0
- package/client/components/ui/empty-state.tsx +197 -0
- package/client/components/ui/file-upload.tsx +214 -0
- package/client/components/ui/footer.tsx +179 -0
- package/client/components/ui/layout.tsx +1182 -0
- package/client/components/ui/pagination-v01.tsx +157 -0
- package/client/components/ui/password-field-v01.tsx +67 -0
- package/client/components/ui/search-field-v01.tsx +192 -0
- package/client/components/ui/section-heading.tsx +49 -0
- package/client/components/ui/sidebar.tsx +2 -3
- package/client/components/ui/standard-navigation.tsx +8 -2
- package/client/components/ui/stepper.tsx +148 -0
- package/client/components/ui/switch-v01.tsx +71 -0
- package/client/components/ui/system-bar.tsx +119 -0
- package/client/components/ui/table-v01.tsx +230 -0
- package/client/components/ui/tabs-v01.tsx +99 -0
- package/client/components/ui/tag-v02.tsx +113 -0
- package/client/components/ui/text-area-v01.tsx +33 -0
- package/client/components/ui/text-field-v01.tsx +108 -0
- package/client/components/ui/timeline.tsx +181 -0
- package/client/components/ui/toast-v01.tsx +206 -0
- package/client/components/ui/tooltip.tsx +264 -71
- package/client/global.css +9565 -4378
- package/client/storybook.css +125 -0
- package/client/ui/Accordion.stories.tsx +1 -1
- package/client/ui/AlertDialogPanel/AlertDialogPanel.stories.tsx +150 -0
- package/client/ui/AlertV02/AlertV02.stories.tsx +188 -0
- package/client/ui/AppBar/app-bar.stories.tsx +280 -0
- package/client/ui/AspectRatio.stories.tsx +1 -1
- package/client/ui/AvatarIdentity/AvatarIdentity.stories.tsx +77 -0
- package/client/ui/Banner/banner.stories.tsx +238 -0
- package/client/ui/BottomNavigation/bottom-navigation.stories.tsx +285 -0
- package/client/ui/BreadcrumbTrail/BreadcrumbTrail.stories.tsx +232 -0
- package/client/ui/Button.stories.tsx +1 -1
- package/client/ui/ButtonV01/ButtonV01.stories.tsx +417 -0
- package/client/ui/Calendar/Calendar.stories.tsx +1 -1
- package/client/ui/Card.stories.tsx +285 -168
- package/client/ui/Carousel/Carousel.stories.tsx +1 -1
- package/client/ui/Chart/Chart.stories.tsx +1 -1
- package/client/ui/Checkbox.stories.tsx +1 -1
- package/client/ui/Collapsible/Collapsible.stories.tsx +1 -1
- package/client/ui/Colors.stories.tsx +1 -1
- package/client/ui/Command/Command.stories.tsx +1 -1
- package/client/ui/ContextMenu/ContextMenu.stories.tsx +1 -1
- package/client/ui/DatePicker/DatePicker.stories.tsx +293 -0
- package/client/ui/Dialog.stories.tsx +1 -1
- package/client/ui/Drawer/Drawer.stories.tsx +1 -1
- package/client/ui/Dropdown/Dropdown.stories.tsx +205 -0
- package/client/ui/EmptyState/empty-state.stories.tsx +251 -0
- package/client/ui/FileUpload/FileUpload.stories.tsx +218 -0
- package/client/ui/Footer/footer.stories.tsx +194 -0
- package/client/ui/Form/Form.stories.tsx +1 -1
- package/client/ui/Hero.stories.tsx +1 -1
- package/client/ui/HoverCard/HoverCard.stories.tsx +1 -1
- package/client/ui/Icons.stories.tsx +1 -1
- package/client/ui/Input.stories.tsx +1 -1
- package/client/ui/InputOTP/InputOTP.stories.tsx +1 -1
- package/client/ui/Label.stories.tsx +1 -1
- package/client/ui/Layout/Center.stories.tsx +100 -0
- package/client/ui/Layout/Columns.stories.tsx +136 -0
- package/client/ui/Layout/Container.stories.tsx +104 -0
- package/client/ui/Layout/FormLayout.stories.tsx +144 -0
- package/client/ui/Layout/Grid.stories.tsx +158 -0
- package/client/ui/Layout/GridSpan.stories.tsx +75 -0
- package/client/ui/Layout/HStack.stories.tsx +128 -0
- package/client/ui/Layout/Layout.stories.tsx +287 -0
- package/client/ui/Layout/LayoutContent.stories.tsx +91 -0
- package/client/ui/Layout/LayoutFooter.stories.tsx +77 -0
- package/client/ui/Layout/LayoutHeader.stories.tsx +104 -0
- package/client/ui/Layout/LayoutPanel.stories.tsx +143 -0
- package/client/ui/Layout/Positioned.stories.tsx +113 -0
- package/client/ui/Layout/Section.stories.tsx +84 -0
- package/client/ui/Layout/ShowHide.stories.tsx +99 -0
- package/client/ui/Layout/Spacer.stories.tsx +149 -0
- package/client/ui/Layout/Stack.stories.tsx +69 -0
- package/client/ui/Layout/StackItem.stories.tsx +124 -0
- package/client/ui/Layout/VStack.stories.tsx +101 -0
- package/client/ui/Layout/controls.ts +33 -0
- package/client/ui/Layout/demo.tsx +37 -0
- package/client/ui/Menubar/Menubar.stories.tsx +1 -1
- package/client/ui/MobileNav.stories.tsx +1 -1
- package/client/ui/Navigation.stories.tsx +1 -1
- package/client/ui/NavigationMenu/NavigationMenu.stories.tsx +1 -1
- package/client/ui/PageHeader.stories.tsx +13 -13
- package/client/ui/Pagination/Pagination.stories.tsx +178 -98
- package/client/ui/Pagination/PaginationLegacy.stories.tsx +121 -0
- package/client/ui/Panel.stories.tsx +1 -1
- package/client/ui/Popover/Popover.stories.tsx +1 -1
- package/client/ui/Progress/Progress.stories.tsx +1 -1
- package/client/ui/RadioGroup/RadioGroup.stories.tsx +1 -1
- package/client/ui/Resizable/Resizable.stories.tsx +1 -1
- package/client/ui/ScrollArea/ScrollArea.stories.tsx +1 -1
- package/client/ui/SearchField/SearchField.stories.tsx +153 -0
- package/client/ui/{SearchField.stories.tsx → SearchField/SearchFieldLegacy.stories.tsx} +2 -2
- package/client/ui/SectionBadge.stories.tsx +1 -1
- package/client/ui/Select.stories.tsx +1 -1
- package/client/ui/Separator/Separator.stories.tsx +1 -1
- package/client/ui/Sheet/Sheet.stories.tsx +1 -1
- package/client/ui/Sidebar/Sidebar.stories.tsx +57 -8
- package/client/ui/Skeleton/Skeleton.stories.tsx +1 -1
- package/client/ui/Slider/Slider.stories.tsx +1 -1
- package/client/ui/StandardNavigation.stories.tsx +1 -1
- package/client/ui/Stepper/Stepper.stories.tsx +344 -0
- package/client/ui/Switch.stories.tsx +1 -1
- package/client/ui/SwitchV01/SwitchV01.stories.tsx +152 -0
- package/client/ui/SystemBar/system-bar.stories.tsx +179 -0
- package/client/ui/Table/TableV01.stories.tsx +256 -0
- package/client/ui/Tabs.stories.tsx +1 -1
- package/client/ui/TabsV01/TabsV01.stories.tsx +144 -0
- package/client/ui/Tag.stories.tsx +1 -1
- package/client/ui/TagV02/TagStatusV02.stories.tsx +132 -0
- package/client/ui/TagV02/TagV02.stories.tsx +142 -0
- package/client/ui/TextAreaV01/TextAreaV01.stories.tsx +146 -0
- package/client/ui/TextFieldV01/PasswordFieldV01.stories.tsx +161 -0
- package/client/ui/TextFieldV01/TextFieldV01.stories.tsx +160 -0
- package/client/ui/Textarea.stories.tsx +1 -1
- package/client/ui/Timeline/timeline.stories.tsx +404 -0
- package/client/ui/Toast/Toast.stories.tsx +1 -1
- package/client/ui/ToastV01/ToastV01.stories.tsx +201 -0
- package/client/ui/Toaster/Toaster.stories.tsx +1 -1
- package/client/ui/Toggle.stories.tsx +1 -1
- package/client/ui/Tooltip.stories.tsx +287 -114
- package/client/ui/Typography.stories.tsx +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.d.ts +649 -13
- package/dist/index.es.js +4405 -653
- package/package.json +1 -1
- package/client/components/ui/badge.tsx +0 -33
- package/client/components/ui/sonner.tsx +0 -26
- package/client/components/ui/toggle-group.tsx +0 -76
- package/client/ui/Alert/Alert.stories.tsx +0 -82
- package/client/ui/AlertDialog/AlertDialog.stories.tsx +0 -106
- package/client/ui/Avatar/Avatar.stories.tsx +0 -94
- package/client/ui/Badge/Badge.stories.tsx +0 -60
- package/client/ui/Breadcrumb/Breadcrumb.stories.tsx +0 -97
- package/client/ui/DropdownMenu/DropdownMenu.stories.tsx +0 -139
- package/client/ui/Sonner/Sonner.stories.tsx +0 -48
- package/client/ui/Table/Table.stories.tsx +0 -135
- 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
|
-
|
|
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 };
|