@coreui/react-pro 4.1.2 → 4.2.0-beta.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.
Files changed (39) hide show
  1. package/README.md +1 -1
  2. package/dist/components/calendar/CCalendar.d.ts +89 -0
  3. package/dist/components/calendar/CCalendar.utils.d.ts +20 -0
  4. package/dist/components/datepicker/CDatePicker.d.ts +13 -0
  5. package/dist/components/daterangepicker/CDateRangePicker.d.ts +50 -0
  6. package/dist/components/dropdown/CDropdown.d.ts +10 -1
  7. package/dist/components/dropdown/CDropdownToggle.d.ts +4 -0
  8. package/dist/components/dropdown/CDropdownToggleCustom.d.ts +15 -0
  9. package/dist/components/modal/CModal.d.ts +1 -0
  10. package/dist/components/picker/CPicker.d.ts +37 -0
  11. package/dist/components/smart-table/CSmartTableInterface.d.ts +0 -9
  12. package/dist/components/timepicker/CTimePicker.d.ts +49 -0
  13. package/dist/components/timepicker/CTimePicker.utils.d.ts +4 -0
  14. package/dist/components/timepicker/CTimePickerRollCol.d.ts +11 -0
  15. package/dist/index.d.ts +5 -1
  16. package/dist/index.es.js +2976 -1148
  17. package/dist/index.es.js.map +1 -1
  18. package/dist/index.js +4691 -2859
  19. package/dist/index.js.map +1 -1
  20. package/package.json +10 -8
  21. package/src/components/accordion/CAccordionButton.tsx +2 -1
  22. package/src/components/calendar/CCalendar.tsx +482 -0
  23. package/src/components/calendar/CCalendar.utils.ts +183 -0
  24. package/src/components/datepicker/CDatePicker.tsx +49 -0
  25. package/src/components/daterangepicker/CDateRangePicker.tsx +366 -0
  26. package/src/components/dropdown/CDropdown.tsx +19 -24
  27. package/src/components/dropdown/CDropdownMenu.tsx +55 -3
  28. package/src/components/dropdown/CDropdownToggle.tsx +28 -5
  29. package/src/components/modal/CModal.tsx +42 -19
  30. package/src/components/modal/__tests__/CModal.spec.tsx +1 -1
  31. package/src/components/modal/__tests__/__snapshots__/CModal.spec.tsx.snap +2 -10
  32. package/src/components/picker/CPicker.tsx +142 -0
  33. package/src/components/smart-table/CSmartTable.tsx +44 -96
  34. package/src/components/smart-table/CSmartTableCleaner.tsx +3 -16
  35. package/src/components/smart-table/CSmartTableInterface.tsx +0 -9
  36. package/src/components/timepicker/CTimePicker.tsx +341 -0
  37. package/src/components/timepicker/CTimePicker.utils.ts +27 -0
  38. package/src/components/timepicker/CTimePickerRollCol.tsx +57 -0
  39. package/src/index.ts +8 -0
@@ -0,0 +1,183 @@
1
+ export const convertToLocalDate = (d: Date, locale: string, options = {}) =>
2
+ d.toLocaleDateString(locale, options)
3
+
4
+ export const convertToLocalTime = (d: Date, locale: string, options = {}) =>
5
+ d.toLocaleTimeString(locale, options)
6
+
7
+ export const createGroupsInArray = (arr: any[], numberOfGroups: number) => {
8
+ const perGroup = Math.ceil(arr.length / numberOfGroups)
9
+ return new Array(numberOfGroups)
10
+ .fill('')
11
+ .map((_, i) => arr.slice(i * perGroup, (i + 1) * perGroup))
12
+ }
13
+
14
+ export const getCurrentYear = () => new Date().getFullYear()
15
+
16
+ export const getCurrentMonth = () => new Date().getMonth()
17
+
18
+ export const getMonthName = (month: number, locale: string) => {
19
+ const d = new Date()
20
+ d.setDate(1)
21
+ d.setMonth(month)
22
+ return d.toLocaleString(locale, { month: 'long' })
23
+ }
24
+
25
+ export const getMonthsNames = (locale: string) => {
26
+ const months = []
27
+ const d = new Date()
28
+ d.setDate(1)
29
+
30
+ for (let i = 0; i < 12; i++) {
31
+ d.setMonth(i)
32
+ months.push(d.toLocaleString(locale, { month: 'short' }))
33
+ }
34
+
35
+ return months
36
+ }
37
+
38
+ export const getYears = (year: number) => {
39
+ const years = []
40
+ for (let _year = year - 6; _year < year + 6; _year++) {
41
+ years.push(_year)
42
+ }
43
+
44
+ return years
45
+ }
46
+
47
+ const getLeadingDays = (year: number, month: number, firstDayOfWeek: number) => {
48
+ // 0: sunday
49
+ // 1: monday
50
+ const dates = []
51
+ const d = new Date(year, month)
52
+ const y = d.getFullYear()
53
+ const m = d.getMonth()
54
+ const firstWeekday = new Date(y, m, 1).getDay()
55
+ let leadingDays = 6 - (6 - firstWeekday) - firstDayOfWeek
56
+
57
+ if (firstDayOfWeek) {
58
+ leadingDays = leadingDays < 0 ? 7 + leadingDays : leadingDays
59
+ }
60
+
61
+ for (let i = leadingDays * -1; i < 0; i++) {
62
+ dates.push({
63
+ date: new Date(y, m, i + 1),
64
+ month: 'previous',
65
+ })
66
+ }
67
+
68
+ return dates
69
+ }
70
+
71
+ const getMonthDays = (year: number, month: number) => {
72
+ const dates = []
73
+ const lastDay = new Date(year, month + 1, 0).getDate()
74
+ for (let i = 1; i <= lastDay; i++) {
75
+ dates.push({
76
+ date: new Date(year, month, i),
77
+ month: 'current',
78
+ })
79
+ }
80
+ return dates
81
+ }
82
+
83
+ const getTrailingDays = (
84
+ year: number,
85
+ month: number,
86
+ leadingDays: { date: Date; month: string }[],
87
+ monthDays: { date: Date; month: string }[],
88
+ ) => {
89
+ const dates = []
90
+ const days = 42 - (leadingDays.length + monthDays.length)
91
+ for (let i = 1; i <= days; i++) {
92
+ dates.push({
93
+ date: new Date(year, month + 1, i),
94
+ month: 'next',
95
+ })
96
+ }
97
+ return dates
98
+ }
99
+
100
+ export const getMonthDetails = (year: number, month: number, firstDayOfWeek: number) => {
101
+ const daysPrevMonth = getLeadingDays(year, month, firstDayOfWeek)
102
+ const daysThisMonth = getMonthDays(year, month)
103
+ const daysNextMonth = getTrailingDays(year, month, daysPrevMonth, daysThisMonth)
104
+ const days = [...daysPrevMonth, ...daysThisMonth, ...daysNextMonth]
105
+
106
+ const weeks: { date: Date; month: string }[][] = []
107
+
108
+ days.forEach((day, index) => {
109
+ if (index % 7 === 0 || weeks.length === 0) {
110
+ weeks.push([])
111
+ }
112
+ weeks[weeks.length - 1].push(day)
113
+ })
114
+
115
+ return weeks
116
+ }
117
+
118
+ export const isDisabled = (date: Date, min?: Date, max?: Date, dates?: Date[] | Date[][]) => {
119
+ let disabled
120
+ if (dates) {
121
+ dates.forEach((_date: Date | Date[]) => {
122
+ if (Array.isArray(_date)) {
123
+ if (isDateInRange(date, _date[0], _date[1])) {
124
+ disabled = true
125
+ }
126
+ }
127
+ if (_date instanceof Date) {
128
+ if (isSameDateAs(date, _date)) {
129
+ disabled = true
130
+ }
131
+ }
132
+ })
133
+ }
134
+ if (min && date < min) {
135
+ disabled = true
136
+ }
137
+
138
+ if (max && date > max) {
139
+ disabled = true
140
+ }
141
+ return disabled
142
+ }
143
+
144
+ export const isDateInRange = (date: Date, start: Date | null, end: Date | null) => {
145
+ return start && end && start <= date && date <= end
146
+ }
147
+
148
+ export const isDateSelected = (date: Date, start: Date | null, end: Date | null) => {
149
+ return (start && isSameDateAs(start, date)) || (end && isSameDateAs(end, date))
150
+ }
151
+
152
+ export const isEndDate = (date: Date, start: Date | null, end: Date | null) => {
153
+ return start && end && isSameDateAs(end, date) && start < end
154
+ }
155
+
156
+ export const isLastDayOfMonth = (date: Date) => {
157
+ const test = new Date(date.getTime())
158
+ const month = test.getMonth()
159
+
160
+ test.setDate(test.getDate() + 1)
161
+ return test.getMonth() !== month
162
+ }
163
+
164
+ export const isSameDateAs = (date: Date, date2: Date) => {
165
+ return (
166
+ date.getDate() == date2.getDate() &&
167
+ date.getMonth() == date2.getMonth() &&
168
+ date.getFullYear() == date2.getFullYear()
169
+ )
170
+ }
171
+
172
+ export const isStartDate = (date: Date, start: Date | null, end: Date | null) => {
173
+ return start && end && isSameDateAs(start, date) && start < end
174
+ }
175
+
176
+ export const isToday = (date: Date) => {
177
+ const today = new Date()
178
+ return (
179
+ date.getDate() == today.getDate() &&
180
+ date.getMonth() == today.getMonth() &&
181
+ date.getFullYear() == today.getFullYear()
182
+ )
183
+ }
@@ -0,0 +1,49 @@
1
+ import React, { forwardRef } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import { CDateRangePicker, CDateRangePickerProps } from '../daterangepicker/CDateRangePicker'
4
+
5
+ export interface CDatePickerProps
6
+ extends Omit<
7
+ CDateRangePickerProps,
8
+ | 'calendars'
9
+ | 'endDate'
10
+ | 'range'
11
+ | 'onEndDateChange'
12
+ | 'onStartDateChange'
13
+ | 'ranges'
14
+ | 'selectEndDate'
15
+ | 'startDate'
16
+ > {
17
+ /**
18
+ * Initial selected date.
19
+ */
20
+ date?: Date | string | null
21
+ /**
22
+ * Callback fired when the date changed.
23
+ */
24
+ onDateChange?: (date: Date | null) => void
25
+ }
26
+
27
+ export const CDatePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDatePickerProps>(
28
+ ({ date, onDateChange, placeholder = 'Select date', ...rest }, ref) => {
29
+ return (
30
+ <CDateRangePicker
31
+ calendars={1}
32
+ startDate={date}
33
+ onStartDateChange={onDateChange}
34
+ placeholder={placeholder}
35
+ range={false}
36
+ ref={ref}
37
+ {...rest}
38
+ />
39
+ )
40
+ },
41
+ )
42
+
43
+ CDatePicker.displayName = 'CDatePicker'
44
+
45
+ CDatePicker.propTypes = {
46
+ ...CDateRangePicker.propTypes,
47
+ date: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
48
+ onDateChange: PropTypes.func,
49
+ }
@@ -0,0 +1,366 @@
1
+ import React, { forwardRef, ReactNode, useEffect, useRef, useState } from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import classNames from 'classnames'
4
+ import { isMobile } from 'react-device-detect'
5
+
6
+ import { CCalendar, CCalendarProps } from '../calendar/CCalendar'
7
+ import { CPicker, CPickerProps } from './../picker/CPicker'
8
+ import { CTimePicker } from './../timepicker/CTimePicker'
9
+ import { convertToLocalDate } from '../calendar/CCalendar.utils'
10
+ import { CButton } from '../button/CButton'
11
+ import { CFormInput } from '../form/CFormInput'
12
+ import { CInputGroup } from '../form/CInputGroup'
13
+ import { CInputGroupText } from '../form/CInputGroupText'
14
+
15
+ export interface CDateRangePickerProps
16
+ extends Omit<CPickerProps, 'autoClose' | 'component' | 'dark' | 'placeholder' | 'variant'>,
17
+ Omit<CCalendarProps, 'onCalendarCellHover' | 'onCalendarDateChange'> {
18
+ /**
19
+ * The number of calendars that render on desktop devices.
20
+ */
21
+ calendars?: number
22
+ /**
23
+ * Enables selection cleaner element.
24
+ */
25
+ cleaner?: boolean
26
+ /**
27
+ * Cleaner icon.
28
+ */
29
+ cleanerIcon?: ReactNode | boolean
30
+ /**
31
+ * Default input icon.
32
+ */
33
+ defaultIcon?: ReactNode | boolean
34
+ /**
35
+ * Default icon between date input fields.
36
+ */
37
+ infixIcon?: ReactNode | boolean
38
+ /**
39
+ * Toggle the readonly state for the component.
40
+ */
41
+ inputReadOnly?: boolean
42
+ /**
43
+ * Specifies short hints that are visible in start date and end date inputs.
44
+ */
45
+ placeholder?: string | string[]
46
+ /**
47
+ * @ignore
48
+ */
49
+ range?: boolean
50
+ /**
51
+ * Predefined date ranges the user can select from.
52
+ */
53
+ ranges?: object
54
+ /**
55
+ * Size the component small or large.
56
+ */
57
+ size?: 'sm' | 'lg'
58
+ /**
59
+ * Provide an additional time selection by adding select boxes to choose times.
60
+ */
61
+ timepicker?: boolean
62
+ }
63
+
64
+ export const CDateRangePicker = forwardRef<HTMLDivElement | HTMLLIElement, CDateRangePickerProps>(
65
+ (
66
+ {
67
+ calendars = 2,
68
+ className,
69
+ cleaner = true,
70
+ cleanerIcon,
71
+ calendarDate,
72
+ disabled,
73
+ disabledDates,
74
+ endDate,
75
+ defaultIcon,
76
+ infixIcon,
77
+ inputReadOnly,
78
+ locale = typeof navigator === 'undefined' ? 'en-US' : navigator.language,
79
+ maxDate,
80
+ minDate,
81
+ onEndDateChange,
82
+ onHide,
83
+ onStartDateChange,
84
+ onShow,
85
+ placeholder = ['Start date', 'End date'],
86
+ range = true,
87
+ ranges,
88
+ size,
89
+ startDate,
90
+ timepicker,
91
+ toggler,
92
+ visible,
93
+ ...rest
94
+ },
95
+ ref,
96
+ ) => {
97
+ const inputStartRef = useRef<HTMLInputElement>(null)
98
+ const inputEndRef = useRef<HTMLInputElement>(null)
99
+ const [_visible, setVisible] = useState(visible)
100
+ const [_startDate, setStartDate] = useState<Date | null>(startDate ? new Date(startDate) : null)
101
+ const [_endDate, setEndDate] = useState<Date | null>(endDate ? new Date(endDate) : null)
102
+ const [inputHoverValue, setInputHoverValue] = useState<Date | null>(null)
103
+ const [inputToHoverValue, setInputToHoverValue] = useState<Date | null>(null)
104
+ const [selectEndDate, setSelectEndDate] = useState(false)
105
+ const [_calendarDate, setCalendarDate] = useState<Date>(
106
+ calendarDate ? new Date(calendarDate) : new Date(),
107
+ )
108
+
109
+ useEffect(() => {
110
+ startDate && setCalendarDate(new Date(startDate))
111
+ }, [startDate])
112
+
113
+ useEffect(() => {
114
+ endDate && setCalendarDate(new Date(endDate))
115
+ }, [endDate])
116
+
117
+ useEffect(() => {
118
+ _visible && onShow && onShow()
119
+ !_visible && onHide && onHide()
120
+ }, [_visible])
121
+
122
+ useEffect(() => {
123
+ if (inputStartRef.current) {
124
+ if (inputHoverValue) {
125
+ inputStartRef.current.value = convertToLocalDate(inputHoverValue, locale)
126
+ return
127
+ }
128
+ if (_startDate) {
129
+ inputStartRef.current.value = timepicker
130
+ ? _startDate.toLocaleString(locale)
131
+ : _startDate.toLocaleDateString(locale)
132
+ return
133
+ }
134
+ inputStartRef.current.value = ''
135
+ }
136
+ }, [inputHoverValue, _startDate])
137
+
138
+ useEffect(() => {
139
+ if (inputEndRef.current) {
140
+ if (inputToHoverValue) {
141
+ inputEndRef.current.value = convertToLocalDate(inputToHoverValue, locale)
142
+ return
143
+ }
144
+ if (_endDate) {
145
+ inputEndRef.current.value = timepicker
146
+ ? _endDate.toLocaleString(locale)
147
+ : _endDate.toLocaleDateString(locale)
148
+ return
149
+ }
150
+ inputEndRef.current.value = ''
151
+ }
152
+ }, [inputToHoverValue, _endDate])
153
+
154
+ const handleCalendarDateChange = (date: Date, difference?: number) => {
155
+ difference
156
+ ? setCalendarDate(new Date(date.getFullYear(), date.getMonth() - difference, 1))
157
+ : setCalendarDate(date)
158
+ }
159
+
160
+ const handleStartDateChange = (date: Date | null) => {
161
+ setStartDate(date)
162
+ range && setSelectEndDate(true)
163
+ onStartDateChange && onStartDateChange(date)
164
+ setInputHoverValue(null)
165
+ }
166
+
167
+ const handleEndDateChange = (date: Date | null) => {
168
+ setEndDate(date)
169
+ setSelectEndDate(false)
170
+ onEndDateChange && onEndDateChange(date)
171
+ setInputToHoverValue(null)
172
+
173
+ if (timepicker !== true) {
174
+ _startDate !== null && setVisible(false)
175
+ }
176
+ }
177
+
178
+ const handleCalendarCellHover = (date: Date | null) => {
179
+ selectEndDate ? setInputToHoverValue(date) : setInputHoverValue(date)
180
+ }
181
+
182
+ const handleClear = (event: React.MouseEvent<HTMLElement>) => {
183
+ event.stopPropagation()
184
+ setStartDate(null)
185
+ setEndDate(null)
186
+ setInputHoverValue(null)
187
+ setInputToHoverValue(null)
188
+ }
189
+
190
+ const _className = classNames('date-picker', className)
191
+
192
+ const InputGroup = () => (
193
+ <CInputGroup className="picker-input-group" size={size}>
194
+ <CFormInput
195
+ className={classNames({
196
+ hover: inputHoverValue,
197
+ })}
198
+ disabled={disabled}
199
+ placeholder={Array.isArray(placeholder) ? placeholder[0] : placeholder}
200
+ readOnly={inputReadOnly}
201
+ onClick={() => setSelectEndDate(false)}
202
+ ref={inputStartRef}
203
+ />
204
+ {range && infixIcon !== false && (
205
+ <CInputGroupText>
206
+ {infixIcon ? (
207
+ infixIcon
208
+ ) : (
209
+ <span className="picker-input-group-icon date-picker-arrow-icon" />
210
+ )}
211
+ </CInputGroupText>
212
+ )}
213
+ {range && (
214
+ <CFormInput
215
+ className={classNames({
216
+ hover: inputToHoverValue,
217
+ })}
218
+ disabled={disabled}
219
+ placeholder={placeholder[1]}
220
+ readOnly={inputReadOnly}
221
+ onClick={() => setSelectEndDate(true)}
222
+ ref={inputEndRef}
223
+ />
224
+ )}
225
+ {(defaultIcon !== false || cleaner) && (
226
+ <CInputGroupText>
227
+ {defaultIcon !== false && (
228
+ <span className="picker-input-group-indicator">
229
+ {defaultIcon ? (
230
+ defaultIcon
231
+ ) : (
232
+ <span className="picker-input-group-icon date-picker-input-icon" />
233
+ )}
234
+ </span>
235
+ )}
236
+ {cleaner && _startDate && (
237
+ <span
238
+ className="picker-input-group-cleaner"
239
+ role="button"
240
+ onClick={(event) => handleClear(event)}
241
+ >
242
+ {cleanerIcon ? (
243
+ cleanerIcon
244
+ ) : (
245
+ <span className="picker-input-group-icon date-picker-cleaner-icon" />
246
+ )}
247
+ </span>
248
+ )}
249
+ </CInputGroupText>
250
+ )}
251
+ </CInputGroup>
252
+ )
253
+
254
+ return (
255
+ <CPicker
256
+ className={_className}
257
+ disabled={disabled}
258
+ footer={timepicker}
259
+ toggler={toggler ? toggler : InputGroup()}
260
+ onCancelClick={() => {
261
+ handleStartDateChange(startDate ? new Date(startDate) : null)
262
+ handleEndDateChange(endDate ? new Date(endDate) : null)
263
+ }}
264
+ visible={_visible}
265
+ {...rest}
266
+ ref={ref}
267
+ >
268
+ <div className="date-picker-body">
269
+ {ranges && (
270
+ <div className="date-picker-ranges">
271
+ {Object.keys(ranges).map((key: string, index: number) => (
272
+ <CButton
273
+ color="secondary"
274
+ key={index}
275
+ onClick={() => {
276
+ setStartDate(ranges[key][0])
277
+ setEndDate(ranges[key][1])
278
+ }}
279
+ variant="ghost"
280
+ >
281
+ {key}
282
+ </CButton>
283
+ ))}
284
+ </div>
285
+ )}
286
+ <div className="date-picker-calendars">
287
+ {[...Array(isMobile ? 1 : calendars)].map((_, index) => (
288
+ <div className="date-picker-calendar" key={index}>
289
+ <CCalendar
290
+ calendarDate={
291
+ new Date(_calendarDate.getFullYear(), _calendarDate.getMonth() + index, 1)
292
+ }
293
+ disabledDates={disabledDates}
294
+ endDate={_endDate}
295
+ locale={locale}
296
+ maxDate={maxDate}
297
+ minDate={minDate}
298
+ range={range}
299
+ selectEndDate={selectEndDate}
300
+ startDate={_startDate}
301
+ onCalendarCellHover={(date) => handleCalendarCellHover(date)}
302
+ onCalendarDateChange={(date) => handleCalendarDateChange(date, index)}
303
+ onStartDateChange={(date) => handleStartDateChange(date)}
304
+ onEndDateChange={(date) => handleEndDateChange(date)}
305
+ />
306
+ {!isMobile && timepicker && (index === 0 || calendars - index === 1) && (
307
+ <CTimePicker
308
+ container="inline"
309
+ disabled={
310
+ index === 0
311
+ ? _startDate === null
312
+ ? true
313
+ : false
314
+ : _endDate === null
315
+ ? true
316
+ : false
317
+ }
318
+ locale={locale}
319
+ onChange={(_, __, date) =>
320
+ index === 0 ? handleStartDateChange(date) : handleEndDateChange(date)
321
+ }
322
+ value={index === 0 ? _startDate : _endDate}
323
+ variant="select"
324
+ />
325
+ )}
326
+ {isMobile && timepicker && (
327
+ <>
328
+ <CTimePicker
329
+ container="inline"
330
+ disabled={_startDate === null ? true : false}
331
+ locale={locale}
332
+ onChange={(_, __, date) => handleStartDateChange(date)}
333
+ value={_startDate}
334
+ variant="select"
335
+ />
336
+ <CTimePicker
337
+ container="inline"
338
+ disabled={_endDate === null ? true : false}
339
+ locale={locale}
340
+ onChange={(_, __, date) => handleEndDateChange(date)}
341
+ value={_endDate}
342
+ variant="select"
343
+ />
344
+ </>
345
+ )}
346
+ </div>
347
+ ))}
348
+ </div>
349
+ </div>
350
+ </CPicker>
351
+ )
352
+ },
353
+ )
354
+
355
+ CDateRangePicker.displayName = 'CDateRangePicker'
356
+
357
+ CDateRangePicker.propTypes = {
358
+ ...CCalendar.propTypes,
359
+ ...CPicker.propTypes,
360
+ calendars: PropTypes.number,
361
+ inputReadOnly: PropTypes.bool,
362
+ placeholder: PropTypes.oneOfType([PropTypes.string, PropTypes.arrayOf(PropTypes.any)]),
363
+ range: PropTypes.bool,
364
+ ranges: PropTypes.object,
365
+ timepicker: PropTypes.bool,
366
+ }
@@ -3,6 +3,7 @@ import React, {
3
3
  ElementType,
4
4
  forwardRef,
5
5
  HTMLAttributes,
6
+ RefObject,
6
7
  useEffect,
7
8
  useRef,
8
9
  useState,
@@ -33,6 +34,14 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
33
34
  * @type 'start' | 'end' | { xs: 'start' | 'end' } | { sm: 'start' | 'end' } | { md: 'start' | 'end' } | { lg: 'start' | 'end' } | { xl: 'start' | 'end'} | { xxl: 'start' | 'end'}
34
35
  */
35
36
  alignment?: Alignments
37
+ /**
38
+ * Configure the auto close behavior of the dropdown:
39
+ * - `true` - the dropdown will be closed by clicking outside or inside the dropdown menu.
40
+ * - `false` - the dropdown will be closed by clicking the toggle button and manually calling hide or toggle method. (Also will not be closed by pressing esc key)
41
+ * - `'inside'` - the dropdown will be closed (only) by clicking inside the dropdown menu.
42
+ * - `'outside'` - the dropdown will be closed (only) by clicking outside the dropdown menu.
43
+ */
44
+ autoClose?: 'inside' | 'outside' | boolean
36
45
  /**
37
46
  * A string of all className you want applied to the base component.
38
47
  */
@@ -80,6 +89,8 @@ export interface CDropdownProps extends HTMLAttributes<HTMLDivElement | HTMLLIEl
80
89
  }
81
90
 
82
91
  interface ContextProps extends CDropdownProps {
92
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
93
+ dropdownToggleRef: RefObject<any> | undefined
83
94
  setVisible: React.Dispatch<React.SetStateAction<boolean | undefined>>
84
95
  }
85
96
 
@@ -90,6 +101,7 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
90
101
  {
91
102
  children,
92
103
  alignment,
104
+ autoClose = true,
93
105
  className,
94
106
  dark,
95
107
  direction,
@@ -106,6 +118,7 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
106
118
  ) => {
107
119
  const [_visible, setVisible] = useState(visible)
108
120
  const dropdownRef = useRef<HTMLDivElement>(null)
121
+ const dropdownToggleRef = useRef(null)
109
122
  const forkedRef = useForkedRef(ref, dropdownRef)
110
123
 
111
124
  const Component = variant === 'nav-item' ? 'li' : component
@@ -117,8 +130,10 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
117
130
 
118
131
  const contextValues = {
119
132
  alignment,
133
+ autoClose,
120
134
  dark,
121
135
  direction: direction,
136
+ dropdownToggleRef,
122
137
  placement: placement,
123
138
  popper,
124
139
  variant,
@@ -135,19 +150,6 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
135
150
  className,
136
151
  )
137
152
 
138
- useEffect(() => {
139
- _visible &&
140
- setTimeout(() => {
141
- window.addEventListener('click', handleClickOutside)
142
- window.addEventListener('keyup', handleKeyup)
143
- })
144
-
145
- return () => {
146
- window.removeEventListener('click', handleClickOutside)
147
- window.removeEventListener('keyup', handleKeyup)
148
- }
149
- }, [_visible])
150
-
151
153
  useEffect(() => {
152
154
  setVisible(visible)
153
155
  }, [visible])
@@ -157,17 +159,6 @@ export const CDropdown = forwardRef<HTMLDivElement | HTMLLIElement, CDropdownPro
157
159
  !_visible && onHide && onHide()
158
160
  }, [_visible])
159
161
 
160
- const handleKeyup = (event: Event) => {
161
- if (!dropdownRef.current?.contains(event.target as HTMLElement)) {
162
- setVisible(false)
163
- }
164
- }
165
- const handleClickOutside = (event: Event) => {
166
- if (!dropdownRef.current?.contains(event.target as HTMLElement)) {
167
- setVisible(false)
168
- }
169
- }
170
-
171
162
  const dropdownContent = () => {
172
163
  return variant === 'input-group' ? (
173
164
  <>{children}</>
@@ -203,6 +194,10 @@ CDropdown.propTypes = {
203
194
  PropTypes.shape({ xl: alignmentDirection }),
204
195
  PropTypes.shape({ xxl: alignmentDirection }),
205
196
  ]),
197
+ autoClose: PropTypes.oneOfType([
198
+ PropTypes.bool,
199
+ PropTypes.oneOf<'inside' | 'outside'>(['inside', 'outside']),
200
+ ]),
206
201
  children: PropTypes.node,
207
202
  className: PropTypes.string,
208
203
  component: PropTypes.elementType,