@srcroot/ui 0.0.45 → 0.0.48

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/package.json CHANGED
@@ -1,7 +1,9 @@
1
1
  {
2
2
  "name": "@srcroot/ui",
3
- "version": "0.0.45",
3
+ "version": "0.0.48",
4
4
  "description": "A UI library with polymorphic, accessible React components",
5
+ "author": "Shifaul Islam",
6
+ "license": "MIT",
5
7
  "type": "module",
6
8
  "bin": {
7
9
  "srcroot-ui": "./dist/index.js"
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @srcroot/ui - Slate Theme (Tailwind 3)
3
- * Cool gray with strong blue undertones (shadcn default)
3
+ * Cool gray with strong blue undertones (default)
4
4
  */
5
5
 
6
6
  @tailwind base;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @srcroot/ui - Slate Theme (Tailwind 4)
3
- * Cool gray with strong blue undertones (shadcn default)
3
+ * Cool gray with strong blue undertones (default)
4
4
  */
5
5
 
6
6
  @import "tailwindcss";
@@ -72,7 +72,8 @@ function getDaysInMonth(year: number, month: number): Date[] {
72
72
  return days
73
73
  }
74
74
 
75
- function isSameDay(d1: Date, d2: Date): boolean {
75
+ function isSameDay(d1: Date | undefined | null, d2: Date | undefined | null): boolean {
76
+ if (!d1 || !d2) return false
76
77
  return d1.getDate() === d2.getDate() &&
77
78
  d1.getMonth() === d2.getMonth() &&
78
79
  d1.getFullYear() === d2.getFullYear()
@@ -509,4 +510,3 @@ const Calendar = React.forwardRef<HTMLDivElement, CalendarProps>(
509
510
  Calendar.displayName = "Calendar"
510
511
 
511
512
  export { Calendar }
512
-
@@ -1,6 +1,7 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from "react"
4
+ import { addDays, startOfMonth, endOfMonth } from "date-fns"
4
5
  import { Calendar } from "./calendar"
5
6
  import { Popover, PopoverContent, PopoverTrigger } from "./popover"
6
7
  import { Button } from "./button"
@@ -125,6 +126,31 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
125
126
  : mode === "multiple" ? "Pick dates"
126
127
  : "Pick a date range"
127
128
 
129
+ // Presets for Range Mode
130
+ const presets = [
131
+ {
132
+ label: "Last 7 Days",
133
+ getValue: () => {
134
+ const today = new Date()
135
+ return [addDays(today, -7), today]
136
+ },
137
+ },
138
+ {
139
+ label: "Last 30 Days",
140
+ getValue: () => {
141
+ const today = new Date()
142
+ return [addDays(today, -30), today]
143
+ },
144
+ },
145
+ {
146
+ label: "This Month",
147
+ getValue: () => {
148
+ const today = new Date()
149
+ return [startOfMonth(today), endOfMonth(today)]
150
+ },
151
+ },
152
+ ]
153
+
128
154
  // Handle selection
129
155
  const handleSelect = (value: any) => {
130
156
  if (mode === "single") {
@@ -135,10 +161,18 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
135
161
  } else {
136
162
  const dates = value || []
137
163
  ; (onSelect as ((dates: Date[]) => void))?.(dates)
138
- // Close when range is complete (2 dates)
139
- if (dates.length === 2) {
140
- setOpen(false)
141
- }
164
+ // Do not close automatically for range to allow adjustment
165
+ }
166
+ }
167
+
168
+ const handlePresetSelect = (preset: { getValue: () => Date[] }) => {
169
+ if (mode === "range") {
170
+ const dates = preset.getValue()
171
+ ; (onSelect as ((dates: Date[]) => void))?.(dates)
172
+ // Update internal state if uncontrolled (not covered here fully but ensures trigger updates if parent consumes correctly)
173
+ // For this component to be fully controlled, parent must pass `selected`.
174
+ // If we want it to close on preset select:
175
+ setOpen(false)
142
176
  }
143
177
  }
144
178
 
@@ -150,9 +184,8 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
150
184
  variant="outline"
151
185
  disabled={disabled}
152
186
  className={cn(
153
- "w-[280px] justify-start text-left font-normal",
187
+ "w-[260px] justify-start text-left font-normal",
154
188
  !displayText && "text-muted-foreground",
155
- numberOfMonths === 2 && "w-[320px]",
156
189
  className
157
190
  )}
158
191
  >
@@ -160,15 +193,31 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
160
193
  {displayText || <span>{placeholder || defaultPlaceholder}</span>}
161
194
  </Button>
162
195
  </PopoverTrigger>
163
- <PopoverContent className="w-auto p-0">
164
- <Calendar
165
- mode={mode}
166
- numberOfMonths={numberOfMonths}
167
- size={size}
168
- selected={selected}
169
- onSelect={handleSelect}
170
- className="rounded-md border-0 shadow-none"
171
- />
196
+ <PopoverContent className="w-auto p-0" align="end">
197
+ <div className="flex">
198
+ {mode === "range" && (
199
+ <div className="border-r p-2 space-y-1 w-[140px]">
200
+ {presets.map((preset) => (
201
+ <Button
202
+ key={preset.label}
203
+ variant="ghost"
204
+ className="w-full justify-start font-normal"
205
+ onClick={() => handlePresetSelect(preset)}
206
+ >
207
+ {preset.label}
208
+ </Button>
209
+ ))}
210
+ </div>
211
+ )}
212
+ <Calendar
213
+ mode={mode}
214
+ numberOfMonths={numberOfMonths}
215
+ size={size}
216
+ selected={selected}
217
+ onSelect={handleSelect}
218
+ className="rounded-md border-0 shadow-none"
219
+ />
220
+ </div>
172
221
  </PopoverContent>
173
222
  </Popover>
174
223
  )
@@ -277,7 +277,7 @@ const DropdownMenuItem = React.forwardRef<
277
277
  tabIndex={disabled ? -1 : 0}
278
278
  aria-disabled={disabled}
279
279
  className={cn(
280
- "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground",
280
+ "relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground hover:bg-accent hover:text-accent-foreground cursor-pointer",
281
281
  inset && "pl-8",
282
282
  disabled && "pointer-events-none opacity-50",
283
283
  className
@@ -83,8 +83,8 @@ PopoverTrigger.displayName = "PopoverTrigger"
83
83
 
84
84
  const PopoverContent = React.forwardRef<
85
85
  HTMLDivElement,
86
- React.HTMLAttributes<HTMLDivElement>
87
- >(({ className, children, ...props }, ref) => {
86
+ React.HTMLAttributes<HTMLDivElement> & { align?: "start" | "center" | "end" }
87
+ >(({ className, children, align = "center", ...props }, ref) => {
88
88
  const context = React.useContext(PopoverContext)
89
89
  if (!context) throw new Error("PopoverContent must be used within Popover")
90
90
 
@@ -121,6 +121,7 @@ const PopoverContent = React.forwardRef<
121
121
  ref={ref}
122
122
  className={cn(
123
123
  "absolute z-50 mt-2 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none",
124
+ align === "end" ? "right-0" : align === "start" ? "left-0" : "left-1/2 -translate-x-1/2",
124
125
  className
125
126
  )}
126
127
  onClick={(e) => e.stopPropagation()}
@@ -181,7 +181,7 @@ const SheetContent = React.forwardRef<HTMLDivElement, SheetContentProps>(
181
181
  >
182
182
  {children}
183
183
  <button
184
- className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2"
184
+ className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 cursor-pointer"
185
185
  onClick={() => context.onOpenChange(false)}
186
186
  >
187
187
  <svg className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
@@ -63,7 +63,7 @@ const SidebarProvider = React.forwardRef<
63
63
 
64
64
  React.useEffect(() => {
65
65
  const checkMobile = () => {
66
- setIsMobile(window.innerWidth < 1024) // lg breakpoint
66
+ setIsMobile(window.innerWidth < 768) // md breakpoint
67
67
  }
68
68
  checkMobile()
69
69
  window.addEventListener("resize", checkMobile)
@@ -169,7 +169,7 @@ const Sidebar = React.forwardRef<
169
169
  <div
170
170
  ref={ref}
171
171
  className={cn(
172
- "group peer hidden md:block text-sidebar-foreground",
172
+ "group peer md:block text-sidebar-foreground",
173
173
  className
174
174
  )}
175
175
  data-state={state}
@@ -192,7 +192,7 @@ const Sidebar = React.forwardRef<
192
192
  {/* Actual Fixed Sidebar */}
193
193
  <div
194
194
  className={cn(
195
- "duration-200 fixed inset-y-0 z-10 hidden h-svh w-[var(--sidebar-width)] transition-[left,right,width] ease-linear md:flex",
195
+ "duration-200 fixed inset-y-0 z-10 h-svh w-[var(--sidebar-width)] transition-[left,right,width] ease-linear md:flex",
196
196
  side === "left"
197
197
  ? "left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
198
198
  : "right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
@@ -448,14 +448,10 @@ const SidebarMenuButton = React.forwardRef<
448
448
  },
449
449
  ref
450
450
  ) => {
451
- const Comp = "button"
452
- // manual asChild handling
453
- // const Comp = asChild ? Slot : "button"
454
- // But we want to avoid Slot if possible per user request?
455
- // Actually, if I can use the same cloneElement approach:
451
+ const { isMobile, state, setOpen } = useSidebar()
456
452
 
457
453
  const buttonClass = cn(
458
- "peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
454
+ "cursor-pointer peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
459
455
  "data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground",
460
456
  "data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground",
461
457
  "group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:group-data-[collapsible=icon]:hidden",
@@ -464,6 +460,13 @@ const SidebarMenuButton = React.forwardRef<
464
460
 
465
461
  // If tooltip is needed, we should implement it. For now, ignoring complexity of tooltip.
466
462
 
463
+ const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
464
+ props.onClick?.(e)
465
+ if (!isMobile && state === "collapsed") {
466
+ setOpen(true)
467
+ }
468
+ }
469
+
467
470
  if (asChild) {
468
471
  const child = React.Children.only(props.children) as React.ReactElement<any>
469
472
 
@@ -474,6 +477,10 @@ const SidebarMenuButton = React.forwardRef<
474
477
  "data-sidebar": "menu-button",
475
478
  "data-size": size,
476
479
  ...props,
480
+ onClick: (e: React.MouseEvent<HTMLButtonElement>) => {
481
+ handleClick(e)
482
+ child.props.onClick?.(e)
483
+ },
477
484
  children: child.props.children
478
485
  })
479
486
  }
@@ -485,6 +492,7 @@ const SidebarMenuButton = React.forwardRef<
485
492
  data-size={size}
486
493
  data-active={isActive}
487
494
  className={buttonClass}
495
+ onClick={handleClick}
488
496
  {...props}
489
497
  />
490
498
  )
@@ -23,9 +23,9 @@ import { cn } from "@/lib/utils"
23
23
 
24
24
  const Table = React.forwardRef<
25
25
  HTMLTableElement,
26
- React.HTMLAttributes<HTMLTableElement>
27
- >(({ className, ...props }, ref) => (
28
- <div className="relative w-full overflow-auto">
26
+ React.HTMLAttributes<HTMLTableElement> & { containerClassName?: string }
27
+ >(({ className, containerClassName, ...props }, ref) => (
28
+ <div className={cn("relative w-full overflow-auto", containerClassName)}>
29
29
  <table
30
30
  ref={ref}
31
31
  className={cn("w-full caption-bottom text-sm", className)}