@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,228 @@
|
|
|
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 HorizontalScrollFaderContextValue {
|
|
22
|
+
isLeftOverflowing: boolean;
|
|
23
|
+
isRightOverflowing: boolean;
|
|
24
|
+
containerRef: React.RefObject<HTMLElement | null>;
|
|
25
|
+
onScroll: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const HorizontalScrollFaderContext =
|
|
29
|
+
createContext<HorizontalScrollFaderContextValue>({
|
|
30
|
+
isLeftOverflowing: false,
|
|
31
|
+
isRightOverflowing: false,
|
|
32
|
+
containerRef: { current: null },
|
|
33
|
+
onScroll: () => {},
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
function HorizontalScrollFaderInner(
|
|
37
|
+
{ asChild, ...props }: AsChildProps<"div">,
|
|
38
|
+
ref: React.ForwardedRef<HTMLDivElement>,
|
|
39
|
+
) {
|
|
40
|
+
const containerRef = useRef<HTMLElement>(null);
|
|
41
|
+
|
|
42
|
+
const [isLeftOverflowing, setIsLeftOverflowing] = useState(false);
|
|
43
|
+
const [isRightOverflowing, setIsRightOverflowing] = useState(false);
|
|
44
|
+
|
|
45
|
+
const onScroll = useCallbackRef(() => {
|
|
46
|
+
if (!containerRef.current) return;
|
|
47
|
+
|
|
48
|
+
const { scrollLeft, scrollWidth, clientWidth } = containerRef.current;
|
|
49
|
+
setIsLeftOverflowing(scrollLeft > 0);
|
|
50
|
+
setIsRightOverflowing(scrollLeft + clientWidth < scrollWidth);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const contextValue = useMemo(
|
|
54
|
+
() => ({ isLeftOverflowing, isRightOverflowing, containerRef, onScroll }),
|
|
55
|
+
[isLeftOverflowing, isRightOverflowing, onScroll],
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const Comp = asChild ? Slot : "div";
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<HorizontalScrollFaderContext.Provider value={contextValue}>
|
|
62
|
+
<Slot className="relative" onScroll={onScroll}>
|
|
63
|
+
<Comp ref={ref} {...props} />
|
|
64
|
+
</Slot>
|
|
65
|
+
</HorizontalScrollFaderContext.Provider>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const HorizontalScrollFader = forwardRef(HorizontalScrollFaderInner);
|
|
70
|
+
|
|
71
|
+
function HorizontalScrollFaderContentInner(
|
|
72
|
+
{ asChild, ...props }: AsChildProps<"div">,
|
|
73
|
+
ref: React.ForwardedRef<HTMLDivElement>,
|
|
74
|
+
) {
|
|
75
|
+
const Comp = asChild ? Slot : "div";
|
|
76
|
+
const { isLeftOverflowing, isRightOverflowing, containerRef, onScroll } =
|
|
77
|
+
useContext(HorizontalScrollFaderContext);
|
|
78
|
+
|
|
79
|
+
useResizeObserver({
|
|
80
|
+
ref: containerRef as React.RefObject<HTMLElement>,
|
|
81
|
+
onResize: onScroll,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// A single horizontal mask gradient fades each edge over its fader size.
|
|
85
|
+
// When an edge isn't overflowing its fader size is 0, so no fade shows.
|
|
86
|
+
// Inline styles (with the -webkit- prefix for Safari) keep this independent
|
|
87
|
+
// of Tailwind mask-utility emission.
|
|
88
|
+
const maskImage = `linear-gradient(to right, transparent 0, #000 var(--left-fader-size), #000 calc(100% - var(--right-fader-size)), transparent 100%)`;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Slot
|
|
92
|
+
onScroll={onScroll}
|
|
93
|
+
className={cn(
|
|
94
|
+
"overflow-auto [transition:--left-fader-size_150ms,--right-fader-size_150ms] [scrollbar-width:none] [&::-webkit-scrollbar]:hidden",
|
|
95
|
+
)}
|
|
96
|
+
style={{
|
|
97
|
+
"--left-fader-size": isLeftOverflowing ? "3rem" : "0rem",
|
|
98
|
+
"--right-fader-size": isRightOverflowing ? "3rem" : "0rem",
|
|
99
|
+
maskImage,
|
|
100
|
+
WebkitMaskImage: maskImage,
|
|
101
|
+
}}
|
|
102
|
+
ref={containerRef}
|
|
103
|
+
>
|
|
104
|
+
<Comp ref={ref} {...props} />
|
|
105
|
+
</Slot>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const HorizontalScrollFaderContent = forwardRef(
|
|
110
|
+
HorizontalScrollFaderContentInner,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
function HorizontalScrollFaderLeftScrollerInner(
|
|
114
|
+
{ asChild, ...props }: AsChildProps<"button">,
|
|
115
|
+
ref: React.ForwardedRef<HTMLButtonElement>,
|
|
116
|
+
) {
|
|
117
|
+
const Comp = asChild ? Slot : "button";
|
|
118
|
+
const { containerRef, isLeftOverflowing } = useContext(
|
|
119
|
+
HorizontalScrollFaderContext,
|
|
120
|
+
);
|
|
121
|
+
const onClick = useCallbackRef(() => {
|
|
122
|
+
if (!containerRef.current) return;
|
|
123
|
+
containerRef.current.scrollBy({
|
|
124
|
+
left: -containerRef.current.clientWidth,
|
|
125
|
+
behavior: "smooth",
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const [hoverRef, hovered] = useHover<HTMLElement>({
|
|
130
|
+
mounted: isLeftOverflowing,
|
|
131
|
+
});
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (!hovered || !isLeftOverflowing) return;
|
|
134
|
+
let stopped = false;
|
|
135
|
+
let animationFrameId: number;
|
|
136
|
+
let startTime: DOMHighResTimeStamp;
|
|
137
|
+
|
|
138
|
+
const animate = (timestamp: DOMHighResTimeStamp) => {
|
|
139
|
+
if (stopped) return;
|
|
140
|
+
if (!startTime) startTime = timestamp;
|
|
141
|
+
const elapsed = Math.min(timestamp - startTime, 2000);
|
|
142
|
+
containerRef.current?.scrollBy({
|
|
143
|
+
left: -Math.log2(elapsed + 1),
|
|
144
|
+
});
|
|
145
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
146
|
+
};
|
|
147
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
148
|
+
|
|
149
|
+
return () => {
|
|
150
|
+
stopped = true;
|
|
151
|
+
cancelAnimationFrame(animationFrameId);
|
|
152
|
+
};
|
|
153
|
+
}, [hovered, isLeftOverflowing, containerRef.current?.scrollBy]);
|
|
154
|
+
|
|
155
|
+
return (
|
|
156
|
+
<Transition show={isLeftOverflowing}>
|
|
157
|
+
<Slot onClick={onClick} ref={hoverRef}>
|
|
158
|
+
<Comp {...(asChild ? {} : { type: "button" })} ref={ref} {...props} />
|
|
159
|
+
</Slot>
|
|
160
|
+
</Transition>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const HorizontalScrollFaderLeftScroller = forwardRef(
|
|
165
|
+
HorizontalScrollFaderLeftScrollerInner,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
function HorizontalScrollFaderRightScrollerInner(
|
|
169
|
+
{ asChild, ...props }: AsChildProps<"button">,
|
|
170
|
+
ref: React.ForwardedRef<HTMLButtonElement>,
|
|
171
|
+
) {
|
|
172
|
+
const Comp = asChild ? Slot : "button";
|
|
173
|
+
const { containerRef, isRightOverflowing } = useContext(
|
|
174
|
+
HorizontalScrollFaderContext,
|
|
175
|
+
);
|
|
176
|
+
const onClick = useCallbackRef(() => {
|
|
177
|
+
if (!containerRef.current) return;
|
|
178
|
+
containerRef.current.scrollBy({
|
|
179
|
+
left: containerRef.current.clientWidth,
|
|
180
|
+
behavior: "smooth",
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const [hoverRef, hovered] = useHover({
|
|
185
|
+
mounted: isRightOverflowing,
|
|
186
|
+
});
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (!hovered || !isRightOverflowing) return;
|
|
189
|
+
let stopped = false;
|
|
190
|
+
let animationFrameId: number;
|
|
191
|
+
let startTime: DOMHighResTimeStamp;
|
|
192
|
+
|
|
193
|
+
const animate = (timestamp: DOMHighResTimeStamp) => {
|
|
194
|
+
if (stopped) return;
|
|
195
|
+
if (!startTime) startTime = timestamp;
|
|
196
|
+
const elapsed = Math.min(timestamp - startTime, 2000);
|
|
197
|
+
containerRef.current?.scrollBy({
|
|
198
|
+
left: Math.log2(elapsed + 1),
|
|
199
|
+
});
|
|
200
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
201
|
+
};
|
|
202
|
+
animationFrameId = requestAnimationFrame(animate);
|
|
203
|
+
|
|
204
|
+
return () => {
|
|
205
|
+
stopped = true;
|
|
206
|
+
cancelAnimationFrame(animationFrameId);
|
|
207
|
+
};
|
|
208
|
+
}, [hovered, isRightOverflowing, containerRef.current?.scrollBy]);
|
|
209
|
+
|
|
210
|
+
return (
|
|
211
|
+
<Transition show={isRightOverflowing}>
|
|
212
|
+
<Slot onClick={onClick} ref={hoverRef}>
|
|
213
|
+
<Comp {...(asChild ? {} : { type: "button" })} ref={ref} {...props} />
|
|
214
|
+
</Slot>
|
|
215
|
+
</Transition>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const HorizontalScrollFaderRightScroller = forwardRef(
|
|
220
|
+
HorizontalScrollFaderRightScrollerInner,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
export {
|
|
224
|
+
HorizontalScrollFader,
|
|
225
|
+
HorizontalScrollFaderContent,
|
|
226
|
+
HorizontalScrollFaderLeftScroller,
|
|
227
|
+
HorizontalScrollFaderRightScroller,
|
|
228
|
+
};
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
import { Input, type InputProps } from "./input";
|
|
6
|
+
import { Textarea, type TextareaProps } from "./textarea";
|
|
7
|
+
import { cn } from "../../lib/utils/css";
|
|
8
|
+
|
|
9
|
+
function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
10
|
+
return (
|
|
11
|
+
// biome-ignore lint/a11y/useSemanticElements: Imported from library
|
|
12
|
+
<div
|
|
13
|
+
className={cn(
|
|
14
|
+
"relative inline-flex w-full min-w-0 items-center rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_1px_--theme(--color-black/6%)] has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/64 has-[input:focus-visible,textarea:focus-visible]:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/16 has-[textarea]:h-auto has-data-[align=block-end]:h-auto has-data-[align=block-start]:h-auto has-data-[align=block-end]:flex-col has-data-[align=block-start]:flex-col has-[input:focus-visible,textarea:focus-visible]:border-ring has-[input[aria-invalid],textarea[aria-invalid]]:border-destructive/36 has-[input:disabled,textarea:disabled]:opacity-64 has-[input:disabled,textarea:disabled,input:focus-visible,textarea:focus-visible,input[aria-invalid],textarea[aria-invalid]]:shadow-none has-[input:focus-visible,textarea:focus-visible]:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-[input[aria-invalid],textarea[aria-invalid]]:ring-destructive/24 dark:not-has-[input:disabled,textarea:disabled]:not-has-[input:focus-visible,textarea:focus-visible]:not-has-[input[aria-invalid],textarea[aria-invalid]]:before:shadow-[0_-1px_--theme(--color-white/6%)] has-data-[align=inline-start]:**:[[data-size=sm]_input]:ps-1.5 has-data-[align=inline-end]:**:[[data-size=sm]_input]:pe-1.5 *:[[data-slot=input-control],[data-slot=textarea-control]]:contents *:[[data-slot=input-control],[data-slot=textarea-control]]:before:hidden has-[[data-align=block-start],[data-align=block-end]]:**:[input]:h-auto has-data-[align=inline-start]:**:[input]:ps-2 has-data-[align=inline-end]:**:[input]:pe-2 has-data-[align=block-end]:**:[input]:pt-1.5 has-data-[align=block-start]:**:[input]:pb-1.5 **:[textarea]:min-h-20.5 **:[textarea]:resize-none **:[textarea]:py-[calc(--spacing(3)-1px)] **:[textarea]:max-sm:min-h-23.5 **:[textarea_button]:rounded-[calc(var(--radius-md)-1px)]",
|
|
15
|
+
className,
|
|
16
|
+
)}
|
|
17
|
+
data-slot="input-group"
|
|
18
|
+
role="group"
|
|
19
|
+
{...props}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const inputGroupAddonVariants = cva(
|
|
25
|
+
"[&_svg]:-mx-0.5 flex h-auto cursor-text select-none items-center justify-center gap-2 leading-none [&>kbd]:rounded-[calc(var(--radius)-5px)] in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 not-has-[button]:**:[svg:not([class*='opacity-'])]:opacity-80",
|
|
26
|
+
{
|
|
27
|
+
defaultVariants: {
|
|
28
|
+
align: "inline-start",
|
|
29
|
+
},
|
|
30
|
+
variants: {
|
|
31
|
+
align: {
|
|
32
|
+
"block-end":
|
|
33
|
+
"order-last w-full justify-start px-[calc(--spacing(3)-1px)] pb-[calc(--spacing(3)-1px)] [.border-t]:pt-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]",
|
|
34
|
+
"block-start":
|
|
35
|
+
"order-first w-full justify-start px-[calc(--spacing(3)-1px)] pt-[calc(--spacing(3)-1px)] [.border-b]:pb-[calc(--spacing(3)-1px)] [[data-size=sm]+&]:px-[calc(--spacing(2.5)-1px)]",
|
|
36
|
+
"inline-end":
|
|
37
|
+
"has-[>:last-child[data-slot=badge]]:-me-1.5 has-[>button]:-me-2 order-last pe-[calc(--spacing(3)-1px)] has-[>kbd:last-child]:me-[-0.35rem] [[data-size=sm]+&]:pe-[calc(--spacing(2.5)-1px)]",
|
|
38
|
+
"inline-start":
|
|
39
|
+
"has-[>:last-child[data-slot=badge]]:-ms-1.5 has-[>button]:-ms-2 order-first ps-[calc(--spacing(3)-1px)] has-[>kbd:last-child]:ms-[-0.35rem] [[data-size=sm]+&]:ps-[calc(--spacing(2.5)-1px)]",
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
function InputGroupAddon({
|
|
46
|
+
className,
|
|
47
|
+
align = "inline-start",
|
|
48
|
+
...props
|
|
49
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
|
|
50
|
+
return (
|
|
51
|
+
// biome-ignore lint/a11y/noStaticElementInteractions: Imported from library
|
|
52
|
+
<div
|
|
53
|
+
className={cn(inputGroupAddonVariants({ align }), className)}
|
|
54
|
+
data-align={align}
|
|
55
|
+
data-slot="input-group-addon"
|
|
56
|
+
onMouseDown={(e) => {
|
|
57
|
+
const target = e.target as HTMLElement;
|
|
58
|
+
const isInteractive = target.closest(
|
|
59
|
+
"button, a, input, select, textarea, [role='button'], [role='combobox'], [role='listbox'], [data-slot='select-trigger']",
|
|
60
|
+
);
|
|
61
|
+
if (isInteractive) return;
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
const parent = e.currentTarget.parentElement;
|
|
64
|
+
const input = parent?.querySelector<
|
|
65
|
+
HTMLInputElement | HTMLTextAreaElement
|
|
66
|
+
>("input, textarea");
|
|
67
|
+
if (input && !parent?.querySelector("input:focus, textarea:focus")) {
|
|
68
|
+
input.focus();
|
|
69
|
+
}
|
|
70
|
+
}}
|
|
71
|
+
{...props}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
|
|
77
|
+
return (
|
|
78
|
+
<span
|
|
79
|
+
className={cn(
|
|
80
|
+
"[&_svg]:-mx-0.5 line-clamp-1 flex items-center gap-2 text-muted-foreground leading-none in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4.5 sm:in-[[data-slot=input-group]:has([data-slot=input-control],[data-slot=textarea-control])]:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none",
|
|
81
|
+
className,
|
|
82
|
+
)}
|
|
83
|
+
{...props}
|
|
84
|
+
/>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function InputGroupInput({ className, ...props }: InputProps) {
|
|
89
|
+
return <Input className={className} unstyled {...props} />;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function InputGroupTextarea({ className, ...props }: TextareaProps) {
|
|
93
|
+
return <Textarea className={className} unstyled {...props} />;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export {
|
|
97
|
+
InputGroup,
|
|
98
|
+
InputGroupAddon,
|
|
99
|
+
InputGroupText,
|
|
100
|
+
InputGroupInput,
|
|
101
|
+
InputGroupTextarea,
|
|
102
|
+
};
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
4
|
+
import { OTPInput, OTPInputContext, REGEXP_ONLY_DIGITS } from "input-otp";
|
|
5
|
+
import { MinusIcon } from "lucide-react";
|
|
6
|
+
import * as React from "react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "../../lib/utils/css";
|
|
9
|
+
|
|
10
|
+
type InputOTPProps = React.ComponentProps<typeof OTPInput>;
|
|
11
|
+
|
|
12
|
+
function InputOTP({
|
|
13
|
+
className,
|
|
14
|
+
containerClassName,
|
|
15
|
+
// Digits-only by default. Override `pattern`/`inputMode` to allow letters.
|
|
16
|
+
inputMode = "numeric",
|
|
17
|
+
pattern = REGEXP_ONLY_DIGITS,
|
|
18
|
+
...props
|
|
19
|
+
}: InputOTPProps) {
|
|
20
|
+
return (
|
|
21
|
+
<OTPInput
|
|
22
|
+
data-slot="input-otp"
|
|
23
|
+
containerClassName={cn(
|
|
24
|
+
"flex items-center gap-2 has-disabled:opacity-50",
|
|
25
|
+
containerClassName,
|
|
26
|
+
)}
|
|
27
|
+
className={cn("disabled:cursor-not-allowed", className)}
|
|
28
|
+
inputMode={inputMode}
|
|
29
|
+
pattern={pattern}
|
|
30
|
+
{...props}
|
|
31
|
+
/>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function InputOTPGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
36
|
+
return (
|
|
37
|
+
<div
|
|
38
|
+
data-slot="input-otp-group"
|
|
39
|
+
className={cn("flex items-center", className)}
|
|
40
|
+
{...props}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const inputOTPSlotVariants = cva(
|
|
46
|
+
"data-[active=true]:border-ring data-[active=true]:ring-ring/50 data-[active=true]:aria-invalid:ring-destructive/20 dark:data-[active=true]:aria-invalid:ring-destructive/40 aria-invalid:border-destructive data-[active=true]:aria-invalid:border-destructive dark:bg-input/30 border-input relative flex items-center justify-center border-y border-r shadow-xs transition-all outline-none first:rounded-l-md first:border-l last:rounded-r-md data-[active=true]:z-10 data-[active=true]:ring-[3px]",
|
|
47
|
+
{
|
|
48
|
+
variants: {
|
|
49
|
+
size: {
|
|
50
|
+
sm: "h-8 w-8 text-xs",
|
|
51
|
+
default: "h-9 w-9 text-sm",
|
|
52
|
+
lg: "h-11 w-11 text-base",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
defaultVariants: { size: "default" },
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
function InputOTPSlot({
|
|
60
|
+
index,
|
|
61
|
+
className,
|
|
62
|
+
size,
|
|
63
|
+
...props
|
|
64
|
+
}: React.ComponentProps<"div"> & {
|
|
65
|
+
index: number;
|
|
66
|
+
} & VariantProps<typeof inputOTPSlotVariants>) {
|
|
67
|
+
const inputOTPContext = React.useContext(OTPInputContext);
|
|
68
|
+
const { char, hasFakeCaret, isActive } = inputOTPContext?.slots[index] ?? {};
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<div
|
|
72
|
+
data-slot="input-otp-slot"
|
|
73
|
+
data-active={isActive}
|
|
74
|
+
className={cn(inputOTPSlotVariants({ size }), className)}
|
|
75
|
+
{...props}
|
|
76
|
+
>
|
|
77
|
+
{char}
|
|
78
|
+
{hasFakeCaret && (
|
|
79
|
+
<div className="pointer-events-none absolute inset-0 flex items-center justify-center">
|
|
80
|
+
<div className="animate-caret-blink bg-foreground h-4 w-px duration-1000" />
|
|
81
|
+
</div>
|
|
82
|
+
)}
|
|
83
|
+
</div>
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function InputOTPSeparator({ ...props }: React.ComponentProps<"div">) {
|
|
88
|
+
return (
|
|
89
|
+
// biome-ignore lint/a11y/useFocusableInteractive lint/a11y/useSemanticElements lint/a11y/useAriaPropsForRole: Installed from shadcn
|
|
90
|
+
<div data-slot="input-otp-separator" role="separator" {...props}>
|
|
91
|
+
<MinusIcon />
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export { InputOTP, InputOTPGroup, InputOTPSlot, InputOTPSeparator };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { Input as InputPrimitive } from "@base-ui/react/input";
|
|
4
|
+
import type * as React from "react";
|
|
5
|
+
|
|
6
|
+
import { cn } from "../../lib/utils/css";
|
|
7
|
+
|
|
8
|
+
type InputProps = Omit<
|
|
9
|
+
InputPrimitive.Props & React.RefAttributes<HTMLInputElement>,
|
|
10
|
+
"size"
|
|
11
|
+
> & {
|
|
12
|
+
size?: "sm" | "default" | "lg" | number;
|
|
13
|
+
unstyled?: boolean;
|
|
14
|
+
nativeInput?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function Input({
|
|
18
|
+
className,
|
|
19
|
+
size = "default",
|
|
20
|
+
unstyled = false,
|
|
21
|
+
nativeInput = false,
|
|
22
|
+
...props
|
|
23
|
+
}: InputProps) {
|
|
24
|
+
const inputClassName = cn(
|
|
25
|
+
"h-8.5 w-full min-w-0 rounded-[inherit] px-[calc(--spacing(3)-1px)] leading-8.5 outline-none placeholder:text-muted-foreground/72 sm:h-7.5 sm:leading-7.5",
|
|
26
|
+
size === "sm" &&
|
|
27
|
+
"h-7.5 px-[calc(--spacing(2.5)-1px)] leading-7.5 sm:h-6.5 sm:leading-6.5",
|
|
28
|
+
size === "lg" && "h-9.5 leading-9.5 sm:h-8.5 sm:leading-8.5",
|
|
29
|
+
props.type === "search" &&
|
|
30
|
+
"[&::-webkit-search-cancel-button]:appearance-none [&::-webkit-search-decoration]:appearance-none [&::-webkit-search-results-button]:appearance-none [&::-webkit-search-results-decoration]:appearance-none",
|
|
31
|
+
props.type === "file" &&
|
|
32
|
+
"text-muted-foreground file:me-3 file:bg-transparent file:font-medium file:text-foreground file:text-sm",
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<span
|
|
37
|
+
className={
|
|
38
|
+
cn(
|
|
39
|
+
!unstyled &&
|
|
40
|
+
"relative inline-flex w-full rounded-lg border border-input bg-background not-dark:bg-clip-padding text-base text-foreground shadow-xs/5 ring-ring/24 transition-shadow before:pointer-events-none before:absolute before:inset-0 before:rounded-[calc(var(--radius-lg)-1px)] not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_1px_--theme(--color-black/6%)] has-focus-visible:has-aria-invalid:border-destructive/64 has-focus-visible:has-aria-invalid:ring-destructive/16 has-aria-invalid:border-destructive/36 has-focus-visible:border-ring has-disabled:opacity-64 has-[:disabled,:focus-visible,[aria-invalid]]:shadow-none has-focus-visible:ring-[3px] sm:text-sm dark:bg-input/32 dark:has-aria-invalid:ring-destructive/24 dark:not-has-disabled:not-has-focus-visible:not-has-aria-invalid:before:shadow-[0_-1px_--theme(--color-white/6%)]",
|
|
41
|
+
className,
|
|
42
|
+
) || undefined
|
|
43
|
+
}
|
|
44
|
+
data-size={size}
|
|
45
|
+
data-slot="input-control"
|
|
46
|
+
>
|
|
47
|
+
{nativeInput ? (
|
|
48
|
+
<input
|
|
49
|
+
className={inputClassName}
|
|
50
|
+
data-slot="input"
|
|
51
|
+
size={typeof size === "number" ? size : undefined}
|
|
52
|
+
{...props}
|
|
53
|
+
/>
|
|
54
|
+
) : (
|
|
55
|
+
<InputPrimitive
|
|
56
|
+
className={inputClassName}
|
|
57
|
+
data-slot="input"
|
|
58
|
+
size={typeof size === "number" ? size : undefined}
|
|
59
|
+
{...props}
|
|
60
|
+
/>
|
|
61
|
+
)}
|
|
62
|
+
</span>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export { Input, type InputProps };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { mergeProps } from "@base-ui/react/merge-props";
|
|
4
|
+
import { useRender } from "@base-ui/react/use-render";
|
|
5
|
+
import { cva, type VariantProps } from "class-variance-authority";
|
|
6
|
+
import type * as React from "react";
|
|
7
|
+
|
|
8
|
+
import { cn } from "../../lib/utils/css";
|
|
9
|
+
import { Separator } from "./separator";
|
|
10
|
+
|
|
11
|
+
function ItemGroup({ className, ...props }: React.ComponentProps<"div">) {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className={cn("group/item-group flex flex-col", className)}
|
|
15
|
+
data-slot="item-group"
|
|
16
|
+
role="list"
|
|
17
|
+
{...props}
|
|
18
|
+
/>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ItemSeparator({
|
|
23
|
+
className,
|
|
24
|
+
...props
|
|
25
|
+
}: React.ComponentProps<typeof Separator>) {
|
|
26
|
+
return (
|
|
27
|
+
<Separator
|
|
28
|
+
className={cn("my-0", className)}
|
|
29
|
+
data-slot="item-separator"
|
|
30
|
+
orientation="horizontal"
|
|
31
|
+
{...props}
|
|
32
|
+
/>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const itemVariants = cva(
|
|
37
|
+
"group/item flex flex-wrap items-center rounded-lg border border-transparent text-base outline-none transition-colors duration-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background sm:text-sm [a]:transition-colors [a]:hover:bg-accent/50",
|
|
38
|
+
{
|
|
39
|
+
defaultVariants: {
|
|
40
|
+
size: "default",
|
|
41
|
+
variant: "default",
|
|
42
|
+
},
|
|
43
|
+
variants: {
|
|
44
|
+
size: {
|
|
45
|
+
default: "gap-4 p-4",
|
|
46
|
+
sm: "gap-2.5 px-4 py-3",
|
|
47
|
+
},
|
|
48
|
+
variant: {
|
|
49
|
+
default: "bg-transparent",
|
|
50
|
+
muted: "bg-muted/50",
|
|
51
|
+
outline:
|
|
52
|
+
"border-border bg-card not-dark:bg-clip-padding shadow-xs/5 dark:bg-input/32",
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
interface ItemProps extends useRender.ComponentProps<"div"> {
|
|
59
|
+
variant?: VariantProps<typeof itemVariants>["variant"];
|
|
60
|
+
size?: VariantProps<typeof itemVariants>["size"];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function Item({ className, variant, size, render, ...props }: ItemProps) {
|
|
64
|
+
const defaultProps = {
|
|
65
|
+
className: cn(itemVariants({ className, size, variant })),
|
|
66
|
+
"data-size": size ?? "default",
|
|
67
|
+
"data-slot": "item",
|
|
68
|
+
"data-variant": variant ?? "default",
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return useRender({
|
|
72
|
+
defaultTagName: "div",
|
|
73
|
+
props: mergeProps<"div">(defaultProps, props),
|
|
74
|
+
render,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const itemMediaVariants = cva(
|
|
79
|
+
"flex shrink-0 items-center justify-center gap-2 group-has-[[data-slot=item-description]]/item:translate-y-0.5 group-has-[[data-slot=item-description]]/item:self-start [&_svg]:pointer-events-none",
|
|
80
|
+
{
|
|
81
|
+
defaultVariants: {
|
|
82
|
+
variant: "default",
|
|
83
|
+
},
|
|
84
|
+
variants: {
|
|
85
|
+
variant: {
|
|
86
|
+
default: "bg-transparent",
|
|
87
|
+
icon: "size-8 rounded-sm border bg-muted [&_svg:not([class*='size-'])]:size-4",
|
|
88
|
+
image:
|
|
89
|
+
"size-10 overflow-hidden rounded-sm [&_img]:size-full [&_img]:object-cover",
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
function ItemMedia({
|
|
96
|
+
className,
|
|
97
|
+
variant = "default",
|
|
98
|
+
...props
|
|
99
|
+
}: React.ComponentProps<"div"> & VariantProps<typeof itemMediaVariants>) {
|
|
100
|
+
return (
|
|
101
|
+
<div
|
|
102
|
+
className={cn(itemMediaVariants({ className, variant }))}
|
|
103
|
+
data-slot="item-media"
|
|
104
|
+
data-variant={variant}
|
|
105
|
+
{...props}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function ItemContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
111
|
+
return (
|
|
112
|
+
<div
|
|
113
|
+
className={cn(
|
|
114
|
+
"flex flex-1 flex-col gap-1 [&+[data-slot=item-content]]:flex-none",
|
|
115
|
+
className,
|
|
116
|
+
)}
|
|
117
|
+
data-slot="item-content"
|
|
118
|
+
{...props}
|
|
119
|
+
/>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function ItemTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
124
|
+
return (
|
|
125
|
+
<div
|
|
126
|
+
className={cn(
|
|
127
|
+
"flex w-fit items-center gap-2 font-medium leading-snug",
|
|
128
|
+
className,
|
|
129
|
+
)}
|
|
130
|
+
data-slot="item-title"
|
|
131
|
+
{...props}
|
|
132
|
+
/>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function ItemDescription({ className, ...props }: React.ComponentProps<"p">) {
|
|
137
|
+
return (
|
|
138
|
+
<p
|
|
139
|
+
className={cn(
|
|
140
|
+
"line-clamp-2 text-balance font-normal text-muted-foreground text-sm leading-normal sm:text-xs",
|
|
141
|
+
"[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
|
|
142
|
+
className,
|
|
143
|
+
)}
|
|
144
|
+
data-slot="item-description"
|
|
145
|
+
{...props}
|
|
146
|
+
/>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function ItemActions({ className, ...props }: React.ComponentProps<"div">) {
|
|
151
|
+
return (
|
|
152
|
+
<div
|
|
153
|
+
className={cn("flex items-center gap-2", className)}
|
|
154
|
+
data-slot="item-actions"
|
|
155
|
+
{...props}
|
|
156
|
+
/>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function ItemHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
161
|
+
return (
|
|
162
|
+
<div
|
|
163
|
+
className={cn(
|
|
164
|
+
"flex basis-full items-center justify-between gap-2",
|
|
165
|
+
className,
|
|
166
|
+
)}
|
|
167
|
+
data-slot="item-header"
|
|
168
|
+
{...props}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function ItemFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
174
|
+
return (
|
|
175
|
+
<div
|
|
176
|
+
className={cn(
|
|
177
|
+
"flex basis-full items-center justify-between gap-2",
|
|
178
|
+
className,
|
|
179
|
+
)}
|
|
180
|
+
data-slot="item-footer"
|
|
181
|
+
{...props}
|
|
182
|
+
/>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export {
|
|
187
|
+
Item,
|
|
188
|
+
ItemMedia,
|
|
189
|
+
ItemContent,
|
|
190
|
+
ItemActions,
|
|
191
|
+
ItemGroup,
|
|
192
|
+
ItemSeparator,
|
|
193
|
+
ItemTitle,
|
|
194
|
+
ItemDescription,
|
|
195
|
+
ItemHeader,
|
|
196
|
+
ItemFooter,
|
|
197
|
+
itemVariants,
|
|
198
|
+
};
|