@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,299 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ // ─── AppBar Root ────────────────────────────────────────────────────────────
5
+
6
+ export interface AppBarProps extends React.HTMLAttributes<HTMLElement> {
7
+ /** Collapses the bar to show only the prepend slot */
8
+ collapse?: boolean;
9
+ /** Rounds the corners of the bar */
10
+ rounded?: boolean;
11
+ /** Elevates the bar with a shadow */
12
+ elevated?: boolean;
13
+ /** Makes the bar flat (no shadow, no border) */
14
+ flat?: boolean;
15
+ /** Renders bar as fixed (default) or sticky */
16
+ position?: "fixed" | "sticky" | "relative" | "static";
17
+ /** Color / surface variant */
18
+ color?: "default" | "primary" | "transparent";
19
+ /**
20
+ * Density of the bar — controls height.
21
+ * - "default" → 64px
22
+ * - "comfortable" → 56px
23
+ * - "compact" → 48px
24
+ * - "prominent" → 128px (title sits at the bottom)
25
+ */
26
+ density?: "default" | "comfortable" | "compact" | "prominent";
27
+ /**
28
+ * URL for a background image on the app bar.
29
+ * Pair with `imageOverlay` for a gradient on top.
30
+ */
31
+ image?: string;
32
+ /**
33
+ * CSS gradient string applied as an overlay on top of the `image`.
34
+ * Example: "to top right, rgba(19,84,122,.8), rgba(128,208,199,.8)"
35
+ * Renders as: linear-gradient(<value>)
36
+ */
37
+ imageOverlay?: string;
38
+ }
39
+
40
+ const DENSITY_HEIGHT: Record<
41
+ NonNullable<AppBarProps["density"]>,
42
+ number
43
+ > = {
44
+ default: 64,
45
+ comfortable: 56,
46
+ compact: 48,
47
+ prominent: 128,
48
+ };
49
+
50
+ const AppBar = React.forwardRef<HTMLElement, AppBarProps>(
51
+ (
52
+ {
53
+ className,
54
+ children,
55
+ collapse = false,
56
+ rounded = false,
57
+ elevated = false,
58
+ flat = false,
59
+ position = "fixed",
60
+ color = "default",
61
+ density = "default",
62
+ image,
63
+ imageOverlay,
64
+ style,
65
+ ...props
66
+ },
67
+ ref
68
+ ) => {
69
+ const height = DENSITY_HEIGHT[density];
70
+ const isProminent = density === "prominent";
71
+
72
+ return (
73
+ <header
74
+ ref={ref}
75
+ role="banner"
76
+ style={{
77
+ height: collapse ? undefined : height,
78
+ ...style,
79
+ }}
80
+ className={cn(
81
+ "app-bar",
82
+ // position
83
+ position === "fixed" && "app-bar--fixed",
84
+ position === "sticky" && "app-bar--sticky",
85
+ position === "relative" && "app-bar--relative",
86
+ position === "static" && "app-bar--static",
87
+ // color variants
88
+ color === "primary" && "app-bar--primary",
89
+ color === "transparent" && "app-bar--transparent",
90
+ // density
91
+ density === "comfortable" && "app-bar--comfortable",
92
+ density === "compact" && "app-bar--compact",
93
+ isProminent && "app-bar--prominent",
94
+ // modifiers
95
+ rounded && "app-bar--rounded",
96
+ elevated && "app-bar--elevated",
97
+ flat && "app-bar--flat",
98
+ collapse && "app-bar--collapse",
99
+ // image
100
+ image && "app-bar--image",
101
+ className
102
+ )}
103
+ {...props}
104
+ >
105
+ {/* Background image layer */}
106
+ {image && (
107
+ <span
108
+ className="app-bar-image-layer"
109
+ style={{ backgroundImage: `url(${image})` }}
110
+ aria-hidden="true"
111
+ >
112
+ {imageOverlay && (
113
+ <span
114
+ className="app-bar-image-overlay"
115
+ style={{
116
+ background: `linear-gradient(${imageOverlay})`,
117
+ }}
118
+ />
119
+ )}
120
+ </span>
121
+ )}
122
+
123
+ {/* Prominent layout: split children into top-row (prepend/append)
124
+ and bottom-row (title) so the title sits at the bottom */}
125
+ {isProminent ? (
126
+ <ProminentLayout>{children}</ProminentLayout>
127
+ ) : (
128
+ children
129
+ )}
130
+ </header>
131
+ );
132
+ }
133
+ );
134
+ AppBar.displayName = "AppBar";
135
+
136
+ // ─── AppBarNavIcon ───────────────────────────────────────────────────────────
137
+
138
+ export interface AppBarNavIconProps
139
+ extends React.ButtonHTMLAttributes<HTMLButtonElement> {
140
+ /** Custom icon; defaults to hamburger menu */
141
+ icon?: React.ReactNode;
142
+ }
143
+
144
+ const AppBarNavIcon = React.forwardRef<HTMLButtonElement, AppBarNavIconProps>(
145
+ ({ className, icon, children, ...props }, ref) => (
146
+ <button
147
+ ref={ref}
148
+ type="button"
149
+ aria-label="Open navigation menu"
150
+ className={cn("app-bar-nav-icon", className)}
151
+ {...props}
152
+ >
153
+ {children ?? icon ?? <HamburgerIcon />}
154
+ </button>
155
+ )
156
+ );
157
+ AppBarNavIcon.displayName = "AppBarNavIcon";
158
+
159
+ // ─── AppBarTitle ─────────────────────────────────────────────────────────────
160
+
161
+ export interface AppBarTitleProps extends React.HTMLAttributes<HTMLDivElement> {}
162
+
163
+ const AppBarTitle = React.forwardRef<HTMLDivElement, AppBarTitleProps>(
164
+ ({ className, ...props }, ref) => (
165
+ <div
166
+ ref={ref}
167
+ className={cn("app-bar-title", className)}
168
+ {...props}
169
+ />
170
+ )
171
+ );
172
+ AppBarTitle.displayName = "AppBarTitle";
173
+
174
+ // ─── AppBarPrepend ───────────────────────────────────────────────────────────
175
+
176
+ export interface AppBarPrependProps
177
+ extends React.HTMLAttributes<HTMLDivElement> {}
178
+
179
+ const AppBarPrepend = React.forwardRef<HTMLDivElement, AppBarPrependProps>(
180
+ ({ className, ...props }, ref) => (
181
+ <div
182
+ ref={ref}
183
+ className={cn("app-bar-prepend", className)}
184
+ {...props}
185
+ />
186
+ )
187
+ );
188
+ AppBarPrepend.displayName = "AppBarPrepend";
189
+
190
+ // ─── AppBarAppend ─────────────────────────────────────────────────────────────
191
+
192
+ export interface AppBarAppendProps
193
+ extends React.HTMLAttributes<HTMLDivElement> {}
194
+
195
+ const AppBarAppend = React.forwardRef<HTMLDivElement, AppBarAppendProps>(
196
+ ({ className, ...props }, ref) => (
197
+ <div
198
+ ref={ref}
199
+ className={cn("app-bar-append", className)}
200
+ {...props}
201
+ />
202
+ )
203
+ );
204
+ AppBarAppend.displayName = "AppBarAppend";
205
+
206
+ // ─── AppBarContent (fills remaining horizontal space) ────────────────────────
207
+
208
+ export interface AppBarContentProps
209
+ extends React.HTMLAttributes<HTMLDivElement> {}
210
+
211
+ const AppBarContent = React.forwardRef<HTMLDivElement, AppBarContentProps>(
212
+ ({ className, ...props }, ref) => (
213
+ <div
214
+ ref={ref}
215
+ className={cn("app-bar-content", className)}
216
+ {...props}
217
+ />
218
+ )
219
+ );
220
+ AppBarContent.displayName = "AppBarContent";
221
+
222
+ // ─── ProminentLayout helper ───────────────────────────────────────────────
223
+ // Splits children into two rows: prepend+append at top, title at bottom.
224
+ // Matches Vuetify's prominent bar layout.
225
+
226
+ function ProminentLayout({ children }: { children: React.ReactNode }) {
227
+ const kids = React.Children.toArray(children);
228
+
229
+ const topKids = kids.filter((child) => {
230
+ if (!React.isValidElement(child)) return false;
231
+ const name =
232
+ (child.type as { displayName?: string }).displayName ?? "";
233
+ return name === "AppBarPrepend" || name === "AppBarAppend";
234
+ });
235
+
236
+ const bottomKids = kids.filter((child) => {
237
+ if (!React.isValidElement(child)) return false;
238
+ const name =
239
+ (child.type as { displayName?: string }).displayName ?? "";
240
+ return name === "AppBarTitle" || name === "AppBarContent";
241
+ });
242
+
243
+ // Anything not matched falls into the top row
244
+ const otherKids = kids.filter((child) => {
245
+ if (!React.isValidElement(child)) return true;
246
+ const name =
247
+ (child.type as { displayName?: string }).displayName ?? "";
248
+ return ![
249
+ "AppBarPrepend",
250
+ "AppBarAppend",
251
+ "AppBarTitle",
252
+ "AppBarContent",
253
+ ].includes(name);
254
+ });
255
+
256
+ return (
257
+ <span className="app-bar-prominent-inner">
258
+ <span className="app-bar-prominent-top">
259
+ {topKids}
260
+ {otherKids}
261
+ </span>
262
+ <span className="app-bar-prominent-bottom">{bottomKids}</span>
263
+ </span>
264
+ );
265
+ }
266
+
267
+ // ─── Internal helpers ────────────────────────────────────────────────────────
268
+
269
+ function HamburgerIcon() {
270
+ return (
271
+ <svg
272
+ xmlns="http://www.w3.org/2000/svg"
273
+ width="24"
274
+ height="24"
275
+ viewBox="0 0 24 24"
276
+ fill="none"
277
+ stroke="currentColor"
278
+ strokeWidth="2"
279
+ strokeLinecap="round"
280
+ strokeLinejoin="round"
281
+ aria-hidden="true"
282
+ >
283
+ <line x1="4" y1="6" x2="20" y2="6" />
284
+ <line x1="4" y1="12" x2="20" y2="12" />
285
+ <line x1="4" y1="18" x2="20" y2="18" />
286
+ </svg>
287
+ );
288
+ }
289
+
290
+ // ─── Exports ─────────────────────────────────────────────────────────────────
291
+
292
+ export {
293
+ AppBar,
294
+ AppBarNavIcon,
295
+ AppBarTitle,
296
+ AppBarPrepend,
297
+ AppBarAppend,
298
+ AppBarContent,
299
+ };
@@ -0,0 +1,122 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/utils";
4
+ import placeholderIcon from "@/assets/avatar-identity/placeholder-icon.svg";
5
+
6
+ const avatarIdentityVariants = cva("avatar-identity", {
7
+ variants: {
8
+ size: {
9
+ xlarge: "avatar-identity--xlarge",
10
+ large: "avatar-identity--large",
11
+ medium: "avatar-identity--medium",
12
+ },
13
+ textDirection: {
14
+ right: "avatar-identity--right",
15
+ left: "avatar-identity--left",
16
+ },
17
+ },
18
+ defaultVariants: {
19
+ size: "xlarge",
20
+ textDirection: "right",
21
+ },
22
+ });
23
+
24
+ export type AvatarIdentitySize = NonNullable<
25
+ VariantProps<typeof avatarIdentityVariants>["size"]
26
+ >;
27
+ export type AvatarIdentityTextDirection = NonNullable<
28
+ VariantProps<typeof avatarIdentityVariants>["textDirection"]
29
+ >;
30
+ export type AvatarIdentityVariant = "placeholder" | "initials" | "image";
31
+
32
+ export interface AvatarIdentityProps
33
+ extends Omit<React.HTMLAttributes<HTMLDivElement>, "color">,
34
+ VariantProps<typeof avatarIdentityVariants> {
35
+ /** Face treatment from Figma `style`. */
36
+ variant?: AvatarIdentityVariant;
37
+ name?: string;
38
+ email?: string;
39
+ /** Override initials. Defaults to the first letters of `name`. */
40
+ initials?: string;
41
+ src?: string;
42
+ alt?: string;
43
+ showText?: boolean;
44
+ }
45
+
46
+ function initialsFromName(name: string) {
47
+ return name
48
+ .split(/\s+/)
49
+ .filter(Boolean)
50
+ .slice(0, 2)
51
+ .map((part) => part[0])
52
+ .join("")
53
+ .toUpperCase();
54
+ }
55
+
56
+ const AvatarIdentity = React.forwardRef<HTMLDivElement, AvatarIdentityProps>(
57
+ (
58
+ {
59
+ className,
60
+ size,
61
+ textDirection,
62
+ variant = "placeholder",
63
+ name = "",
64
+ email = "",
65
+ initials,
66
+ src,
67
+ alt,
68
+ showText = true,
69
+ ...props
70
+ },
71
+ ref,
72
+ ) => {
73
+ const face = variant === "image" && !src ? "placeholder" : variant;
74
+ const letters = (initials || initialsFromName(name) || "JD").slice(0, 2);
75
+
76
+ return (
77
+ <div
78
+ ref={ref}
79
+ className={cn(avatarIdentityVariants({ size, textDirection }), className)}
80
+ {...props}
81
+ >
82
+ <div className="avatar-identity__face" data-face={face}>
83
+ {face === "image" && src ? (
84
+ <img
85
+ className="avatar-identity__photo"
86
+ src={src}
87
+ alt={alt || name}
88
+ />
89
+ ) : null}
90
+
91
+ {face === "initials" ? (
92
+ <span className="avatar-identity__initials" aria-hidden={showText}>
93
+ {letters}
94
+ </span>
95
+ ) : null}
96
+
97
+ {face === "placeholder" ? (
98
+ <img
99
+ className="avatar-identity__placeholder-icon"
100
+ src={placeholderIcon}
101
+ alt=""
102
+ />
103
+ ) : null}
104
+
105
+ {face !== "placeholder" ? (
106
+ <span className="avatar-identity__border" aria-hidden="true" />
107
+ ) : null}
108
+ </div>
109
+
110
+ {showText ? (
111
+ <div className="avatar-identity__text">
112
+ <p className="avatar-identity__name">{name}</p>
113
+ <p className="avatar-identity__email">{email}</p>
114
+ </div>
115
+ ) : null}
116
+ </div>
117
+ );
118
+ },
119
+ );
120
+ AvatarIdentity.displayName = "AvatarIdentity";
121
+
122
+ export { AvatarIdentity, avatarIdentityVariants };
@@ -0,0 +1,108 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ // ─── Variants ─────────────────────────────────────────────────────────────────
6
+
7
+ const bannerVariants = cva("banner", {
8
+ variants: {
9
+ variant: {
10
+ default: "banner--default",
11
+ info: "banner--info",
12
+ success: "banner--success",
13
+ warning: "banner--warning",
14
+ destructive: "banner--destructive",
15
+ },
16
+ },
17
+ defaultVariants: {
18
+ variant: "default",
19
+ },
20
+ });
21
+
22
+ // ─── Banner Root ──────────────────────────────────────────────────────────────
23
+
24
+ export interface BannerProps
25
+ extends React.HTMLAttributes<HTMLDivElement>,
26
+ VariantProps<typeof bannerVariants> {
27
+ /** Renders the banner fixed to the top of the viewport */
28
+ sticky?: boolean;
29
+ /** Called when the close button is clicked — also renders the close button */
30
+ onClose?: () => void;
31
+ }
32
+
33
+ const Banner = React.forwardRef<HTMLDivElement, BannerProps>(
34
+ ({ className, variant, sticky = false, onClose, children, ...props }, ref) => (
35
+ <div
36
+ ref={ref}
37
+ role="banner"
38
+ className={cn(bannerVariants({ variant }), sticky && "banner--sticky", className)}
39
+ {...props}
40
+ >
41
+ <div className="banner-content">{children}</div>
42
+ {onClose && (
43
+ <button
44
+ type="button"
45
+ className="banner-close"
46
+ aria-label="Dismiss banner"
47
+ onClick={onClose}
48
+ >
49
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
50
+ <line x1="18" y1="6" x2="6" y2="18" />
51
+ <line x1="6" y1="6" x2="18" y2="18" />
52
+ </svg>
53
+ </button>
54
+ )}
55
+ </div>
56
+ )
57
+ );
58
+ Banner.displayName = "Banner";
59
+
60
+ // ─── BannerIcon ───────────────────────────────────────────────────────────────
61
+
62
+ const BannerIcon = React.forwardRef<HTMLSpanElement, React.HTMLAttributes<HTMLSpanElement>>(
63
+ ({ className, ...props }, ref) => (
64
+ <span ref={ref} className={cn("banner-icon", className)} aria-hidden="true" {...props} />
65
+ )
66
+ );
67
+ BannerIcon.displayName = "BannerIcon";
68
+
69
+ // ─── BannerTitle ──────────────────────────────────────────────────────────────
70
+
71
+ const BannerTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(
72
+ ({ className, ...props }, ref) => (
73
+ <p ref={ref} className={cn("banner-title", className)} {...props} />
74
+ )
75
+ );
76
+ BannerTitle.displayName = "BannerTitle";
77
+
78
+ // ─── BannerDescription ────────────────────────────────────────────────────────
79
+
80
+ const BannerDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
81
+ ({ className, ...props }, ref) => (
82
+ <p ref={ref} className={cn("banner-description", className)} {...props} />
83
+ )
84
+ );
85
+ BannerDescription.displayName = "BannerDescription";
86
+
87
+ // ─── BannerText ───────────────────────────────────────────────────────────────
88
+ // Wraps BannerTitle + BannerDescription so they stack vertically (2-line / 3-line layouts).
89
+
90
+ const BannerText = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
91
+ ({ className, ...props }, ref) => (
92
+ <div ref={ref} className={cn("banner-text", className)} {...props} />
93
+ )
94
+ );
95
+ BannerText.displayName = "BannerText";
96
+
97
+ // ─── BannerAction ─────────────────────────────────────────────────────────────
98
+
99
+ const BannerAction = React.forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
100
+ ({ className, ...props }, ref) => (
101
+ <button ref={ref} type="button" className={cn("banner-action", className)} {...props} />
102
+ )
103
+ );
104
+ BannerAction.displayName = "BannerAction";
105
+
106
+ // ─── Exports ──────────────────────────────────────────────────────────────────
107
+
108
+ export { Banner, BannerIcon, BannerTitle, BannerText, BannerDescription, BannerAction, bannerVariants };