@snapdragonsnursery/react-components 1.5.0 → 1.6.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/README.md CHANGED
@@ -6,13 +6,14 @@ A collection of reusable React components for Snapdragons Nursery applications.
6
6
 
7
7
  - **ChildSearchModal**: Advanced child search and selection component with filtering, pagination, and multi-select capabilities
8
8
  - **ChildSearchFilters**: Advanced filtering component with date range picker, status, site, and age filters (includes Apply button for better UX)
9
- - **DateRangePicker**: Shadcn-style date range picker component
9
+ - **DateRangePicker**: Shadcn-style date range picker component (supports optional presets)
10
10
  - **DatePicker**: Shadcn-style single date picker component
11
11
  - **Calendar**: Official shadcn calendar component
12
12
  - **Popover**: Official shadcn popover component
13
13
  - **AuthButtons**: Authentication buttons for MSAL integration
14
14
  - **ThemeToggle**: Dark/light theme toggle component
15
15
  - **LandingPage**: Landing page component with authentication
16
+ - **SoftWarningAlert**: Soft-styled alert for non-blocking warnings with optional action
16
17
 
17
18
  ## Installation
18
19
 
@@ -39,6 +40,25 @@ function MyComponent() {
39
40
  }
40
41
  ```
41
42
 
43
+ ### SoftWarningAlert Example
44
+
45
+ ```jsx
46
+ import { SoftWarningAlert } from '@snapdragonsnursery/react-components';
47
+ import { AlertTriangle } from 'lucide-react';
48
+
49
+ function Notice() {
50
+ return (
51
+ <SoftWarningAlert
52
+ icon={AlertTriangle}
53
+ title="Unsubmitted claims"
54
+ description="You have 3 unsubmitted mileage claims. Create a report to submit."
55
+ actionLabel="Create report"
56
+ onAction={() => console.log('clicked')}
57
+ />
58
+ );
59
+ }
60
+ ```
61
+
42
62
  ## Shadcn Components
43
63
 
44
64
  This package includes official shadcn components with proper styling. The components use shadcn CSS variables, so make sure your consuming project has the shadcn CSS variables defined in your CSS file.
@@ -118,11 +138,28 @@ function MyComponent() {
118
138
  };
119
139
 
120
140
  return (
121
- <DateRangePicker
122
- selectedRange={selectedRange}
123
- onSelect={handleDateRangeChange}
124
- placeholder="Select a date range"
125
- />
141
+ <>
142
+ <DateRangePicker
143
+ selectedRange={selectedRange}
144
+ onSelect={handleDateRangeChange}
145
+ placeholder="Select a date range"
146
+ numberOfMonths={2}
147
+ />
148
+ {/* With presets */}
149
+ <DateRangePicker
150
+ selectedRange={selectedRange}
151
+ onSelect={handleDateRangeChange}
152
+ presetsEnabled
153
+ presets={[
154
+ { key: 'thisWeek', label: 'This week', getRange: () => ({ from: startOfWeek(new Date(), { weekStartsOn: 1 }), to: new Date() }) },
155
+ { key: 'lastWeek', label: 'Last week', getRange: () => { const ref = subWeeks(new Date(), 1); return { from: startOfWeek(ref, { weekStartsOn: 1 }), to: endOfWeek(ref, { weekStartsOn: 1 }) } } },
156
+ { key: 'thisMonth', label: 'This month', getRange: () => ({ from: startOfMonth(new Date()), to: new Date() }) },
157
+ { key: 'lastMonth', label: 'Last month', getRange: () => { const ref = subMonths(new Date(), 1); return { from: startOfMonth(ref), to: endOfMonth(ref) } } },
158
+ { key: 'thisYear', label: 'This year', getRange: () => ({ from: startOfYear(new Date()), to: new Date() }) },
159
+ { key: 'lastYear', label: 'Last year', getRange: () => { const ref = subYears(new Date(), 1); return { from: startOfYear(ref), to: endOfYear(ref) } } },
160
+ ]}
161
+ />
162
+ </>
126
163
  );
127
164
  }
128
165
  ```
@@ -162,6 +199,7 @@ VITE_COMMON_API_BASE_URL=https://snaps-common-api.azurewebsites.net
162
199
  - [ChildSearchModal Documentation](./CHILD_SEARCH_MODAL_DOCUMENTATION.md)
163
200
  - [ChildSearchModal README](./CHILD_SEARCH_README.md)
164
201
  - [Release Guide](./RELEASE.md)
202
+ - [SoftWarningAlert](./SOFT_WARNING_ALERT.md)
165
203
 
166
204
  ---
167
205
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snapdragonsnursery/react-components",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -59,6 +59,7 @@
59
59
  "react": "^18.0.0 || ^19.0.0"
60
60
  },
61
61
  "module": "src/index.js",
62
+ "types": "src/index.d.ts",
62
63
  "files": [
63
64
  "src",
64
65
  "src/index.css"
@@ -4,19 +4,28 @@
4
4
  // Usage: <DateRangePicker selectedRange={range} onSelect={setRange} />
5
5
 
6
6
  import React, { useState, useEffect, useRef } from 'react'
7
- import { format } from 'date-fns'
7
+ import { format, addDays, addMonths, endOfMonth, endOfYear, startOfMonth, startOfYear, subDays, subMonths, subYears } from 'date-fns'
8
8
  import { CalendarIcon } from '@heroicons/react/24/outline'
9
9
  import { Popover, PopoverContent, PopoverTrigger } from './popover'
10
10
  import { Calendar } from './calendar'
11
11
  import { Button } from './button'
12
12
  import { cn } from '../../lib/utils'
13
13
 
14
+ // Optional preset type: { key: string, label: string, getRange: () => ({ from: Date, to: Date }) }
14
15
  export function DateRangePicker({
15
16
  selectedRange,
16
17
  onSelect,
17
18
  className,
18
19
  placeholder = "Select a date range",
19
20
  disabled,
21
+ // New: optional presets support (backwards compatible)
22
+ presetsEnabled = false,
23
+ presets,
24
+ // New: allow consumers to control month count
25
+ numberOfMonths = 2,
26
+ // New: allow styling popover content and calendar for layout control
27
+ contentClassName,
28
+ calendarClassName,
20
29
  ...props
21
30
  }) {
22
31
  const [isOpen, setIsOpen] = useState(false)
@@ -185,8 +194,8 @@ export function DateRangePicker({
185
194
  )}
186
195
  </Button>
187
196
  </PopoverTrigger>
188
- <PopoverContent className="w-auto p-0" align="start">
189
- <div className="relative">
197
+ <PopoverContent className={cn('w-[90vw] p-3 sm:w-auto', contentClassName)} align="start">
198
+ <div className="relative w-full sm:w-[520px]">
190
199
  <Calendar
191
200
  mode="range"
192
201
  defaultMonth={internalRange?.from}
@@ -197,8 +206,47 @@ export function DateRangePicker({
197
206
  handleSelect(range)
198
207
  }
199
208
  }}
200
- numberOfMonths={2}
209
+ numberOfMonths={numberOfMonths}
210
+ className={cn('w-full', calendarClassName)}
201
211
  />
212
+ {presetsEnabled && (
213
+ <div className="mt-2 flex flex-wrap gap-2 p-2">
214
+ {(presets && presets.length > 0 ? presets : defaultPresets()).map((p) => {
215
+ const r = p.getRange()
216
+ return (
217
+ <Button
218
+ key={p.key}
219
+ variant="outline"
220
+ size="sm"
221
+ onClick={() => {
222
+ setInternalRange(r)
223
+ isSelectingRange.current = false
224
+ onSelect(r)
225
+ }}
226
+ >
227
+ {p.label}
228
+ </Button>
229
+ )
230
+ })}
231
+ </div>
232
+ )}
233
+ {/* Always-present footer actions for clarity on mobile/desktop */}
234
+ <div className="mt-3 flex items-center justify-between px-2">
235
+ <Button
236
+ variant="ghost"
237
+ size="sm"
238
+ onClick={() => {
239
+ setInternalRange(null)
240
+ isSelectingRange.current = false
241
+ onSelect(null)
242
+ }}
243
+ >
244
+ Clear
245
+ </Button>
246
+ <Button size="sm" onClick={() => setIsOpen(false)}>
247
+ Done
248
+ </Button>
249
+ </div>
202
250
  {internalRange?.from && (
203
251
  <div className="absolute top-2 right-2 flex gap-1">
204
252
  <button
@@ -227,6 +275,23 @@ export function DateRangePicker({
227
275
  )
228
276
  }
229
277
 
278
+ // Provide a default preset set similar to app usage, but minimal
279
+ function defaultPresets() {
280
+ const today = new Date()
281
+ const last7Days = { from: subDays(today, 6), to: today }
282
+ const monthToDate = { from: startOfMonth(today), to: today }
283
+ const yearToDate = { from: startOfYear(today), to: today }
284
+ const lastMonth = { from: startOfMonth(subMonths(today, 1)), to: endOfMonth(subMonths(today, 1)) }
285
+ const lastYear = { from: startOfYear(subYears(today, 1)), to: endOfYear(subYears(today, 1)) }
286
+ return [
287
+ { key: 'last7', label: 'Last 7 days', getRange: () => last7Days },
288
+ { key: 'mtd', label: 'Month to date', getRange: () => monthToDate },
289
+ { key: 'lastMonth', label: 'Last month', getRange: () => lastMonth },
290
+ { key: 'ytd', label: 'Year to date', getRange: () => yearToDate },
291
+ { key: 'lastYear', label: 'Last year', getRange: () => lastYear },
292
+ ]
293
+ }
294
+
230
295
  // Single date picker variant
231
296
  export function DatePicker({
232
297
  selectedDate,
@@ -38,7 +38,7 @@ describe('DateRangePicker', () => {
38
38
  fireEvent.click(button)
39
39
 
40
40
  await waitFor(() => {
41
- expect(screen.getAllByRole('grid')).toHaveLength(2) // Two months for range picker
41
+ expect(screen.getAllByRole('grid')).toHaveLength(2) // Two months default
42
42
  })
43
43
  })
44
44
 
@@ -64,6 +64,24 @@ describe('DateRangePicker', () => {
64
64
  })
65
65
  })
66
66
 
67
+ describe('DateRangePicker presets', () => {
68
+ it('renders presets when enabled and selects a range on click', async () => {
69
+ const onSelect = jest.fn()
70
+ render(<DateRangePicker selectedRange={null} onSelect={onSelect} presetsEnabled numberOfMonths={1} />)
71
+ // Open popover
72
+ fireEvent.click(screen.getByRole('button'))
73
+ // Default preset button e.g. Last 7 days should appear
74
+ await waitFor(() => {
75
+ expect(screen.getByText(/last 7 days/i)).toBeInTheDocument()
76
+ })
77
+ fireEvent.click(screen.getByText(/last 7 days/i))
78
+ expect(onSelect).toHaveBeenCalled()
79
+ const arg = onSelect.mock.calls[0][0]
80
+ expect(arg?.from).toBeInstanceOf(Date)
81
+ expect(arg?.to).toBeInstanceOf(Date)
82
+ })
83
+ })
84
+
67
85
  describe('DatePicker', () => {
68
86
  it('renders with placeholder text', () => {
69
87
  const mockOnSelect = jest.fn()
@@ -0,0 +1,112 @@
1
+ //
2
+ // soft-warning-alert.jsx
3
+ // -----------------------
4
+ // A soft-styled warning alert built on shadcn-like primitives used in this package.
5
+ // Use this for non-blocking warnings with an optional action button.
6
+ //
7
+ // Example:
8
+ // import { SoftWarningAlert } from '@snapdragonsnursery/react-components'
9
+ // import { AlertTriangle } from 'lucide-react'
10
+ //
11
+ // export default function Example() {
12
+ // return (
13
+ // <SoftWarningAlert
14
+ // icon={AlertTriangle}
15
+ // title="Unsubmitted claims"
16
+ // description="You have 3 unsubmitted mileage claims. Create a report to submit."
17
+ // actionLabel="Create report"
18
+ // onAction={() => console.log('clicked')}
19
+ // />
20
+ // )
21
+ // }
22
+
23
+ import React from 'react'
24
+ import { cn } from '../../lib/utils'
25
+
26
+ // Lightweight Alert primitives compatible with package styling
27
+ function Alert({ className, children }) {
28
+ return (
29
+ <div
30
+ className={cn(
31
+ 'w-full rounded-lg border p-4 text-sm',
32
+ 'bg-amber-600/10 text-amber-700 border-amber-200',
33
+ 'dark:bg-amber-400/10 dark:text-amber-300 dark:border-amber-300/20',
34
+ className
35
+ )}
36
+ role="alert"
37
+ >
38
+ {children}
39
+ </div>
40
+ )
41
+ }
42
+
43
+ // Prop types omitted to avoid runtime dependency
44
+
45
+ function AlertTitle({ className, children }) {
46
+ return <div className={cn('font-medium', className)}>{children}</div>
47
+ }
48
+
49
+ // Prop types omitted to avoid runtime dependency
50
+
51
+ function AlertDescription({ className, children }) {
52
+ return (
53
+ <div className={cn('mt-1 text-amber-700/80 dark:text-amber-300/80', className)}>
54
+ {children}
55
+ </div>
56
+ )
57
+ }
58
+
59
+ // Prop types omitted to avoid runtime dependency
60
+
61
+ function Button({ className, children, ...props }) {
62
+ return (
63
+ <button
64
+ type="button"
65
+ className={cn(
66
+ 'inline-flex items-center rounded-md px-3 py-1.5 text-xs font-medium',
67
+ 'bg-amber-600 text-white hover:bg-amber-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-amber-500',
68
+ 'dark:bg-amber-500 dark:text-black dark:hover:bg-amber-400',
69
+ className
70
+ )}
71
+ {...props}
72
+ >
73
+ {children}
74
+ </button>
75
+ )
76
+ }
77
+
78
+ // Prop types omitted to avoid runtime dependency
79
+
80
+ export function SoftWarningAlert({
81
+ title,
82
+ description,
83
+ icon: Icon,
84
+ className,
85
+ actionLabel,
86
+ onAction,
87
+ }) {
88
+ return (
89
+ <Alert className={className}>
90
+ <div className="flex items-start justify-between gap-3">
91
+ <div className="flex items-start gap-3">
92
+ {Icon ? <Icon className="h-5 w-5 mt-0.5 shrink-0" /> : null}
93
+ <div>
94
+ <AlertTitle>{title}</AlertTitle>
95
+ {description ? (
96
+ <AlertDescription>{description}</AlertDescription>
97
+ ) : null}
98
+ </div>
99
+ </div>
100
+ {typeof onAction === 'function' && actionLabel ? (
101
+ <Button onClick={onAction}>{actionLabel}</Button>
102
+ ) : null}
103
+ </div>
104
+ </Alert>
105
+ )
106
+ }
107
+
108
+ // Prop types omitted to avoid runtime dependency
109
+
110
+ export default SoftWarningAlert
111
+
112
+
package/src/index.d.ts ADDED
@@ -0,0 +1,48 @@
1
+ //
2
+ // index.d.ts
3
+ // -------------
4
+ // TypeScript declarations for @snapdragonsnursery/react-components
5
+ // This provides minimal typings for components used in consuming TypeScript apps.
6
+
7
+ import * as React from 'react'
8
+
9
+ export interface SoftWarningAlertProps {
10
+ title: React.ReactNode
11
+ description?: React.ReactNode
12
+ icon?: React.ComponentType<any>
13
+ className?: string
14
+ actionLabel?: string
15
+ onAction?: () => void
16
+ }
17
+
18
+ export const SoftWarningAlert: React.FC<SoftWarningAlertProps>
19
+
20
+ // Existing components (typed as any for now)
21
+ export const AuthButtons: React.ComponentType<any>
22
+ export const ThemeToggle: React.ComponentType<any>
23
+ export const ChildSearchModal: React.ComponentType<any>
24
+ export const ChildSearchPage: React.ComponentType<any>
25
+ export const ChildSearchPageDemo: React.ComponentType<any>
26
+ export const ThemeToggleTest: React.ComponentType<any>
27
+ export const LandingPage: React.ComponentType<any>
28
+ export const ChildSearchFilters: React.ComponentType<any>
29
+ export const DateRangePickerDemo: React.ComponentType<any>
30
+ export const CalendarDemo: React.ComponentType<any>
31
+ export const DateRangePickerTest: React.ComponentType<any>
32
+ export const ApplyButtonDemo: React.ComponentType<any>
33
+ export const EmployeeSearchPage: React.ComponentType<any>
34
+ export const EmployeeSearchModal: React.ComponentType<any>
35
+ export const EmployeeSearchDemo: React.ComponentType<any>
36
+ export const EmployeeSearchFilters: React.ComponentType<any>
37
+
38
+ export const DateRangePicker: React.ComponentType<any>
39
+ export const DatePicker: React.ComponentType<any>
40
+ export const Calendar: React.ComponentType<any>
41
+ export const SimpleCalendar: React.ComponentType<any>
42
+ export const Popover: React.ComponentType<any>
43
+ export const PopoverContent: React.ComponentType<any>
44
+ export const PopoverTrigger: React.ComponentType<any>
45
+
46
+ export function configureTelemetry(...args: any[]): any
47
+
48
+
package/src/index.js CHANGED
@@ -24,3 +24,4 @@ export { DateRangePicker, DatePicker } from "./components/ui/date-range-picker";
24
24
  export { Calendar } from "./components/ui/calendar";
25
25
  export { SimpleCalendar } from "./components/ui/simple-calendar";
26
26
  export { Popover, PopoverContent, PopoverTrigger } from "./components/ui/popover";
27
+ export { default as SoftWarningAlert } from "./components/ui/soft-warning-alert";