@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.
- package/dist/index.cjs +143 -28
- package/dist/index.d.cts +56 -26
- package/dist/index.d.ts +56 -26
- package/dist/index.js +143 -29
- package/package.json +11 -9
- package/src/components/Button/Button.tsx +42 -22
- package/src/components/ButtonDestructive/ButtonDestructive.tsx +3 -3
- package/src/components/CalendarColumnHeader/CalendarColumnHeader.tsx +1 -1
- package/src/components/CalendarViewDropdown/CalendarViewDropdown.tsx +1 -1
- package/src/components/ChartTooltip/ChartTooltip.tsx +1 -1
- package/src/components/Checkbox/checkboxBase.tsx +1 -1
- package/src/components/ContentFeatureText/ContentFeatureText.tsx +3 -2
- package/src/components/ContextMenu/ContextMenu.tsx +1 -1
- package/src/components/FeaturedIcon/FeaturedIcon.tsx +146 -0
- package/src/components/FeaturedIcon/index.ts +7 -0
- package/src/components/MessageAction/MessageAction.tsx +1 -1
- package/src/components/NavAccountCard/NavAccountCard.tsx +42 -5
- package/src/components/NavAccountCard/index.ts +5 -1
- package/src/components/NavAccountCardMenuItem/NavAccountCardMenuItem.tsx +1 -1
- package/src/components/NavButton/NavButton.tsx +1 -1
- package/src/components/NavFeaturedCard/NavFeaturedCard.tsx +1 -1
- package/src/components/NavItemBase/NavItemBase.tsx +1 -1
- package/src/components/NavItemDropdownBase/NavItemDropdownBase.tsx +1 -1
- package/src/components/PageHeader/PageHeader.tsx +1 -1
- package/src/components/PaginationDotIndicator/PaginationDotIndicator.tsx +1 -1
- package/src/components/ProgressBar/ProgressBar.tsx +1 -1
- package/src/components/RadioGroupItem/RadioGroupItem.tsx +1 -1
- package/src/components/SelectMenuItem/SelectMenuItem.tsx +2 -2
- package/src/components/SidebarNavigation/SidebarNavigation.tsx +2 -2
- package/src/components/SocialButton/SocialButton.tsx +2 -2
- package/src/components/TableHeaderLabel/TableHeaderLabel.tsx +1 -1
- package/src/components/Toggle/Toggle.tsx +1 -1
- package/src/components/Tooltip/Tooltip.tsx +1 -1
- package/src/components/TreeViewItem/TreeViewItem.tsx +1 -1
- package/src/components/VerificationCodeInput/VerificationCodeInput.tsx +1 -1
- package/src/index.ts +1 -0
- 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.
|
|
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
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
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
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
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]
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
"
|
|
59
|
-
"
|
|
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 ?
|
|
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) {
|
|
@@ -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
|
|
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-
|
|
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
|
+
}
|
|
@@ -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
|
|
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={
|
|
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
|
|
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={
|
|
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",
|
|
@@ -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:
|
|
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.
|