@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,71 @@
1
+ import * as React from "react";
2
+ import * as SwitchPrimitives from "@radix-ui/react-switch";
3
+ import { cva, type VariantProps } from "class-variance-authority";
4
+
5
+ import { cn } from "@/lib/utils";
6
+
7
+ const switchV01Variants = cva("switch-v01 font-mazzardSoft", {
8
+ variants: {
9
+ textDirection: {
10
+ right: "switch-v01--text-right",
11
+ left: "switch-v01--text-left",
12
+ },
13
+ },
14
+ defaultVariants: {
15
+ textDirection: "right",
16
+ },
17
+ });
18
+
19
+ export type SwitchV01TextDirection = NonNullable<
20
+ VariantProps<typeof switchV01Variants>["textDirection"]
21
+ >;
22
+
23
+ export interface SwitchV01Props
24
+ extends Omit<
25
+ React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>,
26
+ "children"
27
+ >,
28
+ VariantProps<typeof switchV01Variants> {
29
+ label?: React.ReactNode;
30
+ showText?: boolean;
31
+ }
32
+
33
+ const SwitchV01 = React.forwardRef<
34
+ React.ElementRef<typeof SwitchPrimitives.Root>,
35
+ SwitchV01Props
36
+ >(
37
+ (
38
+ {
39
+ className,
40
+ textDirection = "right",
41
+ label = "Label",
42
+ showText = true,
43
+ ...props
44
+ },
45
+ ref,
46
+ ) => {
47
+ const toggle = (
48
+ <SwitchPrimitives.Root ref={ref} className="switch-v01-toggle" {...props}>
49
+ <SwitchPrimitives.Thumb className="switch-v01-thumb" />
50
+ </SwitchPrimitives.Root>
51
+ );
52
+
53
+ if (!showText || label == null || label === "") {
54
+ return (
55
+ <span className={cn(switchV01Variants({ textDirection }), className)}>
56
+ {toggle}
57
+ </span>
58
+ );
59
+ }
60
+
61
+ return (
62
+ <label className={cn(switchV01Variants({ textDirection }), className)}>
63
+ {toggle}
64
+ <span className="switch-v01-label overline-medium">{label}</span>
65
+ </label>
66
+ );
67
+ },
68
+ );
69
+ SwitchV01.displayName = "SwitchV01";
70
+
71
+ export { SwitchV01, switchV01Variants };
@@ -0,0 +1,119 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ // ─── SystemBar Root ───────────────────────────────────────────────────────────
5
+
6
+ export interface SystemBarProps extends React.HTMLAttributes<HTMLElement> {
7
+ /**
8
+ * Background color — any CSS color or var() token.
9
+ * When set, text and icons automatically switch to white for contrast.
10
+ */
11
+ color?: string;
12
+ /**
13
+ * Window mode: renders a desktop-style title bar with
14
+ * message indicator on the left and window controls on the right.
15
+ */
16
+ window?: boolean;
17
+ /** Height of the bar in px (default 24 for status bar, 32 for window) */
18
+ height?: number;
19
+ }
20
+
21
+ const SystemBar = React.forwardRef<HTMLElement, SystemBarProps>(
22
+ (
23
+ {
24
+ className,
25
+ children,
26
+ color,
27
+ window: isWindow = false,
28
+ height,
29
+ style,
30
+ ...props
31
+ },
32
+ ref
33
+ ) => {
34
+ const barHeight = height ?? (isWindow ? 32 : 24);
35
+
36
+ return (
37
+ <header
38
+ ref={ref}
39
+ role="banner"
40
+ style={{
41
+ height: barHeight,
42
+ backgroundColor: color,
43
+ ...style,
44
+ }}
45
+ className={cn(
46
+ "system-bar",
47
+ isWindow && "system-bar--window",
48
+ color && "system-bar--colored",
49
+ className
50
+ )}
51
+ {...props}
52
+ >
53
+ {children}
54
+ </header>
55
+ );
56
+ }
57
+ );
58
+ SystemBar.displayName = "SystemBar";
59
+
60
+ // ─── SystemBarSpacer ──────────────────────────────────────────────────────────
61
+ // Pushes content to the right (flex: 1).
62
+
63
+ export interface SystemBarSpacerProps
64
+ extends React.HTMLAttributes<HTMLSpanElement> {}
65
+
66
+ const SystemBarSpacer = React.forwardRef<HTMLSpanElement, SystemBarSpacerProps>(
67
+ ({ className, ...props }, ref) => (
68
+ <span
69
+ ref={ref}
70
+ className={cn("system-bar-spacer", className)}
71
+ {...props}
72
+ />
73
+ )
74
+ );
75
+ SystemBarSpacer.displayName = "SystemBarSpacer";
76
+
77
+ // ─── SystemBarIcon ────────────────────────────────────────────────────────────
78
+ // Thin wrapper so icon children pick up the right size + color.
79
+
80
+ export interface SystemBarIconProps
81
+ extends React.HTMLAttributes<HTMLSpanElement> {}
82
+
83
+ const SystemBarIcon = React.forwardRef<HTMLSpanElement, SystemBarIconProps>(
84
+ ({ className, ...props }, ref) => (
85
+ <span
86
+ ref={ref}
87
+ className={cn("system-bar-icon", className)}
88
+ {...props}
89
+ />
90
+ )
91
+ );
92
+ SystemBarIcon.displayName = "SystemBarIcon";
93
+
94
+ // ─── SystemBarAction ──────────────────────────────────────────────────────────
95
+ // Icon button used in window mode (minimise / maximise / close).
96
+
97
+ export interface SystemBarActionProps
98
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
99
+ "aria-label": string;
100
+ }
101
+
102
+ const SystemBarAction = React.forwardRef<
103
+ HTMLButtonElement,
104
+ SystemBarActionProps
105
+ >(({ className, children, ...props }, ref) => (
106
+ <button
107
+ ref={ref}
108
+ type="button"
109
+ className={cn("system-bar-action", className)}
110
+ {...props}
111
+ >
112
+ {children}
113
+ </button>
114
+ ));
115
+ SystemBarAction.displayName = "SystemBarAction";
116
+
117
+ // ─── Exports ─────────────────────────────────────────────────────────────────
118
+
119
+ export { SystemBar, SystemBarSpacer, SystemBarIcon, SystemBarAction };
@@ -0,0 +1,230 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ /**
6
+ * Table — built from the Figma component set `Table component` (node 12176:414).
7
+ *
8
+ * Figma models a single *cell* with four properties and ~135 variants. Two of
9
+ * them are structural rather than authored:
10
+ *
11
+ * - `Alignment` (Left / Center / Right) is really the cell's **column
12
+ * position**: it selects which borders and which corner radius the cell
13
+ * carries, not how the text sits. `Center` carries all four, so every column
14
+ * is divided; that grid is derived here from the cell order.
15
+ * - `Placement` (Header / Intermediate / Bottom) is the **row position**.
16
+ * Header is its own element; Intermediate vs Bottom is derived from
17
+ * `tr:last-child`.
18
+ *
19
+ * `Content type` (Text / Text + Tag / Text + Action / Tag / Action) collapses
20
+ * into composition — a cell renders whatever children it is given, with the
21
+ * 10px gap Figma specifies.
22
+ *
23
+ * That leaves `density` as the one real variant, and it is the cell's *vertical*
24
+ * padding only — 16 / 20 / 24, all on the `--space-*` scale. Horizontal padding
25
+ * stays 16 at every density. Cell heights then follow from the content, matching
26
+ * Figma's 40/48/56 for headers, 43/51/59 for text rows and 64/72/80 for tag and
27
+ * action rows.
28
+ */
29
+ type TableV01Density = "compact" | "balanced" | "spacious";
30
+ type TableV01Align = "left" | "center" | "right";
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // Root
34
+ // ---------------------------------------------------------------------------
35
+
36
+ interface TableV01Props extends React.TableHTMLAttributes<HTMLTableElement> {
37
+ /** Figma `Density`. Sets the cell's vertical padding; heights follow. Default `compact`. */
38
+ density?: TableV01Density;
39
+ /** Wrapper class, for width or overflow handling. */
40
+ containerClassName?: string;
41
+ }
42
+
43
+ const TableV01 = React.forwardRef<HTMLTableElement, TableV01Props>(
44
+ ({ className, containerClassName, density = "compact", ...props }, ref) => (
45
+ <div className={cn("table-v01-wrapper", containerClassName)}>
46
+ <table
47
+ ref={ref}
48
+ data-density={density}
49
+ className={cn("table-v01", className)}
50
+ {...props}
51
+ />
52
+ </div>
53
+ ),
54
+ );
55
+ TableV01.displayName = "TableV01";
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // Sections and rows
59
+ // ---------------------------------------------------------------------------
60
+
61
+ const TableV01Header = React.forwardRef<
62
+ HTMLTableSectionElement,
63
+ React.HTMLAttributes<HTMLTableSectionElement>
64
+ >(({ className, ...props }, ref) => (
65
+ <thead ref={ref} className={cn("table-v01-header", className)} {...props} />
66
+ ));
67
+ TableV01Header.displayName = "TableV01Header";
68
+
69
+ const TableV01Body = React.forwardRef<
70
+ HTMLTableSectionElement,
71
+ React.HTMLAttributes<HTMLTableSectionElement>
72
+ >(({ className, ...props }, ref) => (
73
+ <tbody ref={ref} className={cn("table-v01-body", className)} {...props} />
74
+ ));
75
+ TableV01Body.displayName = "TableV01Body";
76
+
77
+ /**
78
+ * Figma's `Placement=Bottom` is the last row of the table. As a section it also
79
+ * takes over the bottom corner radii from the body.
80
+ */
81
+ const TableV01Footer = React.forwardRef<
82
+ HTMLTableSectionElement,
83
+ React.HTMLAttributes<HTMLTableSectionElement>
84
+ >(({ className, ...props }, ref) => (
85
+ <tfoot ref={ref} className={cn("table-v01-footer", className)} {...props} />
86
+ ));
87
+ TableV01Footer.displayName = "TableV01Footer";
88
+
89
+ const TableV01Row = React.forwardRef<
90
+ HTMLTableRowElement,
91
+ React.HTMLAttributes<HTMLTableRowElement>
92
+ >(({ className, ...props }, ref) => (
93
+ <tr ref={ref} className={cn("table-v01-row", className)} {...props} />
94
+ ));
95
+ TableV01Row.displayName = "TableV01Row";
96
+
97
+ // ---------------------------------------------------------------------------
98
+ // Cells
99
+ // ---------------------------------------------------------------------------
100
+
101
+ /**
102
+ * Figma's code/strategy tag (instance `code_strategy_*`): emerald tint with a
103
+ * 1.5px tinted border and tabular figures.
104
+ */
105
+ interface TableV01TagProps extends React.HTMLAttributes<HTMLSpanElement> {}
106
+
107
+ const TableV01Tag = React.forwardRef<HTMLSpanElement, TableV01TagProps>(
108
+ ({ className, ...props }, ref) => (
109
+ <span ref={ref} className={cn("table-v01-tag", className)} {...props} />
110
+ ),
111
+ );
112
+ TableV01Tag.displayName = "TableV01Tag";
113
+
114
+ interface TableV01CellProps
115
+ extends React.ThHTMLAttributes<HTMLTableCellElement> {
116
+ /**
117
+ * Text alignment within the cell. Figma's `Alignment` property is column
118
+ * position, not text alignment, so this is additive — tables need it for
119
+ * numeric columns.
120
+ */
121
+ align?: TableV01Align;
122
+ /** The column label. Figma sets it uppercase in the overline ramp. */
123
+ label?: React.ReactNode;
124
+ }
125
+
126
+ /** Figma `Placement=Header`: cyan/50 fill, uppercase overline label in cyan/800. */
127
+ const TableV01HeaderCell = React.forwardRef<
128
+ HTMLTableCellElement,
129
+ TableV01CellProps
130
+ >(({ className, align = "left", label, children, ...props }, ref) => (
131
+ <th
132
+ ref={ref}
133
+ scope="col"
134
+ data-align={align}
135
+ className={cn("table-v01-header-cell", className)}
136
+ {...props}
137
+ >
138
+ <span className="table-v01-cell-inner overline-medium">
139
+ {label}
140
+ {children}
141
+ </span>
142
+ </th>
143
+ ));
144
+ TableV01HeaderCell.displayName = "TableV01HeaderCell";
145
+
146
+ /**
147
+ * Figma `Placement=Intermediates | Bottom`: white fill, Mazzard Medium 16/21.
148
+ *
149
+ * Figma's `Content type` axis is expressed as content props rather than an
150
+ * enum: pass any of `text`, `tag` and `action` and the cell renders them in
151
+ * Figma's order with the 10px gap. `children` still works for anything the
152
+ * props cannot express (a link, two lines, a custom control).
153
+ */
154
+ interface TableV01BodyCellProps
155
+ extends React.TdHTMLAttributes<HTMLTableCellElement> {
156
+ /** Text alignment within the cell. Additive to Figma. */
157
+ align?: TableV01Align;
158
+ /** Figma `Content type=Text …`: Mazzard Medium 16/21 in gray/800. */
159
+ text?: React.ReactNode;
160
+ /** Figma `Content type=… + Tag`: wrapped in the code/strategy tag. */
161
+ tag?: React.ReactNode;
162
+ /** Figma `Content type=… + Action`: pass one or more `TableV01Action`. */
163
+ action?: React.ReactNode;
164
+ }
165
+
166
+ const TableV01Cell = React.forwardRef<
167
+ HTMLTableCellElement,
168
+ TableV01BodyCellProps
169
+ >(
170
+ (
171
+ { className, align = "left", text, tag, action, children, ...props },
172
+ ref,
173
+ ) => (
174
+ <td
175
+ ref={ref}
176
+ data-align={align}
177
+ className={cn("table-v01-cell", className)}
178
+ {...props}
179
+ >
180
+ <span className="table-v01-cell-inner">
181
+ {text}
182
+ {tag != null ? <TableV01Tag>{tag}</TableV01Tag> : null}
183
+ {action}
184
+ {children}
185
+ </span>
186
+ </td>
187
+ ),
188
+ );
189
+ TableV01Cell.displayName = "TableV01Cell";
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Cell content — the pieces Figma's `Content type` composes
193
+ // ---------------------------------------------------------------------------
194
+
195
+ /**
196
+ * Figma's `button_icon_*`: a 32px white circle with a gray/100 border holding a
197
+ * 14px icon. Pass the icon as children.
198
+ */
199
+ const TableV01Action = React.forwardRef<
200
+ HTMLButtonElement,
201
+ React.ButtonHTMLAttributes<HTMLButtonElement>
202
+ >(({ className, type = "button", ...props }, ref) => (
203
+ <button
204
+ ref={ref}
205
+ type={type}
206
+ className={cn("table-v01-action", className)}
207
+ {...props}
208
+ />
209
+ ));
210
+ TableV01Action.displayName = "TableV01Action";
211
+
212
+ export {
213
+ TableV01,
214
+ TableV01Header,
215
+ TableV01Body,
216
+ TableV01Footer,
217
+ TableV01Row,
218
+ TableV01HeaderCell,
219
+ TableV01Cell,
220
+ TableV01Tag,
221
+ TableV01Action,
222
+ };
223
+
224
+ export type {
225
+ TableV01Props,
226
+ TableV01CellProps,
227
+ TableV01BodyCellProps,
228
+ TableV01Density,
229
+ TableV01Align,
230
+ };
@@ -0,0 +1,99 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ export type TabsV01Item = {
6
+ value: string;
7
+ label: React.ReactNode;
8
+ disabled?: boolean;
9
+ };
10
+
11
+ export interface TabsV01Props
12
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange"> {
13
+ items?: TabsV01Item[];
14
+ label?: React.ReactNode;
15
+ value?: string;
16
+ defaultValue?: string;
17
+ onValueChange?: (value: string) => void;
18
+ }
19
+
20
+ const TabsV01 = React.forwardRef<HTMLDivElement, TabsV01Props>(
21
+ (
22
+ {
23
+ className,
24
+ items = [],
25
+ label,
26
+ value: controlledValue,
27
+ defaultValue,
28
+ onValueChange,
29
+ ...props
30
+ },
31
+ ref,
32
+ ) => {
33
+ const isControlled = controlledValue !== undefined;
34
+ const [uncontrolled, setUncontrolled] = React.useState(
35
+ () => defaultValue ?? items[0]?.value,
36
+ );
37
+ const active = isControlled ? controlledValue : uncontrolled;
38
+
39
+ const select = (next: string) => {
40
+ if (!isControlled) setUncontrolled(next);
41
+ onValueChange?.(next);
42
+ };
43
+
44
+ const refs = React.useRef<(HTMLButtonElement | null)[]>([]);
45
+
46
+ const onKeyDown = (event: React.KeyboardEvent, index: number) => {
47
+ const step =
48
+ event.key === "ArrowRight" ? 1 : event.key === "ArrowLeft" ? -1 : 0;
49
+ if (!step) return;
50
+ event.preventDefault();
51
+ const enabled = items
52
+ .map((item, i) => ({ item, i }))
53
+ .filter(({ item }) => !item.disabled);
54
+ const at = enabled.findIndex(({ i }) => i === index);
55
+ const next = enabled[(at + step + enabled.length) % enabled.length];
56
+ if (!next) return;
57
+ select(next.item.value);
58
+ refs.current[next.i]?.focus();
59
+ };
60
+
61
+ return (
62
+ <div
63
+ ref={ref}
64
+ className={cn("tabs-v01 font-mazzardSoft", className)}
65
+ {...props}
66
+ >
67
+ {label != null && label !== "" ? (
68
+ <div className="tabs-v01-label body-large">{label}</div>
69
+ ) : null}
70
+ <div className="tabs-v01-list" role="tablist">
71
+ {items.map((item, index) => (
72
+ <button
73
+ key={item.value}
74
+ ref={(node) => {
75
+ refs.current[index] = node;
76
+ }}
77
+ type="button"
78
+ role="tab"
79
+ aria-selected={item.value === active}
80
+ tabIndex={item.value === active ? 0 : -1}
81
+ disabled={item.disabled}
82
+ data-state={item.value === active ? "active" : "inactive"}
83
+ className="tabs-v01-tab"
84
+ onClick={() => select(item.value)}
85
+ onKeyDown={(event) => onKeyDown(event, index)}
86
+ >
87
+ <span className="tabs-v01-tab-label overline-large">
88
+ {item.label}
89
+ </span>
90
+ </button>
91
+ ))}
92
+ </div>
93
+ </div>
94
+ );
95
+ },
96
+ );
97
+ TabsV01.displayName = "TabsV01";
98
+
99
+ export { TabsV01 };
@@ -0,0 +1,113 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+
4
+ import { cn } from "@/lib/utils";
5
+ import { UtilityChangeType } from "@/components/icons/UtilityChangeType";
6
+
7
+ const tagV02Variants = cva("tag-v02 font-mazzardSoft", {
8
+ variants: {
9
+ state: {
10
+ active: "tag-v02--active",
11
+ inactive: "tag-v02--inactive",
12
+ },
13
+ },
14
+ defaultVariants: {
15
+ state: "inactive",
16
+ },
17
+ });
18
+
19
+ export type TagV02State = NonNullable<
20
+ VariantProps<typeof tagV02Variants>["state"]
21
+ >;
22
+ export type TagV02ChangeType = "mild" | "medium" | "hot";
23
+
24
+ export interface TagV02Props
25
+ extends Omit<React.ButtonHTMLAttributes<HTMLButtonElement>, "type">,
26
+ VariantProps<typeof tagV02Variants> {
27
+ dot?: boolean;
28
+ change?: boolean;
29
+ changeType?: TagV02ChangeType;
30
+ }
31
+
32
+ const TagV02 = React.forwardRef<HTMLButtonElement, TagV02Props>(
33
+ (
34
+ {
35
+ className,
36
+ state = "inactive",
37
+ dot = false,
38
+ change = false,
39
+ changeType = "mild",
40
+ children,
41
+ ...props
42
+ },
43
+ ref,
44
+ ) => (
45
+ <button
46
+ ref={ref}
47
+ type="button"
48
+ aria-pressed={state === "active"}
49
+ className={cn(tagV02Variants({ state }), className)}
50
+ {...props}
51
+ >
52
+ <span className="tag-v02-label">{children}</span>
53
+ {dot ? <span className="tag-v02-dot" aria-hidden /> : null}
54
+ {change ? (
55
+ <span className="tag-v02-change-frame">
56
+ <UtilityChangeType
57
+ className={`tag-v02-change tag-v02-change--${changeType}`}
58
+ />
59
+ </span>
60
+ ) : null}
61
+ </button>
62
+ ),
63
+ );
64
+ TagV02.displayName = "TagV02";
65
+
66
+ const tagStatusV02Variants = cva("tag-status-v02 font-mazzardSoft", {
67
+ variants: {
68
+ status: {
69
+ todo: "tag-status-v02--todo",
70
+ low: "tag-status-v02--low",
71
+ medium: "tag-status-v02--medium",
72
+ high: "tag-status-v02--high",
73
+ done: "tag-status-v02--done",
74
+ new: "tag-status-v02--new",
75
+ },
76
+ tone: {
77
+ light: "tag-status-v02--light",
78
+ dark: "tag-status-v02--dark",
79
+ },
80
+ },
81
+ defaultVariants: {
82
+ status: "todo",
83
+ tone: "light",
84
+ },
85
+ });
86
+
87
+ export type TagStatusV02Status = NonNullable<
88
+ VariantProps<typeof tagStatusV02Variants>["status"]
89
+ >;
90
+ export type TagStatusV02Tone = NonNullable<
91
+ VariantProps<typeof tagStatusV02Variants>["tone"]
92
+ >;
93
+
94
+ export interface TagStatusV02Props
95
+ extends React.HTMLAttributes<HTMLSpanElement>,
96
+ VariantProps<typeof tagStatusV02Variants> {}
97
+
98
+ const TagStatusV02 = React.forwardRef<HTMLSpanElement, TagStatusV02Props>(
99
+ ({ className, status = "todo", tone = "light", children, ...props }, ref) => (
100
+ <span
101
+ ref={ref}
102
+ className={cn(tagStatusV02Variants({ status, tone }), className)}
103
+ {...props}
104
+ >
105
+ <span className="tag-status-v02-label overline-medium">
106
+ {children ?? status}
107
+ </span>
108
+ </span>
109
+ ),
110
+ );
111
+ TagStatusV02.displayName = "TagStatusV02";
112
+
113
+ export { TagV02, tagV02Variants, TagStatusV02, tagStatusV02Variants };
@@ -0,0 +1,33 @@
1
+ import * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ export interface TextAreaV01Props
6
+ extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {
7
+ focused?: boolean;
8
+ }
9
+
10
+ const TextAreaV01 = React.forwardRef<HTMLTextAreaElement, TextAreaV01Props>(
11
+ (
12
+ {
13
+ className,
14
+ focused,
15
+ disabled,
16
+ placeholder = "Tell us what you think",
17
+ ...props
18
+ },
19
+ ref,
20
+ ) => (
21
+ <textarea
22
+ ref={ref}
23
+ className={cn("text-area-v01 font-mazzardSoft", className)}
24
+ data-focus={focused ? "true" : undefined}
25
+ disabled={disabled}
26
+ placeholder={placeholder}
27
+ {...props}
28
+ />
29
+ ),
30
+ );
31
+ TextAreaV01.displayName = "TextAreaV01";
32
+
33
+ export { TextAreaV01 };