@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.
Files changed (138) hide show
  1. package/dist/chunk-2HQPCV2L.js +895 -0
  2. package/dist/chunk-2HQPCV2L.js.map +1 -0
  3. package/dist/form.d.ts +175 -0
  4. package/dist/form.js +356 -0
  5. package/dist/form.js.map +1 -0
  6. package/dist/index.d.ts +2162 -0
  7. package/dist/index.js +11640 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/input-JCZs61MO.d.ts +24 -0
  10. package/package.json +90 -0
  11. package/src/components/form/checkbox-group-field.tsx +101 -0
  12. package/src/components/form/date-field.tsx +79 -0
  13. package/src/components/form/date-range-field.tsx +106 -0
  14. package/src/components/form/form-context.ts +10 -0
  15. package/src/components/form/form.tsx +54 -0
  16. package/src/components/form/number-field.tsx +69 -0
  17. package/src/components/form/select-field.tsx +76 -0
  18. package/src/components/form/submit-button.tsx +28 -0
  19. package/src/components/form/text-field.tsx +107 -0
  20. package/src/components/layout/dashboard-header.tsx +54 -0
  21. package/src/components/layout/dashboard-panel.tsx +34 -0
  22. package/src/components/layout/dashboard-topbar.tsx +221 -0
  23. package/src/components/theme-provider.tsx +403 -0
  24. package/src/components/ui/accordion.tsx +69 -0
  25. package/src/components/ui/alert-dialog.tsx +169 -0
  26. package/src/components/ui/alert.tsx +80 -0
  27. package/src/components/ui/animated-counter.tsx +109 -0
  28. package/src/components/ui/animated-theme-toggler.tsx +265 -0
  29. package/src/components/ui/app-store-buttons.tsx +182 -0
  30. package/src/components/ui/aspect-ratio.tsx +23 -0
  31. package/src/components/ui/aurora-orb.tsx +163 -0
  32. package/src/components/ui/aurora-text.tsx +76 -0
  33. package/src/components/ui/autocomplete.tsx +296 -0
  34. package/src/components/ui/avatar-group.tsx +150 -0
  35. package/src/components/ui/avatar.tsx +285 -0
  36. package/src/components/ui/badge-group.tsx +160 -0
  37. package/src/components/ui/badge.tsx +172 -0
  38. package/src/components/ui/breadcrumb.tsx +112 -0
  39. package/src/components/ui/button.tsx +77 -0
  40. package/src/components/ui/calendar.tsx +137 -0
  41. package/src/components/ui/card.tsx +244 -0
  42. package/src/components/ui/carousel.tsx +258 -0
  43. package/src/components/ui/chart.tsx +379 -0
  44. package/src/components/ui/chat-divider.tsx +67 -0
  45. package/src/components/ui/chat.tsx +494 -0
  46. package/src/components/ui/checkbox-group.tsx +16 -0
  47. package/src/components/ui/checkbox.tsx +82 -0
  48. package/src/components/ui/collapsible.tsx +45 -0
  49. package/src/components/ui/color-picker.tsx +389 -0
  50. package/src/components/ui/combobox.tsx +411 -0
  51. package/src/components/ui/command.tsx +264 -0
  52. package/src/components/ui/confetti.tsx +196 -0
  53. package/src/components/ui/context-menu.tsx +271 -0
  54. package/src/components/ui/context-meter.tsx +134 -0
  55. package/src/components/ui/credit-card.tsx +214 -0
  56. package/src/components/ui/data-table-paged.tsx +163 -0
  57. package/src/components/ui/date-picker.tsx +154 -0
  58. package/src/components/ui/dialog.tsx +196 -0
  59. package/src/components/ui/drawer.tsx +135 -0
  60. package/src/components/ui/empty.tsx +127 -0
  61. package/src/components/ui/fab.tsx +45 -0
  62. package/src/components/ui/featured-icon.tsx +149 -0
  63. package/src/components/ui/field.tsx +88 -0
  64. package/src/components/ui/fieldset.tsx +29 -0
  65. package/src/components/ui/form.tsx +17 -0
  66. package/src/components/ui/frame.tsx +82 -0
  67. package/src/components/ui/generic-empty.tsx +142 -0
  68. package/src/components/ui/group.tsx +97 -0
  69. package/src/components/ui/help-tip.tsx +94 -0
  70. package/src/components/ui/horizontal-scroll-fader.tsx +228 -0
  71. package/src/components/ui/input-group.tsx +102 -0
  72. package/src/components/ui/input-otp.tsx +96 -0
  73. package/src/components/ui/input.tsx +66 -0
  74. package/src/components/ui/item.tsx +198 -0
  75. package/src/components/ui/kbd.tsx +30 -0
  76. package/src/components/ui/label.tsx +28 -0
  77. package/src/components/ui/markdown.tsx +188 -0
  78. package/src/components/ui/menu.tsx +312 -0
  79. package/src/components/ui/menubar.tsx +93 -0
  80. package/src/components/ui/meter.tsx +67 -0
  81. package/src/components/ui/multi-select.tsx +308 -0
  82. package/src/components/ui/navigation-menu.tsx +143 -0
  83. package/src/components/ui/number-field.tsx +160 -0
  84. package/src/components/ui/pagination-controls.tsx +74 -0
  85. package/src/components/ui/pagination.tsx +149 -0
  86. package/src/components/ui/pipeline.tsx +102 -0
  87. package/src/components/ui/popover.tsx +119 -0
  88. package/src/components/ui/preview-card.tsx +55 -0
  89. package/src/components/ui/progress.tsx +289 -0
  90. package/src/components/ui/qr-code.tsx +150 -0
  91. package/src/components/ui/radio-group.tsx +103 -0
  92. package/src/components/ui/resizable.tsx +56 -0
  93. package/src/components/ui/scroll-area.tsx +90 -0
  94. package/src/components/ui/scroller.tsx +38 -0
  95. package/src/components/ui/section-header.tsx +118 -0
  96. package/src/components/ui/segmented-control.tsx +83 -0
  97. package/src/components/ui/select.tsx +181 -0
  98. package/src/components/ui/separator.tsx +23 -0
  99. package/src/components/ui/sheet.tsx +224 -0
  100. package/src/components/ui/sidebar.tsx +774 -0
  101. package/src/components/ui/skeleton.tsx +16 -0
  102. package/src/components/ui/slider.tsx +108 -0
  103. package/src/components/ui/smooth-scroll.tsx +206 -0
  104. package/src/components/ui/social-button.tsx +247 -0
  105. package/src/components/ui/spinner-on-demand.tsx +32 -0
  106. package/src/components/ui/spinner.tsx +18 -0
  107. package/src/components/ui/stat.tsx +187 -0
  108. package/src/components/ui/stepper.tsx +167 -0
  109. package/src/components/ui/switch.tsx +56 -0
  110. package/src/components/ui/table.tsx +126 -0
  111. package/src/components/ui/tabs.tsx +90 -0
  112. package/src/components/ui/tag.tsx +242 -0
  113. package/src/components/ui/target-countdown.tsx +46 -0
  114. package/src/components/ui/text-editor.tsx +313 -0
  115. package/src/components/ui/textarea.tsx +51 -0
  116. package/src/components/ui/thinking-orb.tsx +6 -0
  117. package/src/components/ui/time-picker.tsx +308 -0
  118. package/src/components/ui/timeline.tsx +116 -0
  119. package/src/components/ui/toast.tsx +268 -0
  120. package/src/components/ui/toggle-group.tsx +101 -0
  121. package/src/components/ui/toggle.tsx +45 -0
  122. package/src/components/ui/toolbar.tsx +89 -0
  123. package/src/components/ui/tooltip.tsx +102 -0
  124. package/src/components/ui/vertical-scroll-fader.tsx +250 -0
  125. package/src/components/ui/video-player.tsx +275 -0
  126. package/src/components/upload/avatar-upload-base.tsx +131 -0
  127. package/src/components/upload/image-upload-base.tsx +112 -0
  128. package/src/form.ts +17 -0
  129. package/src/index.ts +143 -0
  130. package/src/lib/hooks/use-callback-ref.ts +15 -0
  131. package/src/lib/hooks/use-first-render.ts +11 -0
  132. package/src/lib/hooks/use-hover.ts +53 -0
  133. package/src/lib/hooks/use-is-tab-active.ts +17 -0
  134. package/src/lib/hooks/use-media-query.ts +164 -0
  135. package/src/lib/utils/css.ts +6 -0
  136. package/src/styles.css +329 -0
  137. package/src/types/helpers.ts +24 -0
  138. 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
+ };