@snapdragonsnursery/react-components 1.13.0 → 1.16.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.
@@ -72,6 +72,13 @@ const EmployeeSearchPage = ({
72
72
  showEndDateFilter = false,
73
73
  showWorkingHoursFilter = true,
74
74
  loadAllResults = false, // If true, loads all results instead of paginating
75
+ // Auth options: provide token directly or a function to fetch it
76
+ authToken = null,
77
+ getAccessToken = null,
78
+ // Column customization
79
+ visibleColumns = null, // e.g. ['full_name','site_name','role_name','email','start_date','employee_status','total_hours_per_week']
80
+ columnLabels = {}, // e.g. { full_name: 'Employee', site_name: 'Location' }
81
+ columnRenderers = {}, // e.g. { email: (row) => <a>{row.email}</a> }
75
82
  }) => {
76
83
  const [searchTerm, setSearchTerm] = useState("");
77
84
  const [employees, setEmployees] = useState([]);
@@ -157,98 +164,80 @@ const EmployeeSearchPage = ({
157
164
  };
158
165
  };
159
166
 
160
- // Define table columns
161
- const columns = [
162
- // Checkbox column for multi-select
163
- ...(multiSelect
164
- ? [
165
- columnHelper.display({
166
- id: "select",
167
- header: ({ table }) => (
168
- <input
169
- type="checkbox"
170
- checked={table.getIsAllPageRowsSelected()}
171
- onChange={table.getToggleAllPageRowsSelectedHandler()}
172
- className="rounded border-gray-300"
173
- />
174
- ),
175
- cell: ({ row }) => (
176
- <input
177
- type="checkbox"
178
- checked={row.getIsSelected()}
179
- onChange={row.getToggleSelectedHandler()}
180
- className="rounded border-gray-300"
181
- />
182
- ),
183
- size: 50,
184
- }),
185
- ]
186
- : []),
187
- // Name column - sortable
167
+ // Define default data columns (excluding the optional select column)
168
+ const defaultDataColumns = [
188
169
  columnHelper.accessor("full_name", {
189
- header: createSortableHeader("full_name", "Name"),
190
- cell: ({ row }) => (
191
- <div>
192
- <div className="font-medium">{row.original.full_name}</div>
193
- <div className="text-sm text-gray-500">
194
- ID: {row.original.employee_id}
170
+ header: createSortableHeader("full_name", columnLabels.full_name || "Name"),
171
+ cell: ({ row }) =>
172
+ columnRenderers.full_name ? (
173
+ columnRenderers.full_name(row.original)
174
+ ) : (
175
+ <div>
176
+ <div className="font-medium">{row.original.full_name}</div>
177
+ <div className="text-sm text-gray-500">ID: {row.original.employee_id}</div>
195
178
  </div>
196
- </div>
197
- ),
179
+ ),
198
180
  }),
199
- // Site column - sortable
200
181
  columnHelper.accessor("site_name", {
201
- header: createSortableHeader("site_name", "Site"),
202
- cell: ({ row }) => (
203
- <span>{row.original.site_name}</span>
204
- ),
182
+ header: createSortableHeader("site_name", columnLabels.site_name || "Site"),
183
+ cell: ({ row }) =>
184
+ columnRenderers.site_name ? (
185
+ columnRenderers.site_name(row.original)
186
+ ) : (
187
+ <span>{row.original.site_name}</span>
188
+ ),
205
189
  }),
206
- // Role column - sortable
207
190
  columnHelper.accessor("role_name", {
208
- header: createSortableHeader("role_name", "Role"),
209
- cell: ({ row }) => (
210
- <span>{row.original.role_name}</span>
211
- ),
191
+ header: createSortableHeader("role_name", columnLabels.role_name || "Role"),
192
+ cell: ({ row }) =>
193
+ columnRenderers.role_name ? (
194
+ columnRenderers.role_name(row.original)
195
+ ) : (
196
+ <span>{row.original.role_name}</span>
197
+ ),
212
198
  }),
213
- // Manager column - sortable
214
199
  columnHelper.accessor("manager_name", {
215
- header: createSortableHeader("manager_name", "Manager"),
216
- cell: ({ row }) => (
217
- <span className="text-sm">
218
- {row.original.manager_name || "N/A"}
219
- </span>
220
- ),
200
+ header: createSortableHeader("manager_name", columnLabels.manager_name || "Manager"),
201
+ cell: ({ row }) =>
202
+ columnRenderers.manager_name ? (
203
+ columnRenderers.manager_name(row.original)
204
+ ) : (
205
+ <span className="text-sm">{row.original.manager_name || "N/A"}</span>
206
+ ),
221
207
  }),
222
- // Email column
223
208
  columnHelper.accessor("email", {
224
- header: "Email",
225
- cell: ({ row }) => (
226
- <span className="text-sm">{row.original.email}</span>
227
- ),
209
+ header: columnLabels.email || "Email",
210
+ cell: ({ row }) =>
211
+ columnRenderers.email ? (
212
+ columnRenderers.email(row.original)
213
+ ) : (
214
+ <span className="text-sm">{row.original.email}</span>
215
+ ),
228
216
  }),
229
- // Start Date column - sortable
230
217
  columnHelper.accessor("start_date", {
231
- header: createSortableHeader("start_date", "Start Date"),
232
- cell: ({ row }) => (
233
- <span>
234
- {row.original.start_date
235
- ? new Date(row.original.start_date).toLocaleDateString("en-GB")
236
- : "N/A"}
237
- </span>
238
- ),
218
+ header: createSortableHeader("start_date", columnLabels.start_date || "Start Date"),
219
+ cell: ({ row }) =>
220
+ columnRenderers.start_date ? (
221
+ columnRenderers.start_date(row.original)
222
+ ) : (
223
+ <span>
224
+ {row.original.start_date
225
+ ? new Date(row.original.start_date).toLocaleDateString("en-GB")
226
+ : "N/A"}
227
+ </span>
228
+ ),
239
229
  }),
240
- // Status column - sortable
241
230
  columnHelper.accessor("employee_status", {
242
- header: createSortableHeader("employee_status", "Status"),
231
+ header: createSortableHeader("employee_status", columnLabels.employee_status || "Status"),
243
232
  cell: ({ row }) => {
233
+ if (columnRenderers.employee_status) return columnRenderers.employee_status(row.original);
244
234
  const status = row.original.employee_status;
245
235
  const statusColors = {
246
- 'Active': 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
247
- 'Inactive': 'bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200',
248
- 'On Leave': 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
249
- 'Terminated': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
236
+ Active: "bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200",
237
+ Inactive: "bg-gray-100 text-gray-800 dark:bg-gray-900 dark:text-gray-200",
238
+ "On Leave": "bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200",
239
+ Terminated: "bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200",
250
240
  };
251
-
252
241
  return (
253
242
  <span
254
243
  className={cn(
@@ -261,45 +250,86 @@ const EmployeeSearchPage = ({
261
250
  );
262
251
  },
263
252
  }),
264
- // Working Hours column
265
253
  columnHelper.accessor("total_hours_per_week", {
266
- header: "Hours/Week",
267
- cell: ({ row }) => (
268
- <span>
269
- {row.original.total_hours_per_week
270
- ? `${row.original.total_hours_per_week}h`
271
- : "N/A"}
272
- </span>
273
- ),
254
+ header: columnLabels.total_hours_per_week || "Hours/Week",
255
+ cell: ({ row }) =>
256
+ columnRenderers.total_hours_per_week ? (
257
+ columnRenderers.total_hours_per_week(row.original)
258
+ ) : (
259
+ <span>
260
+ {row.original.total_hours_per_week
261
+ ? `${row.original.total_hours_per_week}h`
262
+ : "N/A"}
263
+ </span>
264
+ ),
274
265
  }),
275
- // Term Time Only column
276
266
  columnHelper.accessor("term_time_only", {
277
- header: "Term Time",
278
- cell: ({ row }) => (
279
- <span>
280
- {row.original.term_time_only ? (
281
- <CheckCircleIcon className="h-4 w-4 text-green-500" />
282
- ) : (
283
- <XCircleIcon className="h-4 w-4 text-gray-400" />
284
- )}
285
- </span>
286
- ),
267
+ header: columnLabels.term_time_only || "Term Time",
268
+ cell: ({ row }) =>
269
+ columnRenderers.term_time_only ? (
270
+ columnRenderers.term_time_only(row.original)
271
+ ) : (
272
+ <span>
273
+ {row.original.term_time_only ? (
274
+ <CheckCircleIcon className="h-4 w-4 text-green-500" />
275
+ ) : (
276
+ <XCircleIcon className="h-4 w-4 text-gray-400" />
277
+ )}
278
+ </span>
279
+ ),
287
280
  }),
288
- // Maternity Leave column
289
281
  columnHelper.accessor("on_maternity_leave", {
290
- header: "Maternity",
291
- cell: ({ row }) => (
292
- <span>
293
- {row.original.on_maternity_leave ? (
294
- <CheckCircleIcon className="h-4 w-4 text-blue-500" />
295
- ) : (
296
- <XCircleIcon className="h-4 w-4 text-gray-400" />
297
- )}
298
- </span>
299
- ),
282
+ header: columnLabels.on_maternity_leave || "Maternity",
283
+ cell: ({ row }) =>
284
+ columnRenderers.on_maternity_leave ? (
285
+ columnRenderers.on_maternity_leave(row.original)
286
+ ) : (
287
+ <span>
288
+ {row.original.on_maternity_leave ? (
289
+ <CheckCircleIcon className="h-4 w-4 text-blue-500" />
290
+ ) : (
291
+ <XCircleIcon className="h-4 w-4 text-gray-400" />
292
+ )}
293
+ </span>
294
+ ),
300
295
  }),
301
296
  ];
302
297
 
298
+ // Build final columns order
299
+ const selectColumn = columnHelper.display({
300
+ id: "select",
301
+ header: ({ table }) => (
302
+ <input
303
+ type="checkbox"
304
+ checked={table.getIsAllPageRowsSelected()}
305
+ onChange={table.getToggleAllPageRowsSelectedHandler()}
306
+ className="rounded border-gray-300"
307
+ />
308
+ ),
309
+ cell: ({ row }) => (
310
+ <input
311
+ type="checkbox"
312
+ checked={row.getIsSelected()}
313
+ onChange={row.getToggleSelectedHandler()}
314
+ className="rounded border-gray-300"
315
+ />
316
+ ),
317
+ size: 50,
318
+ });
319
+
320
+ // Filter/reorder if visibleColumns provided
321
+ let dataColumns = defaultDataColumns;
322
+ if (Array.isArray(visibleColumns) && visibleColumns.length > 0) {
323
+ const mapByKey = new Map(
324
+ defaultDataColumns.map((c) => [c.accessorKey || c.id, c])
325
+ );
326
+ dataColumns = visibleColumns
327
+ .map((key) => mapByKey.get(key))
328
+ .filter(Boolean);
329
+ }
330
+
331
+ const columns = multiSelect ? [selectColumn, ...dataColumns] : dataColumns;
332
+
303
333
  // Create table instance
304
334
  const table = useReactTable({
305
335
  data: employees,
@@ -367,7 +397,34 @@ const EmployeeSearchPage = ({
367
397
  setError(null);
368
398
 
369
399
  try {
370
- const functionKey = import.meta.env.VITE_COMMON_API_FUNCTION_KEY || "";
400
+ // Resolve access token for APIM
401
+ const apimScope = import.meta.env.VITE_APIM_SCOPE;
402
+
403
+ let accessToken = null;
404
+ if (typeof getAccessToken === "function") {
405
+ accessToken = await getAccessToken();
406
+ } else if (authToken) {
407
+ accessToken = authToken;
408
+ } else if (apimScope) {
409
+ try {
410
+ const response = await instance.acquireTokenSilent({
411
+ account: accounts[0],
412
+ scopes: [apimScope],
413
+ });
414
+ accessToken = response.accessToken;
415
+ } catch (silentErr) {
416
+ const response = await instance.acquireTokenPopup({
417
+ scopes: [apimScope],
418
+ });
419
+ accessToken = response.accessToken;
420
+ }
421
+ }
422
+
423
+ if (!accessToken) {
424
+ throw new Error(
425
+ "Missing access token. Provide authToken/getAccessToken or set VITE_APIM_SCOPE."
426
+ );
427
+ }
371
428
 
372
429
  const params = new URLSearchParams({
373
430
  entra_id: accounts[0].localAccountId,
@@ -445,14 +502,11 @@ const EmployeeSearchPage = ({
445
502
  params.append("sort_order", sortOrder);
446
503
 
447
504
  const apiResponse = await fetch(
448
- `${
449
- import.meta.env.VITE_COMMON_API_BASE_URL ||
450
- "https://snaps-common-api.azurewebsites.net"
451
- }/api/search-employees?${params}`,
505
+ `https://snapdragons.azure-api.net/api/employees/search-employees?${params}`,
452
506
  {
453
507
  headers: {
454
508
  "Content-Type": "application/json",
455
- "x-functions-key": functionKey,
509
+ Authorization: `Bearer ${accessToken}`,
456
510
  },
457
511
  }
458
512
  );
@@ -752,35 +806,36 @@ const EmployeeSearchPage = ({
752
806
  <thead className="[&_tr]:border-b">
753
807
  {table.getHeaderGroups().map((headerGroup) => (
754
808
  <tr key={headerGroup.id} className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
755
- {headerGroup.headers.map((header, index) => {
756
- // Define generous column widths to avoid truncation - table will scroll if too wide
757
- const columnWidths = {
758
- 0: multiSelect ? '50px' : '250px', // Select checkbox or Name
759
- 1: multiSelect ? '250px' : '180px', // Name or Site
760
- 2: multiSelect ? '180px' : '150px', // Site or Role
761
- 3: multiSelect ? '150px' : '180px', // Role or Manager
762
- 4: multiSelect ? '180px' : '300px', // Manager or Email
763
- 5: multiSelect ? '300px' : '150px', // Email or Start Date
764
- 6: multiSelect ? '150px' : '120px', // Start Date or Status
765
- 7: multiSelect ? '120px' : '100px', // Status or Hours/Week
766
- 8: multiSelect ? '100px' : '90px', // Hours/Week or Term Time
767
- 9: multiSelect ? '90px' : '90px', // Term Time or Maternity
768
- 10: '90px' // Maternity (only if multiSelect)
809
+ {headerGroup.headers.map((header) => {
810
+ // Widths keyed by column id/accessor for stability when columns are customized
811
+ const defaultWidthByKey = {
812
+ select: '50px',
813
+ full_name: multiSelect ? '250px' : '250px',
814
+ site_name: '180px',
815
+ role_name: '150px',
816
+ manager_name: '180px',
817
+ email: '300px',
818
+ start_date: '150px',
819
+ employee_status: '120px',
820
+ total_hours_per_week: '100px',
821
+ term_time_only: '90px',
822
+ on_maternity_leave: '90px',
769
823
  };
770
-
824
+ const key = header.column.columnDef.accessorKey || header.column.id;
825
+ const width = defaultWidthByKey[key] || '120px';
771
826
  return (
772
- <th
773
- key={header.id}
774
- className="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0"
775
- style={{ width: columnWidths[index] || '120px', minWidth: columnWidths[index] || '120px' }}
776
- >
777
- {header.isPlaceholder
778
- ? null
779
- : flexRender(
780
- header.column.columnDef.header,
781
- header.getContext()
782
- )}
783
- </th>
827
+ <th
828
+ key={header.id}
829
+ className="h-12 px-4 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0"
830
+ style={{ width, minWidth: width }}
831
+ >
832
+ {header.isPlaceholder
833
+ ? null
834
+ : flexRender(
835
+ header.column.columnDef.header,
836
+ header.getContext()
837
+ )}
838
+ </th>
784
839
  );
785
840
  })}
786
841
  </tr>
@@ -1,5 +1,4 @@
1
1
  // Mock for import.meta.env to handle Vite environment variables in Jest tests
2
2
  export default {
3
- VITE_COMMON_API_FUNCTION_KEY: 'test-key',
4
- VITE_COMMON_API_BASE_URL: 'https://test-api.example.com',
5
- };
3
+ VITE_APIM_SCOPE: 'api://test-scope/.default',
4
+ };
@@ -32,6 +32,14 @@ export function DateRangePicker({
32
32
  const [internalRange, setInternalRange] = useState(selectedRange)
33
33
  const isSelectingRange = useRef(false)
34
34
 
35
+ const normalizeDate = (date) =>
36
+ date
37
+ ? new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()))
38
+ : null
39
+
40
+ const datesEqual = (a, b) =>
41
+ a instanceof Date && b instanceof Date && a.getTime() === b.getTime()
42
+
35
43
  const handleOpenChange = (open) => {
36
44
  // If we're in the middle of selecting a range and trying to close, prevent it
37
45
  if (!open && isSelectingRange.current) {
@@ -59,90 +67,75 @@ export function DateRangePicker({
59
67
  setInternalRange(selectedRange)
60
68
  }, [selectedRange])
61
69
 
62
- const handleSelect = (range) => {
63
- console.log('🔍 handleSelect called with:', range)
64
- console.log('🔍 Current internalRange:', internalRange)
65
-
66
- // Normalize dates to avoid timezone issues
67
- const normalizedRange = range ? {
68
- from: range.from ? new Date(Date.UTC(range.from.getFullYear(), range.from.getMonth(), range.from.getDate())) : null,
69
- to: range.to ? new Date(Date.UTC(range.to.getFullYear(), range.to.getMonth(), range.to.getDate())) : null
70
- } : null
71
-
72
- console.log('🔍 Normalized range:', normalizedRange)
73
-
74
- // Implement cycling behavior: start date -> end date -> start date -> end date...
70
+ const handleSelect = (range, selectedDay) => {
71
+ if (!range || (!range.from && !range.to)) {
72
+ return
73
+ }
74
+
75
+ const normalizedRange = {
76
+ from: normalizeDate(range.from),
77
+ to: normalizeDate(range.to),
78
+ }
79
+ const normalizedSelectedDay = normalizeDate(selectedDay)
80
+
81
+ const hasCompleteCurrentRange = Boolean(
82
+ internalRange?.from && internalRange?.to
83
+ )
84
+
75
85
  let newRange = normalizedRange
76
-
77
- if (normalizedRange?.from && normalizedRange?.to) {
78
- // We have both dates from react-day-picker
79
- const currentFrom = internalRange?.from
80
- const currentTo = internalRange?.to
81
-
82
- console.log('🔍 Both dates provided - from:', normalizedRange.from, 'to:', normalizedRange.to)
83
- console.log('🔍 Current state - from:', currentFrom, 'to:', currentTo)
84
-
85
- if (currentFrom && currentTo) {
86
- // We have a complete range, so the next click should start a new cycle
87
- // The clicked date is the 'to' date, so use that as the new start date
88
- console.log('🔍 Complete range detected, starting new cycle')
89
- newRange = { from: normalizedRange.to, to: null }
90
- } else if (currentFrom && !currentTo) {
91
- // We have a start date but no end date, so this should complete the range
92
- console.log('🔍 Completing range with end date')
93
- newRange = { from: currentFrom, to: normalizedRange.to }
94
- } else {
95
- // No current selection, so this is the first click
96
- console.log('🔍 First click, setting start date')
97
- newRange = { from: normalizedRange.from, to: null }
86
+
87
+ if (normalizedRange.from && normalizedRange.to) {
88
+ if (hasCompleteCurrentRange && !isSelectingRange.current) {
89
+ const sameStart = datesEqual(normalizedRange.from, internalRange.from)
90
+
91
+ if (sameStart) {
92
+ // Adjusting end date on an existing range
93
+ newRange = {
94
+ from: internalRange.from,
95
+ to: normalizedRange.to,
96
+ }
97
+ } else {
98
+ // Starting a new range selection cycle
99
+ newRange = {
100
+ from: normalizedSelectedDay ?? normalizedRange.from,
101
+ to: null,
102
+ }
103
+ }
104
+ } else if (internalRange?.from && !internalRange?.to) {
105
+ // Completing an in-progress range
106
+ newRange = {
107
+ from: internalRange.from,
108
+ to: normalizedRange.to,
109
+ }
98
110
  }
99
- } else if (normalizedRange?.from && !normalizedRange?.to) {
100
- // We only have a from date (single date selection)
101
- const currentFrom = internalRange?.from
102
- const currentTo = internalRange?.to
103
-
104
- console.log('🔍 Only from date provided:', normalizedRange.from)
105
- console.log('🔍 Current state - from:', currentFrom, 'to:', currentTo)
106
-
107
- if (currentFrom && currentTo) {
108
- // We have a complete range, so the next click should start a new cycle
109
- console.log('🔍 Complete range detected, starting new cycle (single date)')
110
- newRange = { from: normalizedRange.from, to: null }
111
- } else if (currentFrom && !currentTo) {
112
- // We have a start date but no end date, so this should complete the range
113
- console.log('🔍 Completing range with end date (single date)')
114
- newRange = { from: currentFrom, to: normalizedRange.from }
111
+ } else if (normalizedRange.from && !normalizedRange.to) {
112
+ if (hasCompleteCurrentRange && !isSelectingRange.current) {
113
+ // New cycle starting from the selected day
114
+ newRange = {
115
+ from: normalizedSelectedDay ?? normalizedRange.from,
116
+ to: null,
117
+ }
115
118
  } else {
116
- // No current selection, so this is the first click
117
- console.log('🔍 First click, setting start date (single date)')
118
- newRange = { from: normalizedRange.from, to: null }
119
+ newRange = {
120
+ from: normalizedRange.from,
121
+ to: null,
122
+ }
119
123
  }
124
+ } else {
125
+ newRange = null
120
126
  }
121
-
122
- console.log('🔍 Final newRange:', newRange)
123
-
124
- // Update internal state immediately for UI responsiveness
127
+
125
128
  setInternalRange(newRange)
126
-
127
- // Update the selecting range flag
129
+
128
130
  if (newRange?.from && !newRange?.to) {
129
131
  isSelectingRange.current = true
130
- } else if (newRange?.from && newRange?.to) {
131
- isSelectingRange.current = false
132
132
  } else {
133
133
  isSelectingRange.current = false
134
134
  }
135
-
136
- // Only call onSelect when both dates are selected
137
- if (newRange?.from && newRange?.to) {
138
- onSelect(newRange)
139
- // Don't close the popover automatically - let user click outside to close
140
- } else if (!newRange?.from && !newRange?.to) {
141
- // Range cleared - call onSelect immediately
135
+
136
+ if (!newRange?.from || newRange?.to) {
142
137
  onSelect(newRange)
143
138
  }
144
- // Don't call onSelect when only first date is selected
145
- // Popover stays open until both dates are selected or user clicks outside
146
139
  }
147
140
 
148
141
  return (
@@ -200,10 +193,9 @@ export function DateRangePicker({
200
193
  mode="range"
201
194
  defaultMonth={internalRange?.from}
202
195
  selected={internalRange}
203
- onSelect={(range) => {
204
- // Only handle the selection if we have a valid range
196
+ onSelect={(range, selectedDay) => {
205
197
  if (range && (range.from || range.to)) {
206
- handleSelect(range)
198
+ handleSelect(range, selectedDay)
207
199
  }
208
200
  }}
209
201
  numberOfMonths={numberOfMonths}
@@ -289,7 +281,7 @@ export function DatePicker({
289
281
  }
290
282
 
291
283
  return (
292
- <Popover open={isOpen} onOpenChange={setIsOpen}>
284
+ <Popover open={isOpen} onOpenChange={setIsOpen} modal={false}>
293
285
  <PopoverTrigger asChild>
294
286
  <Button
295
287
  variant="outline"
@@ -320,4 +312,4 @@ export function DatePicker({
320
312
  </PopoverContent>
321
313
  </Popover>
322
314
  )
323
- }
315
+ }
@@ -29,7 +29,7 @@ function PopoverContent({
29
29
  align={align}
30
30
  sideOffset={sideOffset}
31
31
  className={cn(
32
- "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
32
+ "bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-[60] w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
33
33
  className
34
34
  )}
35
35
  {...props} />