@borisj74/bv-ds 0.1.1 → 0.1.3

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 (37) hide show
  1. package/dist/index.cjs +143 -28
  2. package/dist/index.d.cts +56 -26
  3. package/dist/index.d.ts +56 -26
  4. package/dist/index.js +143 -29
  5. package/package.json +11 -9
  6. package/src/components/Button/Button.tsx +42 -22
  7. package/src/components/ButtonDestructive/ButtonDestructive.tsx +3 -3
  8. package/src/components/CalendarColumnHeader/CalendarColumnHeader.tsx +1 -1
  9. package/src/components/CalendarViewDropdown/CalendarViewDropdown.tsx +1 -1
  10. package/src/components/ChartTooltip/ChartTooltip.tsx +1 -1
  11. package/src/components/Checkbox/checkboxBase.tsx +1 -1
  12. package/src/components/ContentFeatureText/ContentFeatureText.tsx +3 -2
  13. package/src/components/ContextMenu/ContextMenu.tsx +1 -1
  14. package/src/components/FeaturedIcon/FeaturedIcon.tsx +146 -0
  15. package/src/components/FeaturedIcon/index.ts +7 -0
  16. package/src/components/MessageAction/MessageAction.tsx +1 -1
  17. package/src/components/NavAccountCard/NavAccountCard.tsx +42 -5
  18. package/src/components/NavAccountCard/index.ts +5 -1
  19. package/src/components/NavAccountCardMenuItem/NavAccountCardMenuItem.tsx +1 -1
  20. package/src/components/NavButton/NavButton.tsx +1 -1
  21. package/src/components/NavFeaturedCard/NavFeaturedCard.tsx +1 -1
  22. package/src/components/NavItemBase/NavItemBase.tsx +1 -1
  23. package/src/components/NavItemDropdownBase/NavItemDropdownBase.tsx +1 -1
  24. package/src/components/PageHeader/PageHeader.tsx +1 -1
  25. package/src/components/PaginationDotIndicator/PaginationDotIndicator.tsx +1 -1
  26. package/src/components/ProgressBar/ProgressBar.tsx +1 -1
  27. package/src/components/RadioGroupItem/RadioGroupItem.tsx +1 -1
  28. package/src/components/SelectMenuItem/SelectMenuItem.tsx +2 -2
  29. package/src/components/SidebarNavigation/SidebarNavigation.tsx +2 -2
  30. package/src/components/SocialButton/SocialButton.tsx +2 -2
  31. package/src/components/TableHeaderLabel/TableHeaderLabel.tsx +1 -1
  32. package/src/components/Toggle/Toggle.tsx +1 -1
  33. package/src/components/Tooltip/Tooltip.tsx +1 -1
  34. package/src/components/TreeViewItem/TreeViewItem.tsx +1 -1
  35. package/src/components/VerificationCodeInput/VerificationCodeInput.tsx +1 -1
  36. package/src/index.ts +1 -0
  37. package/tailwind-preset.js +223 -210
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@borisj74/bv-ds",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "bv-ds — React component library synced from Figma (Untitled UI v8.0), built on Tailwind CSS",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -38,20 +38,22 @@
38
38
  "react-dom": ">=18.0.0"
39
39
  },
40
40
  "devDependencies": {
41
+ "@borisj74/bv-ds-icons": "^0.1.0",
42
+ "@storybook/addon-a11y": "^8.0.0",
43
+ "@storybook/addon-essentials": "^8.6.14",
44
+ "@storybook/react-vite": "^8.0.0",
41
45
  "@types/react": "^18.2.0",
42
46
  "@types/react-dom": "^18.2.0",
47
+ "autoprefixer": "^10.4.0",
48
+ "clsx": "^2.1.0",
49
+ "postcss": "^8.4.0",
43
50
  "react": "^18.2.0",
44
51
  "react-dom": "^18.2.0",
52
+ "recharts": "^2.15.4",
53
+ "storybook": "^8.0.0",
45
54
  "tailwindcss": "^3.4.0",
46
- "postcss": "^8.4.0",
47
- "autoprefixer": "^10.4.0",
48
- "clsx": "^2.1.0",
49
55
  "tsup": "^8.0.0",
50
- "typescript": "^5.4.0",
51
- "@storybook/react-vite": "^8.0.0",
52
- "@storybook/addon-essentials": "^8.0.0",
53
- "@storybook/addon-a11y": "^8.0.0",
54
- "storybook": "^8.0.0"
56
+ "typescript": "^5.4.0"
55
57
  },
56
58
  "publishConfig": {
57
59
  "access": "public"
@@ -15,32 +15,44 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
15
15
  loading?: boolean;
16
16
  }
17
17
 
18
- // NOTE: this is a placeholder implementation to verify the build/Storybook
19
- // pipeline end-to-end. The figma-to-react skill should overwrite this with
20
- // the full per-hierarchy/per-size token mapping from design.md §3 (all 5
21
- // sizes, all 5 hierarchies, skeuomorphic shadow stack on Primary/Secondary).
18
+ // Token class names use the doubled-prefix form that matches the nested
19
+ // tailwind-preset.js color groups (v0.1.2+):
20
+ // colors.bg["brand-solid"] → bg-bg-brand-solid
21
+ // colors.text["secondary"] → text-text-secondary
22
+ // colors.border["primary"] → border-border-primary
22
23
  //
23
- // Padding values below use Tailwind arbitrary values ([14px], [10px], etc)
24
- // rather than the spacing-* scale on purpose — design.md §3 confirms the
25
- // real Figma component uses raw px at md/lg/xl, not the nearest token.
26
- // Don't "fix" these to spacing-md/spacing-lg; that would silently diverge
27
- // from the source design.
24
+ // Padding values use Tailwind arbitrary values ([14px], [10px], etc) rather
25
+ // than the spacing-* scale — design.md §3 confirms the real Figma component
26
+ // uses raw px at certain sizes. Don't "fix" these to spacing tokens; that
27
+ // would silently diverge from the source design.
28
+ //
29
+ // Primary/Secondary use the skeuomorphic shadow stack confirmed in design.md:
30
+ // shadow-xs outer + inner border highlight + inner bottom shading.
28
31
 
29
32
  const hierarchyClasses: Record<ButtonHierarchy, string> = {
30
33
  Primary:
31
- "bg-brand-solid text-white border-2 border-white/[0.12] hover:bg-bg-brand-solid-hover",
32
- Secondary: "bg-bg-primary text-text-secondary border border-border-primary",
33
- Tertiary: "bg-transparent text-text-tertiary border-0",
34
- "Link color": "bg-transparent text-text-brand-secondary border-0 p-0",
35
- "Link gray": "bg-transparent text-text-tertiary border-0 p-0",
34
+ "bg-bg-brand-solid text-text-white border-2 border-white/[0.12] shadow-skeuomorphic " +
35
+ "hover:bg-bg-brand-solid-hover",
36
+ Secondary:
37
+ "bg-bg-primary text-text-secondary border border-border-primary shadow-skeuomorphic " +
38
+ "hover:bg-bg-primary-hover",
39
+ Tertiary:
40
+ "bg-transparent text-text-tertiary border-0 " +
41
+ "hover:bg-bg-secondary",
42
+ "Link color":
43
+ "bg-transparent text-text-brand-secondary border-0 p-0 " +
44
+ "hover:text-text-brand-secondary-hover",
45
+ "Link gray":
46
+ "bg-transparent text-text-tertiary border-0 p-0 " +
47
+ "hover:text-text-tertiary-hover",
36
48
  };
37
49
 
38
50
  const sizeClasses: Record<ButtonSize, string> = {
39
- xs: "px-[10px] py-sm text-xs",
40
- sm: "px-lg py-md text-xs",
41
- md: "px-[14px] py-[10px] text-sm",
42
- lg: "px-xl py-[10px] text-md",
43
- xl: "px-[18px] py-lg text-md",
51
+ xs: "px-[10px] py-sm gap-xs text-xs font-semibold",
52
+ sm: "px-lg py-md gap-xs text-xs font-semibold",
53
+ md: "px-[14px] py-[10px] gap-xs text-sm font-semibold",
54
+ lg: "px-xl py-[10px] gap-sm text-md font-semibold",
55
+ xl: "px-[18px] py-lg gap-sm text-md font-semibold",
44
56
  };
45
57
 
46
58
  export function Button({
@@ -55,8 +67,10 @@ export function Button({
55
67
  return (
56
68
  <button
57
69
  className={clsx(
58
- "font-body font-semibold rounded-md cursor-pointer transition-colors",
59
- "disabled:opacity-50 disabled:cursor-not-allowed",
70
+ "inline-flex items-center justify-center font-body rounded-md",
71
+ "cursor-pointer transition-colors",
72
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-brand focus-visible:ring-offset-2",
73
+ "disabled:opacity-50 disabled:cursor-not-allowed disabled:shadow-none",
60
74
  hierarchyClasses[hierarchy],
61
75
  sizeClasses[size],
62
76
  className,
@@ -65,7 +79,13 @@ export function Button({
65
79
  aria-disabled={disabled || loading}
66
80
  {...rest}
67
81
  >
68
- {loading ? "Loading…" : children}
82
+ {loading ? (
83
+ <span className="opacity-70">Loading…</span>
84
+ ) : (
85
+ children
86
+ )}
69
87
  </button>
70
88
  );
71
89
  }
90
+
91
+ export default Button;
@@ -17,10 +17,10 @@ export interface ButtonDestructiveProps
17
17
 
18
18
  // Destructive palette (design.md §3 / Figma Button destructive 6218:85578).
19
19
  // Primary/Secondary carry the skeuomorphic shadow stack; focus ring is the
20
- // error ring (border-error).
20
+ // error ring (border-border-error).
21
21
  const hierarchyClasses: Record<ButtonDestructiveHierarchy, string> = {
22
22
  Primary:
23
- "bg-error-solid text-white border-2 border-white/[0.12] shadow-skeuomorphic hover:bg-error-solid-hover",
23
+ "bg-bg-error-solid text-text-white border-2 border-white/[0.12] shadow-skeuomorphic hover:bg-bg-error-solid-hover",
24
24
  Secondary:
25
25
  "bg-bg-primary text-text-error-primary border border-border-error-subtle shadow-skeuomorphic hover:bg-bg-error-primary",
26
26
  Tertiary:
@@ -51,7 +51,7 @@ export function ButtonDestructive({
51
51
  <button
52
52
  className={clsx(
53
53
  "font-body font-semibold rounded-md cursor-pointer transition-colors",
54
- "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-error focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary",
54
+ "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-border-error focus-visible:ring-offset-2 focus-visible:ring-offset-bg-bg-primary",
55
55
  "disabled:opacity-50 disabled:cursor-not-allowed",
56
56
  hierarchyClasses[hierarchy],
57
57
  hierarchy === "Link" ? "" : sizeClasses[size],
@@ -33,7 +33,7 @@ export function CalendarColumnHeader({
33
33
  className={clsx(
34
34
  "flex items-center justify-center text-xs font-semibold",
35
35
  current
36
- ? "size-6 rounded-full bg-brand-solid text-white"
36
+ ? "size-6 rounded-full bg-bg-brand-solid text-white"
37
37
  : "text-text-secondary",
38
38
  )}
39
39
  aria-current={current ? "date" : undefined}
@@ -83,7 +83,7 @@ export function CalendarViewDropdown({
83
83
  )}
84
84
  >
85
85
  {isSelected ? (
86
- <span className="size-2 rounded-full bg-brand-solid" />
86
+ <span className="size-2 rounded-full bg-bg-brand-solid" />
87
87
  ) : null}
88
88
  </span>
89
89
  <span className="flex-1 text-left">{opt.label}</span>
@@ -20,7 +20,7 @@ export interface ChartTooltipProps {
20
20
 
21
21
  /**
22
22
  * Themed tooltip card for the Recharts chart wrappers — pass as the chart's
23
- * `<Tooltip content={<ChartTooltip />} />`. Renders a `bg-primary` card with the
23
+ * `<Tooltip content={<ChartTooltip />} />`. Renders a `bg-bg-primary` card with the
24
24
  * point label and one colored-dot row per series. Returns null when inactive.
25
25
  */
26
26
  export function ChartTooltip({ active, payload, label, formatValue }: ChartTooltipProps) {
@@ -46,7 +46,7 @@ export function CheckControlVisual({
46
46
  disabled
47
47
  ? "border-border-primary bg-bg-tertiary"
48
48
  : active
49
- ? "border-brand-solid bg-brand-solid"
49
+ ? "border-bg-brand-solid bg-bg-brand-solid"
50
50
  : "border-border-primary bg-bg-primary",
51
51
  )}
52
52
  >
@@ -14,12 +14,13 @@ export interface ContentFeatureTextProps {
14
14
  }
15
15
 
16
16
  // md verified (p-4xl, rounded-2xl, heading text-xl, body text-lg).
17
- // sm inferred (scaled down a step).
17
+ // sm verified 2026-06-27 (node 3959:413352): p-3xl, rounded-2xl, heading text-lg,
18
+ // body text-md. Radius corrected rounded-xl→rounded-2xl (was inferred wrong).
18
19
  const sizeConfig: Record<
19
20
  ContentFeatureTextSize,
20
21
  { box: string; heading: string; body: string }
21
22
  > = {
22
- sm: { box: "p-3xl rounded-xl", heading: "text-lg", body: "text-md" },
23
+ sm: { box: "p-3xl rounded-2xl", heading: "text-lg", body: "text-md" },
23
24
  md: { box: "p-4xl rounded-2xl", heading: "text-xl", body: "text-lg" },
24
25
  };
25
26
 
@@ -18,7 +18,7 @@ export interface ContextMenuProps {
18
18
 
19
19
  /**
20
20
  * A thin positioned panel for right-click menus. Reuses the dropdown panel
21
- * tokens (shadow-lg, border-secondary-alt, radius-md) and renders whatever
21
+ * tokens (shadow-lg, border-border-secondary-alt, radius-md) and renders whatever
22
22
  * rows you pass — compose DropdownMenuListItem children; it does NOT
23
23
  * reimplement list items. Pair with `useContextMenu`.
24
24
  */
@@ -0,0 +1,146 @@
1
+ import React from "react";
2
+ import clsx from "clsx";
3
+
4
+ export type FeaturedIconColor =
5
+ | "brand"
6
+ | "gray"
7
+ | "error"
8
+ | "warning"
9
+ | "success";
10
+
11
+ export type FeaturedIconTheme =
12
+ | "light"
13
+ | "dark"
14
+ | "gradient"
15
+ | "modern"
16
+ | "modern-neue";
17
+
18
+ export type FeaturedIconSize = "sm" | "md" | "lg" | "xl";
19
+
20
+ // `color` is Omitted because the native HTMLAttributes `color` prop collides
21
+ // with our richer FeaturedIconColor union (see claude.md interface-collision rule).
22
+ export interface FeaturedIconProps
23
+ extends Omit<React.HTMLAttributes<HTMLSpanElement>, "color"> {
24
+ /** Icon element (e.g. from @borisj74/bv-ds-icons). Inherits currentColor. */
25
+ icon: React.ReactNode;
26
+ color?: FeaturedIconColor;
27
+ theme?: FeaturedIconTheme;
28
+ size?: FeaturedIconSize;
29
+ }
30
+
31
+ // Confirmed from Figma node 1102:5338 (Misc icons page, batch 30).
32
+ // container px → size-N (32/40/48/56); icon px → wrapper span width/height.
33
+ const boxSize: Record<FeaturedIconSize, string> = {
34
+ sm: "size-8", // 32px
35
+ md: "size-10", // 40px
36
+ lg: "size-12", // 48px
37
+ xl: "size-14", // 56px
38
+ };
39
+
40
+ const iconPx: Record<FeaturedIconSize, number> = {
41
+ sm: 16,
42
+ md: 20,
43
+ lg: 24,
44
+ xl: 28,
45
+ };
46
+
47
+ // Light theme = the only circular treatment; modern-neue = 10px (radius-lg);
48
+ // every other theme = 12px (radius-xl).
49
+ function radiusFor(theme: FeaturedIconTheme): string {
50
+ if (theme === "light") return "rounded-full";
51
+ if (theme === "modern-neue") return "rounded-lg";
52
+ return "rounded-xl";
53
+ }
54
+
55
+ // Light: tinted secondary bg + colour-matched icon (fg). Gray maps to the
56
+ // neutral semantic layer (no dedicated gray featured token in the preset).
57
+ const lightBg: Record<FeaturedIconColor, string> = {
58
+ brand: "bg-bg-brand-secondary",
59
+ gray: "bg-bg-secondary",
60
+ error: "bg-bg-error-secondary",
61
+ warning: "bg-bg-warning-secondary",
62
+ success: "bg-bg-success-secondary",
63
+ };
64
+
65
+ const lightFg: Record<FeaturedIconColor, string> = {
66
+ brand: "text-fg-brand-primary",
67
+ gray: "text-fg-secondary",
68
+ error: "text-fg-error-primary",
69
+ warning: "text-fg-warning-primary",
70
+ success: "text-fg-success-primary",
71
+ };
72
+
73
+ // Dark: solid bg + white icon + skeuomorphic shadow + a 2px white/12 highlight
74
+ // border (arbitrary value — no Figma variable). Gray uses the secondary solid.
75
+ const darkBg: Record<FeaturedIconColor, string> = {
76
+ brand: "bg-bg-brand-solid",
77
+ gray: "bg-bg-secondary-solid",
78
+ error: "bg-bg-error-solid",
79
+ warning: "bg-bg-warning-solid",
80
+ success: "bg-bg-success-solid",
81
+ };
82
+
83
+ // Gradient: 2-stop diagonal fill (utility-{family}-50 → -200) + white icon.
84
+ const gradientFill: Record<FeaturedIconColor, string> = {
85
+ brand: "bg-gradient-to-br from-utility-brand-50 to-utility-brand-200",
86
+ gray: "bg-gradient-to-br from-utility-neutral-50 to-utility-neutral-200",
87
+ error: "bg-gradient-to-br from-utility-red-50 to-utility-red-200",
88
+ warning: "bg-gradient-to-br from-utility-amber-50 to-utility-amber-200",
89
+ success: "bg-gradient-to-br from-utility-green-50 to-utility-green-200",
90
+ };
91
+
92
+ /**
93
+ * FeaturedIcon — Untitled UI "Featured icon" (node 1102:5338).
94
+ *
95
+ * A coloured container wrapping an icon, used by Empty state, Alert, Metric,
96
+ * Notification, etc. Pass the icon via the `icon` slot — it inherits
97
+ * currentColor, so no fill/stroke props are needed.
98
+ *
99
+ * `modern` and `modern-neue` are neutral white-card treatments (gray-only in
100
+ * Figma); the `color` prop is ignored for those two themes.
101
+ */
102
+ export function FeaturedIcon({
103
+ icon,
104
+ color = "brand",
105
+ theme = "light",
106
+ size = "md",
107
+ className,
108
+ ...rest
109
+ }: FeaturedIconProps) {
110
+ const px = iconPx[size];
111
+
112
+ const themeClasses = clsx(
113
+ theme === "light" && [lightBg[color], lightFg[color]],
114
+ theme === "dark" && [
115
+ darkBg[color],
116
+ "text-fg-white border-2 border-[rgba(255,255,255,0.12)] shadow-skeuomorphic",
117
+ ],
118
+ theme === "gradient" && [gradientFill[color], "text-fg-white"],
119
+ theme === "modern" && [
120
+ "bg-bg-primary text-fg-secondary border border-border-primary shadow-skeuomorphic",
121
+ ],
122
+ theme === "modern-neue" && [
123
+ "bg-bg-primary text-fg-secondary border border-border-primary shadow-xs",
124
+ ],
125
+ );
126
+
127
+ return (
128
+ <span
129
+ className={clsx(
130
+ "inline-flex shrink-0 items-center justify-center",
131
+ boxSize[size],
132
+ radiusFor(theme),
133
+ themeClasses,
134
+ className,
135
+ )}
136
+ {...rest}
137
+ >
138
+ <span
139
+ aria-hidden="true"
140
+ style={{ width: px, height: px, display: "inline-flex" }}
141
+ >
142
+ {icon}
143
+ </span>
144
+ </span>
145
+ );
146
+ }
@@ -0,0 +1,7 @@
1
+ export { FeaturedIcon } from "./FeaturedIcon";
2
+ export type {
3
+ FeaturedIconProps,
4
+ FeaturedIconColor,
5
+ FeaturedIconTheme,
6
+ FeaturedIconSize,
7
+ } from "./FeaturedIcon";
@@ -100,7 +100,7 @@ const inputBox =
100
100
  * - **minimal** — single-line field + square send button (row).
101
101
  * - **textarea** — multiline box, attach/emoji + brand Send pinned bottom-right,
102
102
  * record button top-right.
103
- * - **advanced** — AI prompt box on `bg-secondary`; author chip + Shortcuts/Attach
103
+ * - **advanced** — AI prompt box on `bg-bg-secondary`; author chip + Shortcuts/Attach
104
104
  * footer, mic top-right, resize handle.
105
105
  *
106
106
  * Renders real `<input>`/`<textarea>` (controlled via `value`/`onValueChange`);
@@ -2,6 +2,7 @@ import { type HTMLAttributes, type ReactNode } from "react";
2
2
  import clsx from "clsx";
3
3
 
4
4
  export type NavAccountCardVariant = "simple" | "card";
5
+ export type NavAccountCardBreakpoint = "desktop" | "mobile";
5
6
 
6
7
  export interface NavAccountCardProps
7
8
  extends Omit<HTMLAttributes<HTMLDivElement>, "title"> {
@@ -9,7 +10,14 @@ export interface NavAccountCardProps
9
10
  variant?: NavAccountCardVariant;
10
11
  /** card only — whether the dropdown menu is shown. */
11
12
  open?: boolean;
13
+ /** Width: `desktop` = 280px · `mobile` = 256px. */
14
+ breakpoint?: NavAccountCardBreakpoint;
15
+ /** Custom avatar node — takes precedence over the built-in `src` treatment. */
12
16
  avatar?: ReactNode;
17
+ /** Image URL for the built-in layered avatar (used when `avatar` is not supplied). */
18
+ src?: string;
19
+ /** Status dot on the built-in avatar — `true` online (green) · `false` offline (gray) · omit for none. */
20
+ online?: boolean;
13
21
  name?: ReactNode;
14
22
  email?: ReactNode;
15
23
  /** card only — toggles the dropdown (fires on trigger click). */
@@ -41,6 +49,28 @@ const LogOut = () => (
41
49
  </svg>
42
50
  );
43
51
 
52
+ /**
53
+ * Layered avatar treatment from Figma (node 7891:87996): white outer ring +
54
+ * inner hairline border + status dot. `border-[rgba(0,0,0,0.16)]` is a one-off
55
+ * value (no matching token) per the Figma spec. Used when `src` is supplied and
56
+ * no `avatar` slot is given.
57
+ */
58
+ const LayeredAvatar = ({ src, online }: { src?: string; online?: boolean }) => (
59
+ <span className="relative inline-flex size-10 shrink-0 rounded-full border-[0.75px] border-border-secondary-alt bg-bg-primary p-[1px] shadow-xs">
60
+ <span className="flex size-full overflow-hidden rounded-full border-[0.5px] border-[rgba(0,0,0,0.16)]">
61
+ <img src={src} alt="" className="size-full rounded-full object-cover" />
62
+ </span>
63
+ {online !== undefined && (
64
+ <span
65
+ className={clsx(
66
+ "absolute bottom-[-2px] right-[-2px] size-[14px] rounded-full border-[1.5px] border-bg-primary",
67
+ online ? "bg-fg-success-secondary" : "bg-utility-neutral-300",
68
+ )}
69
+ />
70
+ )}
71
+ </span>
72
+ );
73
+
44
74
  const AvatarLabel = ({ avatar, name, email }: { avatar?: ReactNode; name?: ReactNode; email?: ReactNode }) => (
45
75
  <div className="flex min-w-0 flex-1 items-center gap-md">
46
76
  {avatar && <span className="shrink-0">{avatar}</span>}
@@ -56,7 +86,7 @@ const AvatarLabel = ({ avatar, name, email }: { avatar?: ReactNode; name?: React
56
86
  * with a sign-out button; `card` renders a boxed trigger that, when `open`,
57
87
  * reveals a dropdown (the `menu` slot — compose `NavAccountCardMenuItem` rows).
58
88
  *
59
- * The active account is marked with `bg-brand-solid` (the radio inside the
89
+ * The active account is marked with `bg-bg-brand-solid` (the radio inside the
60
90
  * account menu items). Note: Figma opens the desktop menu as a right-side
61
91
  * flyout; this opens it above the trigger (`bottom-full`) — a layout
62
92
  * simplification (see figma-map). Width defaults to 280px; override via
@@ -65,7 +95,10 @@ const AvatarLabel = ({ avatar, name, email }: { avatar?: ReactNode; name?: React
65
95
  export function NavAccountCard({
66
96
  variant = "card",
67
97
  open = false,
98
+ breakpoint = "desktop",
68
99
  avatar,
100
+ src,
101
+ online,
69
102
  name,
70
103
  email,
71
104
  onToggle,
@@ -74,16 +107,20 @@ export function NavAccountCard({
74
107
  className,
75
108
  ...rest
76
109
  }: NavAccountCardProps) {
110
+ const widthClass = breakpoint === "mobile" ? "w-[256px]" : "w-[280px]";
111
+ const avatarNode = avatar ?? (src !== undefined ? <LayeredAvatar src={src} online={online} /> : undefined);
112
+
77
113
  if (variant === "simple") {
78
114
  return (
79
115
  <div
80
116
  className={clsx(
81
- "relative flex w-[280px] items-start gap-xl border-t border-border-secondary px-md pt-2xl",
117
+ "relative flex items-start gap-xl border-t border-border-secondary px-md pt-2xl",
118
+ widthClass,
82
119
  className,
83
120
  )}
84
121
  {...rest}
85
122
  >
86
- <AvatarLabel avatar={avatar} name={name} email={email} />
123
+ <AvatarLabel avatar={avatarNode} name={name} email={email} />
87
124
  <button
88
125
  type="button"
89
126
  onClick={onSignOut}
@@ -97,13 +134,13 @@ export function NavAccountCard({
97
134
  }
98
135
 
99
136
  return (
100
- <div className={clsx("relative w-[280px]", className)} {...rest}>
137
+ <div className={clsx("relative", widthClass, className)} {...rest}>
101
138
  <button
102
139
  type="button"
103
140
  onClick={onToggle}
104
141
  className="relative flex w-full items-start gap-xl rounded-xl border border-border-secondary bg-bg-primary-alt p-lg text-left shadow-xs"
105
142
  >
106
- <AvatarLabel avatar={avatar} name={name} email={email} />
143
+ <AvatarLabel avatar={avatarNode} name={name} email={email} />
107
144
  <span
108
145
  className={clsx(
109
146
  "absolute right-[7px] top-[7px] flex items-center justify-center rounded-sm p-sm text-fg-quaternary",
@@ -1,2 +1,6 @@
1
1
  export { NavAccountCard } from "./NavAccountCard";
2
- export type { NavAccountCardProps, NavAccountCardVariant } from "./NavAccountCard";
2
+ export type {
3
+ NavAccountCardProps,
4
+ NavAccountCardVariant,
5
+ NavAccountCardBreakpoint,
6
+ } from "./NavAccountCard";
@@ -27,7 +27,7 @@ export interface NavAccountCardMenuItemProps
27
27
  * A single row inside `NavAccountCard`'s dropdown. Two shapes:
28
28
  * - `menu-item` — icon + label + optional shortcut chip (View profile, Settings…).
29
29
  * - `account` — avatar + name/email with a radio indicator; `current` fills the
30
- * radio with `bg-brand-solid` (the active-account marker).
30
+ * radio with `bg-bg-brand-solid` (the active-account marker).
31
31
  */
32
32
  export function NavAccountCardMenuItem({
33
33
  type = "menu-item",
@@ -3,7 +3,7 @@ import clsx from "clsx";
3
3
 
4
4
  export interface NavButtonProps
5
5
  extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
6
- /** Active route — applies the selected (`bg-secondary`) background. */
6
+ /** Active route — applies the selected (`bg-bg-secondary`) background. */
7
7
  current?: boolean;
8
8
  /** Render only the leading icon (square button). */
9
9
  iconOnly?: boolean;
@@ -11,7 +11,7 @@ export interface NavFeaturedCardProps
11
11
  children?: ReactNode;
12
12
  /** CTA row — compose Button(s) / link buttons. */
13
13
  action?: ReactNode;
14
- /** Bordered surface (`bg-primary` + border) vs subtle (`bg-secondary`). */
14
+ /** Bordered surface (`bg-bg-primary` + border) vs subtle (`bg-bg-secondary`). */
15
15
  bordered?: boolean;
16
16
  /** Renders an x-close button top-right. */
17
17
  onClose?: () => void;
@@ -3,7 +3,7 @@ import clsx from "clsx";
3
3
 
4
4
  export interface NavItemBaseProps
5
5
  extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
6
- /** Active route — selected (`bg-secondary`) background. */
6
+ /** Active route — selected (`bg-bg-secondary`) background. */
7
7
  current?: boolean;
8
8
  /** Leading icon (20px). */
9
9
  icon?: ReactNode;
@@ -3,7 +3,7 @@ import clsx from "clsx";
3
3
 
4
4
  export interface NavItemDropdownBaseProps
5
5
  extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
6
- /** Active group — selected (`bg-secondary`) background on the trigger. */
6
+ /** Active group — selected (`bg-bg-secondary`) background on the trigger. */
7
7
  current?: boolean;
8
8
  /** Expanded state — reveals the submenu. */
9
9
  open?: boolean;
@@ -20,7 +20,7 @@ export interface PageHeaderProps
20
20
  actions?: ReactNode;
21
21
  /** Optional breadcrumbs slot above the title. */
22
22
  breadcrumbs?: ReactNode;
23
- /** Banner background image (banner styles); falls back to `bg-tertiary`. */
23
+ /** Banner background image (banner styles); falls back to `bg-bg-tertiary`. */
24
24
  bannerUrl?: string;
25
25
  }
26
26
 
@@ -13,7 +13,7 @@ export interface PaginationDotIndicatorProps
13
13
 
14
14
  /**
15
15
  * Single pagination indicator — a dot or a line. `current` fills it with the
16
- * brand colour, otherwise `bg-quaternary`. Dimensions per size/variant match
16
+ * brand colour, otherwise `bg-bg-quaternary`. Dimensions per size/variant match
17
17
  * Figma (dot md 8 / lg 10; line md h6 / lg h8, both 40 wide).
18
18
  */
19
19
  export function PaginationDotIndicator({
@@ -32,7 +32,7 @@ const Bubble = ({ pct, where }: { pct: number; where: "top" | "bottom" }) => (
32
32
 
33
33
  /**
34
34
  * Progress bar — pure CSS, no chart lib. `value` (0–100) drives an animated fill
35
- * (`fg-brand-primary` on a `bg-quaternary` track). `label`: none / right (inline
35
+ * (`fg-brand-primary` on a `bg-bg-quaternary` track). `label`: none / right (inline
36
36
  * %) / bottom (% under) / top|bottom-floating (tooltip bubble tracking the fill).
37
37
  */
38
38
  export function ProgressBar({ value, label = "none", className, ...rest }: ProgressBarProps) {
@@ -50,7 +50,7 @@ function Control({ kind, selected, size }: { kind: "radio" | "checkbox"; selecte
50
50
 
51
51
  /**
52
52
  * Selectable radio/checkbox card. One component over the 6 Figma `type`s — the
53
- * shared shell (border, padding, `radius-xl`, selected `border-brand`) is
53
+ * shared shell (border, padding, `radius-xl`, selected `border-border-brand`) is
54
54
  * constant; only the leading element and control placement change. `radioButton`
55
55
  * uses a radio (leading); `checkbox` a checkbox (leading); `iconSimple`/`avatar`/
56
56
  * `paymentIcon` put a `leading` visual on the left and the checkbox trailing;
@@ -40,8 +40,8 @@ function CheckboxBox({ selected, big }: { selected: boolean; big: boolean }) {
40
40
  /**
41
41
  * List-item primitive for `Select` and Multi-select dropdowns. One component over
42
42
  * both: `multi` swaps the trailing selected-check for a leading checkbox. The
43
- * shared shell is a full-width row with hover `bg-primary-hover`; `label` is
44
- * `text-primary`, `supportingText` sits inline in `text-tertiary`.
43
+ * shared shell is a full-width row with hover `bg-bg-primary-hover`; `label` is
44
+ * `text-text-primary`, `supportingText` sits inline in `text-text-tertiary`.
45
45
  */
46
46
  export function SelectMenuItem({
47
47
  label,
@@ -37,7 +37,7 @@ const WIDTH: Record<SidebarNavigationType, string> = {
37
37
  * a fixed-width rail (`slim` collapses to 68px). Composes existing `Nav*`
38
38
  * components via slots/children — it does not reimplement nav items. On mobile
39
39
  * the rail is hidden behind a hamburger (`NavMenuButton`); opening it shows a
40
- * `bg-overlay` + `backdrop-blur-md` scrim and slides the rail in as a drawer.
40
+ * `bg-bg-overlay` + `backdrop-blur-md` scrim and slides the rail in as a drawer.
41
41
  *
42
42
  * NOTE: `dualTier`'s icon-rail + expanding-panel split is simplified to a single
43
43
  * 280px column here — compose the icon rail separately if you need the two-tier
@@ -79,7 +79,7 @@ export function SidebarNavigation({
79
79
  return (
80
80
  <>
81
81
  {/* Desktop rail */}
82
- <aside className="hidden h-full md:block">{rail}</aside>
82
+ <aside className="hidden h-full md:flex">{rail}</aside>
83
83
 
84
84
  {/* Mobile trigger + drawer */}
85
85
  <div className="md:hidden">
@@ -51,8 +51,8 @@ const GLYPH: Record<SocialBrand, ReactNode> = {
51
51
  };
52
52
 
53
53
  /**
54
- * Social-login button. All themes share the neutral white shell (border-primary,
55
- * shadow-xs, `text-secondary` label) — `theme` only controls the brand glyph:
54
+ * Social-login button. All themes share the neutral white shell (border-border-primary,
55
+ * shadow-xs, `text-text-secondary` label) — `theme` only controls the brand glyph:
56
56
  * `color`/`brand` = full color, `gray` = monochrome (CSS grayscale, flagged
57
57
  * approximation of UntitledUI's true single-color gray logos). `iconOnly` drops
58
58
  * the label for a square button.