@moloodev/ui 0.0.0-stage → 0.1.0
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 +139 -3
- package/registry/components.ts +135 -0
- package/registry/fonts.css +81 -0
- package/registry/suit-OFL.txt +94 -0
- package/registry/wanted-sans-OFL.txt +93 -0
- package/src/components/ui/accordion.tsx +74 -0
- package/src/components/ui/alert-dialog.tsx +187 -0
- package/src/components/ui/alert.tsx +83 -0
- package/src/components/ui/aspect-ratio.tsx +22 -0
- package/src/components/ui/attachment.tsx +207 -0
- package/src/components/ui/avatar.tsx +109 -0
- package/src/components/ui/badge.tsx +54 -0
- package/src/components/ui/breadcrumb.tsx +126 -0
- package/src/components/ui/bubble.tsx +128 -0
- package/src/components/ui/button-group.tsx +103 -0
- package/src/components/ui/button.tsx +68 -0
- package/src/components/ui/calendar.tsx +222 -0
- package/src/components/ui/card.tsx +103 -0
- package/src/components/ui/carousel.tsx +242 -0
- package/src/components/ui/chart.tsx +398 -0
- package/src/components/ui/checkbox.tsx +28 -0
- package/src/components/ui/collapsible.tsx +40 -0
- package/src/components/ui/combobox.tsx +299 -0
- package/src/components/ui/command.tsx +218 -0
- package/src/components/ui/context-menu.tsx +271 -0
- package/src/components/ui/data-table.tsx +919 -0
- package/src/components/ui/date-picker.tsx +65 -0
- package/src/components/ui/dialog.tsx +224 -0
- package/src/components/ui/drawer.tsx +231 -0
- package/src/components/ui/dropdown-menu.tsx +268 -0
- package/src/components/ui/dropzone.tsx +186 -0
- package/src/components/ui/empty.tsx +101 -0
- package/src/components/ui/field.tsx +238 -0
- package/src/components/ui/form.tsx +83 -0
- package/src/components/ui/highlighted-source.tsx +200 -0
- package/src/components/ui/hover-card.tsx +51 -0
- package/src/components/ui/image-field.tsx +796 -0
- package/src/components/ui/input-group.tsx +149 -0
- package/src/components/ui/input-otp.tsx +86 -0
- package/src/components/ui/input-step.ts +19 -0
- package/src/components/ui/input.tsx +100 -0
- package/src/components/ui/item.tsx +201 -0
- package/src/components/ui/kbd.tsx +91 -0
- package/src/components/ui/kind-icon-glob.ts +24 -0
- package/src/components/ui/kind-icon-load.ts +52 -0
- package/src/components/ui/kind-icon-static.ts +46 -0
- package/src/components/ui/kind-icon.tsx +84 -0
- package/src/components/ui/kind-mark.tsx +65 -0
- package/src/components/ui/label.tsx +29 -0
- package/src/components/ui/marker.tsx +71 -0
- package/src/components/ui/menubar.tsx +277 -0
- package/src/components/ui/message-scroller.tsx +137 -0
- package/src/components/ui/message.tsx +92 -0
- package/src/components/ui/moloo-logo.tsx +45 -0
- package/src/components/ui/native-select.tsx +61 -0
- package/src/components/ui/navigation-menu.tsx +168 -0
- package/src/components/ui/outline-list.tsx +73 -0
- package/src/components/ui/pagination.tsx +130 -0
- package/src/components/ui/panel.tsx +108 -0
- package/src/components/ui/popover.tsx +90 -0
- package/src/components/ui/progress.tsx +117 -0
- package/src/components/ui/radio-group.tsx +38 -0
- package/src/components/ui/resizable.tsx +52 -0
- package/src/components/ui/scroll-area.tsx +54 -0
- package/src/components/ui/select.tsx +205 -0
- package/src/components/ui/separator.tsx +25 -0
- package/src/components/ui/sheet.tsx +140 -0
- package/src/components/ui/sidebar.tsx +746 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +52 -0
- package/src/components/ui/spinner.tsx +18 -0
- package/src/components/ui/switch.tsx +32 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +83 -0
- package/src/components/ui/tag-list-input.tsx +194 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/timeline.tsx +265 -0
- package/src/components/ui/toast.tsx +237 -0
- package/src/components/ui/toggle-group.tsx +90 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/tooltip.tsx +66 -0
- package/src/css.d.ts +1 -0
- package/src/hooks/use-clipboard.ts +82 -0
- package/src/hooks/use-mobile.ts +21 -0
- package/src/index.ts +106 -0
- package/src/kind-icon.ts +4 -0
- package/src/lib/format-relative-time.ts +41 -0
- package/src/lib/list-selection.ts +61 -0
- package/src/lib/utils.ts +6 -0
- package/src/moloo.css +360 -0
- package/src/styles.css +36 -0
- package/src/typeset.css +518 -0
- package/README.md +0 -3
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import * as React from "react"
|
|
4
|
+
import {
|
|
5
|
+
MessageScroller as MessageScrollerPrimitive,
|
|
6
|
+
useMessageScroller,
|
|
7
|
+
useMessageScrollerScrollable,
|
|
8
|
+
useMessageScrollerVisibility,
|
|
9
|
+
} from "@shadcn/react/message-scroller"
|
|
10
|
+
|
|
11
|
+
import { cn } from "../../lib/utils"
|
|
12
|
+
import { Button } from "./button"
|
|
13
|
+
import { IconArrowDown } from "@tabler/icons-react"
|
|
14
|
+
|
|
15
|
+
function MessageScrollerProvider(
|
|
16
|
+
props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>
|
|
17
|
+
) {
|
|
18
|
+
return <MessageScrollerPrimitive.Provider {...props} />
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function MessageScroller({
|
|
22
|
+
className,
|
|
23
|
+
...props
|
|
24
|
+
}: React.ComponentProps<typeof MessageScrollerPrimitive.Root>) {
|
|
25
|
+
return (
|
|
26
|
+
<MessageScrollerPrimitive.Root
|
|
27
|
+
data-slot="message-scroller"
|
|
28
|
+
className={cn(
|
|
29
|
+
"group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden",
|
|
30
|
+
className
|
|
31
|
+
)}
|
|
32
|
+
{...props}
|
|
33
|
+
/>
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function MessageScrollerViewport({
|
|
38
|
+
className,
|
|
39
|
+
...props
|
|
40
|
+
}: React.ComponentProps<typeof MessageScrollerPrimitive.Viewport>) {
|
|
41
|
+
return (
|
|
42
|
+
<MessageScrollerPrimitive.Viewport
|
|
43
|
+
data-slot="message-scroller-viewport"
|
|
44
|
+
className={cn(
|
|
45
|
+
"size-full min-h-0 min-w-0 scroll-fade-b scrollbar-thin scrollbar-gutter-stable overflow-y-auto overscroll-contain contain-content data-autoscrolling:scrollbar-thumb-transparent data-autoscrolling:scrollbar-track-transparent",
|
|
46
|
+
className
|
|
47
|
+
)}
|
|
48
|
+
{...props}
|
|
49
|
+
/>
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function MessageScrollerContent({
|
|
54
|
+
className,
|
|
55
|
+
...props
|
|
56
|
+
}: React.ComponentProps<typeof MessageScrollerPrimitive.Content>) {
|
|
57
|
+
return (
|
|
58
|
+
<MessageScrollerPrimitive.Content
|
|
59
|
+
data-slot="message-scroller-content"
|
|
60
|
+
className={cn("flex h-max min-h-full flex-col gap-6", className)}
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function MessageScrollerItem({
|
|
67
|
+
className,
|
|
68
|
+
scrollAnchor = false,
|
|
69
|
+
...props
|
|
70
|
+
}: React.ComponentProps<typeof MessageScrollerPrimitive.Item>) {
|
|
71
|
+
return (
|
|
72
|
+
<MessageScrollerPrimitive.Item
|
|
73
|
+
data-slot="message-scroller-item"
|
|
74
|
+
scrollAnchor={scrollAnchor}
|
|
75
|
+
className={cn(
|
|
76
|
+
"flex min-w-0 shrink-0 flex-col [contain-intrinsic-size:auto_10rem] [content-visibility:auto]",
|
|
77
|
+
className
|
|
78
|
+
)}
|
|
79
|
+
{...props}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function MessageScrollerButton({
|
|
85
|
+
direction = "end",
|
|
86
|
+
className,
|
|
87
|
+
children,
|
|
88
|
+
render,
|
|
89
|
+
variant = "outline",
|
|
90
|
+
size = "icon-sm",
|
|
91
|
+
...props
|
|
92
|
+
}: React.ComponentProps<typeof MessageScrollerPrimitive.Button> &
|
|
93
|
+
Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
|
|
94
|
+
return (
|
|
95
|
+
<MessageScrollerPrimitive.Button
|
|
96
|
+
data-slot="message-scroller-button"
|
|
97
|
+
data-direction={direction}
|
|
98
|
+
data-variant={variant}
|
|
99
|
+
data-size={size}
|
|
100
|
+
direction={direction}
|
|
101
|
+
className={cn(
|
|
102
|
+
// Position + motion only. Chrome comes from Button outline
|
|
103
|
+
// (border-2 + shadow-moloo); bg-background keeps it visible on card surfaces.
|
|
104
|
+
"absolute inset-s-1/2 z-10 -translate-x-1/2 bg-background transition-[translate,scale,opacity] duration-200",
|
|
105
|
+
"data-[active=false]:pointer-events-none data-[active=false]:scale-95 data-[active=false]:opacity-0 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)]",
|
|
106
|
+
"data-[active=true]:translate-y-0 data-[active=true]:scale-100 data-[active=true]:opacity-100 data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)]",
|
|
107
|
+
"data-[direction=end]:bottom-4 data-[direction=end]:data-[active=false]:translate-y-full",
|
|
108
|
+
"data-[direction=start]:top-4 data-[direction=start]:data-[active=false]:-translate-y-full",
|
|
109
|
+
"rtl:translate-x-1/2 data-[direction=start]:[&_svg]:rotate-180",
|
|
110
|
+
className
|
|
111
|
+
)}
|
|
112
|
+
render={render ?? <Button variant={variant} size={size} />}
|
|
113
|
+
{...props}
|
|
114
|
+
>
|
|
115
|
+
{children ?? (
|
|
116
|
+
<>
|
|
117
|
+
<IconArrowDown />
|
|
118
|
+
<span className="sr-only">
|
|
119
|
+
{direction === "end" ? "Scroll to end" : "Scroll to start"}
|
|
120
|
+
</span>
|
|
121
|
+
</>
|
|
122
|
+
)}
|
|
123
|
+
</MessageScrollerPrimitive.Button>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
MessageScrollerProvider,
|
|
129
|
+
MessageScroller,
|
|
130
|
+
MessageScrollerViewport,
|
|
131
|
+
MessageScrollerContent,
|
|
132
|
+
MessageScrollerItem,
|
|
133
|
+
MessageScrollerButton,
|
|
134
|
+
useMessageScroller,
|
|
135
|
+
useMessageScrollerScrollable,
|
|
136
|
+
useMessageScrollerVisibility,
|
|
137
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
function MessageGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
data-slot="message-group"
|
|
9
|
+
className={cn("flex min-w-0 flex-col gap-1.5", className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function Message({
|
|
16
|
+
className,
|
|
17
|
+
align = "start",
|
|
18
|
+
...props
|
|
19
|
+
}: React.ComponentProps<"div"> & { align?: "start" | "end" }) {
|
|
20
|
+
return (
|
|
21
|
+
<div
|
|
22
|
+
data-slot="message"
|
|
23
|
+
data-align={align}
|
|
24
|
+
className={cn(
|
|
25
|
+
"group/message relative flex w-full min-w-0 gap-1.5 text-sm data-[align=end]:flex-row-reverse",
|
|
26
|
+
className
|
|
27
|
+
)}
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function MessageAvatar({ className, ...props }: React.ComponentProps<"div">) {
|
|
34
|
+
return (
|
|
35
|
+
<div
|
|
36
|
+
data-slot="message-avatar"
|
|
37
|
+
className={cn(
|
|
38
|
+
"flex w-fit min-w-8 shrink-0 items-center justify-center self-end overflow-hidden rounded-full bg-muted group-has-data-[slot=message-footer]/message:-translate-y-6",
|
|
39
|
+
className
|
|
40
|
+
)}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function MessageContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
47
|
+
return (
|
|
48
|
+
<div
|
|
49
|
+
data-slot="message-content"
|
|
50
|
+
className={cn(
|
|
51
|
+
"flex w-full min-w-0 flex-col gap-1 wrap-break-word group-data-[align=end]/message:*:data-slot:self-end",
|
|
52
|
+
className
|
|
53
|
+
)}
|
|
54
|
+
{...props}
|
|
55
|
+
/>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function MessageHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
data-slot="message-header"
|
|
63
|
+
className={cn(
|
|
64
|
+
"flex max-w-full min-w-0 items-center px-2.5 text-xs font-semibold text-muted-foreground group-has-data-[variant=ghost]/message:px-0",
|
|
65
|
+
className
|
|
66
|
+
)}
|
|
67
|
+
{...props}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function MessageFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
73
|
+
return (
|
|
74
|
+
<div
|
|
75
|
+
data-slot="message-footer"
|
|
76
|
+
className={cn(
|
|
77
|
+
"flex max-w-full min-w-0 items-center px-2.5 text-xs font-semibold text-muted-foreground group-has-data-[variant=ghost]/message:px-0 group-data-[align=end]/message:justify-end",
|
|
78
|
+
className
|
|
79
|
+
)}
|
|
80
|
+
{...props}
|
|
81
|
+
/>
|
|
82
|
+
)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
MessageGroup,
|
|
87
|
+
Message,
|
|
88
|
+
MessageAvatar,
|
|
89
|
+
MessageContent,
|
|
90
|
+
MessageFooter,
|
|
91
|
+
MessageHeader,
|
|
92
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { cn } from "../../lib/utils"
|
|
2
|
+
|
|
3
|
+
function MolooLogo({
|
|
4
|
+
className,
|
|
5
|
+
decorative = false,
|
|
6
|
+
title,
|
|
7
|
+
"aria-label": ariaLabel,
|
|
8
|
+
...props
|
|
9
|
+
}: React.ComponentProps<"svg"> & {
|
|
10
|
+
/** Hide the mark from assistive technology. */
|
|
11
|
+
decorative?: boolean
|
|
12
|
+
/** SVG title. Tooltip, and the accessible name when aria-label is omitted. */
|
|
13
|
+
title?: string
|
|
14
|
+
}) {
|
|
15
|
+
const label = ariaLabel ?? title ?? "moloo"
|
|
16
|
+
const titleText = decorative ? title : (title ?? label)
|
|
17
|
+
return (
|
|
18
|
+
<svg
|
|
19
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
20
|
+
viewBox="0 0 64 64"
|
|
21
|
+
data-slot="moloo-logo"
|
|
22
|
+
className={cn("inline-block size-6 shrink-0", className)}
|
|
23
|
+
{...props}
|
|
24
|
+
role={decorative ? undefined : "img"}
|
|
25
|
+
aria-hidden={decorative ? true : undefined}
|
|
26
|
+
aria-label={decorative ? undefined : label}
|
|
27
|
+
>
|
|
28
|
+
{titleText ? <title>{titleText}</title> : null}
|
|
29
|
+
<rect className="fill-primary" x={34} y={4} width={26} height={26} rx={6} />
|
|
30
|
+
<rect className="fill-primary" x={34} y={34} width={26} height={26} rx={6} />
|
|
31
|
+
<rect className="fill-primary" x={4} y={34} width={26} height={26} rx={6} />
|
|
32
|
+
<rect
|
|
33
|
+
className="fill-accent"
|
|
34
|
+
x={2}
|
|
35
|
+
y={2}
|
|
36
|
+
width={26}
|
|
37
|
+
height={26}
|
|
38
|
+
rx={6}
|
|
39
|
+
transform="rotate(-12 15 15)"
|
|
40
|
+
/>
|
|
41
|
+
</svg>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { MolooLogo }
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import * as React from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
import { IconChevronDown } from "@tabler/icons-react"
|
|
5
|
+
|
|
6
|
+
type NativeSelectProps = Omit<React.ComponentProps<"select">, "size"> & {
|
|
7
|
+
size?: "sm" | "default"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function NativeSelect({
|
|
11
|
+
className,
|
|
12
|
+
size = "default",
|
|
13
|
+
...props
|
|
14
|
+
}: NativeSelectProps) {
|
|
15
|
+
return (
|
|
16
|
+
<div
|
|
17
|
+
className={cn(
|
|
18
|
+
"moloo-field moloo-focus-within group/native-select relative w-fit has-[select:disabled]:opacity-50 has-[select[aria-invalid=true]]:border-destructive has-[select[aria-invalid=true]]:shadow-[var(--moloo-invalid-shadow)]",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
data-slot="native-select-wrapper"
|
|
22
|
+
data-size={size}
|
|
23
|
+
>
|
|
24
|
+
<select
|
|
25
|
+
data-slot="native-select"
|
|
26
|
+
data-size={size}
|
|
27
|
+
className="h-9 w-full min-w-0 appearance-none rounded-md border-0 bg-transparent py-1 pr-8 pl-2.5 text-sm font-semibold whitespace-nowrap text-foreground transition-colors outline-none select-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed data-[size=sm]:h-8 dark:hover:bg-input/50"
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
<IconChevronDown className="pointer-events-none absolute top-1/2 right-2.5 size-4 -translate-y-1/2 text-muted-foreground select-none" aria-hidden="true" data-slot="native-select-icon" />
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function NativeSelectOption({
|
|
36
|
+
className,
|
|
37
|
+
...props
|
|
38
|
+
}: React.ComponentProps<"option">) {
|
|
39
|
+
return (
|
|
40
|
+
<option
|
|
41
|
+
data-slot="native-select-option"
|
|
42
|
+
className={cn("bg-[Canvas] text-[CanvasText]", className)}
|
|
43
|
+
{...props}
|
|
44
|
+
/>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function NativeSelectOptGroup({
|
|
49
|
+
className,
|
|
50
|
+
...props
|
|
51
|
+
}: React.ComponentProps<"optgroup">) {
|
|
52
|
+
return (
|
|
53
|
+
<optgroup
|
|
54
|
+
data-slot="native-select-optgroup"
|
|
55
|
+
className={cn("bg-[Canvas] text-[CanvasText]", className)}
|
|
56
|
+
{...props}
|
|
57
|
+
/>
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { NativeSelect, NativeSelectOptGroup, NativeSelectOption }
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { NavigationMenu as NavigationMenuPrimitive } from "@base-ui/react/navigation-menu"
|
|
2
|
+
import { cva } from "class-variance-authority"
|
|
3
|
+
|
|
4
|
+
import { cn } from "../../lib/utils"
|
|
5
|
+
import { IconChevronDown } from "@tabler/icons-react"
|
|
6
|
+
|
|
7
|
+
function NavigationMenu({
|
|
8
|
+
align = "start",
|
|
9
|
+
className,
|
|
10
|
+
children,
|
|
11
|
+
...props
|
|
12
|
+
}: NavigationMenuPrimitive.Root.Props &
|
|
13
|
+
Pick<NavigationMenuPrimitive.Positioner.Props, "align">) {
|
|
14
|
+
return (
|
|
15
|
+
<NavigationMenuPrimitive.Root
|
|
16
|
+
data-slot="navigation-menu"
|
|
17
|
+
className={cn(
|
|
18
|
+
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
|
|
19
|
+
className
|
|
20
|
+
)}
|
|
21
|
+
{...props}
|
|
22
|
+
>
|
|
23
|
+
{children}
|
|
24
|
+
<NavigationMenuPositioner align={align} />
|
|
25
|
+
</NavigationMenuPrimitive.Root>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function NavigationMenuList({
|
|
30
|
+
className,
|
|
31
|
+
...props
|
|
32
|
+
}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.List>) {
|
|
33
|
+
return (
|
|
34
|
+
<NavigationMenuPrimitive.List
|
|
35
|
+
data-slot="navigation-menu-list"
|
|
36
|
+
className={cn(
|
|
37
|
+
"group flex flex-1 list-none items-center justify-center gap-0",
|
|
38
|
+
className
|
|
39
|
+
)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function NavigationMenuItem({
|
|
46
|
+
className,
|
|
47
|
+
...props
|
|
48
|
+
}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Item>) {
|
|
49
|
+
return (
|
|
50
|
+
<NavigationMenuPrimitive.Item
|
|
51
|
+
data-slot="navigation-menu-item"
|
|
52
|
+
className={cn("relative", className)}
|
|
53
|
+
{...props}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const navigationMenuTriggerStyle = cva(
|
|
59
|
+
"moloo-focus group/navigation-menu-trigger inline-flex h-9 w-max items-center justify-center rounded-md px-2.5 py-1.5 font-display text-sm font-bold tracking-[-0.02em] transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-popup-open:bg-accent data-popup-open:text-accent-foreground data-popup-open:hover:bg-accent data-open:bg-accent data-open:text-accent-foreground data-open:hover:bg-accent data-open:focus:bg-accent"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
function NavigationMenuTrigger({
|
|
63
|
+
className,
|
|
64
|
+
children,
|
|
65
|
+
...props
|
|
66
|
+
}: NavigationMenuPrimitive.Trigger.Props) {
|
|
67
|
+
return (
|
|
68
|
+
<NavigationMenuPrimitive.Trigger
|
|
69
|
+
data-slot="navigation-menu-trigger"
|
|
70
|
+
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
|
71
|
+
{...props}
|
|
72
|
+
>
|
|
73
|
+
{children}{" "}
|
|
74
|
+
<IconChevronDown className="relative top-px ml-1 size-3 transition duration-300 group-data-popup-open/navigation-menu-trigger:rotate-180 group-data-open/navigation-menu-trigger:rotate-180" aria-hidden="true" />
|
|
75
|
+
</NavigationMenuPrimitive.Trigger>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function NavigationMenuContent({
|
|
80
|
+
className,
|
|
81
|
+
...props
|
|
82
|
+
}: NavigationMenuPrimitive.Content.Props) {
|
|
83
|
+
return (
|
|
84
|
+
<NavigationMenuPrimitive.Content
|
|
85
|
+
data-slot="navigation-menu-content"
|
|
86
|
+
className={cn(
|
|
87
|
+
"data-ending-style:data-activation-direction=left:translate-x-[50%] data-ending-style:data-activation-direction=right:translate-x-[-50%] data-starting-style:data-activation-direction=left:translate-x-[-50%] data-starting-style:data-activation-direction=right:translate-x-[50%] h-full w-auto p-1 transition-[opacity,transform,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:moloo-surface bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:shadow-moloo-lg group-data-[viewport=false]/navigation-menu:duration-300 data-ending-style:opacity-0 data-starting-style:opacity-0 data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 data-[motion^=from-]:animate-in data-[motion^=from-]:fade-in data-[motion^=to-]:animate-out data-[motion^=to-]:fade-out **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none group-data-[viewport=false]/navigation-menu:data-open:animate-in group-data-[viewport=false]/navigation-menu:data-open:fade-in-0 group-data-[viewport=false]/navigation-menu:data-open:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-closed:animate-out group-data-[viewport=false]/navigation-menu:data-closed:fade-out-0 group-data-[viewport=false]/navigation-menu:data-closed:zoom-out-95",
|
|
88
|
+
className
|
|
89
|
+
)}
|
|
90
|
+
{...props}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function NavigationMenuPositioner({
|
|
96
|
+
className,
|
|
97
|
+
side = "bottom",
|
|
98
|
+
sideOffset = 8,
|
|
99
|
+
align = "start",
|
|
100
|
+
alignOffset = 0,
|
|
101
|
+
...props
|
|
102
|
+
}: NavigationMenuPrimitive.Positioner.Props) {
|
|
103
|
+
return (
|
|
104
|
+
<NavigationMenuPrimitive.Portal>
|
|
105
|
+
<NavigationMenuPrimitive.Positioner
|
|
106
|
+
side={side}
|
|
107
|
+
sideOffset={sideOffset}
|
|
108
|
+
align={align}
|
|
109
|
+
alignOffset={alignOffset}
|
|
110
|
+
className={cn(
|
|
111
|
+
"isolate z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] data-instant:transition-none data-[side=bottom]:before:top-[-10px] data-[side=bottom]:before:right-0 data-[side=bottom]:before:left-0",
|
|
112
|
+
className
|
|
113
|
+
)}
|
|
114
|
+
{...props}
|
|
115
|
+
>
|
|
116
|
+
<NavigationMenuPrimitive.Popup className="data-[ending-style]:easing-[ease] xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) overflow-hidden rounded-md moloo-surface bg-popover text-popover-foreground shadow-moloo-lg transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:scale-90 data-starting-style:opacity-0">
|
|
117
|
+
<NavigationMenuPrimitive.Viewport className="relative size-full overflow-hidden rounded-md" />
|
|
118
|
+
</NavigationMenuPrimitive.Popup>
|
|
119
|
+
</NavigationMenuPrimitive.Positioner>
|
|
120
|
+
</NavigationMenuPrimitive.Portal>
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function NavigationMenuLink({
|
|
125
|
+
className,
|
|
126
|
+
...props
|
|
127
|
+
}: NavigationMenuPrimitive.Link.Props) {
|
|
128
|
+
return (
|
|
129
|
+
<NavigationMenuPrimitive.Link
|
|
130
|
+
data-slot="navigation-menu-link"
|
|
131
|
+
className={cn(
|
|
132
|
+
"moloo-focus flex items-center gap-2 rounded-md p-2 text-sm font-semibold transition-all outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground in-data-[slot=navigation-menu-content]:rounded-md data-active:bg-accent data-active:text-accent-foreground data-active:hover:bg-accent data-active:focus:bg-accent [&_svg:not([class*='size-'])]:size-4",
|
|
133
|
+
className
|
|
134
|
+
)}
|
|
135
|
+
{...props}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function NavigationMenuIndicator({
|
|
141
|
+
className,
|
|
142
|
+
...props
|
|
143
|
+
}: React.ComponentPropsWithRef<typeof NavigationMenuPrimitive.Icon>) {
|
|
144
|
+
return (
|
|
145
|
+
<NavigationMenuPrimitive.Icon
|
|
146
|
+
data-slot="navigation-menu-indicator"
|
|
147
|
+
className={cn(
|
|
148
|
+
"top-full z-1 flex h-1.5 items-end justify-center overflow-hidden data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:animate-in data-[state=visible]:fade-in",
|
|
149
|
+
className
|
|
150
|
+
)}
|
|
151
|
+
{...props}
|
|
152
|
+
>
|
|
153
|
+
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-md bg-border shadow-moloo-lg" />
|
|
154
|
+
</NavigationMenuPrimitive.Icon>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export {
|
|
159
|
+
NavigationMenu,
|
|
160
|
+
NavigationMenuContent,
|
|
161
|
+
NavigationMenuIndicator,
|
|
162
|
+
NavigationMenuItem,
|
|
163
|
+
NavigationMenuLink,
|
|
164
|
+
NavigationMenuList,
|
|
165
|
+
NavigationMenuTrigger,
|
|
166
|
+
navigationMenuTriggerStyle,
|
|
167
|
+
NavigationMenuPositioner,
|
|
168
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ReactNode } from "react"
|
|
2
|
+
|
|
3
|
+
import { cn } from "../../lib/utils"
|
|
4
|
+
|
|
5
|
+
export type OutlineListItem = {
|
|
6
|
+
id: string
|
|
7
|
+
label: string
|
|
8
|
+
level: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type OutlineListProps = {
|
|
12
|
+
items: readonly OutlineListItem[]
|
|
13
|
+
onSelect: (id: string) => void
|
|
14
|
+
empty?: ReactNode
|
|
15
|
+
className?: string
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function levelIndent(level: number) {
|
|
19
|
+
return `${0.5 + Math.max(level - 1, 0) * 0.625}rem`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function levelTone(level: number) {
|
|
23
|
+
if (level <= 1) return "text-foreground"
|
|
24
|
+
if (level === 2) return "text-foreground/90"
|
|
25
|
+
if (level >= 3) return "text-muted-foreground"
|
|
26
|
+
return null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ariaLevel(level: number) {
|
|
30
|
+
if (!Number.isFinite(level)) return undefined
|
|
31
|
+
return Math.max(1, Math.trunc(level))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function OutlineList({
|
|
35
|
+
items,
|
|
36
|
+
onSelect,
|
|
37
|
+
empty,
|
|
38
|
+
className,
|
|
39
|
+
}: OutlineListProps) {
|
|
40
|
+
return (
|
|
41
|
+
<ul
|
|
42
|
+
data-slot="outline-list"
|
|
43
|
+
className={cn(
|
|
44
|
+
"flex min-h-0 flex-1 list-none flex-col gap-0.5 overflow-auto p-1.5",
|
|
45
|
+
className
|
|
46
|
+
)}
|
|
47
|
+
>
|
|
48
|
+
{items.length === 0 ? (
|
|
49
|
+
empty ? (
|
|
50
|
+
<li className="px-2 py-1 text-xs text-muted-foreground">{empty}</li>
|
|
51
|
+
) : null
|
|
52
|
+
) : (
|
|
53
|
+
items.map((item) => (
|
|
54
|
+
<li key={item.id} aria-level={ariaLevel(item.level)}>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
data-slot="outline-list-item"
|
|
58
|
+
onClick={() => onSelect(item.id)}
|
|
59
|
+
style={{ paddingInlineStart: levelIndent(item.level) }}
|
|
60
|
+
className={cn(
|
|
61
|
+
"moloo-focus flex w-full rounded-md py-1 pr-2 text-left font-display text-xs font-semibold tracking-[-0.02em] outline-none transition-colors",
|
|
62
|
+
"hover:bg-muted/60",
|
|
63
|
+
levelTone(item.level)
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
{item.label}
|
|
67
|
+
</button>
|
|
68
|
+
</li>
|
|
69
|
+
))
|
|
70
|
+
)}
|
|
71
|
+
</ul>
|
|
72
|
+
)
|
|
73
|
+
}
|