@srcroot/ui 0.0.1 → 0.0.3

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,119 @@
1
+ import * as React from "react"
2
+ import { cn } from "@/lib/utils"
3
+
4
+ interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
5
+ /** Orientation of scrollbar */
6
+ orientation?: "vertical" | "horizontal"
7
+ /** Scrollbar size: "thin" (4px), "default" (8px), "thick" (12px) */
8
+ scrollbarSize?: "thin" | "default" | "thick"
9
+ /** Hide scrollbar until hover */
10
+ hideScrollbar?: boolean
11
+ }
12
+
13
+ // CSS for custom scrollbar styling
14
+ const scrollbarStyles = {
15
+ thin: {
16
+ width: "4px",
17
+ height: "4px",
18
+ },
19
+ default: {
20
+ width: "8px",
21
+ height: "8px",
22
+ },
23
+ thick: {
24
+ width: "12px",
25
+ height: "12px",
26
+ },
27
+ }
28
+
29
+ /**
30
+ * ScrollArea - Custom scrollbar container
31
+ *
32
+ * Provides a styled scrollbar that is thin and consistent across browsers.
33
+ * Supports customizable scrollbar size and orientation.
34
+ */
35
+ const ScrollArea = React.forwardRef<HTMLDivElement, ScrollAreaProps>(
36
+ ({ className, children, orientation = "vertical", scrollbarSize = "thin", hideScrollbar = false, style, ...props }, ref) => {
37
+ const sizes = scrollbarStyles[scrollbarSize]
38
+
39
+ const scrollbarCSS: React.CSSProperties = {
40
+ ...style,
41
+ // Webkit browsers (Chrome, Safari, Edge)
42
+ // @ts-ignore - CSS custom properties for scrollbar
43
+ "--scrollbar-width": sizes.width,
44
+ "--scrollbar-height": sizes.height,
45
+ }
46
+
47
+ return (
48
+ <div
49
+ ref={ref}
50
+ className={cn(
51
+ "relative",
52
+ // Container overflow based on orientation
53
+ orientation === "vertical" && "overflow-y-auto overflow-x-hidden",
54
+ orientation === "horizontal" && "overflow-x-auto overflow-y-hidden",
55
+ // Custom scrollbar classes
56
+ "scrollbar-custom",
57
+ hideScrollbar && "scrollbar-hide hover:scrollbar-show",
58
+ className
59
+ )}
60
+ style={scrollbarCSS}
61
+ {...props}
62
+ >
63
+ <style>{`
64
+ .scrollbar-custom::-webkit-scrollbar {
65
+ width: var(--scrollbar-width, 4px);
66
+ height: var(--scrollbar-height, 4px);
67
+ }
68
+ .scrollbar-custom::-webkit-scrollbar-track {
69
+ background: transparent;
70
+ border-radius: 9999px;
71
+ }
72
+ .scrollbar-custom::-webkit-scrollbar-thumb {
73
+ background: hsl(var(--muted-foreground) / 0.3);
74
+ border-radius: 9999px;
75
+ }
76
+ .scrollbar-custom::-webkit-scrollbar-thumb:hover {
77
+ background: hsl(var(--muted-foreground) / 0.5);
78
+ }
79
+ .scrollbar-custom {
80
+ scrollbar-width: thin;
81
+ scrollbar-color: hsl(var(--muted-foreground) / 0.3) transparent;
82
+ }
83
+ .scrollbar-hide::-webkit-scrollbar {
84
+ opacity: 0;
85
+ }
86
+ .scrollbar-hide:hover::-webkit-scrollbar,
87
+ .scrollbar-show::-webkit-scrollbar {
88
+ opacity: 1;
89
+ }
90
+ `}</style>
91
+ {children}
92
+ </div>
93
+ )
94
+ }
95
+ )
96
+ ScrollArea.displayName = "ScrollArea"
97
+
98
+ // ScrollBar component for explicit scrollbar styling reference (optional usage)
99
+ interface ScrollBarProps extends React.HTMLAttributes<HTMLDivElement> {
100
+ orientation?: "vertical" | "horizontal"
101
+ }
102
+
103
+ const ScrollBar = React.forwardRef<HTMLDivElement, ScrollBarProps>(
104
+ ({ className, orientation = "vertical", ...props }, ref) => (
105
+ <div
106
+ ref={ref}
107
+ className={cn(
108
+ "flex touch-none select-none transition-colors",
109
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent p-[1px]",
110
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent p-[1px]",
111
+ className
112
+ )}
113
+ {...props}
114
+ />
115
+ )
116
+ )
117
+ ScrollBar.displayName = "ScrollBar"
118
+
119
+ export { ScrollArea, ScrollBar }
@@ -97,7 +97,8 @@ const Search = React.forwardRef<HTMLInputElement, SearchProps>(
97
97
  "flex h-10 w-full rounded-md border border-input bg-transparent pl-10 pr-10 py-2 text-sm shadow-sm transition-colors",
98
98
  "placeholder:text-muted-foreground",
99
99
  "focus:outline-none focus:ring-1 focus:ring-ring",
100
- "disabled:cursor-not-allowed disabled:opacity-50"
100
+ "disabled:cursor-not-allowed disabled:opacity-50",
101
+ "[&::-webkit-search-cancel-button]:appearance-none"
101
102
  )}
102
103
  {...props}
103
104
  />
@@ -1,3 +1,4 @@
1
+ 'use client'
1
2
  import * as React from "react"
2
3
  import { cva, type VariantProps } from "class-variance-authority"
3
4
  import { cn } from "@/lib/utils"
@@ -17,20 +18,7 @@ interface SheetProps {
17
18
  }
18
19
 
19
20
  /**
20
- * Sheet (slide-in panel) component
21
- *
22
- * @example
23
- * <Sheet>
24
- * <SheetTrigger asChild>
25
- * <Button>Open Sheet</Button>
26
- * </SheetTrigger>
27
- * <SheetContent side="right">
28
- * <SheetHeader>
29
- * <SheetTitle>Title</SheetTitle>
30
- * <SheetDescription>Description</SheetDescription>
31
- * </SheetHeader>
32
- * </SheetContent>
33
- * </Sheet>
21
+ * Sheet (slide-in panel) component with smooth animations
34
22
  */
35
23
  function Sheet({ children, open: controlledOpen, onOpenChange, defaultOpen = false }: SheetProps) {
36
24
  const [uncontrolledOpen, setUncontrolledOpen] = React.useState(defaultOpen)
@@ -76,7 +64,7 @@ const SheetTrigger = React.forwardRef<HTMLButtonElement, SheetTriggerProps>(
76
64
  SheetTrigger.displayName = "SheetTrigger"
77
65
 
78
66
  const sheetVariants = cva(
79
- "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out",
67
+ "fixed z-50 gap-4 bg-background p-6 shadow-lg",
80
68
  {
81
69
  variants: {
82
70
  side: {
@@ -92,6 +80,26 @@ const sheetVariants = cva(
92
80
  }
93
81
  )
94
82
 
83
+ // Animation classes for each side
84
+ const animationClasses = {
85
+ top: {
86
+ open: "translate-y-0",
87
+ closed: "-translate-y-full",
88
+ },
89
+ bottom: {
90
+ open: "translate-y-0",
91
+ closed: "translate-y-full",
92
+ },
93
+ left: {
94
+ open: "translate-x-0",
95
+ closed: "-translate-x-full",
96
+ },
97
+ right: {
98
+ open: "translate-x-0",
99
+ closed: "translate-x-full",
100
+ },
101
+ }
102
+
95
103
  interface SheetContentProps
96
104
  extends React.HTMLAttributes<HTMLDivElement>,
97
105
  VariantProps<typeof sheetVariants> { }
@@ -101,6 +109,29 @@ const SheetContent = React.forwardRef<HTMLDivElement, SheetContentProps>(
101
109
  const context = React.useContext(SheetContext)
102
110
  if (!context) throw new Error("SheetContent must be used within Sheet")
103
111
 
112
+ const [isVisible, setIsVisible] = React.useState(false)
113
+ const [isAnimating, setIsAnimating] = React.useState(false)
114
+
115
+ React.useEffect(() => {
116
+ if (context.open) {
117
+ // First make visible (off-screen)
118
+ setIsVisible(true)
119
+ // Use a small timeout to ensure the browser has painted the initial state
120
+ const timer = setTimeout(() => {
121
+ setIsAnimating(true)
122
+ }, 10)
123
+ return () => clearTimeout(timer)
124
+ } else {
125
+ // Start close animation
126
+ setIsAnimating(false)
127
+ // Wait for animation to complete before hiding
128
+ const timer = setTimeout(() => {
129
+ setIsVisible(false)
130
+ }, 300)
131
+ return () => clearTimeout(timer)
132
+ }
133
+ }, [context.open])
134
+
104
135
  React.useEffect(() => {
105
136
  const handleEscape = (e: KeyboardEvent) => {
106
137
  if (e.key === "Escape") {
@@ -119,19 +150,33 @@ const SheetContent = React.forwardRef<HTMLDivElement, SheetContentProps>(
119
150
  }
120
151
  }, [context.open, context])
121
152
 
122
- if (!context.open) return null
153
+ if (!isVisible) return null
154
+
155
+ const sideKey = side || "right"
123
156
 
124
157
  return (
125
158
  <>
159
+ {/* Overlay with fade animation */}
126
160
  <div
127
- className="fixed inset-0 z-50 bg-black/80"
161
+ className={cn(
162
+ "fixed inset-0 z-50 bg-black/80 transition-opacity duration-300",
163
+ isAnimating ? "opacity-100" : "opacity-0"
164
+ )}
128
165
  onClick={() => context.onOpenChange(false)}
129
166
  />
167
+ {/* Sheet content with slide animation */}
130
168
  <div
131
169
  ref={ref}
132
170
  role="dialog"
133
171
  aria-modal="true"
134
- className={cn(sheetVariants({ side }), className)}
172
+ className={cn(
173
+ sheetVariants({ side }),
174
+ "transition-transform duration-300 ease-out",
175
+ isAnimating
176
+ ? animationClasses[sideKey].open
177
+ : animationClasses[sideKey].closed,
178
+ className
179
+ )}
135
180
  {...props}
136
181
  >
137
182
  {children}