minka-ds 0.3.10 → 0.3.12
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/package.json
CHANGED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
import { cn } from "../../lib/utils"
|
|
3
|
+
|
|
4
|
+
const SIZE: Record<NonNullable<AvatarProps["size"]>, string> = {
|
|
5
|
+
sm: "size-7 text-caption-serif",
|
|
6
|
+
md: "size-9 text-body-sm-serif",
|
|
7
|
+
lg: "size-12 text-body-serif",
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface AvatarProps {
|
|
11
|
+
/** Image URL. When absent, initials (or a fallback) are shown. */
|
|
12
|
+
src?: string
|
|
13
|
+
/** Full name — used for alt text and to derive initials. */
|
|
14
|
+
name?: string
|
|
15
|
+
/** Explicit initials override; otherwise derived from `name`. */
|
|
16
|
+
initials?: string
|
|
17
|
+
size?: "sm" | "md" | "lg"
|
|
18
|
+
/** Background for the initials state. Defaults to a brand color. */
|
|
19
|
+
background?: string
|
|
20
|
+
className?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function deriveInitials(name?: string): string {
|
|
24
|
+
if (!name) return "?"
|
|
25
|
+
return name.trim().split(/\s+/).map(p => p[0]).join("").slice(0, 2).toUpperCase()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function Avatar({ src, name, initials, size = "md", background, className }: AvatarProps) {
|
|
29
|
+
return (
|
|
30
|
+
<div
|
|
31
|
+
data-slot="avatar"
|
|
32
|
+
className={cn(
|
|
33
|
+
"shrink-0 rounded-full flex items-center justify-center overflow-hidden text-[var(--color-text-inverse)]",
|
|
34
|
+
SIZE[size],
|
|
35
|
+
className
|
|
36
|
+
)}
|
|
37
|
+
style={{ background: src ? undefined : (background ?? "var(--color-brand-blue)") }}
|
|
38
|
+
>
|
|
39
|
+
{src
|
|
40
|
+
? <img src={src} alt={name ?? ""} className="size-full object-cover" />
|
|
41
|
+
: <span className="leading-none">{initials ?? deriveInitials(name)}</span>}
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export { Avatar }
|
|
47
|
+
export type { AvatarProps }
|
|
@@ -29,32 +29,37 @@ export function DateTimeRangePicker({
|
|
|
29
29
|
const range: DateRange | undefined =
|
|
30
30
|
value?.from ? { from: value.from, to: value.to } : undefined
|
|
31
31
|
|
|
32
|
-
//
|
|
33
|
-
|
|
32
|
+
// `anchor` is the source of truth for selection phase:
|
|
33
|
+
// anchor === null → no active pick (nothing selected, or a complete range)
|
|
34
|
+
// anchor !== null → first date is set, waiting for the second click
|
|
35
|
+
// This avoids the ambiguity of inferring phase from `from === to`.
|
|
36
|
+
const [anchor, setAnchor] = React.useState<Date | null>(null)
|
|
34
37
|
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
38
|
+
function handleDay(day: Date) {
|
|
39
|
+
const startTime = value?.startTime ?? ""
|
|
40
|
+
const endTime = value?.endTime ?? ""
|
|
41
|
+
|
|
42
|
+
// Picking the second date.
|
|
43
|
+
if (anchor) {
|
|
44
|
+
const spanMs = Math.abs(day.getTime() - anchor.getTime())
|
|
45
|
+
const withinCap = maxRangeDays == null || spanMs <= maxRangeDays * 86_400_000
|
|
46
|
+
if (withinCap) {
|
|
47
|
+
// Complete the range (order endpoints; can extend backward or forward).
|
|
48
|
+
const from = day < anchor ? day : anchor
|
|
49
|
+
const to = day < anchor ? anchor : day
|
|
50
|
+
setAnchor(null)
|
|
51
|
+
onChange({ from, to, startTime, endTime })
|
|
52
|
+
} else {
|
|
53
|
+
// Outside the cap → treat as a fresh start anchored on the clicked day.
|
|
54
|
+
setAnchor(day)
|
|
55
|
+
onChange({ from: day, to: day, startTime, endTime })
|
|
56
|
+
}
|
|
49
57
|
return
|
|
50
58
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
startTime: value?.startTime ?? "",
|
|
56
|
-
endTime: value?.endTime ?? "",
|
|
57
|
-
})
|
|
59
|
+
|
|
60
|
+
// No active pick (fresh, or restarting from a complete range).
|
|
61
|
+
setAnchor(day)
|
|
62
|
+
onChange({ from: day, to: day, startTime, endTime })
|
|
58
63
|
}
|
|
59
64
|
|
|
60
65
|
function handleStartTime(e: React.ChangeEvent<HTMLInputElement>) {
|
|
@@ -67,14 +72,6 @@ export function DateTimeRangePicker({
|
|
|
67
72
|
onChange({ ...value, endTime: e.target.value })
|
|
68
73
|
}
|
|
69
74
|
|
|
70
|
-
// Only cap the calendar while the user is picking the second date (from set,
|
|
71
|
-
// range not yet complete). Once complete, all dates stay clickable so a fresh
|
|
72
|
-
// click elsewhere can start a new range.
|
|
73
|
-
const disabledAfter =
|
|
74
|
-
maxRangeDays && range?.from && !isComplete
|
|
75
|
-
? { after: new Date(range.from.getTime() + maxRangeDays * 86_400_000) }
|
|
76
|
-
: undefined
|
|
77
|
-
|
|
78
75
|
return (
|
|
79
76
|
<div className={cn(
|
|
80
77
|
"[border-radius:var(--radius-card)] border border-[var(--color-border-default)] bg-[var(--color-bg-raised)] overflow-hidden w-fit",
|
|
@@ -85,8 +82,17 @@ export function DateTimeRangePicker({
|
|
|
85
82
|
numberOfMonths={1}
|
|
86
83
|
captionLayout="label"
|
|
87
84
|
selected={range}
|
|
88
|
-
onSelect={
|
|
89
|
-
|
|
85
|
+
onSelect={(_, selectedDay) => handleDay(selectedDay)}
|
|
86
|
+
// While picking the second date, soften days outside the ±maxRangeDays
|
|
87
|
+
// window — a visual hint of the recommended span. They stay clickable
|
|
88
|
+
// (clicking one re-anchors) and hover still works; this is a hint, not
|
|
89
|
+
// a block.
|
|
90
|
+
modifiers={
|
|
91
|
+
anchor && maxRangeDays != null
|
|
92
|
+
? { outOfRange: (d: Date) => Math.abs(d.getTime() - anchor.getTime()) > maxRangeDays * 86_400_000 }
|
|
93
|
+
: undefined
|
|
94
|
+
}
|
|
95
|
+
modifiersClassNames={{ outOfRange: "text-[var(--color-text-hint)]" }}
|
|
90
96
|
/>
|
|
91
97
|
<div className="border-t border-[var(--color-border-default)] px-4 py-3 flex flex-col gap-3">
|
|
92
98
|
<div className="flex flex-col gap-1.5">
|
|
@@ -7,6 +7,7 @@ import { Slot } from "radix-ui"
|
|
|
7
7
|
|
|
8
8
|
import { useIsMobile } from "../../hooks/use-mobile"
|
|
9
9
|
import { cn } from "../../lib/utils"
|
|
10
|
+
import { Avatar } from "./avatar"
|
|
10
11
|
import { Button } from "./button"
|
|
11
12
|
import { Input } from "./input"
|
|
12
13
|
import { Separator } from "./separator"
|
|
@@ -368,6 +369,38 @@ function SidebarSeparator({
|
|
|
368
369
|
)
|
|
369
370
|
}
|
|
370
371
|
|
|
372
|
+
// Footer user block: avatar + name/role, with an optional trailing action
|
|
373
|
+
// (e.g. a kebab dropdown trigger). Composed for the sidebar footer.
|
|
374
|
+
function SidebarUser({
|
|
375
|
+
name,
|
|
376
|
+
role,
|
|
377
|
+
avatarSrc,
|
|
378
|
+
avatarBackground,
|
|
379
|
+
action,
|
|
380
|
+
className,
|
|
381
|
+
}: {
|
|
382
|
+
name: string
|
|
383
|
+
role?: string
|
|
384
|
+
avatarSrc?: string
|
|
385
|
+
avatarBackground?: string
|
|
386
|
+
action?: React.ReactNode
|
|
387
|
+
className?: string
|
|
388
|
+
}) {
|
|
389
|
+
return (
|
|
390
|
+
<div
|
|
391
|
+
data-slot="sidebar-user"
|
|
392
|
+
className={cn("flex items-center gap-2.5 px-2 py-1.5", className)}
|
|
393
|
+
>
|
|
394
|
+
<Avatar name={name} src={avatarSrc} background={avatarBackground} />
|
|
395
|
+
<div className="flex flex-col gap-0.5 flex-1 min-w-0">
|
|
396
|
+
<span className="text-body-sm text-[var(--color-text-default)] truncate">{name}</span>
|
|
397
|
+
{role && <span className="text-caption-light text-[var(--color-text-muted)] truncate">{role}</span>}
|
|
398
|
+
</div>
|
|
399
|
+
{action}
|
|
400
|
+
</div>
|
|
401
|
+
)
|
|
402
|
+
}
|
|
403
|
+
|
|
371
404
|
function SidebarContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
372
405
|
return (
|
|
373
406
|
<div
|
|
@@ -722,5 +755,6 @@ export {
|
|
|
722
755
|
SidebarRail,
|
|
723
756
|
SidebarSeparator,
|
|
724
757
|
SidebarTrigger,
|
|
758
|
+
SidebarUser,
|
|
725
759
|
useSidebar,
|
|
726
760
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { usePlatform } from "./hooks/use-platform"
|
|
|
6
6
|
|
|
7
7
|
// Components
|
|
8
8
|
export * from "./components/ui/alert"
|
|
9
|
+
export * from "./components/ui/avatar"
|
|
9
10
|
export * from "./components/ui/badge"
|
|
10
11
|
export * from "./components/ui/breadcrumb"
|
|
11
12
|
export * from "./components/ui/calendar"
|