@the21og/yc 0.0.1 → 0.0.2

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.
@@ -0,0 +1,327 @@
1
+ import React, { useState, useEffect, useCallback, createContext, useContext, forwardRef } from "react";
2
+ import { ChevronLeft, ChevronRight } from "lucide-react";
3
+
4
+ // Utility for class merging (simulated for standalone usage, usually imported from lib/utils)
5
+ function cn(...classes: (string | undefined | null | false)[]) {
6
+ return classes.filter(Boolean).join(" ");
7
+ }
8
+
9
+ type CarouselApi = {
10
+ scrollPrev: () => void;
11
+ scrollNext: () => void;
12
+ canScrollPrev: boolean;
13
+ canScrollNext: boolean;
14
+ orientation: "horizontal" | "vertical";
15
+ };
16
+
17
+ const CarouselContext = createContext<CarouselApi | null>(null);
18
+
19
+ function useCarousel() {
20
+ const context = useContext(CarouselContext);
21
+ if (!context) {
22
+ throw new Error("useCarousel must be used within a <Carousel />");
23
+ }
24
+ return context;
25
+ }
26
+
27
+ interface CarouselProps extends React.HTMLAttributes<HTMLDivElement> {
28
+ orientation?: "horizontal" | "vertical";
29
+ opts?: { align?: "start" | "center" | "end"; loop?: boolean };
30
+ }
31
+
32
+ const Carousel = forwardRef<HTMLDivElement, CarouselProps>(
33
+ ({ orientation = "horizontal", opts, className, children, ...props }, ref) => {
34
+ const [carouselRef, setCarouselRef] = useState<HTMLDivElement | null>(null);
35
+ const [canScrollPrev, setCanScrollPrev] = useState(false);
36
+ const [canScrollNext, setCanScrollNext] = useState(false);
37
+
38
+ const checkScrollability = useCallback(() => {
39
+ if (!carouselRef) return;
40
+ const { scrollLeft, scrollWidth, clientWidth } = carouselRef;
41
+ setCanScrollPrev(scrollLeft > 0);
42
+ // Allow a small buffer (1px) for floating point rendering differences
43
+ setCanScrollNext(scrollLeft < scrollWidth - clientWidth - 1);
44
+ }, [carouselRef]);
45
+
46
+ useEffect(() => {
47
+ if (!carouselRef) return;
48
+ checkScrollability();
49
+ const handleScroll = () => checkScrollability();
50
+ const handleResize = () => checkScrollability();
51
+
52
+ carouselRef.addEventListener("scroll", handleScroll);
53
+ window.addEventListener("resize", handleResize);
54
+
55
+ return () => {
56
+ carouselRef.removeEventListener("scroll", handleScroll);
57
+ window.removeEventListener("resize", handleResize);
58
+ };
59
+ }, [carouselRef, checkScrollability]);
60
+
61
+ const scrollPrev = useCallback(() => {
62
+ if (!carouselRef) return;
63
+ carouselRef.scrollBy({ left: -carouselRef.clientWidth, behavior: "smooth" });
64
+ }, [carouselRef]);
65
+
66
+ const scrollNext = useCallback(() => {
67
+ if (!carouselRef) return;
68
+ carouselRef.scrollBy({ left: carouselRef.clientWidth, behavior: "smooth" });
69
+ }, [carouselRef]);
70
+
71
+ const handleKeyDown = useCallback(
72
+ (event: React.KeyboardEvent<HTMLDivElement>) => {
73
+ if (event.key === "ArrowLeft") {
74
+ event.preventDefault();
75
+ scrollPrev();
76
+ } else if (event.key === "ArrowRight") {
77
+ event.preventDefault();
78
+ scrollNext();
79
+ }
80
+ },
81
+ [scrollPrev, scrollNext]
82
+ );
83
+
84
+ return (
85
+ <CarouselContext.Provider
86
+ value={{ scrollPrev, scrollNext, canScrollPrev, canScrollNext, orientation }}
87
+ >
88
+ <div
89
+ ref={ref}
90
+ onKeyDown={handleKeyDown}
91
+ className={cn("relative group", className)}
92
+ role="region"
93
+ aria-roledescription="carousel"
94
+ {...props}
95
+ >
96
+ {/* We pass setCarouselRef to the content via context or direct prop drilling if structure allows.
97
+ Here we clone the Content child to pass the ref, or simpler: expects specific structure.
98
+ To keep it clean in React, we'll actually put the ref on the Content component via a callback prop or context.
99
+ However, for composition flexibility, let's use a data attribute or simple context setter.
100
+ */}
101
+ <div className="hidden" data-carousel-store-ref={setCarouselRef} />
102
+ {children}
103
+ </div>
104
+ </CarouselContext.Provider>
105
+ );
106
+ }
107
+ );
108
+ Carousel.displayName = "Carousel";
109
+
110
+ const CarouselContent = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
111
+ ({ className, ...props }, ref) => {
112
+ // We need to capture the ref for the scrolling container
113
+ const { orientation } = useCarousel();
114
+ const [localRef, setLocalRef] = useState<HTMLDivElement | null>(null);
115
+
116
+ // HACK: To pass this ref up to the parent Context without complex Context Setters,
117
+ // we look for the parent <Carousel> mechanism or just assume this component IS the scroll container.
118
+ // In this implementation, CarouselContent IS the scroll view.
119
+
120
+ // We need to connect this local ref to the parent's state.
121
+ // Since we are inside the context, we can't easily set the parent state during render.
122
+ // We use a useEffect to traverse up or a callback context if we had one.
123
+ // Simplified approach: The user must pass a ref, but to make it auto-wire:
124
+
125
+ // Let's rely on standard composed refs pattern if possible,
126
+ // but for this snippet, let's use a "RefSetter" pattern via a mounted effect.
127
+ // Actually, simpler: The Carousel component renders a Context.
128
+ // We can't set state in Parent from Child render easily.
129
+ // Instead, let's make CarouselContent THE place where logic lives?
130
+ // No, buttons are siblings.
131
+
132
+ // Solution: Effect to register self.
133
+ const context = useContext(CarouselContext);
134
+
135
+ // We access the setter we hid in the parent JSX (dirty but effective for zero-dep)
136
+ // OR we just assume the user uses the library correctly.
137
+ // Let's do the robust Context Setter pattern properly:
138
+ // We can't change the Context interface now easily without re-declaring types above.
139
+ // Let's fix the Context to include `setApi` logic if we were using Embla.
140
+
141
+ // Native Scroll Snap implementation fix:
142
+ // We need the `Carousel` wrapper to know about `CarouselContent` ref.
143
+ // We will use a callback ref approach that finds the parent context if we updated it.
144
+ // ALTERNATIVE: Just look for the context via a custom hook that allows registration.
145
+
146
+ // TO KEEP IT SIMPLE AND WORKING:
147
+ // We will find the parent provider's setter via a slightly different Context pattern or
148
+ // just render the scroll container in the Wrapper? No, that breaks composition.
149
+
150
+ // Let's use a mutable ref in context.
151
+ const contentRef = React.useRef<HTMLDivElement>(null);
152
+ const { setCarouselRef } = useInternalRefSetter(); // See below helper
153
+
154
+ useEffect(() => {
155
+ if (contentRef.current && setCarouselRef) {
156
+ setCarouselRef(contentRef.current);
157
+ }
158
+ }, [setCarouselRef]);
159
+
160
+ return (
161
+ <div
162
+ ref={(node) => {
163
+ contentRef.current = node;
164
+ if (typeof ref === 'function') ref(node);
165
+ else if (ref) (ref as React.MutableRefObject<HTMLDivElement | null>).current = node;
166
+ }}
167
+ className={cn(
168
+ "flex",
169
+ orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
170
+ "overflow-x-auto snap-x snap-mandatory scroll-smooth no-scrollbar", // Core Scroll Logic
171
+ className
172
+ )}
173
+ style={{ scrollbarWidth: 'none', msOverflowStyle: 'none' }}
174
+ {...props}
175
+ />
176
+ );
177
+ }
178
+ );
179
+ CarouselContent.displayName = "CarouselContent";
180
+
181
+ const CarouselItem = forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
182
+ ({ className, ...props }, ref) => {
183
+ const { orientation } = useCarousel();
184
+
185
+ return (
186
+ <div
187
+ ref={ref}
188
+ role="group"
189
+ aria-roledescription="slide"
190
+ className={cn(
191
+ "min-w-0 shrink-0 grow-0 basis-full snap-start",
192
+ orientation === "horizontal" ? "pl-4" : "pt-4",
193
+ className
194
+ )}
195
+ {...props}
196
+ />
197
+ );
198
+ }
199
+ );
200
+ CarouselItem.displayName = "CarouselItem";
201
+
202
+ const CarouselPrevious = forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
203
+ ({ className, ...props }, ref) => {
204
+ const { orientation, scrollPrev, canScrollPrev } = useCarousel();
205
+
206
+ return (
207
+ <button
208
+ ref={ref}
209
+ type="button"
210
+ disabled={!canScrollPrev}
211
+ onClick={scrollPrev}
212
+ className={cn(
213
+ "absolute h-8 w-8 rounded-full flex items-center justify-center bg-white border border-slate-200 shadow-sm hover:bg-slate-100 disabled:opacity-50 disabled:cursor-not-allowed z-10",
214
+ orientation === "horizontal"
215
+ ? "-left-12 top-1/2 -translate-y-1/2"
216
+ : "-top-12 left-1/2 -translate-x-1/2 rotate-90",
217
+ className
218
+ )}
219
+ {...props}
220
+ >
221
+ <ChevronLeft className="h-4 w-4 text-slate-900" />
222
+ <span className="sr-only">Previous slide</span>
223
+ </button>
224
+ );
225
+ }
226
+ );
227
+ CarouselPrevious.displayName = "CarouselPrevious";
228
+
229
+ const CarouselNext = forwardRef<HTMLButtonElement, React.ButtonHTMLAttributes<HTMLButtonElement>>(
230
+ ({ className, ...props }, ref) => {
231
+ const { orientation, scrollNext, canScrollNext } = useCarousel();
232
+
233
+ return (
234
+ <button
235
+ ref={ref}
236
+ type="button"
237
+ disabled={!canScrollNext}
238
+ onClick={scrollNext}
239
+ className={cn(
240
+ "absolute h-8 w-8 rounded-full flex items-center justify-center bg-white border border-slate-200 shadow-sm hover:bg-slate-100 disabled:opacity-50 disabled:cursor-not-allowed z-10",
241
+ orientation === "horizontal"
242
+ ? "-right-12 top-1/2 -translate-y-1/2"
243
+ : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
244
+ className
245
+ )}
246
+ {...props}
247
+ >
248
+ <ChevronRight className="h-4 w-4 text-slate-900" />
249
+ <span className="sr-only">Next slide</span>
250
+ </button>
251
+ );
252
+ }
253
+ );
254
+ CarouselNext.displayName = "CarouselNext";
255
+
256
+ /*
257
+ Internal Helper to connect Content ref to Main Wrapper
258
+ This avoids cluttering the public API with complex callbacks.
259
+ */
260
+ const RefContext = createContext<{ setCarouselRef: (el: HTMLDivElement) => void } | null>(null);
261
+
262
+ // Overwriting the main export to include the RefProvider wrapper internally
263
+ const CarouselMain = forwardRef<HTMLDivElement, CarouselProps>((props, ref) => {
264
+ const [carouselRef, setCarouselRef] = useState<HTMLDivElement | null>(null);
265
+
266
+ // Re-implementing logic here for clean closure access
267
+ const [canScrollPrev, setCanScrollPrev] = useState(false);
268
+ const [canScrollNext, setCanScrollNext] = useState(false);
269
+
270
+ const checkScrollability = useCallback(() => {
271
+ if (!carouselRef) return;
272
+ const { scrollLeft, scrollWidth, clientWidth } = carouselRef;
273
+ // Tolerance for high-DPI screens
274
+ setCanScrollPrev(scrollLeft > 1);
275
+ setCanScrollNext(scrollLeft < scrollWidth - clientWidth - 1);
276
+ }, [carouselRef]);
277
+
278
+ useEffect(() => {
279
+ if (!carouselRef) return;
280
+ checkScrollability();
281
+ const el = carouselRef;
282
+ const handle = () => checkScrollability();
283
+ el.addEventListener("scroll", handle, { passive: true });
284
+ window.addEventListener("resize", handle);
285
+ return () => {
286
+ el.removeEventListener("scroll", handle);
287
+ window.removeEventListener("resize", handle);
288
+ };
289
+ }, [carouselRef, checkScrollability]);
290
+
291
+ const scrollPrev = useCallback(() => {
292
+ carouselRef?.scrollBy({ left: -carouselRef.clientWidth, behavior: "smooth" });
293
+ }, [carouselRef]);
294
+
295
+ const scrollNext = useCallback(() => {
296
+ carouselRef?.scrollBy({ left: carouselRef.clientWidth, behavior: "smooth" });
297
+ }, [carouselRef]);
298
+
299
+ return (
300
+ <CarouselContext.Provider value={{ scrollPrev, scrollNext, canScrollPrev, canScrollNext, orientation: props.orientation || "horizontal" }}>
301
+ <RefContext.Provider value={{ setCarouselRef }}>
302
+ <div
303
+ ref={ref}
304
+ className={cn("relative", props.className)}
305
+ role="region"
306
+ aria-roledescription="carousel"
307
+ {...props}
308
+ />
309
+ </RefContext.Provider>
310
+ </CarouselContext.Provider>
311
+ );
312
+ });
313
+ CarouselMain.displayName = "Carousel";
314
+
315
+ function useInternalRefSetter() {
316
+ const ctx = useContext(RefContext);
317
+ if (!ctx) return { setCarouselRef: () => {} };
318
+ return ctx;
319
+ }
320
+
321
+ export {
322
+ CarouselMain as Carousel,
323
+ CarouselContent,
324
+ CarouselItem,
325
+ CarouselPrevious,
326
+ CarouselNext,
327
+ };
package/index.ts CHANGED
@@ -2,4 +2,5 @@ import "./index.css";
2
2
  export * from "./components/ui/card";
3
3
  export * from "./components/ui/input";
4
4
  export * from "./components/ui/button";
5
+ export * from "./components/ui/carousel";
5
6
  export * from "./lib/utils";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@the21og/yc",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "Y Combinator (YC) is a startup accelerator and venture capital firm",
5
5
  "main": "index.js",
6
6
  "style": "index.css",