@borisj74/bv-ds 0.1.3 → 0.1.5
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 +178 -83
- package/dist/index.d.cts +82 -25
- package/dist/index.d.ts +82 -25
- package/dist/index.js +178 -83
- package/package.json +3 -2
- package/src/components/CalendarCell/CalendarCell.tsx +63 -10
- package/src/components/CalendarCell/index.ts +5 -1
- package/src/components/CalendarCellDayWeekView/CalendarCellDayWeekView.tsx +28 -2
- package/src/components/CalendarCellDayWeekView/index.ts +5 -1
- package/src/components/CalendarColumnHeader/CalendarColumnHeader.tsx +21 -5
- package/src/components/CalendarColumnHeader/index.ts +2 -0
- package/src/components/CalendarEvent/CalendarEvent.tsx +69 -14
- package/src/components/CalendarEvent/index.ts +6 -1
- package/src/components/CalendarHeader/CalendarHeader.tsx +5 -1
- package/src/components/CalendarRowLabel/CalendarRowLabel.tsx +5 -3
- package/src/components/CalendarTimemarker/CalendarTimemarker.tsx +8 -2
- package/src/components/CalendarTimemarker/index.ts +1 -0
- package/src/components/CalendarViewDropdown/CalendarViewDropdown.tsx +18 -2
- package/src/components/NavAccountCard/NavAccountCard.tsx +12 -18
- package/src/components/NavItemBase/NavItemBase.tsx +4 -7
- package/src/components/NavItemDropdownBase/NavItemDropdownBase.tsx +6 -7
- package/src/components/NavMenuButton/NavMenuButton.tsx +18 -12
- package/src/types/bv-ds-icons.d.ts +6 -0
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
import clsx from "clsx";
|
|
3
3
|
|
|
4
|
+
export type CalendarTimeslotType =
|
|
5
|
+
| "empty"
|
|
6
|
+
| "30min"
|
|
7
|
+
| "60min"
|
|
8
|
+
| "90min"
|
|
9
|
+
| "120min";
|
|
10
|
+
export type CalendarTimeslotState = "default" | "hover";
|
|
11
|
+
|
|
4
12
|
export interface CalendarCellDayWeekViewProps {
|
|
13
|
+
/**
|
|
14
|
+
* Slot duration — drives the row height (empty/30min → 48px, 60min → 96px,
|
|
15
|
+
* 90min → 144px, 120min → 192px). Omit to keep the auto min-height.
|
|
16
|
+
*/
|
|
17
|
+
type?: CalendarTimeslotType;
|
|
18
|
+
/** `hover` tints the slot (also rendered via CSS :hover). */
|
|
19
|
+
state?: CalendarTimeslotState;
|
|
5
20
|
/** Event block(s) occupying the slot — typically CalendarEventDayWeekView. */
|
|
6
21
|
children?: ReactNode;
|
|
7
22
|
/** Renders a hover "+" add button bottom-right. */
|
|
@@ -11,6 +26,14 @@ export interface CalendarCellDayWeekViewProps {
|
|
|
11
26
|
className?: string;
|
|
12
27
|
}
|
|
13
28
|
|
|
29
|
+
const heightClasses: Record<CalendarTimeslotType, string> = {
|
|
30
|
+
empty: "h-[48px]",
|
|
31
|
+
"30min": "h-[48px]",
|
|
32
|
+
"60min": "h-[96px]",
|
|
33
|
+
"90min": "h-[144px]",
|
|
34
|
+
"120min": "h-[192px]",
|
|
35
|
+
};
|
|
36
|
+
|
|
14
37
|
function PlusIcon() {
|
|
15
38
|
return (
|
|
16
39
|
<svg viewBox="0 0 20 20" fill="none" className="size-4" aria-hidden>
|
|
@@ -27,6 +50,8 @@ function PlusIcon() {
|
|
|
27
50
|
|
|
28
51
|
/** A time-slot cell in day/week view. Holds CalendarEventDayWeekView blocks. */
|
|
29
52
|
export function CalendarCellDayWeekView({
|
|
53
|
+
type,
|
|
54
|
+
state = "default",
|
|
30
55
|
children,
|
|
31
56
|
onAdd,
|
|
32
57
|
muted = false,
|
|
@@ -35,8 +60,9 @@ export function CalendarCellDayWeekView({
|
|
|
35
60
|
return (
|
|
36
61
|
<div
|
|
37
62
|
className={clsx(
|
|
38
|
-
"group relative
|
|
39
|
-
|
|
63
|
+
"group relative border-b border-border-secondary p-xs font-body transition-colors hover:bg-bg-secondary",
|
|
64
|
+
type ? heightClasses[type] : "min-h-[64px]",
|
|
65
|
+
muted ? "bg-bg-secondary" : state === "hover" ? "bg-bg-secondary" : "bg-bg-primary",
|
|
40
66
|
className,
|
|
41
67
|
)}
|
|
42
68
|
>
|
|
@@ -1,2 +1,6 @@
|
|
|
1
1
|
export { CalendarCellDayWeekView } from "./CalendarCellDayWeekView";
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
CalendarCellDayWeekViewProps,
|
|
4
|
+
CalendarTimeslotType,
|
|
5
|
+
CalendarTimeslotState,
|
|
6
|
+
} from "./CalendarCellDayWeekView";
|
|
@@ -1,30 +1,45 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
|
|
3
3
|
export type CalendarColumnHeaderOrientation = "horizontal" | "vertical";
|
|
4
|
+
export type CalendarColumnHeaderType = "default" | "selected" | "today";
|
|
5
|
+
export type CalendarBreakpoint = "desktop" | "mobile";
|
|
4
6
|
|
|
5
7
|
export interface CalendarColumnHeaderProps {
|
|
6
8
|
/** Weekday label (e.g. "Mon"). */
|
|
7
9
|
weekday: string;
|
|
8
10
|
/** Date number. */
|
|
9
11
|
date: string | number;
|
|
10
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Cell state. `selected` → brand-solid circle behind the date; `today` →
|
|
14
|
+
* brand text + brand underline. Takes precedence over `current` when set.
|
|
15
|
+
*/
|
|
16
|
+
type?: CalendarColumnHeaderType;
|
|
17
|
+
/** Legacy boolean — equivalent to `type="selected"`. Kept for back-compat. */
|
|
11
18
|
current?: boolean;
|
|
12
19
|
orientation?: CalendarColumnHeaderOrientation;
|
|
20
|
+
/** Desktop fixes the column to 160px; mobile lets it size to content. */
|
|
21
|
+
breakpoint?: CalendarBreakpoint;
|
|
13
22
|
className?: string;
|
|
14
23
|
}
|
|
15
24
|
|
|
16
25
|
export function CalendarColumnHeader({
|
|
17
26
|
weekday,
|
|
18
27
|
date,
|
|
28
|
+
type,
|
|
19
29
|
current = false,
|
|
20
30
|
orientation = "vertical",
|
|
31
|
+
breakpoint = "desktop",
|
|
21
32
|
className,
|
|
22
33
|
}: CalendarColumnHeaderProps) {
|
|
34
|
+
// `type` wins; otherwise the legacy `current` flag maps to "selected".
|
|
35
|
+
const state: CalendarColumnHeaderType = type ?? (current ? "selected" : "default");
|
|
36
|
+
|
|
23
37
|
return (
|
|
24
38
|
<div
|
|
25
39
|
className={clsx(
|
|
26
40
|
"flex items-center justify-center gap-md py-md font-body",
|
|
27
41
|
orientation === "vertical" && "flex-col gap-xs",
|
|
42
|
+
breakpoint === "desktop" && "w-[160px]",
|
|
28
43
|
className,
|
|
29
44
|
)}
|
|
30
45
|
>
|
|
@@ -32,11 +47,12 @@ export function CalendarColumnHeader({
|
|
|
32
47
|
<span
|
|
33
48
|
className={clsx(
|
|
34
49
|
"flex items-center justify-center text-xs font-semibold",
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
50
|
+
state === "selected" && "size-6 rounded-full bg-bg-brand-solid text-white",
|
|
51
|
+
state === "today" &&
|
|
52
|
+
"border-b-2 border-border-brand pb-xxs text-text-brand-secondary",
|
|
53
|
+
state === "default" && "text-text-secondary",
|
|
38
54
|
)}
|
|
39
|
-
aria-current={
|
|
55
|
+
aria-current={state !== "default" ? "date" : undefined}
|
|
40
56
|
>
|
|
41
57
|
{date}
|
|
42
58
|
</span>
|
|
@@ -11,6 +11,9 @@ export type CalendarEventColor =
|
|
|
11
11
|
| "orange"
|
|
12
12
|
| "amber";
|
|
13
13
|
|
|
14
|
+
export type CalendarEventBreakpoint = "desktop" | "mobile";
|
|
15
|
+
export type CalendarEventState = "default" | "hover";
|
|
16
|
+
|
|
14
17
|
export interface CalendarEventProps {
|
|
15
18
|
/** Event title. */
|
|
16
19
|
title: string;
|
|
@@ -19,10 +22,14 @@ export interface CalendarEventProps {
|
|
|
19
22
|
color?: CalendarEventColor;
|
|
20
23
|
/** Filled colour-fill style vs the subtle white style. */
|
|
21
24
|
filled?: boolean;
|
|
25
|
+
/** Desktop renders the full chip; mobile collapses to an 8px dot. */
|
|
26
|
+
breakpoint?: CalendarEventBreakpoint;
|
|
27
|
+
/** `hover` darkens the fill one shade (filled chips only). */
|
|
28
|
+
state?: CalendarEventState;
|
|
22
29
|
className?: string;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
//
|
|
32
|
+
// Status dot (utility-<c>-500) — also the mobile collapsed form.
|
|
26
33
|
const dotClasses: Record<CalendarEventColor, string> = {
|
|
27
34
|
neutral: "bg-utility-neutral-500",
|
|
28
35
|
brand: "bg-utility-brand-500",
|
|
@@ -35,16 +42,43 @@ const dotClasses: Record<CalendarEventColor, string> = {
|
|
|
35
42
|
amber: "bg-utility-amber-500",
|
|
36
43
|
};
|
|
37
44
|
|
|
45
|
+
// Filled chip — Figma-confirmed: bg-50 / border-200 / title text-700 (node 7991:54084).
|
|
38
46
|
const filledClasses: Record<CalendarEventColor, string> = {
|
|
39
|
-
neutral: "bg-utility-neutral-
|
|
40
|
-
brand: "bg-utility-brand-
|
|
41
|
-
emerald: "bg-utility-emerald-
|
|
42
|
-
blue: "bg-utility-blue-
|
|
43
|
-
indigo: "bg-utility-indigo-
|
|
44
|
-
purple: "bg-utility-purple-
|
|
45
|
-
pink: "bg-utility-pink-
|
|
46
|
-
orange: "bg-utility-orange-
|
|
47
|
-
amber: "bg-utility-amber-
|
|
47
|
+
neutral: "bg-utility-neutral-50 border-utility-neutral-200 text-utility-neutral-700",
|
|
48
|
+
brand: "bg-utility-brand-50 border-utility-brand-200 text-utility-brand-700",
|
|
49
|
+
emerald: "bg-utility-emerald-50 border-utility-emerald-200 text-utility-emerald-700",
|
|
50
|
+
blue: "bg-utility-blue-50 border-utility-blue-200 text-utility-blue-700",
|
|
51
|
+
indigo: "bg-utility-indigo-50 border-utility-indigo-200 text-utility-indigo-700",
|
|
52
|
+
purple: "bg-utility-purple-50 border-utility-purple-200 text-utility-purple-700",
|
|
53
|
+
pink: "bg-utility-pink-50 border-utility-pink-200 text-utility-pink-700",
|
|
54
|
+
orange: "bg-utility-orange-50 border-utility-orange-200 text-utility-orange-700",
|
|
55
|
+
amber: "bg-utility-amber-50 border-utility-amber-200 text-utility-amber-700",
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Hover fill — one shade darker (bg-100).
|
|
59
|
+
const filledHoverClasses: Record<CalendarEventColor, string> = {
|
|
60
|
+
neutral: "bg-utility-neutral-100",
|
|
61
|
+
brand: "bg-utility-brand-100",
|
|
62
|
+
emerald: "bg-utility-emerald-100",
|
|
63
|
+
blue: "bg-utility-blue-100",
|
|
64
|
+
indigo: "bg-utility-indigo-100",
|
|
65
|
+
purple: "bg-utility-purple-100",
|
|
66
|
+
pink: "bg-utility-pink-100",
|
|
67
|
+
orange: "bg-utility-orange-100",
|
|
68
|
+
amber: "bg-utility-amber-100",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
// Time-label colour for filled chips (utility-<c>-600).
|
|
72
|
+
const timeClasses: Record<CalendarEventColor, string> = {
|
|
73
|
+
neutral: "text-utility-neutral-600",
|
|
74
|
+
brand: "text-utility-brand-600",
|
|
75
|
+
emerald: "text-utility-emerald-600",
|
|
76
|
+
blue: "text-utility-blue-600",
|
|
77
|
+
indigo: "text-utility-indigo-600",
|
|
78
|
+
purple: "text-utility-purple-600",
|
|
79
|
+
pink: "text-utility-pink-600",
|
|
80
|
+
orange: "text-utility-orange-600",
|
|
81
|
+
amber: "text-utility-amber-600",
|
|
48
82
|
};
|
|
49
83
|
|
|
50
84
|
export function CalendarEvent({
|
|
@@ -52,22 +86,43 @@ export function CalendarEvent({
|
|
|
52
86
|
time,
|
|
53
87
|
color = "neutral",
|
|
54
88
|
filled = false,
|
|
89
|
+
breakpoint = "desktop",
|
|
90
|
+
state = "default",
|
|
55
91
|
className,
|
|
56
92
|
}: CalendarEventProps) {
|
|
93
|
+
// Mobile view collapses the chip down to a single 8px status dot.
|
|
94
|
+
if (breakpoint === "mobile") {
|
|
95
|
+
return (
|
|
96
|
+
<span
|
|
97
|
+
className={clsx("inline-block size-2 rounded-full", dotClasses[color], className)}
|
|
98
|
+
role="img"
|
|
99
|
+
aria-label={time ? `${title}, ${time}` : title}
|
|
100
|
+
/>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
57
104
|
return (
|
|
58
105
|
<div
|
|
59
106
|
className={clsx(
|
|
60
|
-
"flex w-full items-center gap-md rounded-sm px-md py-xs font-body text-xs",
|
|
107
|
+
"flex w-full items-center gap-md rounded-sm border px-md py-xs font-body text-xs",
|
|
61
108
|
filled
|
|
62
|
-
? filledClasses[color]
|
|
63
|
-
:
|
|
109
|
+
? clsx(filledClasses[color], state === "hover" && filledHoverClasses[color])
|
|
110
|
+
: clsx(
|
|
111
|
+
"border-border-secondary bg-bg-primary text-text-secondary",
|
|
112
|
+
state === "hover" && "bg-bg-primary-hover",
|
|
113
|
+
),
|
|
64
114
|
className,
|
|
65
115
|
)}
|
|
66
116
|
>
|
|
67
117
|
<span className={clsx("size-2 shrink-0 rounded-full", dotClasses[color])} aria-hidden />
|
|
68
118
|
<span className="truncate font-semibold">{title}</span>
|
|
69
119
|
{time ? (
|
|
70
|
-
<span
|
|
120
|
+
<span
|
|
121
|
+
className={clsx(
|
|
122
|
+
"ml-auto shrink-0 font-normal",
|
|
123
|
+
filled ? timeClasses[color] : "text-text-tertiary",
|
|
124
|
+
)}
|
|
125
|
+
>
|
|
71
126
|
{time}
|
|
72
127
|
</span>
|
|
73
128
|
) : null}
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
export { CalendarEvent } from "./CalendarEvent";
|
|
2
|
-
export type {
|
|
2
|
+
export type {
|
|
3
|
+
CalendarEventProps,
|
|
4
|
+
CalendarEventColor,
|
|
5
|
+
CalendarEventBreakpoint,
|
|
6
|
+
CalendarEventState,
|
|
7
|
+
} from "./CalendarEvent";
|
|
@@ -6,6 +6,8 @@ export interface CalendarHeaderProps {
|
|
|
6
6
|
title: string;
|
|
7
7
|
/** Date range subtitle, e.g. "Jan 1, 2027 – Jan 31, 2027". */
|
|
8
8
|
range?: string;
|
|
9
|
+
/** Alias for `range` — Batch-36 prop name. `range` wins if both set. */
|
|
10
|
+
supportingText?: string;
|
|
9
11
|
/** Badge beside the title (e.g. a PillBadge "Week 1"). */
|
|
10
12
|
badge?: ReactNode;
|
|
11
13
|
/** Leading date chip — typically a CalendarDateIcon. */
|
|
@@ -19,11 +21,13 @@ export interface CalendarHeaderProps {
|
|
|
19
21
|
export function CalendarHeader({
|
|
20
22
|
title,
|
|
21
23
|
range,
|
|
24
|
+
supportingText,
|
|
22
25
|
badge,
|
|
23
26
|
dateIcon,
|
|
24
27
|
actions,
|
|
25
28
|
className,
|
|
26
29
|
}: CalendarHeaderProps) {
|
|
30
|
+
const subtitle = range ?? supportingText;
|
|
27
31
|
return (
|
|
28
32
|
<div
|
|
29
33
|
className={clsx(
|
|
@@ -38,7 +42,7 @@ export function CalendarHeader({
|
|
|
38
42
|
<h2 className="text-lg font-bold text-text-primary">{title}</h2>
|
|
39
43
|
{badge}
|
|
40
44
|
</div>
|
|
41
|
-
{
|
|
45
|
+
{subtitle ? <p className="text-sm text-text-tertiary">{subtitle}</p> : null}
|
|
42
46
|
</div>
|
|
43
47
|
</div>
|
|
44
48
|
{actions ? <div className="flex items-center gap-md">{actions}</div> : null}
|
|
@@ -2,12 +2,14 @@ import clsx from "clsx";
|
|
|
2
2
|
|
|
3
3
|
export interface CalendarRowLabelProps {
|
|
4
4
|
/** Time label for the row gutter (e.g. "9 AM"). */
|
|
5
|
-
label
|
|
5
|
+
label?: string;
|
|
6
|
+
/** Alias for `label` — Batch-36 prop name. One of the two is required. */
|
|
7
|
+
time?: string;
|
|
6
8
|
className?: string;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
/** Left-gutter time label aligned to the top of a day/week-view row. */
|
|
10
|
-
export function CalendarRowLabel({ label, className }: CalendarRowLabelProps) {
|
|
12
|
+
export function CalendarRowLabel({ label, time, className }: CalendarRowLabelProps) {
|
|
11
13
|
return (
|
|
12
14
|
<div
|
|
13
15
|
className={clsx(
|
|
@@ -15,7 +17,7 @@ export function CalendarRowLabel({ label, className }: CalendarRowLabelProps) {
|
|
|
15
17
|
className,
|
|
16
18
|
)}
|
|
17
19
|
>
|
|
18
|
-
{label}
|
|
20
|
+
{time ?? label}
|
|
19
21
|
</div>
|
|
20
22
|
);
|
|
21
23
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import clsx from "clsx";
|
|
2
2
|
|
|
3
3
|
export type CalendarTimemarkerAlign = "left" | "center";
|
|
4
|
+
export type CalendarTimemarkerBreakpoint = "desktop" | "mobile";
|
|
4
5
|
|
|
5
6
|
export interface CalendarTimemarkerProps {
|
|
6
7
|
/** Time label (e.g. "2:20 PM"). */
|
|
7
8
|
time: string;
|
|
8
9
|
/** Label position: leading, or centered between two line halves. */
|
|
9
10
|
align?: CalendarTimemarkerAlign;
|
|
11
|
+
/** Desktop shows the time label; mobile collapses to dot + line only. */
|
|
12
|
+
breakpoint?: CalendarTimemarkerBreakpoint;
|
|
10
13
|
className?: string;
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -23,20 +26,23 @@ const Label = ({ time }: { time: string }) => (
|
|
|
23
26
|
export function CalendarTimemarker({
|
|
24
27
|
time,
|
|
25
28
|
align = "left",
|
|
29
|
+
breakpoint = "desktop",
|
|
26
30
|
className,
|
|
27
31
|
}: CalendarTimemarkerProps) {
|
|
32
|
+
const showLabel = breakpoint === "desktop";
|
|
33
|
+
|
|
28
34
|
return (
|
|
29
35
|
<div className={clsx("flex w-full items-center gap-xs font-body", className)}>
|
|
30
36
|
{align === "center" ? (
|
|
31
37
|
<>
|
|
32
38
|
<Dot />
|
|
33
39
|
<Line />
|
|
34
|
-
<Label time={time} />
|
|
40
|
+
{showLabel ? <Label time={time} /> : null}
|
|
35
41
|
<Line />
|
|
36
42
|
</>
|
|
37
43
|
) : (
|
|
38
44
|
<>
|
|
39
|
-
<Label time={time} />
|
|
45
|
+
{showLabel ? <Label time={time} /> : null}
|
|
40
46
|
<Dot />
|
|
41
47
|
<Line />
|
|
42
48
|
</>
|
|
@@ -12,6 +12,12 @@ export interface CalendarViewOption {
|
|
|
12
12
|
export interface CalendarViewDropdownProps {
|
|
13
13
|
value: CalendarView;
|
|
14
14
|
onChange?: (value: CalendarView) => void;
|
|
15
|
+
/** Alias for `onChange` — Batch-36 prop name. Both fire on selection. */
|
|
16
|
+
onSelect?: (value: CalendarView) => void;
|
|
17
|
+
/** Controlled open state. Omit to let the component manage its own. */
|
|
18
|
+
open?: boolean;
|
|
19
|
+
/** Fires whenever the menu wants to open/close (controlled or not). */
|
|
20
|
+
onOpenChange?: (open: boolean) => void;
|
|
15
21
|
options?: CalendarViewOption[];
|
|
16
22
|
className?: string;
|
|
17
23
|
}
|
|
@@ -39,10 +45,19 @@ function Chevron() {
|
|
|
39
45
|
export function CalendarViewDropdown({
|
|
40
46
|
value,
|
|
41
47
|
onChange,
|
|
48
|
+
onSelect,
|
|
49
|
+
open: openProp,
|
|
50
|
+
onOpenChange,
|
|
42
51
|
options = DEFAULT_OPTIONS,
|
|
43
52
|
className,
|
|
44
53
|
}: CalendarViewDropdownProps) {
|
|
45
|
-
const [
|
|
54
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
55
|
+
const isControlled = openProp !== undefined;
|
|
56
|
+
const open = isControlled ? openProp : internalOpen;
|
|
57
|
+
const setOpen = (next: boolean) => {
|
|
58
|
+
if (!isControlled) setInternalOpen(next);
|
|
59
|
+
onOpenChange?.(next);
|
|
60
|
+
};
|
|
46
61
|
const selected = options.find((o) => o.value === value) ?? options[0];
|
|
47
62
|
|
|
48
63
|
return (
|
|
@@ -51,7 +66,7 @@ export function CalendarViewDropdown({
|
|
|
51
66
|
type="button"
|
|
52
67
|
aria-haspopup="listbox"
|
|
53
68
|
aria-expanded={open}
|
|
54
|
-
onClick={() => setOpen(
|
|
69
|
+
onClick={() => setOpen(!open)}
|
|
55
70
|
className="inline-flex items-center gap-md rounded-md border border-border-primary bg-bg-primary px-lg py-md text-sm font-semibold text-text-secondary shadow-skeuomorphic transition-colors hover:bg-bg-primary-hover focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-border-brand focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary"
|
|
56
71
|
>
|
|
57
72
|
{selected.label}
|
|
@@ -72,6 +87,7 @@ export function CalendarViewDropdown({
|
|
|
72
87
|
type="button"
|
|
73
88
|
onClick={() => {
|
|
74
89
|
onChange?.(opt.value);
|
|
90
|
+
onSelect?.(opt.value);
|
|
75
91
|
setOpen(false);
|
|
76
92
|
}}
|
|
77
93
|
className="flex w-full items-center gap-md rounded-sm px-md py-xs text-sm font-medium text-text-secondary transition-colors hover:bg-bg-primary-hover"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type HTMLAttributes, type ReactNode } from "react";
|
|
2
2
|
import clsx from "clsx";
|
|
3
|
+
import { ChevronSelectorVertical, LogOut01 } from "@borisj74/bv-ds-icons";
|
|
3
4
|
|
|
4
5
|
export type NavAccountCardVariant = "simple" | "card";
|
|
5
6
|
export type NavAccountCardBreakpoint = "desktop" | "mobile";
|
|
@@ -31,22 +32,11 @@ export interface NavAccountCardProps
|
|
|
31
32
|
menu?: ReactNode;
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
const LogOut = () => (
|
|
41
|
-
<svg viewBox="0 0 16 16" fill="none" className="size-4" aria-hidden>
|
|
42
|
-
<path
|
|
43
|
-
d="M10.67 11.33 14 8m0 0-3.33-3.33M14 8H6M6 2H4.67c-.93 0-1.4 0-1.76.18-.31.16-.57.42-.73.73C2 3.27 2 3.73 2 4.67v6.66c0 .94 0 1.4.18 1.76.16.31.42.57.73.73.36.18.83.18 1.76.18H6"
|
|
44
|
-
stroke="currentColor"
|
|
45
|
-
strokeWidth="1.33"
|
|
46
|
-
strokeLinecap="round"
|
|
47
|
-
strokeLinejoin="round"
|
|
48
|
-
/>
|
|
49
|
-
</svg>
|
|
35
|
+
// 16px icon container (Figma nav account card).
|
|
36
|
+
const IconBox16 = ({ children }: { children: ReactNode }) => (
|
|
37
|
+
<span className="overflow-clip relative shrink-0 size-[16px] flex items-center justify-center">
|
|
38
|
+
{children}
|
|
39
|
+
</span>
|
|
50
40
|
);
|
|
51
41
|
|
|
52
42
|
/**
|
|
@@ -127,7 +117,9 @@ export function NavAccountCard({
|
|
|
127
117
|
aria-label="Sign out"
|
|
128
118
|
className="absolute right-0 top-[15px] flex items-center justify-center rounded-sm p-sm text-fg-quaternary transition-colors hover:text-fg-quaternary-hover"
|
|
129
119
|
>
|
|
130
|
-
<
|
|
120
|
+
<IconBox16>
|
|
121
|
+
<LogOut01 className="w-full h-full" />
|
|
122
|
+
</IconBox16>
|
|
131
123
|
</button>
|
|
132
124
|
</div>
|
|
133
125
|
);
|
|
@@ -147,7 +139,9 @@ export function NavAccountCard({
|
|
|
147
139
|
open && "bg-bg-primary-hover",
|
|
148
140
|
)}
|
|
149
141
|
>
|
|
150
|
-
<
|
|
142
|
+
<IconBox16>
|
|
143
|
+
<ChevronSelectorVertical className="w-full h-full" />
|
|
144
|
+
</IconBox16>
|
|
151
145
|
</span>
|
|
152
146
|
</button>
|
|
153
147
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
2
2
|
import clsx from "clsx";
|
|
3
|
+
import { ChevronDown } from "@borisj74/bv-ds-icons";
|
|
3
4
|
|
|
4
5
|
export interface NavItemBaseProps
|
|
5
6
|
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
|
|
@@ -16,12 +17,6 @@ export interface NavItemBaseProps
|
|
|
16
17
|
trailingChevron?: boolean;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
const ChevronDown = () => (
|
|
20
|
-
<svg viewBox="0 0 16 16" fill="none" className="size-4" aria-hidden>
|
|
21
|
-
<path d="M4 6l4 4 4-4" stroke="currentColor" strokeWidth="1.33" strokeLinecap="round" strokeLinejoin="round" />
|
|
22
|
-
</svg>
|
|
23
|
-
);
|
|
24
|
-
|
|
25
20
|
/**
|
|
26
21
|
* Base sidebar nav link (272px). `current` selects it; optional leading `dot`,
|
|
27
22
|
* leading `icon`, trailing count `badge`, and `trailingChevron`. Brand focus ring.
|
|
@@ -70,7 +65,9 @@ export function NavItemBase({
|
|
|
70
65
|
)}
|
|
71
66
|
{trailingChevron && (
|
|
72
67
|
<span className="shrink-0 text-fg-quaternary">
|
|
73
|
-
<
|
|
68
|
+
<span className="overflow-clip relative shrink-0 size-[16px] flex items-center justify-center">
|
|
69
|
+
<ChevronDown className="w-full h-full" />
|
|
70
|
+
</span>
|
|
74
71
|
</span>
|
|
75
72
|
)}
|
|
76
73
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
2
2
|
import clsx from "clsx";
|
|
3
|
+
import { ChevronDown } from "@borisj74/bv-ds-icons";
|
|
3
4
|
|
|
4
5
|
export interface NavItemDropdownBaseProps
|
|
5
6
|
extends Omit<ButtonHTMLAttributes<HTMLButtonElement>, "children"> {
|
|
@@ -16,12 +17,6 @@ export interface NavItemDropdownBaseProps
|
|
|
16
17
|
children?: ReactNode;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
const Chevron = ({ up }: { up?: boolean }) => (
|
|
20
|
-
<svg viewBox="0 0 16 16" fill="none" className="size-4" aria-hidden>
|
|
21
|
-
<path d={up ? "M4 10l4-4 4 4" : "M4 6l4 4 4-4"} stroke="currentColor" strokeWidth="1.33" strokeLinecap="round" strokeLinejoin="round" />
|
|
22
|
-
</svg>
|
|
23
|
-
);
|
|
24
|
-
|
|
25
20
|
/**
|
|
26
21
|
* Expandable sidebar nav group (272px). The trigger mirrors `NavItemBase`; when
|
|
27
22
|
* `open`, `children` (indented submenu rows) render below with a chevron flip.
|
|
@@ -62,7 +57,11 @@ export function NavItemDropdownBase({
|
|
|
62
57
|
</span>
|
|
63
58
|
</div>
|
|
64
59
|
<span className="shrink-0 text-fg-quaternary">
|
|
65
|
-
<
|
|
60
|
+
<span className="overflow-clip relative shrink-0 size-[16px] flex items-center justify-center">
|
|
61
|
+
<ChevronDown
|
|
62
|
+
className={clsx("w-full h-full transition-transform", open && "rotate-180")}
|
|
63
|
+
/>
|
|
64
|
+
</span>
|
|
66
65
|
</span>
|
|
67
66
|
</div>
|
|
68
67
|
</button>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type ButtonHTMLAttributes } from "react";
|
|
1
|
+
import { type ButtonHTMLAttributes, type ReactNode } from "react";
|
|
2
2
|
import clsx from "clsx";
|
|
3
|
+
import { Menu02, XClose } from "@borisj74/bv-ds-icons";
|
|
3
4
|
|
|
4
5
|
export interface NavMenuButtonProps
|
|
5
6
|
extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
@@ -7,16 +8,11 @@ export interface NavMenuButtonProps
|
|
|
7
8
|
opened?: boolean;
|
|
8
9
|
}
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const XClose = () => (
|
|
17
|
-
<svg viewBox="0 0 20 20" fill="none" className="size-5" aria-hidden>
|
|
18
|
-
<path d="M15 5 5 15M5 5l10 10" stroke="currentColor" strokeWidth="1.67" strokeLinecap="round" strokeLinejoin="round" />
|
|
19
|
-
</svg>
|
|
11
|
+
// 20px icon container (Figma nav menu button).
|
|
12
|
+
const IconBox = ({ children }: { children: ReactNode }) => (
|
|
13
|
+
<span className="overflow-clip relative shrink-0 size-[20px] flex items-center justify-center">
|
|
14
|
+
{children}
|
|
15
|
+
</span>
|
|
20
16
|
);
|
|
21
17
|
|
|
22
18
|
/**
|
|
@@ -41,7 +37,17 @@ export function NavMenuButton({
|
|
|
41
37
|
)}
|
|
42
38
|
{...rest}
|
|
43
39
|
>
|
|
44
|
-
{opened ?
|
|
40
|
+
{opened ? (
|
|
41
|
+
<span className="opacity-70">
|
|
42
|
+
<IconBox>
|
|
43
|
+
<XClose className="w-full h-full" />
|
|
44
|
+
</IconBox>
|
|
45
|
+
</span>
|
|
46
|
+
) : (
|
|
47
|
+
<IconBox>
|
|
48
|
+
<Menu02 className="w-full h-full" />
|
|
49
|
+
</IconBox>
|
|
50
|
+
)}
|
|
45
51
|
</button>
|
|
46
52
|
);
|
|
47
53
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// The @borisj74/bv-ds-icons package (v0.1.0) ships runtime JS but its published
|
|
2
|
+
// tarball is missing the `dist/index.d.ts` its package.json points at, so TS
|
|
3
|
+
// can't resolve types for the icon imports (TS7016). Declare it as an untyped
|
|
4
|
+
// module until the icons package ships its declarations. Icons are simple SVG
|
|
5
|
+
// React components that accept standard SVG props (className, etc.).
|
|
6
|
+
declare module "@borisj74/bv-ds-icons";
|