@chassis-ui/react 0.0.0-20260831090525
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/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/index.d.ts +5909 -0
- package/dist/index.js +2 -0
- package/dist/style.css +1 -0
- package/package.json +114 -0
- package/src/components/accordion/Accordion.tsx +190 -0
- package/src/components/accordion/AccordionBody.tsx +46 -0
- package/src/components/accordion/AccordionHeader.tsx +21 -0
- package/src/components/accordion/AccordionItem.tsx +59 -0
- package/src/components/accordion/context.ts +8 -0
- package/src/components/accordion/index.ts +8 -0
- package/src/components/autocomplete/Autocomplete.tsx +464 -0
- package/src/components/autocomplete/AutocompleteGroup.tsx +20 -0
- package/src/components/autocomplete/AutocompleteItem.tsx +18 -0
- package/src/components/autocomplete/index.ts +6 -0
- package/src/components/avatar/Avatar.tsx +134 -0
- package/src/components/avatar/AvatarImage.tsx +19 -0
- package/src/components/avatar/AvatarStack.tsx +98 -0
- package/src/components/avatar/index.ts +6 -0
- package/src/components/badge/Badge.tsx +94 -0
- package/src/components/badge/index.ts +2 -0
- package/src/components/breadcrumb/Breadcrumb.tsx +59 -0
- package/src/components/breadcrumb/BreadcrumbItem.tsx +38 -0
- package/src/components/breadcrumb/index.ts +4 -0
- package/src/components/button/Button.tsx +229 -0
- package/src/components/button/index.ts +2 -0
- package/src/components/button-group/ButtonGroup.tsx +55 -0
- package/src/components/button-group/ButtonToolbar.tsx +47 -0
- package/src/components/button-group/index.ts +4 -0
- package/src/components/calendar/Calendar.scss +57 -0
- package/src/components/calendar/Calendar.tsx +324 -0
- package/src/components/calendar/CalendarMonthBlock.tsx +57 -0
- package/src/components/calendar/CalendarMonthGrid.tsx +92 -0
- package/src/components/calendar/CalendarMonthYearPicker.tsx +162 -0
- package/src/components/calendar/CalendarNavButton.tsx +24 -0
- package/src/components/calendar/CalendarWeekGrid.tsx +58 -0
- package/src/components/calendar/CalendarYearGrid.tsx +126 -0
- package/src/components/calendar/RangeCalendar.scss +95 -0
- package/src/components/calendar/RangeCalendar.tsx +361 -0
- package/src/components/calendar/index.ts +5 -0
- package/src/components/card/Card.tsx +173 -0
- package/src/components/card/CardBody.tsx +75 -0
- package/src/components/card/CardFooter.tsx +47 -0
- package/src/components/card/CardGroup.tsx +51 -0
- package/src/components/card/CardHeader.tsx +47 -0
- package/src/components/card/CardImage.tsx +70 -0
- package/src/components/card/CardImageOverlay.tsx +47 -0
- package/src/components/card/CardLink.tsx +40 -0
- package/src/components/card/CardSubtitle.tsx +47 -0
- package/src/components/card/CardText.tsx +47 -0
- package/src/components/card/CardTitle.tsx +47 -0
- package/src/components/card/index.ts +22 -0
- package/src/components/carousel/Carousel.tsx +742 -0
- package/src/components/carousel/CarouselControlButton.tsx +74 -0
- package/src/components/carousel/CarouselControlNext.tsx +22 -0
- package/src/components/carousel/CarouselControlPrev.tsx +22 -0
- package/src/components/carousel/CarouselIndicators.tsx +44 -0
- package/src/components/carousel/CarouselInner.tsx +56 -0
- package/src/components/carousel/CarouselItem.tsx +51 -0
- package/src/components/carousel/CarouselOverlay.tsx +51 -0
- package/src/components/carousel/CarouselPlayPause.tsx +61 -0
- package/src/components/carousel/carouselEngine.ts +170 -0
- package/src/components/carousel/context.ts +29 -0
- package/src/components/carousel/index.ts +21 -0
- package/src/components/checkbox/Checkbox.tsx +236 -0
- package/src/components/checkbox/CheckboxGroup.tsx +145 -0
- package/src/components/checkbox/context.ts +14 -0
- package/src/components/checkbox/index.ts +4 -0
- package/src/components/chip/Chip.tsx +183 -0
- package/src/components/chip/index.ts +2 -0
- package/src/components/chip-input/ChipInput.tsx +363 -0
- package/src/components/chip-input/ChipList.tsx +95 -0
- package/src/components/chip-input/index.ts +2 -0
- package/src/components/close-button/CloseButton.tsx +205 -0
- package/src/components/close-button/index.ts +2 -0
- package/src/components/collapse/Collapse.tsx +128 -0
- package/src/components/collapse/index.ts +2 -0
- package/src/components/color-input/ColorInput.tsx +106 -0
- package/src/components/color-input/index.ts +2 -0
- package/src/components/combobox/Combobox.tsx +320 -0
- package/src/components/combobox/ComboboxGroup.tsx +27 -0
- package/src/components/combobox/ComboboxItem.tsx +44 -0
- package/src/components/combobox/ComboboxListBox.tsx +98 -0
- package/src/components/combobox/index.ts +6 -0
- package/src/components/datepicker/CalendarToggleButton.tsx +58 -0
- package/src/components/datepicker/ClearButton.tsx +35 -0
- package/src/components/datepicker/DateField.tsx +54 -0
- package/src/components/datepicker/DatePicker.scss +51 -0
- package/src/components/datepicker/DatePicker.tsx +574 -0
- package/src/components/datepicker/DateRangePicker.scss +11 -0
- package/src/components/datepicker/DateRangePicker.tsx +343 -0
- package/src/components/datepicker/index.ts +4 -0
- package/src/components/datepicker/renderDatePickerShell.tsx +104 -0
- package/src/components/drawer/Drawer.tsx +211 -0
- package/src/components/drawer/DrawerBody.tsx +47 -0
- package/src/components/drawer/DrawerFooter.tsx +51 -0
- package/src/components/drawer/DrawerHeader.tsx +60 -0
- package/src/components/drawer/DrawerTitle.tsx +49 -0
- package/src/components/drawer/index.ts +10 -0
- package/src/components/file-input/FileInput.tsx +100 -0
- package/src/components/file-input/index.ts +2 -0
- package/src/components/flex/Flex.tsx +134 -0
- package/src/components/flex/index.ts +2 -0
- package/src/components/floating-input/FloatingInput.tsx +94 -0
- package/src/components/floating-input/index.ts +2 -0
- package/src/components/form/Form.tsx +26 -0
- package/src/components/form/FormFeedback.tsx +64 -0
- package/src/components/form/FormHelp.tsx +46 -0
- package/src/components/form/FormLabel.tsx +26 -0
- package/src/components/form/index.ts +8 -0
- package/src/components/form/renderFormCheck.tsx +86 -0
- package/src/components/form-field/FormField.tsx +84 -0
- package/src/components/form-field/index.ts +2 -0
- package/src/components/form-field/renderFormField.tsx +74 -0
- package/src/components/grid/Col.tsx +69 -0
- package/src/components/grid/Container.tsx +59 -0
- package/src/components/grid/Grid.tsx +86 -0
- package/src/components/grid/GridItem.tsx +123 -0
- package/src/components/grid/Row.tsx +71 -0
- package/src/components/grid/gap.ts +8 -0
- package/src/components/grid/index.ts +10 -0
- package/src/components/icon/Icon.tsx +74 -0
- package/src/components/icon/index.ts +2 -0
- package/src/components/input-adorn/InputAdorn.tsx +55 -0
- package/src/components/input-adorn/index.ts +2 -0
- package/src/components/input-group/InputGroup.tsx +50 -0
- package/src/components/input-group/InputGroupAddon.tsx +50 -0
- package/src/components/input-group/index.ts +4 -0
- package/src/components/link/Link.tsx +148 -0
- package/src/components/link/index.ts +2 -0
- package/src/components/list/List.tsx +193 -0
- package/src/components/list/ListItem.tsx +91 -0
- package/src/components/list/index.ts +4 -0
- package/src/components/menu/Menu.tsx +398 -0
- package/src/components/menu/MenuDivider.tsx +19 -0
- package/src/components/menu/MenuHeader.tsx +55 -0
- package/src/components/menu/MenuItem.tsx +85 -0
- package/src/components/menu/MenuItemDef.ts +83 -0
- package/src/components/menu/MenuList.tsx +142 -0
- package/src/components/menu/MenuSubmenu.tsx +284 -0
- package/src/components/menu/MenuSubmenuBack.tsx +39 -0
- package/src/components/menu/MenuText.tsx +47 -0
- package/src/components/menu/MenuToggle.tsx +148 -0
- package/src/components/menu/index.ts +19 -0
- package/src/components/menu/menuNavigation.ts +106 -0
- package/src/components/menu/submenuGroup.ts +43 -0
- package/src/components/modal/Modal.tsx +162 -0
- package/src/components/modal/ModalBody.tsx +47 -0
- package/src/components/modal/ModalFooter.tsx +51 -0
- package/src/components/modal/ModalHeader.tsx +60 -0
- package/src/components/modal/ModalTitle.tsx +49 -0
- package/src/components/modal/index.ts +10 -0
- package/src/components/nav/Nav.tsx +97 -0
- package/src/components/nav/NavItem.tsx +42 -0
- package/src/components/nav/NavLink.tsx +48 -0
- package/src/components/nav/NavTitle.tsx +22 -0
- package/src/components/nav/index.ts +7 -0
- package/src/components/navbar/Navbar.tsx +112 -0
- package/src/components/navbar/NavbarBrand.tsx +55 -0
- package/src/components/navbar/NavbarNav.tsx +47 -0
- package/src/components/navbar/NavbarText.tsx +47 -0
- package/src/components/navbar/NavbarToggler.tsx +34 -0
- package/src/components/navbar/index.ts +10 -0
- package/src/components/notification/Notification.scss +15 -0
- package/src/components/notification/Notification.tsx +187 -0
- package/src/components/notification/NotificationIcon.tsx +21 -0
- package/src/components/notification/NotificationStack.tsx +66 -0
- package/src/components/notification/NotificationText.tsx +47 -0
- package/src/components/notification/NotificationTitle.tsx +47 -0
- package/src/components/notification/context.ts +14 -0
- package/src/components/notification/index.ts +17 -0
- package/src/components/notification/notificationQueue.ts +48 -0
- package/src/components/otp-input/OtpBox.tsx +68 -0
- package/src/components/otp-input/OtpInput.tsx +341 -0
- package/src/components/otp-input/index.ts +2 -0
- package/src/components/pagination/Pagination.tsx +199 -0
- package/src/components/pagination/PaginationItem.tsx +133 -0
- package/src/components/pagination/index.ts +4 -0
- package/src/components/password-strength/PasswordStrength.tsx +155 -0
- package/src/components/password-strength/index.ts +2 -0
- package/src/components/password-strength/strengthScore.ts +79 -0
- package/src/components/placeholder/Placeholder.tsx +178 -0
- package/src/components/placeholder/index.ts +2 -0
- package/src/components/popover/Popover.tsx +243 -0
- package/src/components/popover/index.ts +2 -0
- package/src/components/progress/Progress.tsx +154 -0
- package/src/components/progress/ProgressBar.tsx +88 -0
- package/src/components/progress/index.ts +4 -0
- package/src/components/radio/Radio.tsx +88 -0
- package/src/components/radio/RadioGroup.tsx +142 -0
- package/src/components/radio/context.ts +14 -0
- package/src/components/radio/index.ts +4 -0
- package/src/components/range-input/RangeInput.tsx +109 -0
- package/src/components/range-input/index.ts +2 -0
- package/src/components/select/Select.tsx +277 -0
- package/src/components/select/index.ts +2 -0
- package/src/components/skeleton/Skeleton.tsx +89 -0
- package/src/components/skeleton/SkeletonLoader.tsx +91 -0
- package/src/components/skeleton/index.ts +4 -0
- package/src/components/spinner/Spinner.tsx +77 -0
- package/src/components/spinner/index.ts +2 -0
- package/src/components/stack/Stack.tsx +81 -0
- package/src/components/stack/index.ts +2 -0
- package/src/components/stepper/Stepper.tsx +172 -0
- package/src/components/stepper/StepperItem.tsx +66 -0
- package/src/components/stepper/index.ts +4 -0
- package/src/components/switch/Switch.tsx +246 -0
- package/src/components/switch/index.ts +2 -0
- package/src/components/table/Table.css +39 -0
- package/src/components/table/Table.tsx +390 -0
- package/src/components/table/TableBody.tsx +25 -0
- package/src/components/table/TableCell.tsx +27 -0
- package/src/components/table/TableColumn.tsx +31 -0
- package/src/components/table/TableHeader.tsx +26 -0
- package/src/components/table/TableRow.tsx +24 -0
- package/src/components/table/index.ts +12 -0
- package/src/components/tabs/Tab.tsx +36 -0
- package/src/components/tabs/TabList.tsx +76 -0
- package/src/components/tabs/TabPanel.tsx +68 -0
- package/src/components/tabs/Tabs.tsx +136 -0
- package/src/components/tabs/context.ts +24 -0
- package/src/components/tabs/index.ts +8 -0
- package/src/components/text-input/TextInput.tsx +188 -0
- package/src/components/text-input/index.ts +2 -0
- package/src/components/textarea/Textarea.tsx +145 -0
- package/src/components/textarea/index.ts +2 -0
- package/src/components/toast/Toast.tsx +219 -0
- package/src/components/toast/ToastBody.tsx +64 -0
- package/src/components/toast/ToastFooter.tsx +47 -0
- package/src/components/toast/ToastHeader.tsx +104 -0
- package/src/components/toast/ToastIcon.tsx +21 -0
- package/src/components/toast/Toaster.tsx +87 -0
- package/src/components/toast/context.ts +14 -0
- package/src/components/toast/index.ts +18 -0
- package/src/components/toast/toastQueue.ts +56 -0
- package/src/components/tooltip/Tooltip.tsx +155 -0
- package/src/components/tooltip/index.ts +2 -0
- package/src/global.d.ts +2 -0
- package/src/hooks/index.ts +57 -0
- package/src/hooks/useAutoDismiss.ts +88 -0
- package/src/hooks/useButtonSemantics.ts +33 -0
- package/src/hooks/useControllableState.ts +24 -0
- package/src/hooks/useDialogElement.ts +216 -0
- package/src/hooks/useDisabledAnchorGuard.ts +22 -0
- package/src/hooks/useDismissibleTransition.ts +126 -0
- package/src/hooks/useDrawer.ts +9 -0
- package/src/hooks/useFloatingOverlay.ts +115 -0
- package/src/hooks/useForkedRef.ts +50 -0
- package/src/hooks/useFormField.ts +89 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +9 -0
- package/src/hooks/useModal.ts +9 -0
- package/src/hooks/useNotification.ts +10 -0
- package/src/hooks/useOverlayPlacement.ts +93 -0
- package/src/hooks/usePagination.ts +85 -0
- package/src/hooks/useToast.ts +9 -0
- package/src/hooks/useToastRegionQueue.ts +27 -0
- package/src/index.ts +276 -0
- package/src/types.ts +133 -0
- package/src/utils/breakpoints.ts +43 -0
- package/src/utils/comboboxCollection.tsx +152 -0
- package/src/utils/dateRangePresets.ts +6 -0
- package/src/utils/dialogTransition.ts +39 -0
- package/src/utils/focusRedirect.ts +29 -0
- package/src/utils/isWholeUnitDisabled.ts +17 -0
- package/src/utils/mergeIsDateUnavailable.ts +15 -0
- package/src/utils/overlayPlacement.ts +93 -0
- package/src/utils/pointerInteraction.ts +21 -0
- package/src/utils/polymorphic.ts +36 -0
- package/src/utils/renderMenuItemContent.tsx +62 -0
- package/src/utils/setVisibleRangeStart.ts +64 -0
- package/src/utils/spacingClassName.ts +14 -0
- package/src/utils/suppressFocusRingGlobally.ts +69 -0
- package/src/utils/validationClassName.ts +8 -0
- package/src/utils/virtualFocusStyle.ts +16 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import React, { forwardRef, HTMLAttributes, useMemo, useRef, useState } from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import {
|
|
4
|
+
AriaRangeCalendarProps,
|
|
5
|
+
mergeProps,
|
|
6
|
+
RangeValue,
|
|
7
|
+
useCalendarCell,
|
|
8
|
+
useLocale,
|
|
9
|
+
useRangeCalendar
|
|
10
|
+
} from 'react-aria'
|
|
11
|
+
import { DateValue, RangeCalendarState, useRangeCalendarState } from 'react-stately'
|
|
12
|
+
import {
|
|
13
|
+
CalendarDate,
|
|
14
|
+
createCalendar,
|
|
15
|
+
getLocalTimeZone,
|
|
16
|
+
isSameDay,
|
|
17
|
+
isToday,
|
|
18
|
+
isWeekend
|
|
19
|
+
} from '@internationalized/date'
|
|
20
|
+
|
|
21
|
+
import { useForkedRef } from '../../hooks'
|
|
22
|
+
import { CalendarMonthBlock } from './CalendarMonthBlock'
|
|
23
|
+
import { CalendarNavButton } from './CalendarNavButton'
|
|
24
|
+
import { DateRangePreset } from '../../utils/dateRangePresets'
|
|
25
|
+
import { mergeIsDateUnavailable } from '../../utils/mergeIsDateUnavailable'
|
|
26
|
+
import { suppressFocusRing } from '../../utils/suppressFocusRingGlobally'
|
|
27
|
+
import './Calendar.scss'
|
|
28
|
+
import './RangeCalendar.scss'
|
|
29
|
+
|
|
30
|
+
export interface RangeCalendarProps extends Omit<
|
|
31
|
+
HTMLAttributes<HTMLDivElement>,
|
|
32
|
+
'onChange' | 'defaultValue'
|
|
33
|
+
> {
|
|
34
|
+
/**
|
|
35
|
+
* An accessible label for the calendar, used when there's no visible label. Required for
|
|
36
|
+
* standalone use — a calendar grid has no other accessible name of its own.
|
|
37
|
+
*/
|
|
38
|
+
'aria-label'?: string
|
|
39
|
+
/**
|
|
40
|
+
* Identifies a visible label element for the calendar.
|
|
41
|
+
*/
|
|
42
|
+
'aria-labelledby'?: string
|
|
43
|
+
/**
|
|
44
|
+
* Whether to automatically focus the calendar when it mounts.
|
|
45
|
+
*/
|
|
46
|
+
autoFocus?: boolean
|
|
47
|
+
/**
|
|
48
|
+
* A string of all className you want applied to the base component.
|
|
49
|
+
*/
|
|
50
|
+
className?: string
|
|
51
|
+
/**
|
|
52
|
+
* The initial selected date range (uncontrolled).
|
|
53
|
+
*/
|
|
54
|
+
defaultValue?: RangeValue<DateValue> | null
|
|
55
|
+
/**
|
|
56
|
+
* Prevents the calendar from being focused or interacted with.
|
|
57
|
+
*/
|
|
58
|
+
disabled?: boolean
|
|
59
|
+
/**
|
|
60
|
+
* The day that starts the week, overriding the default set by the active locale.
|
|
61
|
+
*
|
|
62
|
+
* @default 'mon'
|
|
63
|
+
*/
|
|
64
|
+
firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
|
|
65
|
+
/**
|
|
66
|
+
* Callback that is called for each date in the calendar. If it returns `true`, that date is
|
|
67
|
+
* shown but cannot be selected.
|
|
68
|
+
*/
|
|
69
|
+
isDateUnavailable?: (date: DateValue) => boolean
|
|
70
|
+
/**
|
|
71
|
+
* The maximum allowed date that a user may select.
|
|
72
|
+
*/
|
|
73
|
+
maxValue?: DateValue | null
|
|
74
|
+
/**
|
|
75
|
+
* The minimum allowed date that a user may select.
|
|
76
|
+
*/
|
|
77
|
+
minValue?: DateValue | null
|
|
78
|
+
/**
|
|
79
|
+
* Callback fired when a complete range is selected (both a start and end date).
|
|
80
|
+
*/
|
|
81
|
+
onChange?: (value: RangeValue<DateValue>) => void
|
|
82
|
+
/**
|
|
83
|
+
* A list of quick-select range presets shown beside the calendar. Selecting a preset commits
|
|
84
|
+
* its range immediately, the same as picking a start and end date from the grid. The preset
|
|
85
|
+
* matching the current selection (if any) is marked selected. Omit to not show a preset list.
|
|
86
|
+
*/
|
|
87
|
+
presets?: DateRangePreset[]
|
|
88
|
+
/**
|
|
89
|
+
* ISO 8601 dates (`YYYY-MM-DD`) to mark unselectable, as a convenience alternative to
|
|
90
|
+
* `isDateUnavailable` for data-driven cases (e.g. booked dates fetched from an API). Composed
|
|
91
|
+
* with `isDateUnavailable` when both are given — a date unavailable by either is unavailable.
|
|
92
|
+
*/
|
|
93
|
+
unavailableDates?: string[]
|
|
94
|
+
/**
|
|
95
|
+
* The selected date range (controlled).
|
|
96
|
+
*/
|
|
97
|
+
value?: RangeValue<DateValue> | null
|
|
98
|
+
/**
|
|
99
|
+
* Number of months to display side by side, sharing one selection. Wraps to multiple rows in
|
|
100
|
+
* a narrow container (e.g. a popover on a small screen) rather than overflowing — the wrap is
|
|
101
|
+
* driven by the calendar's own width, not the viewport, so it adapts correctly regardless of
|
|
102
|
+
* where the calendar is embedded.
|
|
103
|
+
*
|
|
104
|
+
* @default 1
|
|
105
|
+
*/
|
|
106
|
+
visibleMonths?: number
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Range counterpart to `Calendar` — same dialog-agnostic composition boundary (see that
|
|
110
|
+
// component's own comment) and the same header/month-year-picker/grid visual language, reusing
|
|
111
|
+
// `Calendar.scss`'s shared classes directly. Kept as its own component rather than a `mode` prop
|
|
112
|
+
// on `Calendar`: the underlying react-stately/react-aria hooks are a genuinely different pair
|
|
113
|
+
// (`useRangeCalendarState`/`useRangeCalendar` vs `useCalendarState`/`useCalendar`), and the cell
|
|
114
|
+
// rendering has range-only concerns (start/end/in-between pill styling) with no single-date
|
|
115
|
+
// equivalent.
|
|
116
|
+
export const RangeCalendar = forwardRef<HTMLDivElement, RangeCalendarProps>(
|
|
117
|
+
(
|
|
118
|
+
{
|
|
119
|
+
autoFocus,
|
|
120
|
+
className,
|
|
121
|
+
defaultValue,
|
|
122
|
+
disabled,
|
|
123
|
+
firstDayOfWeek = 'mon',
|
|
124
|
+
isDateUnavailable,
|
|
125
|
+
maxValue,
|
|
126
|
+
minValue,
|
|
127
|
+
onChange,
|
|
128
|
+
presets,
|
|
129
|
+
unavailableDates,
|
|
130
|
+
value,
|
|
131
|
+
visibleMonths = 1,
|
|
132
|
+
...rest
|
|
133
|
+
},
|
|
134
|
+
forwardedRef
|
|
135
|
+
) => {
|
|
136
|
+
const { locale } = useLocale()
|
|
137
|
+
const internalRef = useRef<HTMLDivElement>(null)
|
|
138
|
+
const ref = useForkedRef(internalRef, forwardedRef)
|
|
139
|
+
const combinedIsDateUnavailable = useMemo(
|
|
140
|
+
() => mergeIsDateUnavailable(unavailableDates, isDateUnavailable),
|
|
141
|
+
[unavailableDates, isDateUnavailable]
|
|
142
|
+
)
|
|
143
|
+
const resolvedPresets = presets && presets.length > 0 ? presets : null
|
|
144
|
+
// Tracks each visible month block's own view, keyed by `monthIndex` — needed only to know
|
|
145
|
+
// whether *any* block has switched away from the day grid. `CalendarYearGrid` owns its own
|
|
146
|
+
// prev/next pager, which would otherwise sit right underneath the global `.datepicker-controls`
|
|
147
|
+
// overlay below (see its own comment) — and even in the month view, where there's no such
|
|
148
|
+
// literal overlap, the global pager would still page the day grid one block is no longer
|
|
149
|
+
// showing, which reads as it doing nothing.
|
|
150
|
+
const [monthViews, setMonthViews] = useState<Record<number, 'days' | 'months' | 'years'>>({})
|
|
151
|
+
const hasPickerView = Object.values(monthViews).some((view) => view !== 'days')
|
|
152
|
+
|
|
153
|
+
const state = useRangeCalendarState({
|
|
154
|
+
autoFocus,
|
|
155
|
+
createCalendar,
|
|
156
|
+
defaultValue,
|
|
157
|
+
firstDayOfWeek,
|
|
158
|
+
isDateUnavailable: combinedIsDateUnavailable,
|
|
159
|
+
isDisabled: disabled,
|
|
160
|
+
locale,
|
|
161
|
+
maxValue,
|
|
162
|
+
minValue,
|
|
163
|
+
onChange,
|
|
164
|
+
// Prev/next always slide the visible window by one month, regardless of `visibleMonths` —
|
|
165
|
+
// react-stately's own default (`pageBehavior: 'visible'`) instead pages by the *entire*
|
|
166
|
+
// visible span at once (e.g. jumping straight from Nov/Dec to Jan/Feb for `visibleMonths={2}`,
|
|
167
|
+
// skipping Dec/Jan entirely), which reads as broken next to `CalendarMonthYearDropdowns`'
|
|
168
|
+
// own one-month-at-a-time jumps.
|
|
169
|
+
pageBehavior: 'single',
|
|
170
|
+
value,
|
|
171
|
+
visibleDuration: { months: visibleMonths }
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const ariaProps: AriaRangeCalendarProps<DateValue> = {
|
|
175
|
+
'aria-label': rest['aria-label'],
|
|
176
|
+
'aria-labelledby': rest['aria-labelledby'],
|
|
177
|
+
autoFocus,
|
|
178
|
+
defaultValue,
|
|
179
|
+
isDateUnavailable: combinedIsDateUnavailable,
|
|
180
|
+
isDisabled: disabled,
|
|
181
|
+
maxValue,
|
|
182
|
+
minValue,
|
|
183
|
+
onChange,
|
|
184
|
+
value
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const { calendarProps, prevButtonProps, nextButtonProps } = useRangeCalendar(
|
|
188
|
+
ariaProps,
|
|
189
|
+
state,
|
|
190
|
+
internalRef
|
|
191
|
+
)
|
|
192
|
+
// A single pair of buttons, placed in exactly one spot depending on `visibleMonths` — inline
|
|
193
|
+
// in the (only) month's own header for a single month, or once as a global overlay for
|
|
194
|
+
// several — never both, so there's no risk of two DOM nodes fighting over one ref.
|
|
195
|
+
const prevButton = <CalendarNavButton buttonProps={prevButtonProps} direction="prev" />
|
|
196
|
+
const nextButton = <CalendarNavButton buttonProps={nextButtonProps} direction="next" />
|
|
197
|
+
const singleMonthArrows = visibleMonths === 1 ? { next: nextButton, prev: prevButton } : null
|
|
198
|
+
|
|
199
|
+
// Goes through the same `state.setValue` path a two-click grid selection does (rather than
|
|
200
|
+
// calling `onChange` directly), so it behaves identically whether `value` is controlled or
|
|
201
|
+
// uncontrolled — including `DateRangePicker`, which relies on this to auto-close its popover
|
|
202
|
+
// the same way completing a range in the grid already does.
|
|
203
|
+
const handlePresetSelect = (range: RangeValue<DateValue>) => {
|
|
204
|
+
state.setValue(range)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div
|
|
209
|
+
{...mergeProps(calendarProps, rest)}
|
|
210
|
+
className={classNames('datepicker', className)}
|
|
211
|
+
data-cx-inline="true"
|
|
212
|
+
ref={ref}
|
|
213
|
+
>
|
|
214
|
+
<div className="datepicker-grid">
|
|
215
|
+
{resolvedPresets && (
|
|
216
|
+
<DateRangePresets
|
|
217
|
+
onSelect={handlePresetSelect}
|
|
218
|
+
presets={resolvedPresets}
|
|
219
|
+
value={state.value}
|
|
220
|
+
/>
|
|
221
|
+
)}
|
|
222
|
+
<div className="datepicker-body">
|
|
223
|
+
{visibleMonths > 1 && !hasPickerView && (
|
|
224
|
+
<div className="datepicker-controls">
|
|
225
|
+
{prevButton}
|
|
226
|
+
{nextButton}
|
|
227
|
+
</div>
|
|
228
|
+
)}
|
|
229
|
+
<div className="datepicker-grid">
|
|
230
|
+
{[...new Array(visibleMonths).keys()].map((monthIndex) => (
|
|
231
|
+
<CalendarMonthBlock
|
|
232
|
+
arrows={monthIndex === 0 ? singleMonthArrows : null}
|
|
233
|
+
firstDayOfWeek={firstDayOfWeek}
|
|
234
|
+
key={monthIndex}
|
|
235
|
+
monthIndex={monthIndex}
|
|
236
|
+
onViewChange={(view) =>
|
|
237
|
+
setMonthViews((prev) => ({ ...prev, [monthIndex]: view }))
|
|
238
|
+
}
|
|
239
|
+
renderCell={(date, i, week) => (
|
|
240
|
+
<CalendarCell
|
|
241
|
+
date={date}
|
|
242
|
+
isFirstInRow={i === 0}
|
|
243
|
+
isLastInRow={i === week.length - 1}
|
|
244
|
+
locale={locale}
|
|
245
|
+
state={state}
|
|
246
|
+
/>
|
|
247
|
+
)}
|
|
248
|
+
state={state}
|
|
249
|
+
/>
|
|
250
|
+
))}
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
RangeCalendar.displayName = 'RangeCalendar'
|
|
260
|
+
|
|
261
|
+
interface CalendarCellProps {
|
|
262
|
+
date: CalendarDate
|
|
263
|
+
isFirstInRow: boolean
|
|
264
|
+
isLastInRow: boolean
|
|
265
|
+
locale: string
|
|
266
|
+
state: RangeCalendarState
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const CalendarCell = ({ date, isFirstInRow, isLastInRow, locale, state }: CalendarCellProps) => {
|
|
270
|
+
const ref = useRef<HTMLButtonElement>(null)
|
|
271
|
+
const {
|
|
272
|
+
cellProps,
|
|
273
|
+
buttonProps,
|
|
274
|
+
isSelected,
|
|
275
|
+
isOutsideVisibleRange,
|
|
276
|
+
isDisabled,
|
|
277
|
+
isUnavailable,
|
|
278
|
+
formattedDate
|
|
279
|
+
} = useCalendarCell({ date }, state, ref)
|
|
280
|
+
|
|
281
|
+
// `isSelected` is true for every day in the range, not just its endpoints — the pill's rounded
|
|
282
|
+
// caps belong only at the true start/end (or wherever a row wraps mid-range, so the band still
|
|
283
|
+
// reads as continuous week to week); everything else in between is a flush, square-edged band.
|
|
284
|
+
const { highlightedRange } = state
|
|
285
|
+
const isRangeStart = Boolean(highlightedRange && isSameDay(date, highlightedRange.start))
|
|
286
|
+
const isRangeEnd = Boolean(highlightedRange && isSameDay(date, highlightedRange.end))
|
|
287
|
+
const isCurrentDate = isToday(date, getLocalTimeZone())
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div
|
|
291
|
+
{...cellProps}
|
|
292
|
+
aria-current={isCurrentDate ? 'date' : undefined}
|
|
293
|
+
className={classNames('datepicker-date', {
|
|
294
|
+
'datepicker-date-today': isCurrentDate,
|
|
295
|
+
'datepicker-date-in-range': isSelected,
|
|
296
|
+
'datepicker-date-range-start': isSelected && (isRangeStart || isFirstInRow),
|
|
297
|
+
'datepicker-date-range-end': isSelected && (isRangeEnd || isLastInRow),
|
|
298
|
+
'datepicker-date-outside': isOutsideVisibleRange,
|
|
299
|
+
'datepicker-date-disabled': isDisabled,
|
|
300
|
+
'datepicker-date-unavailable': isUnavailable,
|
|
301
|
+
'datepicker-date-weekend': isWeekend(date, locale)
|
|
302
|
+
})}
|
|
303
|
+
>
|
|
304
|
+
<button
|
|
305
|
+
{...mergeProps(buttonProps, {
|
|
306
|
+
// Hovering to preview a range's end date moves real DOM focus via `element.focus()`
|
|
307
|
+
// (`useCalendarCell`'s `onPointerEnter` -> `state.highlightDate`), which browsers can
|
|
308
|
+
// paint as a visible focus ring even though the input modality is a pointer, not a
|
|
309
|
+
// keyboard — suppress it the same way a mouse press does elsewhere in the library.
|
|
310
|
+
onPointerEnter: () => {
|
|
311
|
+
if (state.anchorDate && ref.current) suppressFocusRing(ref.current)
|
|
312
|
+
}
|
|
313
|
+
})}
|
|
314
|
+
type="button"
|
|
315
|
+
className={classNames('datepicker-date-btn', {
|
|
316
|
+
'datepicker-date-range-start': isRangeStart,
|
|
317
|
+
'datepicker-date-range-end': isRangeEnd
|
|
318
|
+
})}
|
|
319
|
+
ref={ref}
|
|
320
|
+
>
|
|
321
|
+
{formattedDate}
|
|
322
|
+
</button>
|
|
323
|
+
</div>
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
interface DateRangePresetsProps {
|
|
328
|
+
onSelect: (range: RangeValue<DateValue>) => void
|
|
329
|
+
presets: DateRangePreset[]
|
|
330
|
+
value: RangeValue<DateValue> | null
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// A preset "matches" the current selection when both endpoints land on the same day — the same
|
|
334
|
+
// granularity `CalendarCell` already uses to compare dates, so a preset stays marked selected
|
|
335
|
+
// regardless of whether `value` is a `CalendarDate`, `CalendarDateTime`, or `ZonedDateTime`.
|
|
336
|
+
const isSameRange = (a: RangeValue<DateValue>, b: RangeValue<DateValue>) =>
|
|
337
|
+
isSameDay(a.start, b.start) && isSameDay(a.end, b.end)
|
|
338
|
+
|
|
339
|
+
// Plain buttons in a list, not a listbox — a group of independent actions (each one commits
|
|
340
|
+
// immediately) rather than a single-selection widget, so native Tab/Enter/Space is the right
|
|
341
|
+
// interaction model without extra roving-tabindex/arrow-key wiring.
|
|
342
|
+
const DateRangePresets = ({ onSelect, presets, value }: DateRangePresetsProps) => (
|
|
343
|
+
<ul className="datepicker-presets">
|
|
344
|
+
{presets.map((preset) => {
|
|
345
|
+
const isSelected = Boolean(value && isSameRange(value, preset.range))
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<li key={preset.label}>
|
|
349
|
+
<button
|
|
350
|
+
aria-current={isSelected ? 'true' : undefined}
|
|
351
|
+
className={classNames('datepicker-preset', { selected: isSelected })}
|
|
352
|
+
onClick={() => onSelect(preset.range)}
|
|
353
|
+
type="button"
|
|
354
|
+
>
|
|
355
|
+
{preset.label}
|
|
356
|
+
</button>
|
|
357
|
+
</li>
|
|
358
|
+
)
|
|
359
|
+
})}
|
|
360
|
+
</ul>
|
|
361
|
+
)
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { Calendar } from './Calendar'
|
|
2
|
+
export type { CalendarProps, CalendarSingleProps, CalendarMultipleProps } from './Calendar'
|
|
3
|
+
export { RangeCalendar } from './RangeCalendar'
|
|
4
|
+
export type { RangeCalendarProps } from './RangeCalendar'
|
|
5
|
+
export type { DateRangePreset } from '../../utils/dateRangePresets'
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import React, { ElementType, ForwardRefRenderFunction, ReactElement, ReactNode } from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
|
|
4
|
+
import { Breakpoint, ContextColor, ContextStyle, Sizing } from '../../types'
|
|
5
|
+
import {
|
|
6
|
+
buildResponsiveClassNames,
|
|
7
|
+
flexDirectionClassNames,
|
|
8
|
+
FlexDirection
|
|
9
|
+
} from '../../utils/breakpoints'
|
|
10
|
+
import {
|
|
11
|
+
createPolymorphicComponent,
|
|
12
|
+
PolymorphicComponentProps,
|
|
13
|
+
PolymorphicRef
|
|
14
|
+
} from '../../utils/polymorphic'
|
|
15
|
+
import { CardBody } from './CardBody'
|
|
16
|
+
import { CardFooter } from './CardFooter'
|
|
17
|
+
import { CardImage } from './CardImage'
|
|
18
|
+
import { CardSubtitle } from './CardSubtitle'
|
|
19
|
+
import { CardText } from './CardText'
|
|
20
|
+
import { CardTitle } from './CardTitle'
|
|
21
|
+
|
|
22
|
+
type CardOwnProps<C extends ElementType> = {
|
|
23
|
+
/**
|
|
24
|
+
* A string of all className you want applied to the base component.
|
|
25
|
+
*/
|
|
26
|
+
className?: string
|
|
27
|
+
/**
|
|
28
|
+
* Component used for the root node. Either a string to use a HTML element or a component.
|
|
29
|
+
*/
|
|
30
|
+
component?: C
|
|
31
|
+
/**
|
|
32
|
+
* Sets the color of the component to one of Chassis context colors.
|
|
33
|
+
*/
|
|
34
|
+
color?: ContextColor
|
|
35
|
+
/**
|
|
36
|
+
* Switches the card from its default stacked (column) layout to a side-by-side (row) layout.
|
|
37
|
+
* Wrap the image and body in `Col` to control each side's width.
|
|
38
|
+
*/
|
|
39
|
+
direction?: FlexDirection
|
|
40
|
+
/**
|
|
41
|
+
* Shorthand for a `CardFooter`, rendered after the image/title/subtitle/text/`children` block.
|
|
42
|
+
*/
|
|
43
|
+
footer?: ReactNode
|
|
44
|
+
/**
|
|
45
|
+
* Shorthand for a `CardImage` — pass either a `src` URL (combine with `imageAlt`/
|
|
46
|
+
* `imageOrientation`) or a fully-formed element — a `<CardImage>`, `<Skeleton>`,
|
|
47
|
+
* `<Placeholder>`, or anything else — rendered as-is in its place. `imageAlt`/`imageOrientation`
|
|
48
|
+
* only apply to the `src` form; set them directly on your own element otherwise. For a
|
|
49
|
+
* horizontal layout or an overlay, omit this and compose `CardImage` directly as a child
|
|
50
|
+
* instead.
|
|
51
|
+
*/
|
|
52
|
+
image?: ReactNode
|
|
53
|
+
/**
|
|
54
|
+
* Accessible alt text for `image`. Ignored unless `image` is set.
|
|
55
|
+
*/
|
|
56
|
+
imageAlt?: string
|
|
57
|
+
/**
|
|
58
|
+
* Orientates `image` to the top (default) or bottom of the card.
|
|
59
|
+
*/
|
|
60
|
+
imageOrientation?: 'top' | 'bottom'
|
|
61
|
+
/**
|
|
62
|
+
* Overrides `direction` at one or more breakpoints — e.g. `{ large: 'row' }` to lay the card
|
|
63
|
+
* out horizontally from `large` up while stacking below it.
|
|
64
|
+
*/
|
|
65
|
+
responsive?: Partial<Record<Breakpoint, FlexDirection>>
|
|
66
|
+
/**
|
|
67
|
+
* Sets the size of the component to one of Chassis component sizes.
|
|
68
|
+
*/
|
|
69
|
+
size?: Sizing
|
|
70
|
+
/**
|
|
71
|
+
* Shorthand for a `CardSubtitle`, rendered directly after `title`.
|
|
72
|
+
*/
|
|
73
|
+
subtitle?: ReactNode
|
|
74
|
+
/**
|
|
75
|
+
* Shorthand for a `CardText`, rendered after `title`/`subtitle`.
|
|
76
|
+
*/
|
|
77
|
+
text?: ReactNode
|
|
78
|
+
/**
|
|
79
|
+
* Shorthand for a `CardTitle`. Set alongside `subtitle`/`text` (or plain `children`, e.g. a
|
|
80
|
+
* `Button`) to build a standard card without composing `CardBody`/`CardTitle` yourself — all
|
|
81
|
+
* render together inside a single `CardBody`, in that order. Only takes effect when at least
|
|
82
|
+
* one of `image`/`title`/`subtitle`/`text`/`footer` is set; otherwise `children` render as-is,
|
|
83
|
+
* so full manual composition (`CardHeader`, lists, overlays, nav headers, …) keeps working
|
|
84
|
+
* unchanged. Mixing shorthand props with a manually-composed `children` tree isn't supported —
|
|
85
|
+
* pick one approach per card.
|
|
86
|
+
*/
|
|
87
|
+
title?: ReactNode
|
|
88
|
+
/**
|
|
89
|
+
* Sets the context style of the component. `basic` (the default) renders with no extra class.
|
|
90
|
+
*/
|
|
91
|
+
variant?: ContextStyle
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export type CardProps<C extends ElementType = 'div'> = PolymorphicComponentProps<C, CardOwnProps<C>>
|
|
95
|
+
|
|
96
|
+
type CardComponent = (<C extends ElementType = 'div'>(
|
|
97
|
+
props: CardProps<C> & { ref?: PolymorphicRef<C> }
|
|
98
|
+
) => ReactElement | null) & { displayName?: string }
|
|
99
|
+
|
|
100
|
+
function CardRender<C extends ElementType = 'div'>(
|
|
101
|
+
{
|
|
102
|
+
children,
|
|
103
|
+
className,
|
|
104
|
+
color,
|
|
105
|
+
component,
|
|
106
|
+
direction,
|
|
107
|
+
footer,
|
|
108
|
+
image,
|
|
109
|
+
imageAlt,
|
|
110
|
+
imageOrientation = 'top',
|
|
111
|
+
responsive,
|
|
112
|
+
size,
|
|
113
|
+
subtitle,
|
|
114
|
+
text,
|
|
115
|
+
title,
|
|
116
|
+
variant,
|
|
117
|
+
...rest
|
|
118
|
+
}: CardProps<C>,
|
|
119
|
+
ref: PolymorphicRef<C>
|
|
120
|
+
) {
|
|
121
|
+
const Component = component ?? 'div'
|
|
122
|
+
const _className = classNames(
|
|
123
|
+
'card',
|
|
124
|
+
color,
|
|
125
|
+
{
|
|
126
|
+
context: !!color,
|
|
127
|
+
solid: variant === 'solid',
|
|
128
|
+
smooth: variant === 'smooth',
|
|
129
|
+
outline: variant === 'outline',
|
|
130
|
+
small: size === 'small',
|
|
131
|
+
large: size === 'large'
|
|
132
|
+
},
|
|
133
|
+
buildResponsiveClassNames(flexDirectionClassNames, direction, responsive),
|
|
134
|
+
className
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
const hasShorthand =
|
|
138
|
+
image != null || title != null || subtitle != null || footer != null || text != null
|
|
139
|
+
const hasBodyContent = title != null || subtitle != null || text != null || children != null
|
|
140
|
+
const renderedImage =
|
|
141
|
+
typeof image === 'string' ? (
|
|
142
|
+
<CardImage orientation={imageOrientation} src={image} alt={imageAlt ?? ''} />
|
|
143
|
+
) : (
|
|
144
|
+
image
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<Component className={_className} {...rest} ref={ref}>
|
|
149
|
+
{hasShorthand ? (
|
|
150
|
+
<>
|
|
151
|
+
{image != null && imageOrientation === 'top' && renderedImage}
|
|
152
|
+
{hasBodyContent && (
|
|
153
|
+
<CardBody>
|
|
154
|
+
{title != null && <CardTitle>{title}</CardTitle>}
|
|
155
|
+
{subtitle != null && <CardSubtitle>{subtitle}</CardSubtitle>}
|
|
156
|
+
{text != null && <CardText>{text}</CardText>}
|
|
157
|
+
{children}
|
|
158
|
+
</CardBody>
|
|
159
|
+
)}
|
|
160
|
+
{image != null && imageOrientation === 'bottom' && renderedImage}
|
|
161
|
+
{footer != null && <CardFooter>{footer}</CardFooter>}
|
|
162
|
+
</>
|
|
163
|
+
) : (
|
|
164
|
+
children
|
|
165
|
+
)}
|
|
166
|
+
</Component>
|
|
167
|
+
)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export const Card = createPolymorphicComponent<CardComponent>(
|
|
171
|
+
CardRender as ForwardRefRenderFunction<Element, CardProps<ElementType>>,
|
|
172
|
+
'Card'
|
|
173
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React, { ElementType, ForwardRefRenderFunction, ReactElement } from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
|
|
4
|
+
import { Breakpoint, Spacing } from '../../types'
|
|
5
|
+
import {
|
|
6
|
+
buildResponsiveClassNames,
|
|
7
|
+
flexDirectionClassNames,
|
|
8
|
+
FlexDirection
|
|
9
|
+
} from '../../utils/breakpoints'
|
|
10
|
+
import {
|
|
11
|
+
createPolymorphicComponent,
|
|
12
|
+
PolymorphicComponentProps,
|
|
13
|
+
PolymorphicRef
|
|
14
|
+
} from '../../utils/polymorphic'
|
|
15
|
+
import { spacingClassName } from '../../utils/spacingClassName'
|
|
16
|
+
|
|
17
|
+
type CardBodyOwnProps<C extends ElementType> = {
|
|
18
|
+
/**
|
|
19
|
+
* A string of all className you want applied to the base component.
|
|
20
|
+
*/
|
|
21
|
+
className?: string
|
|
22
|
+
/**
|
|
23
|
+
* Component used for the root node. Either a string to use a HTML element or a component.
|
|
24
|
+
*/
|
|
25
|
+
component?: C
|
|
26
|
+
/**
|
|
27
|
+
* Switches the body from its default stacked (column) layout to a side-by-side (row) layout —
|
|
28
|
+
* for placing an image beside text within a single padded region. Wrap the image and text in
|
|
29
|
+
* `Col` to control each side's width, and nest another `CardBody` (with `.p-0`) for the text
|
|
30
|
+
* side so it doesn't receive double padding.
|
|
31
|
+
*/
|
|
32
|
+
direction?: FlexDirection
|
|
33
|
+
/**
|
|
34
|
+
* Spacing between children, mapped to the `gap-*` utility classes. Overrides the card's default
|
|
35
|
+
* gap between body children.
|
|
36
|
+
*/
|
|
37
|
+
gap?: Spacing | 0
|
|
38
|
+
/**
|
|
39
|
+
* Overrides `direction` at one or more breakpoints.
|
|
40
|
+
*/
|
|
41
|
+
responsive?: Partial<Record<Breakpoint, FlexDirection>>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type CardBodyProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
|
|
45
|
+
C,
|
|
46
|
+
CardBodyOwnProps<C>
|
|
47
|
+
>
|
|
48
|
+
|
|
49
|
+
type CardBodyComponent = (<C extends ElementType = 'div'>(
|
|
50
|
+
props: CardBodyProps<C> & { ref?: PolymorphicRef<C> }
|
|
51
|
+
) => ReactElement | null) & { displayName?: string }
|
|
52
|
+
|
|
53
|
+
function CardBodyRender<C extends ElementType = 'div'>(
|
|
54
|
+
{ children, className, component, direction, gap, responsive, ...rest }: CardBodyProps<C>,
|
|
55
|
+
ref: PolymorphicRef<C>
|
|
56
|
+
) {
|
|
57
|
+
const Component = component ?? 'div'
|
|
58
|
+
const _className = classNames(
|
|
59
|
+
'card-body',
|
|
60
|
+
buildResponsiveClassNames(flexDirectionClassNames, direction, responsive),
|
|
61
|
+
spacingClassName('gap', gap),
|
|
62
|
+
className
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return (
|
|
66
|
+
<Component className={_className} {...rest} ref={ref}>
|
|
67
|
+
{children}
|
|
68
|
+
</Component>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export const CardBody = createPolymorphicComponent<CardBodyComponent>(
|
|
73
|
+
CardBodyRender as ForwardRefRenderFunction<Element, CardBodyProps<ElementType>>,
|
|
74
|
+
'CardBody'
|
|
75
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import React, { ElementType, ForwardRefRenderFunction, ReactElement } from 'react'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createPolymorphicComponent,
|
|
6
|
+
PolymorphicComponentProps,
|
|
7
|
+
PolymorphicRef
|
|
8
|
+
} from '../../utils/polymorphic'
|
|
9
|
+
|
|
10
|
+
type CardFooterOwnProps<C extends ElementType> = {
|
|
11
|
+
/**
|
|
12
|
+
* A string of all className you want applied to the base component.
|
|
13
|
+
*/
|
|
14
|
+
className?: string
|
|
15
|
+
/**
|
|
16
|
+
* Component used for the root node. Either a string to use a HTML element or a component.
|
|
17
|
+
*/
|
|
18
|
+
component?: C
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type CardFooterProps<C extends ElementType = 'div'> = PolymorphicComponentProps<
|
|
22
|
+
C,
|
|
23
|
+
CardFooterOwnProps<C>
|
|
24
|
+
>
|
|
25
|
+
|
|
26
|
+
type CardFooterComponent = (<C extends ElementType = 'div'>(
|
|
27
|
+
props: CardFooterProps<C> & { ref?: PolymorphicRef<C> }
|
|
28
|
+
) => ReactElement | null) & { displayName?: string }
|
|
29
|
+
|
|
30
|
+
function CardFooterRender<C extends ElementType = 'div'>(
|
|
31
|
+
{ children, className, component, ...rest }: CardFooterProps<C>,
|
|
32
|
+
ref: PolymorphicRef<C>
|
|
33
|
+
) {
|
|
34
|
+
const Component = component ?? 'div'
|
|
35
|
+
const _className = classNames('card-footer', className)
|
|
36
|
+
|
|
37
|
+
return (
|
|
38
|
+
<Component className={_className} {...rest} ref={ref}>
|
|
39
|
+
{children}
|
|
40
|
+
</Component>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const CardFooter = createPolymorphicComponent<CardFooterComponent>(
|
|
45
|
+
CardFooterRender as ForwardRefRenderFunction<Element, CardFooterProps<ElementType>>,
|
|
46
|
+
'CardFooter'
|
|
47
|
+
)
|