@momentumworld/ui-react 0.17.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/dist/chunk-2HQPCV2L.js +895 -0
- package/dist/chunk-2HQPCV2L.js.map +1 -0
- package/dist/form.d.ts +175 -0
- package/dist/form.js +356 -0
- package/dist/form.js.map +1 -0
- package/dist/index.d.ts +2162 -0
- package/dist/index.js +11640 -0
- package/dist/index.js.map +1 -0
- package/dist/input-JCZs61MO.d.ts +24 -0
- package/package.json +90 -0
- package/src/components/form/checkbox-group-field.tsx +101 -0
- package/src/components/form/date-field.tsx +79 -0
- package/src/components/form/date-range-field.tsx +106 -0
- package/src/components/form/form-context.ts +10 -0
- package/src/components/form/form.tsx +54 -0
- package/src/components/form/number-field.tsx +69 -0
- package/src/components/form/select-field.tsx +76 -0
- package/src/components/form/submit-button.tsx +28 -0
- package/src/components/form/text-field.tsx +107 -0
- package/src/components/layout/dashboard-header.tsx +54 -0
- package/src/components/layout/dashboard-panel.tsx +34 -0
- package/src/components/layout/dashboard-topbar.tsx +221 -0
- package/src/components/theme-provider.tsx +403 -0
- package/src/components/ui/accordion.tsx +69 -0
- package/src/components/ui/alert-dialog.tsx +169 -0
- package/src/components/ui/alert.tsx +80 -0
- package/src/components/ui/animated-counter.tsx +109 -0
- package/src/components/ui/animated-theme-toggler.tsx +265 -0
- package/src/components/ui/app-store-buttons.tsx +182 -0
- package/src/components/ui/aspect-ratio.tsx +23 -0
- package/src/components/ui/aurora-orb.tsx +163 -0
- package/src/components/ui/aurora-text.tsx +76 -0
- package/src/components/ui/autocomplete.tsx +296 -0
- package/src/components/ui/avatar-group.tsx +150 -0
- package/src/components/ui/avatar.tsx +285 -0
- package/src/components/ui/badge-group.tsx +160 -0
- package/src/components/ui/badge.tsx +172 -0
- package/src/components/ui/breadcrumb.tsx +112 -0
- package/src/components/ui/button.tsx +77 -0
- package/src/components/ui/calendar.tsx +137 -0
- package/src/components/ui/card.tsx +244 -0
- package/src/components/ui/carousel.tsx +258 -0
- package/src/components/ui/chart.tsx +379 -0
- package/src/components/ui/chat-divider.tsx +67 -0
- package/src/components/ui/chat.tsx +494 -0
- package/src/components/ui/checkbox-group.tsx +16 -0
- package/src/components/ui/checkbox.tsx +82 -0
- package/src/components/ui/collapsible.tsx +45 -0
- package/src/components/ui/color-picker.tsx +389 -0
- package/src/components/ui/combobox.tsx +411 -0
- package/src/components/ui/command.tsx +264 -0
- package/src/components/ui/confetti.tsx +196 -0
- package/src/components/ui/context-menu.tsx +271 -0
- package/src/components/ui/context-meter.tsx +134 -0
- package/src/components/ui/credit-card.tsx +214 -0
- package/src/components/ui/data-table-paged.tsx +163 -0
- package/src/components/ui/date-picker.tsx +154 -0
- package/src/components/ui/dialog.tsx +196 -0
- package/src/components/ui/drawer.tsx +135 -0
- package/src/components/ui/empty.tsx +127 -0
- package/src/components/ui/fab.tsx +45 -0
- package/src/components/ui/featured-icon.tsx +149 -0
- package/src/components/ui/field.tsx +88 -0
- package/src/components/ui/fieldset.tsx +29 -0
- package/src/components/ui/form.tsx +17 -0
- package/src/components/ui/frame.tsx +82 -0
- package/src/components/ui/generic-empty.tsx +142 -0
- package/src/components/ui/group.tsx +97 -0
- package/src/components/ui/help-tip.tsx +94 -0
- package/src/components/ui/horizontal-scroll-fader.tsx +228 -0
- package/src/components/ui/input-group.tsx +102 -0
- package/src/components/ui/input-otp.tsx +96 -0
- package/src/components/ui/input.tsx +66 -0
- package/src/components/ui/item.tsx +198 -0
- package/src/components/ui/kbd.tsx +30 -0
- package/src/components/ui/label.tsx +28 -0
- package/src/components/ui/markdown.tsx +188 -0
- package/src/components/ui/menu.tsx +312 -0
- package/src/components/ui/menubar.tsx +93 -0
- package/src/components/ui/meter.tsx +67 -0
- package/src/components/ui/multi-select.tsx +308 -0
- package/src/components/ui/navigation-menu.tsx +143 -0
- package/src/components/ui/number-field.tsx +160 -0
- package/src/components/ui/pagination-controls.tsx +74 -0
- package/src/components/ui/pagination.tsx +149 -0
- package/src/components/ui/pipeline.tsx +102 -0
- package/src/components/ui/popover.tsx +119 -0
- package/src/components/ui/preview-card.tsx +55 -0
- package/src/components/ui/progress.tsx +289 -0
- package/src/components/ui/qr-code.tsx +150 -0
- package/src/components/ui/radio-group.tsx +103 -0
- package/src/components/ui/resizable.tsx +56 -0
- package/src/components/ui/scroll-area.tsx +90 -0
- package/src/components/ui/scroller.tsx +38 -0
- package/src/components/ui/section-header.tsx +118 -0
- package/src/components/ui/segmented-control.tsx +83 -0
- package/src/components/ui/select.tsx +181 -0
- package/src/components/ui/separator.tsx +23 -0
- package/src/components/ui/sheet.tsx +224 -0
- package/src/components/ui/sidebar.tsx +774 -0
- package/src/components/ui/skeleton.tsx +16 -0
- package/src/components/ui/slider.tsx +108 -0
- package/src/components/ui/smooth-scroll.tsx +206 -0
- package/src/components/ui/social-button.tsx +247 -0
- package/src/components/ui/spinner-on-demand.tsx +32 -0
- package/src/components/ui/spinner.tsx +18 -0
- package/src/components/ui/stat.tsx +187 -0
- package/src/components/ui/stepper.tsx +167 -0
- package/src/components/ui/switch.tsx +56 -0
- package/src/components/ui/table.tsx +126 -0
- package/src/components/ui/tabs.tsx +90 -0
- package/src/components/ui/tag.tsx +242 -0
- package/src/components/ui/target-countdown.tsx +46 -0
- package/src/components/ui/text-editor.tsx +313 -0
- package/src/components/ui/textarea.tsx +51 -0
- package/src/components/ui/thinking-orb.tsx +6 -0
- package/src/components/ui/time-picker.tsx +308 -0
- package/src/components/ui/timeline.tsx +116 -0
- package/src/components/ui/toast.tsx +268 -0
- package/src/components/ui/toggle-group.tsx +101 -0
- package/src/components/ui/toggle.tsx +45 -0
- package/src/components/ui/toolbar.tsx +89 -0
- package/src/components/ui/tooltip.tsx +102 -0
- package/src/components/ui/vertical-scroll-fader.tsx +250 -0
- package/src/components/ui/video-player.tsx +275 -0
- package/src/components/upload/avatar-upload-base.tsx +131 -0
- package/src/components/upload/image-upload-base.tsx +112 -0
- package/src/form.ts +17 -0
- package/src/index.ts +143 -0
- package/src/lib/hooks/use-callback-ref.ts +15 -0
- package/src/lib/hooks/use-first-render.ts +11 -0
- package/src/lib/hooks/use-hover.ts +53 -0
- package/src/lib/hooks/use-is-tab-active.ts +17 -0
- package/src/lib/hooks/use-media-query.ts +164 -0
- package/src/lib/utils/css.ts +6 -0
- package/src/styles.css +329 -0
- package/src/types/helpers.ts +24 -0
- package/src/types/react.d.ts +7 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import type { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
|
|
4
|
+
import { ToggleGroup as ToggleGroupPrimitive } from "@base-ui/react/toggle-group";
|
|
5
|
+
import type { VariantProps } from "class-variance-authority";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
import { Separator } from "./separator";
|
|
8
|
+
import {
|
|
9
|
+
Toggle as ToggleComponent,
|
|
10
|
+
type toggleVariants,
|
|
11
|
+
} from "./toggle";
|
|
12
|
+
import { cn } from "../../lib/utils/css";
|
|
13
|
+
|
|
14
|
+
const ToggleGroupContext = React.createContext<
|
|
15
|
+
VariantProps<typeof toggleVariants>
|
|
16
|
+
>({
|
|
17
|
+
size: "default",
|
|
18
|
+
variant: "default",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function ToggleGroup({
|
|
22
|
+
className,
|
|
23
|
+
variant = "default",
|
|
24
|
+
size = "default",
|
|
25
|
+
orientation = "horizontal",
|
|
26
|
+
children,
|
|
27
|
+
...props
|
|
28
|
+
}: ToggleGroupPrimitive.Props & VariantProps<typeof toggleVariants>) {
|
|
29
|
+
return (
|
|
30
|
+
<ToggleGroupPrimitive
|
|
31
|
+
className={cn(
|
|
32
|
+
"flex w-fit *:focus-visible:z-10 dark:*:[[data-slot=separator]:has(+[data-slot=toggle]:hover)]:before:bg-input/64 dark:*:[[data-slot=separator]:has(+[data-slot=toggle][data-pressed])]:before:bg-input dark:*:[[data-slot=toggle]:hover+[data-slot=separator]]:before:bg-input/64 dark:*:[[data-slot=toggle][data-pressed]+[data-slot=separator]]:before:bg-input",
|
|
33
|
+
orientation === "horizontal"
|
|
34
|
+
? "*:pointer-coarse:after:min-w-auto"
|
|
35
|
+
: "*:pointer-coarse:after:min-h-auto",
|
|
36
|
+
variant === "default"
|
|
37
|
+
? "gap-0.5"
|
|
38
|
+
: orientation === "horizontal"
|
|
39
|
+
? "*:not-first:not-data-[slot=separator]:before:-start-[0.5px] *:not-last:not-data-[slot=separator]:before:-end-[0.5px] *:not-first:rounded-s-none *:not-last:rounded-e-none *:not-first:border-s-0 *:not-last:border-e-0 *:not-first:before:rounded-s-none *:not-last:before:rounded-e-none"
|
|
40
|
+
: "*:not-first:not-data-[slot=separator]:before:-top-[0.5px] *:not-last:not-data-[slot=separator]:before:-bottom-[0.5px] flex-col *:not-first:rounded-t-none *:not-last:rounded-b-none *:not-first:border-t-0 *:not-last:border-b-0 *:not-first:before:rounded-t-none *:not-last:before:rounded-b-none *:data-[slot=toggle]:not-last:before:hidden dark:*:last:before:hidden dark:*:first:before:block",
|
|
41
|
+
className,
|
|
42
|
+
)}
|
|
43
|
+
data-size={size}
|
|
44
|
+
data-slot="toggle-group"
|
|
45
|
+
data-variant={variant}
|
|
46
|
+
orientation={orientation}
|
|
47
|
+
{...props}
|
|
48
|
+
>
|
|
49
|
+
<ToggleGroupContext.Provider value={{ size, variant }}>
|
|
50
|
+
{children}
|
|
51
|
+
</ToggleGroupContext.Provider>
|
|
52
|
+
</ToggleGroupPrimitive>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function Toggle({
|
|
57
|
+
className,
|
|
58
|
+
children,
|
|
59
|
+
variant,
|
|
60
|
+
size,
|
|
61
|
+
...props
|
|
62
|
+
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
|
|
63
|
+
const context = React.useContext(ToggleGroupContext);
|
|
64
|
+
|
|
65
|
+
const resolvedVariant = context.variant || variant;
|
|
66
|
+
const resolvedSize = context.size || size;
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<ToggleComponent
|
|
70
|
+
className={className}
|
|
71
|
+
data-size={resolvedSize}
|
|
72
|
+
data-variant={resolvedVariant}
|
|
73
|
+
size={resolvedSize}
|
|
74
|
+
variant={resolvedVariant}
|
|
75
|
+
{...props}
|
|
76
|
+
>
|
|
77
|
+
{children}
|
|
78
|
+
</ToggleComponent>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function ToggleGroupSeparator({
|
|
83
|
+
className,
|
|
84
|
+
orientation = "vertical",
|
|
85
|
+
...props
|
|
86
|
+
}: {
|
|
87
|
+
className?: string;
|
|
88
|
+
} & React.ComponentProps<typeof Separator>) {
|
|
89
|
+
return (
|
|
90
|
+
<Separator
|
|
91
|
+
className={cn(
|
|
92
|
+
"pointer-events-none relative bg-input before:absolute before:inset-0 dark:before:bg-input/32",
|
|
93
|
+
className,
|
|
94
|
+
)}
|
|
95
|
+
orientation={orientation}
|
|
96
|
+
{...props}
|
|
97
|
+
/>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export { ToggleGroup, Toggle, Toggle as ToggleGroupItem, ToggleGroupSeparator };
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Toggle as TogglePrimitive } from "@base-ui/react/toggle";
|
|
4
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils/css";
|
|
7
|
+
|
|
8
|
+
const toggleVariants = cva(
|
|
9
|
+
"[&_svg]:-mx-0.5 relative inline-flex shrink-0 cursor-pointer select-none items-center justify-center gap-2 whitespace-nowrap rounded-lg border font-medium text-base text-foreground outline-none transition-[box-shadow,background-color] duration-100 before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 hover:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 data-pressed:bg-input/64 data-pressed:text-accent-foreground sm:text-sm [&_svg:not([class*='opacity-'])]:opacity-80 [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:transition-transform [&_svg]:duration-150 [&_svg]:ease-[cubic-bezier(0.34,1.56,0.64,1)] data-pressed:[&_svg]:scale-115",
|
|
10
|
+
{
|
|
11
|
+
defaultVariants: {
|
|
12
|
+
size: "default",
|
|
13
|
+
variant: "default",
|
|
14
|
+
},
|
|
15
|
+
variants: {
|
|
16
|
+
size: {
|
|
17
|
+
default: "h-9 min-w-9 px-[calc(--spacing(2)-1px)] sm:h-8 sm:min-w-8",
|
|
18
|
+
lg: "h-10 min-w-10 px-[calc(--spacing(2.5)-1px)] sm:h-9 sm:min-w-9",
|
|
19
|
+
sm: "h-8 min-w-8 px-[calc(--spacing(1.5)-1px)] sm:h-7 sm:min-w-7",
|
|
20
|
+
},
|
|
21
|
+
variant: {
|
|
22
|
+
default: "border-transparent",
|
|
23
|
+
outline:
|
|
24
|
+
"border-input bg-background not-dark:bg-clip-padding shadow-xs/5 not-disabled:not-active:not-data-pressed:before:shadow-[0_1px_--theme(--color-black/6%)] dark:bg-input/32 dark:data-pressed:bg-input dark:hover:bg-input/64 dark:not-disabled:not-active:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/6%)] dark:not-disabled:not-data-pressed:before:shadow-[0_-1px_--theme(--color-white/2%)] [:disabled,:active,[data-pressed]]:shadow-none",
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
function Toggle({
|
|
31
|
+
className,
|
|
32
|
+
variant,
|
|
33
|
+
size,
|
|
34
|
+
...props
|
|
35
|
+
}: TogglePrimitive.Props & VariantProps<typeof toggleVariants>) {
|
|
36
|
+
return (
|
|
37
|
+
<TogglePrimitive
|
|
38
|
+
className={cn(toggleVariants({ className, size, variant }))}
|
|
39
|
+
data-slot="toggle"
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export { Toggle, toggleVariants };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Toolbar as ToolbarPrimitive } from "@base-ui/react/toolbar";
|
|
4
|
+
|
|
5
|
+
import { cn } from "../../lib/utils/css";
|
|
6
|
+
|
|
7
|
+
function Toolbar({ className, ...props }: ToolbarPrimitive.Root.Props) {
|
|
8
|
+
return (
|
|
9
|
+
<ToolbarPrimitive.Root
|
|
10
|
+
className={cn(
|
|
11
|
+
"relative flex gap-2 rounded-xl border bg-card not-dark:bg-clip-padding p-1 text-card-foreground",
|
|
12
|
+
className,
|
|
13
|
+
)}
|
|
14
|
+
data-slot="toolbar"
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function ToolbarButton({ className, ...props }: ToolbarPrimitive.Button.Props) {
|
|
21
|
+
return (
|
|
22
|
+
<ToolbarPrimitive.Button
|
|
23
|
+
className={cn(
|
|
24
|
+
"transition-[box-shadow,background-color,color,filter] duration-150 ease-out [:active:not(:disabled)]:brightness-95",
|
|
25
|
+
className,
|
|
26
|
+
)}
|
|
27
|
+
data-slot="toolbar-button"
|
|
28
|
+
{...props}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function ToolbarLink({ className, ...props }: ToolbarPrimitive.Link.Props) {
|
|
34
|
+
return (
|
|
35
|
+
<ToolbarPrimitive.Link
|
|
36
|
+
className={cn(
|
|
37
|
+
"transition-[color,opacity] duration-150 ease-out",
|
|
38
|
+
className,
|
|
39
|
+
)}
|
|
40
|
+
data-slot="toolbar-link"
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function ToolbarInput({ className, ...props }: ToolbarPrimitive.Input.Props) {
|
|
47
|
+
return (
|
|
48
|
+
<ToolbarPrimitive.Input
|
|
49
|
+
className={cn(className)}
|
|
50
|
+
data-slot="toolbar-input"
|
|
51
|
+
{...props}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ToolbarGroup({ className, ...props }: ToolbarPrimitive.Group.Props) {
|
|
57
|
+
return (
|
|
58
|
+
<ToolbarPrimitive.Group
|
|
59
|
+
className={cn("flex items-center gap-1", className)}
|
|
60
|
+
data-slot="toolbar-group"
|
|
61
|
+
{...props}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function ToolbarSeparator({
|
|
67
|
+
className,
|
|
68
|
+
...props
|
|
69
|
+
}: ToolbarPrimitive.Separator.Props) {
|
|
70
|
+
return (
|
|
71
|
+
<ToolbarPrimitive.Separator
|
|
72
|
+
className={cn(
|
|
73
|
+
"shrink-0 bg-border data-[orientation=horizontal]:my-0.5 data-[orientation=vertical]:my-1.5 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:w-px data-[orientation=vertical]:not-[[class^='h-']]:not-[[class*='_h-']]:self-stretch",
|
|
74
|
+
className,
|
|
75
|
+
)}
|
|
76
|
+
data-slot="toolbar-separator"
|
|
77
|
+
{...props}
|
|
78
|
+
/>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export {
|
|
83
|
+
Toolbar,
|
|
84
|
+
ToolbarGroup,
|
|
85
|
+
ToolbarSeparator,
|
|
86
|
+
ToolbarButton,
|
|
87
|
+
ToolbarLink,
|
|
88
|
+
ToolbarInput,
|
|
89
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils/css";
|
|
7
|
+
|
|
8
|
+
const TooltipCreateHandle = TooltipPrimitive.createHandle;
|
|
9
|
+
|
|
10
|
+
const TooltipProvider = TooltipPrimitive.Provider;
|
|
11
|
+
|
|
12
|
+
const Tooltip = TooltipPrimitive.Root;
|
|
13
|
+
|
|
14
|
+
function TooltipTrigger(props: TooltipPrimitive.Trigger.Props) {
|
|
15
|
+
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function TooltipTitle({
|
|
19
|
+
className,
|
|
20
|
+
...props
|
|
21
|
+
}: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
22
|
+
return (
|
|
23
|
+
<p
|
|
24
|
+
data-slot="tooltip-title"
|
|
25
|
+
className={cn("font-medium leading-snug", className)}
|
|
26
|
+
{...props}
|
|
27
|
+
/>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function TooltipDescription({
|
|
32
|
+
className,
|
|
33
|
+
...props
|
|
34
|
+
}: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
35
|
+
return (
|
|
36
|
+
<p
|
|
37
|
+
data-slot="tooltip-description"
|
|
38
|
+
className={cn("text-muted-foreground leading-snug", className)}
|
|
39
|
+
{...props}
|
|
40
|
+
/>
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function TooltipPopup({
|
|
45
|
+
className,
|
|
46
|
+
align = "center",
|
|
47
|
+
sideOffset = 4,
|
|
48
|
+
side = "top",
|
|
49
|
+
arrow = false,
|
|
50
|
+
children,
|
|
51
|
+
...props
|
|
52
|
+
}: TooltipPrimitive.Popup.Props & {
|
|
53
|
+
align?: TooltipPrimitive.Positioner.Props["align"];
|
|
54
|
+
side?: TooltipPrimitive.Positioner.Props["side"];
|
|
55
|
+
sideOffset?: TooltipPrimitive.Positioner.Props["sideOffset"];
|
|
56
|
+
arrow?: boolean;
|
|
57
|
+
}) {
|
|
58
|
+
return (
|
|
59
|
+
<TooltipPrimitive.Portal>
|
|
60
|
+
<TooltipPrimitive.Positioner
|
|
61
|
+
align={align}
|
|
62
|
+
className="z-50 h-(--positioner-height) w-(--positioner-width) max-w-(--available-width) transition-[top,left,right,bottom,transform] data-instant:transition-none"
|
|
63
|
+
data-slot="tooltip-positioner"
|
|
64
|
+
side={side}
|
|
65
|
+
sideOffset={arrow ? (sideOffset as number) + 5 : sideOffset}
|
|
66
|
+
>
|
|
67
|
+
<TooltipPrimitive.Popup
|
|
68
|
+
className={cn(
|
|
69
|
+
"relative flex h-(--popup-height,auto) w-(--popup-width,auto) origin-(--transform-origin) text-balance rounded-md border bg-popover not-dark:bg-clip-padding text-popover-foreground text-xs shadow-md/5 transition-[width,height,scale,opacity] before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-md)-1px)] before:shadow-[0_1px_--theme(--color-black/6%)] data-ending-style:scale-98 data-starting-style:scale-98 data-ending-style:opacity-0 data-starting-style:opacity-0 data-instant:duration-0 dark:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
70
|
+
className,
|
|
71
|
+
)}
|
|
72
|
+
data-slot="tooltip-popup"
|
|
73
|
+
{...props}
|
|
74
|
+
>
|
|
75
|
+
<TooltipPrimitive.Viewport
|
|
76
|
+
className="relative size-full overflow-clip px-(--viewport-inline-padding) py-1 [--viewport-inline-padding:--spacing(2)] data-instant:transition-none **:data-current:data-ending-style:opacity-0 **:data-current:data-starting-style:opacity-0 **:data-previous:data-ending-style:opacity-0 **:data-previous:data-starting-style:opacity-0 **:data-current:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:w-[calc(var(--popup-width)-2*var(--viewport-inline-padding)-2px)] **:data-previous:truncate **:data-current:opacity-100 **:data-previous:opacity-100 **:data-current:transition-opacity **:data-previous:transition-opacity"
|
|
77
|
+
data-slot="tooltip-viewport"
|
|
78
|
+
>
|
|
79
|
+
{children}
|
|
80
|
+
</TooltipPrimitive.Viewport>
|
|
81
|
+
{arrow && (
|
|
82
|
+
<TooltipPrimitive.Arrow
|
|
83
|
+
data-slot="tooltip-arrow"
|
|
84
|
+
className="absolute block size-2.5 bg-popover data-[side=top]:[clip-path:polygon(0%_0%,100%_0%,50%_100%)] data-[side=bottom]:[clip-path:polygon(50%_0%,0%_100%,100%_100%)] data-[side=left]:[clip-path:polygon(0%_0%,100%_50%,0%_100%)] data-[side=right]:[clip-path:polygon(100%_0%,0%_50%,100%_100%)]"
|
|
85
|
+
/>
|
|
86
|
+
)}
|
|
87
|
+
</TooltipPrimitive.Popup>
|
|
88
|
+
</TooltipPrimitive.Positioner>
|
|
89
|
+
</TooltipPrimitive.Portal>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export {
|
|
94
|
+
TooltipCreateHandle,
|
|
95
|
+
TooltipProvider,
|
|
96
|
+
Tooltip,
|
|
97
|
+
TooltipTrigger,
|
|
98
|
+
TooltipPopup,
|
|
99
|
+
TooltipPopup as TooltipContent,
|
|
100
|
+
TooltipTitle,
|
|
101
|
+
TooltipDescription,
|
|
102
|
+
};
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Transition } from "@headlessui/react";
|
|
4
|
+
import { Slot } from "@radix-ui/react-slot";
|
|
5
|
+
import type React from "react";
|
|
6
|
+
import {
|
|
7
|
+
createContext,
|
|
8
|
+
forwardRef,
|
|
9
|
+
useContext,
|
|
10
|
+
useEffect,
|
|
11
|
+
useMemo,
|
|
12
|
+
useRef,
|
|
13
|
+
useState,
|
|
14
|
+
} from "react";
|
|
15
|
+
import useResizeObserver from "use-resize-observer";
|
|
16
|
+
import useCallbackRef from "../../lib/hooks/use-callback-ref";
|
|
17
|
+
import { useHover } from "../../lib/hooks/use-hover";
|
|
18
|
+
import { cn } from "../../lib/utils/css";
|
|
19
|
+
import type { AsChildProps } from "../../types/helpers";
|
|
20
|
+
|
|
21
|
+
interface VerticalScrollFaderContextValue {
|
|
22
|
+
isTopOverflowing: boolean;
|
|
23
|
+
isBottomOverflowing: boolean;
|
|
24
|
+
containerRef: React.RefObject<HTMLElement | null>;
|
|
25
|
+
onScroll: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const VerticalScrollFaderContext =
|
|
29
|
+
createContext<VerticalScrollFaderContextValue>({
|
|
30
|
+
isTopOverflowing: false,
|
|
31
|
+
isBottomOverflowing: false,
|
|
32
|
+
containerRef: { current: null },
|
|
33
|
+
onScroll: () => {},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const getScrollDirection = (element: HTMLElement) =>
|
|
37
|
+
getComputedStyle(element).flexDirection === "column-reverse" ? -1 : 1;
|
|
38
|
+
|
|
39
|
+
function VerticalScrollFaderInner(
|
|
40
|
+
{ asChild, ...props }: AsChildProps<"div">,
|
|
41
|
+
ref: React.ForwardedRef<HTMLDivElement>,
|
|
42
|
+
) {
|
|
43
|
+
const containerRef = useRef<HTMLElement>(null);
|
|
44
|
+
|
|
45
|
+
const [isTopOverflowing, setIsTopOverflowing] = useState(false);
|
|
46
|
+
const [isBottomOverflowing, setIsBottomOverflowing] = useState(false);
|
|
47
|
+
|
|
48
|
+
const onScroll = useCallbackRef(() => {
|
|
49
|
+
if (!containerRef.current) return;
|
|
50
|
+
|
|
51
|
+
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
|
52
|
+
const scrollDirection = getScrollDirection(containerRef.current!);
|
|
53
|
+
setIsTopOverflowing(
|
|
54
|
+
scrollDirection > 0
|
|
55
|
+
? Math.floor(scrollTop - 1) > 0
|
|
56
|
+
: Math.round(-scrollTop + clientHeight + 1) < scrollHeight,
|
|
57
|
+
);
|
|
58
|
+
setIsBottomOverflowing(
|
|
59
|
+
scrollDirection > 0
|
|
60
|
+
? Math.round(scrollTop + clientHeight + 1) < scrollHeight
|
|
61
|
+
: Math.floor(-scrollTop - 1) > 0,
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
const contextValue = useMemo(
|
|
66
|
+
() => ({ isTopOverflowing, isBottomOverflowing, containerRef, onScroll }),
|
|
67
|
+
[isTopOverflowing, isBottomOverflowing, onScroll],
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const Comp = asChild ? Slot : "div";
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
<VerticalScrollFaderContext.Provider value={contextValue}>
|
|
74
|
+
<Slot className="relative" onScroll={onScroll}>
|
|
75
|
+
<Comp ref={ref} {...props} />
|
|
76
|
+
</Slot>
|
|
77
|
+
</VerticalScrollFaderContext.Provider>
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const VerticalScrollFader = forwardRef(VerticalScrollFaderInner);
|
|
82
|
+
|
|
83
|
+
function VerticalScrollFaderContentInner(
|
|
84
|
+
{
|
|
85
|
+
asChild,
|
|
86
|
+
faderSize = "3rem",
|
|
87
|
+
showScrollbar,
|
|
88
|
+
...props
|
|
89
|
+
}: AsChildProps<"div"> & {
|
|
90
|
+
faderSize?: string;
|
|
91
|
+
showScrollbar?: boolean;
|
|
92
|
+
},
|
|
93
|
+
ref: React.ForwardedRef<HTMLDivElement>,
|
|
94
|
+
) {
|
|
95
|
+
const Comp = asChild ? Slot : "div";
|
|
96
|
+
const { isTopOverflowing, isBottomOverflowing, containerRef, onScroll } =
|
|
97
|
+
useContext(VerticalScrollFaderContext);
|
|
98
|
+
|
|
99
|
+
useResizeObserver({
|
|
100
|
+
ref: containerRef as React.RefObject<Element>,
|
|
101
|
+
onResize: onScroll,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// A single vertical mask gradient fades the top and bottom edges over their
|
|
105
|
+
// fader sizes. An edge that isn't overflowing has a 0 fader size, so it shows
|
|
106
|
+
// no fade. Inline styles (with -webkit- for Safari) keep this independent of
|
|
107
|
+
// Tailwind mask-utility emission.
|
|
108
|
+
const maskImage = `linear-gradient(to bottom, transparent 0, #000 var(--top-fader-size), #000 calc(100% - var(--bottom-fader-size)), transparent 100%)`;
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<Slot
|
|
112
|
+
onScroll={onScroll}
|
|
113
|
+
className={cn(
|
|
114
|
+
"overflow-auto [transition:--top-fader-size_150ms,--bottom-fader-size_150ms]",
|
|
115
|
+
!showScrollbar &&
|
|
116
|
+
"[scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
117
|
+
)}
|
|
118
|
+
style={{
|
|
119
|
+
"--fader-size": faderSize,
|
|
120
|
+
"--top-fader-size": isTopOverflowing ? "var(--fader-size)" : "0rem",
|
|
121
|
+
"--bottom-fader-size": isBottomOverflowing
|
|
122
|
+
? "var(--fader-size)"
|
|
123
|
+
: "0rem",
|
|
124
|
+
maskImage,
|
|
125
|
+
WebkitMaskImage: maskImage,
|
|
126
|
+
}}
|
|
127
|
+
ref={containerRef}
|
|
128
|
+
>
|
|
129
|
+
<Comp ref={ref} {...props} />
|
|
130
|
+
</Slot>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const VerticalScrollFaderContent = forwardRef(VerticalScrollFaderContentInner);
|
|
135
|
+
|
|
136
|
+
function VerticalScrollFaderTopScrollerInner(
|
|
137
|
+
{ asChild, ...props }: AsChildProps<"button">,
|
|
138
|
+
ref: React.ForwardedRef<HTMLButtonElement>,
|
|
139
|
+
) {
|
|
140
|
+
const Comp = asChild ? Slot : "button";
|
|
141
|
+
const { containerRef, isTopOverflowing } = useContext(
|
|
142
|
+
VerticalScrollFaderContext,
|
|
143
|
+
);
|
|
144
|
+
const onClick = useCallbackRef(() => {
|
|
145
|
+
if (!containerRef.current) return;
|
|
146
|
+
containerRef.current.scrollBy({
|
|
147
|
+
top: -containerRef.current.clientHeight,
|
|
148
|
+
behavior: "smooth",
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const [hoverRef, hovered] = useHover<HTMLElement>({
|
|
153
|
+
mounted: isTopOverflowing,
|
|
154
|
+
});
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!hovered || !isTopOverflowing) return;
|
|
157
|
+
let stopped = false;
|
|
158
|
+
let animationFrameId: number;
|
|
159
|
+
let startTime: DOMHighResTimeStamp;
|
|
160
|
+
|
|
161
|
+
const animate = (timestamp: DOMHighResTimeStamp) => {
|
|
162
|
+
if (stopped) return;
|
|
163
|
+
if (!startTime) startTime = timestamp;
|
|
164
|
+
const elapsed = Math.min(timestamp - startTime, 2000);
|
|
165
|
+
containerRef.current?.scrollBy({
|
|
166
|
+
top: -Math.log2(elapsed + 1),
|
|
167
|
+
});
|
|
168
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
169
|
+
};
|
|
170
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
171
|
+
|
|
172
|
+
return () => {
|
|
173
|
+
stopped = true;
|
|
174
|
+
cancelAnimationFrame(animationFrameId);
|
|
175
|
+
};
|
|
176
|
+
}, [hovered, isTopOverflowing, containerRef.current?.scrollBy]);
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<Transition show={isTopOverflowing}>
|
|
180
|
+
<Slot onClick={onClick} ref={hoverRef}>
|
|
181
|
+
<Comp {...(asChild ? {} : { type: "button" })} ref={ref} {...props} />
|
|
182
|
+
</Slot>
|
|
183
|
+
</Transition>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const VerticalScrollFaderTopScroller = forwardRef(
|
|
188
|
+
VerticalScrollFaderTopScrollerInner,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
function VerticalScrollFaderBottomScrollerInner(
|
|
192
|
+
{ asChild, ...props }: AsChildProps<"button">,
|
|
193
|
+
ref: React.ForwardedRef<HTMLButtonElement>,
|
|
194
|
+
) {
|
|
195
|
+
const Comp = asChild ? Slot : "button";
|
|
196
|
+
const { containerRef, isBottomOverflowing } = useContext(
|
|
197
|
+
VerticalScrollFaderContext,
|
|
198
|
+
);
|
|
199
|
+
const onClick = useCallbackRef(() => {
|
|
200
|
+
if (!containerRef.current) return;
|
|
201
|
+
containerRef.current.scrollBy({
|
|
202
|
+
top: containerRef.current.clientHeight,
|
|
203
|
+
behavior: "smooth",
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
const [hoverRef, hovered] = useHover({
|
|
207
|
+
mounted: isBottomOverflowing,
|
|
208
|
+
});
|
|
209
|
+
useEffect(() => {
|
|
210
|
+
if (!hovered || !isBottomOverflowing) return;
|
|
211
|
+
let stopped = false;
|
|
212
|
+
let animationFrameId: number;
|
|
213
|
+
let startTime: DOMHighResTimeStamp;
|
|
214
|
+
|
|
215
|
+
const animate = (timestamp: DOMHighResTimeStamp) => {
|
|
216
|
+
if (stopped) return;
|
|
217
|
+
if (!startTime) startTime = timestamp;
|
|
218
|
+
const elapsed = Math.min(timestamp - startTime, 2000);
|
|
219
|
+
containerRef.current?.scrollBy({
|
|
220
|
+
top: Math.log2(elapsed + 1),
|
|
221
|
+
});
|
|
222
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
223
|
+
};
|
|
224
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
225
|
+
|
|
226
|
+
return () => {
|
|
227
|
+
stopped = true;
|
|
228
|
+
cancelAnimationFrame(animationFrameId);
|
|
229
|
+
};
|
|
230
|
+
}, [hovered, isBottomOverflowing, containerRef.current?.scrollBy]);
|
|
231
|
+
|
|
232
|
+
return (
|
|
233
|
+
<Transition show={isBottomOverflowing}>
|
|
234
|
+
<Slot onClick={onClick} ref={hoverRef}>
|
|
235
|
+
<Comp {...(asChild ? {} : { type: "button" })} ref={ref} {...props} />
|
|
236
|
+
</Slot>
|
|
237
|
+
</Transition>
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const VerticalScrollFaderBottomScroller = forwardRef(
|
|
242
|
+
VerticalScrollFaderBottomScrollerInner,
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
export {
|
|
246
|
+
VerticalScrollFader,
|
|
247
|
+
VerticalScrollFaderContent,
|
|
248
|
+
VerticalScrollFaderTopScroller,
|
|
249
|
+
VerticalScrollFaderBottomScroller,
|
|
250
|
+
};
|