@snapdragonsnursery/react-components 1.1.37 → 1.2.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 +115 -0
- package/package.json +23 -6
- package/src/ApplyButtonDemo.jsx +94 -0
- package/src/CalendarDemo.jsx +39 -0
- package/src/ChildSearchPage.jsx +104 -269
- package/src/DateRangePickerDebug.jsx +1 -0
- package/src/DateRangePickerDemo.jsx +58 -0
- package/src/DateRangePickerTest.jsx +71 -0
- package/src/components/ChildSearchFilters.jsx +237 -0
- package/src/components/ChildSearchFilters.test.jsx +308 -0
- package/src/components/ui/calendar.jsx +173 -0
- package/src/components/ui/date-range-picker.jsx +277 -0
- package/src/components/ui/date-range-picker.test.jsx +95 -0
- package/src/components/ui/popover.jsx +46 -0
- package/src/components/ui/simple-calendar.jsx +65 -0
- package/src/index.css +59 -0
- package/src/index.js +11 -0
package/README.md
CHANGED
|
@@ -5,6 +5,11 @@ A collection of reusable React components for Snapdragons Nursery applications.
|
|
|
5
5
|
## Components
|
|
6
6
|
|
|
7
7
|
- **ChildSearchModal**: Advanced child search and selection component with filtering, pagination, and multi-select capabilities
|
|
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
|
|
10
|
+
- **DatePicker**: Shadcn-style single date picker component
|
|
11
|
+
- **Calendar**: Official shadcn calendar component
|
|
12
|
+
- **Popover**: Official shadcn popover component
|
|
8
13
|
- **AuthButtons**: Authentication buttons for MSAL integration
|
|
9
14
|
- **ThemeToggle**: Dark/light theme toggle component
|
|
10
15
|
- **LandingPage**: Landing page component with authentication
|
|
@@ -34,6 +39,116 @@ function MyComponent() {
|
|
|
34
39
|
}
|
|
35
40
|
```
|
|
36
41
|
|
|
42
|
+
## Shadcn Components
|
|
43
|
+
|
|
44
|
+
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.
|
|
45
|
+
|
|
46
|
+
### For Consuming Projects
|
|
47
|
+
|
|
48
|
+
1. **Ensure your project has shadcn CSS variables** in your main CSS file (like `src/index.css`):
|
|
49
|
+
|
|
50
|
+
```css
|
|
51
|
+
@layer base {
|
|
52
|
+
:root {
|
|
53
|
+
--background: 0 0% 100%;
|
|
54
|
+
--foreground: 222.2 84% 4.9%;
|
|
55
|
+
--primary: 221.2 83.2% 53.3%;
|
|
56
|
+
--primary-foreground: 210 40% 98%;
|
|
57
|
+
--accent: 210 40% 96%;
|
|
58
|
+
--accent-foreground: 222.2 84% 4.9%;
|
|
59
|
+
--muted: 210 40% 96%;
|
|
60
|
+
--muted-foreground: 215.4 16.3% 46.9%;
|
|
61
|
+
--popover: 0 0% 100%;
|
|
62
|
+
--popover-foreground: 222.2 84% 4.9%;
|
|
63
|
+
--border: 214.3 31.8% 91.4%;
|
|
64
|
+
--input: 214.3 31.8% 91.4%;
|
|
65
|
+
--ring: 221.2 83.2% 53.3%;
|
|
66
|
+
--radius: 0.5rem;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
2. **Update your Tailwind config** to include the react-components package:
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
// tailwind.config.js
|
|
75
|
+
module.exports = {
|
|
76
|
+
content: [
|
|
77
|
+
'./src/**/*.{js,ts,jsx,tsx}',
|
|
78
|
+
'./node_modules/@snapdragonsnursery/react-components/src/**/*.{js,jsx}'
|
|
79
|
+
],
|
|
80
|
+
// ... rest of your config
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
3. **Install tailwindcss-animate** for proper animations:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
npm install tailwindcss-animate
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Date Range Picker Example
|
|
91
|
+
|
|
92
|
+
```jsx
|
|
93
|
+
import { DateRangePicker } from '@snapdragonsnursery/react-components';
|
|
94
|
+
|
|
95
|
+
function MyComponent() {
|
|
96
|
+
const [selectedRange, setSelectedRange] = useState(null);
|
|
97
|
+
|
|
98
|
+
const handleDateRangeChange = (range) => {
|
|
99
|
+
// Only update your filters when both dates are selected
|
|
100
|
+
if (range?.from && range?.to) {
|
|
101
|
+
// Both dates selected - update your filters
|
|
102
|
+
setFilters(prev => ({
|
|
103
|
+
...prev,
|
|
104
|
+
dobFrom: range.from.toISOString().split('T')[0],
|
|
105
|
+
dobTo: range.to.toISOString().split('T')[0],
|
|
106
|
+
}));
|
|
107
|
+
} else if (!range?.from && !range?.to) {
|
|
108
|
+
// Range cleared - clear your filters
|
|
109
|
+
setFilters(prev => ({
|
|
110
|
+
...prev,
|
|
111
|
+
dobFrom: '',
|
|
112
|
+
dobTo: '',
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
// Don't update filters when only first date is selected
|
|
116
|
+
|
|
117
|
+
setSelectedRange(range);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<DateRangePicker
|
|
122
|
+
selectedRange={selectedRange}
|
|
123
|
+
onSelect={handleDateRangeChange}
|
|
124
|
+
placeholder="Select a date range"
|
|
125
|
+
/>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Important**: The DateRangePicker now maintains its own internal state and only calls `onSelect` when both dates are selected or when the range is cleared. This prevents premature re-renders and keeps the calendar open until the user completes their selection. The ChildSearchFilters component includes an "Apply Filters" button that only triggers searches when clicked, providing better user control.
|
|
131
|
+
|
|
132
|
+
### Apply Button Functionality
|
|
133
|
+
|
|
134
|
+
The `ChildSearchFilters` component now includes an "Apply Filters" button that provides better user control:
|
|
135
|
+
|
|
136
|
+
- **Local State**: Filter changes are stored locally until applied
|
|
137
|
+
- **Visual Feedback**: Shows "You have unsaved filter changes" when filters are modified
|
|
138
|
+
- **Apply Button**: Only triggers search when clicked
|
|
139
|
+
- **Cancel Button**: Reverts changes without applying
|
|
140
|
+
- **Better UX**: Users can configure multiple filters before searching
|
|
141
|
+
|
|
142
|
+
```jsx
|
|
143
|
+
<ChildSearchFilters
|
|
144
|
+
filters={filters}
|
|
145
|
+
onFiltersChange={setFilters}
|
|
146
|
+
onApplyFilters={setFilters} // New callback for applying filters
|
|
147
|
+
sites={sites}
|
|
148
|
+
// ... other props
|
|
149
|
+
/>
|
|
150
|
+
```
|
|
151
|
+
|
|
37
152
|
## Environment Variables
|
|
38
153
|
|
|
39
154
|
Set these environment variables in your application:
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snapdragonsnursery/react-components",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "
|
|
7
|
+
"test": "jest",
|
|
8
8
|
"version:patch": "npm version patch",
|
|
9
9
|
"version:minor": "npm version minor",
|
|
10
10
|
"version:major": "npm version major",
|
|
@@ -40,13 +40,19 @@
|
|
|
40
40
|
"@headlessui/react": "^2.2.4",
|
|
41
41
|
"@heroicons/react": "^2.2.0",
|
|
42
42
|
"@popperjs/core": "^2.11.8",
|
|
43
|
+
"@radix-ui/react-popover": "^1.1.14",
|
|
44
|
+
"@radix-ui/react-slot": "^1.2.3",
|
|
43
45
|
"@tanstack/react-table": "^8.21.3",
|
|
44
46
|
"class-variance-authority": "^0.7.1",
|
|
45
47
|
"clsx": "^2.1.1",
|
|
48
|
+
"date-fns": "^4.1.0",
|
|
46
49
|
"lucide-react": "^0.526.0",
|
|
47
|
-
"react": "^18.
|
|
50
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
51
|
+
"react-day-picker": "^9.8.1",
|
|
48
52
|
"react-dom": "^18.3.1",
|
|
49
|
-
"tailwind-merge": "^3.3.1"
|
|
53
|
+
"tailwind-merge": "^3.3.1",
|
|
54
|
+
"tailwindcss": "^4.1.11",
|
|
55
|
+
"tailwindcss-animate": "^1.0.7"
|
|
50
56
|
},
|
|
51
57
|
"peerDependencies": {
|
|
52
58
|
"@azure/msal-react": ">=1.0.0",
|
|
@@ -54,6 +60,17 @@
|
|
|
54
60
|
},
|
|
55
61
|
"module": "src/index.js",
|
|
56
62
|
"files": [
|
|
57
|
-
"src"
|
|
58
|
-
|
|
63
|
+
"src",
|
|
64
|
+
"src/index.css"
|
|
65
|
+
],
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@babel/preset-env": "^7.28.0",
|
|
68
|
+
"@babel/preset-react": "^7.27.1",
|
|
69
|
+
"@testing-library/jest-dom": "^6.6.4",
|
|
70
|
+
"@testing-library/react": "^16.3.0",
|
|
71
|
+
"@testing-library/user-event": "^14.6.1",
|
|
72
|
+
"babel-jest": "^30.0.5",
|
|
73
|
+
"jest": "^30.0.5",
|
|
74
|
+
"jest-environment-jsdom": "^30.0.5"
|
|
75
|
+
}
|
|
59
76
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Demo component to showcase the Apply button functionality
|
|
2
|
+
// This demonstrates how the ChildSearchFilters component now works with local state and Apply button
|
|
3
|
+
|
|
4
|
+
import React, { useState } from 'react'
|
|
5
|
+
import ChildSearchFilters from './components/ChildSearchFilters'
|
|
6
|
+
|
|
7
|
+
export default function ApplyButtonDemo() {
|
|
8
|
+
const [filters, setFilters] = useState({
|
|
9
|
+
status: "active",
|
|
10
|
+
selectedSiteId: "",
|
|
11
|
+
dobFrom: "",
|
|
12
|
+
dobTo: "",
|
|
13
|
+
ageFrom: "",
|
|
14
|
+
ageTo: "",
|
|
15
|
+
sortBy: "last_name",
|
|
16
|
+
sortOrder: "asc",
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const [searchTriggered, setSearchTriggered] = useState(false)
|
|
20
|
+
|
|
21
|
+
const mockSites = [
|
|
22
|
+
{ site_id: 1, site_name: "Site A" },
|
|
23
|
+
{ site_id: 2, site_name: "Site B" },
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
const handleApplyFilters = (newFilters) => {
|
|
27
|
+
setFilters(newFilters)
|
|
28
|
+
setSearchTriggered(true)
|
|
29
|
+
// In a real app, this would trigger the search
|
|
30
|
+
console.log('Search triggered with filters:', newFilters)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const handleClearFilters = () => {
|
|
34
|
+
const clearedFilters = {
|
|
35
|
+
status: "active",
|
|
36
|
+
selectedSiteId: "",
|
|
37
|
+
dobFrom: "",
|
|
38
|
+
dobTo: "",
|
|
39
|
+
ageFrom: "",
|
|
40
|
+
ageTo: "",
|
|
41
|
+
sortBy: "last_name",
|
|
42
|
+
sortOrder: "asc",
|
|
43
|
+
}
|
|
44
|
+
setFilters(clearedFilters)
|
|
45
|
+
setSearchTriggered(false)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div className="p-6 space-y-6 max-w-4xl mx-auto">
|
|
50
|
+
<h2 className="text-2xl font-bold">Apply Button Demo</h2>
|
|
51
|
+
|
|
52
|
+
<div className="space-y-4">
|
|
53
|
+
<div className="bg-blue-50 dark:bg-blue-900/20 p-4 rounded-lg">
|
|
54
|
+
<h3 className="text-lg font-semibold mb-2">How it works:</h3>
|
|
55
|
+
<ul className="text-sm space-y-1">
|
|
56
|
+
<li>• Change any filter value - notice no search is triggered</li>
|
|
57
|
+
<li>• The "Apply Filters" button appears when you have unsaved changes</li>
|
|
58
|
+
<li>• Click "Apply Filters" to trigger the search</li>
|
|
59
|
+
<li>• Click "Cancel" to revert changes without applying</li>
|
|
60
|
+
</ul>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<ChildSearchFilters
|
|
64
|
+
filters={filters}
|
|
65
|
+
onFiltersChange={setFilters}
|
|
66
|
+
onApplyFilters={handleApplyFilters}
|
|
67
|
+
sites={mockSites}
|
|
68
|
+
activeOnly={true}
|
|
69
|
+
isAdvancedFiltersOpen={true}
|
|
70
|
+
onToggleAdvancedFilters={() => {}}
|
|
71
|
+
onClearFilters={handleClearFilters}
|
|
72
|
+
/>
|
|
73
|
+
|
|
74
|
+
{searchTriggered && (
|
|
75
|
+
<div className="bg-green-50 dark:bg-green-900/20 p-4 rounded-lg">
|
|
76
|
+
<h3 className="text-lg font-semibold text-green-800 dark:text-green-200">
|
|
77
|
+
✅ Search Triggered!
|
|
78
|
+
</h3>
|
|
79
|
+
<p className="text-sm text-green-700 dark:text-green-300">
|
|
80
|
+
The search was triggered when you clicked "Apply Filters"
|
|
81
|
+
</p>
|
|
82
|
+
</div>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
<div className="bg-gray-50 dark:bg-gray-800 p-4 rounded-lg">
|
|
86
|
+
<h3 className="text-lg font-semibold mb-2">Current Filters:</h3>
|
|
87
|
+
<pre className="text-sm overflow-auto">
|
|
88
|
+
{JSON.stringify(filters, null, 2)}
|
|
89
|
+
</pre>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// Demo component to test calendar styling
|
|
2
|
+
// This can be used to verify that the shadcn calendar is working properly
|
|
3
|
+
|
|
4
|
+
import React, { useState } from 'react'
|
|
5
|
+
import { Calendar } from './components/ui/calendar'
|
|
6
|
+
|
|
7
|
+
export default function CalendarDemo() {
|
|
8
|
+
const [date, setDate] = useState(null)
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="p-6 space-y-4">
|
|
12
|
+
<h2 className="text-2xl font-bold">Calendar Demo</h2>
|
|
13
|
+
|
|
14
|
+
<div className="space-y-2">
|
|
15
|
+
<h3 className="text-lg font-semibold">Single Date Picker</h3>
|
|
16
|
+
<Calendar
|
|
17
|
+
mode="single"
|
|
18
|
+
selected={date}
|
|
19
|
+
onSelect={setDate}
|
|
20
|
+
className="rounded-md border"
|
|
21
|
+
/>
|
|
22
|
+
{date && (
|
|
23
|
+
<p className="text-sm text-gray-600">
|
|
24
|
+
Selected: {date.toLocaleDateString()}
|
|
25
|
+
</p>
|
|
26
|
+
)}
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div className="space-y-2">
|
|
30
|
+
<h3 className="text-lg font-semibold">Date Range Picker</h3>
|
|
31
|
+
<Calendar
|
|
32
|
+
mode="range"
|
|
33
|
+
numberOfMonths={2}
|
|
34
|
+
className="rounded-md border"
|
|
35
|
+
/>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
)
|
|
39
|
+
}
|