mgh-components 1.1.3 → 1.2.1
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 +70 -2
- package/dist/components/MGHDateRangePicker/MGHDateRangeCalendar.d.ts +8 -0
- package/dist/components/MGHDateRangePicker/MGHDateRangePicker.d.ts +10 -0
- package/dist/components/MGHDateRangePicker/MGHDateRangePicker.styles.d.ts +9 -0
- package/dist/components/MGHDateRangePicker/MGHDateRangePicker.types.d.ts +125 -0
- package/dist/components/MGHDateRangePicker/icons.d.ts +8 -0
- package/dist/components/MGHDateRangePicker/index.d.ts +7 -0
- package/dist/components/MGHDateRangePicker/utils.d.ts +46 -0
- package/dist/components/index.d.ts +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
Metrolina Greenhouses (MGH) component library for React applications.
|
|
4
4
|
|
|
5
|
-
This package ships two data grids:
|
|
5
|
+
This package ships two data grids and a date range picker:
|
|
6
6
|
|
|
7
7
|
- **`MGHGrid`** – a self-contained data grid with **no MUI dependency** (and therefore no
|
|
8
8
|
`@mui/x-data-grid-premium` license). This is the grid to use going forward.
|
|
9
9
|
- **`MGHDataGrid`** – the legacy wrapper around MUI X Data Grid Premium. It is kept for backwards
|
|
10
10
|
compatibility and still requires the MUI peer dependencies.
|
|
11
|
+
- **`MGHDateRangePicker`** – a MUI-free port of MUI X Pro's `DateRangePicker` (no
|
|
12
|
+
`@mui/x-date-pickers-pro` license).
|
|
11
13
|
|
|
12
14
|
Plus a few helpers built on MUI (`Label`, `ConfirmDialog`, `ExportingDialog`, `Iconify`).
|
|
13
15
|
|
|
@@ -21,7 +23,7 @@ pnpm add mgh-components
|
|
|
21
23
|
|
|
22
24
|
## Peer Dependencies
|
|
23
25
|
|
|
24
|
-
`MGHGrid` only
|
|
26
|
+
`MGHGrid` and `MGHDateRangePicker` only need:
|
|
25
27
|
|
|
26
28
|
- `react`
|
|
27
29
|
- `react-dom`
|
|
@@ -257,6 +259,71 @@ Or globally in your CSS:
|
|
|
257
259
|
|
|
258
260
|
---
|
|
259
261
|
|
|
262
|
+
## MGHDateRangePicker (MUI-free)
|
|
263
|
+
|
|
264
|
+
A port of MUI X Pro's `DateRangePicker` (desktop variant) with **no MUI dependency** and no
|
|
265
|
+
`@mui/x-date-pickers-pro` license. Values are dayjs `[start, end]` tuples, so existing
|
|
266
|
+
`DateRange<Dayjs>` state works unchanged.
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
import { useState } from 'react';
|
|
270
|
+
import type { Dayjs } from 'dayjs';
|
|
271
|
+
import { MGHDateRangePicker, MGH_DATE_RANGE_DEFAULT_SHORTCUTS, type MGHDateRange } from 'mgh-components';
|
|
272
|
+
|
|
273
|
+
export default function Example() {
|
|
274
|
+
const [range, setRange] = useState<MGHDateRange>([null, null]);
|
|
275
|
+
|
|
276
|
+
return (
|
|
277
|
+
<MGHDateRangePicker
|
|
278
|
+
label="Ship date"
|
|
279
|
+
value={range}
|
|
280
|
+
onChange={(value) => setRange(value)}
|
|
281
|
+
onAccept={(value) => refetch(value)} // fires once the range is complete / accepted
|
|
282
|
+
shortcuts={MGH_DATE_RANGE_DEFAULT_SHORTCUTS} // This Week, Last Week, Last 7 Days, Current Month, Next Month, Reset
|
|
283
|
+
clearable
|
|
284
|
+
/>
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Drop it into the grid toolbar with `toolbarProps.renderDateRange` (or the card header with
|
|
290
|
+
`renderHeaderCenterContent`) – inside an `MGHGrid` card it inherits the grid palette automatically,
|
|
291
|
+
and without an explicit `themeMode` it follows the card's light / dark mode (popover included).
|
|
292
|
+
|
|
293
|
+
### Props
|
|
294
|
+
|
|
295
|
+
| Area | Details |
|
|
296
|
+
| --- | --- |
|
|
297
|
+
| Value | `value` / `defaultValue` / `onChange(value, { validationError, shortcut })`, `onAccept(value)`, `onError(error, value)` |
|
|
298
|
+
| Field | `label`, `format` (`'MM/DD/YYYY'`), `fieldVariant` (`'single'` → "start – end" in one field, `'multi'` → Start and End fields), `size` (`'small'` \| `'medium'`), `fullWidth`, `clearable`, `placeholder`, `helperText`, `error`, `required`, `disabled`, `readOnly`, `name`, `id`, `inputRef` |
|
|
299
|
+
| Opening | Calendar icon button (like MUI), `openOnClick` to also open from the text, `disableOpenPicker`, controlled `open` / `onOpen` / `onClose`, `placement` |
|
|
300
|
+
| Calendar | `calendars` (1–3, default 2), `referenceDate`, `showDaysOutsideCurrentMonth`, `fixedWeekNumber`, `dayOfWeekFormatter`, `disableHighlightToday`, `showRangePreview`, `onMonthChange` |
|
|
301
|
+
| Validation | `minDate`, `maxDate`, `disablePast`, `disableFuture`, `shouldDisableDate(day, position)`; errors: `invalidDate`, `invalidRange`, `minDate`, `maxDate`, `disablePast`, `disableFuture`, `shouldDisableDate` |
|
|
302
|
+
| Selection | `rangePosition` / `onRangePositionChange`, `closeOnSelect` (default `true`, `false` when `actions` contains `'accept'`) |
|
|
303
|
+
| Layout | `shortcuts` (`{ label, getValue }`, same shape as MUI `slotProps.shortcuts.items`), `actions` (`'clear'`, `'today'`, `'cancel'`, `'accept'`), `showToolbar` |
|
|
304
|
+
| Theming | `themeMode="light" \| "dark"`, `themeVars` (any `--mgh-drp-*` variable), `className` / `style`, `popoverClassName` / `popoverStyle`, `localeText` |
|
|
305
|
+
| Keyboard | Type dates directly (strict `format` plus `M/D/YYYY`, `YYYY-MM-DD` fallbacks), **↓** opens the calendar, arrows / Home / End / PageUp / PageDown move between days, Enter or Space selects, Escape closes |
|
|
306
|
+
|
|
307
|
+
Selection follows MUI: the first click sets the start, the second sets the end and closes the
|
|
308
|
+
popover (`closeOnSelect`). Picking an end before the start makes it the new start. Hovering while
|
|
309
|
+
choosing the end previews the interval with a dashed outline.
|
|
310
|
+
|
|
311
|
+
`MGHDateRangeCalendar` (the calendar without the field / popover) is exported for inline use.
|
|
312
|
+
|
|
313
|
+
### Theming
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
<MGHDateRangePicker themeMode="dark" themeVars={{ '--mgh-drp-primary': '#00a76f' }} … />
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Or globally in your CSS:
|
|
320
|
+
|
|
321
|
+
```css
|
|
322
|
+
.mgh-drp, .mgh-drp-popover { --mgh-drp-primary: #00a76f; --mgh-drp-day-size: 40px; }
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
260
327
|
## Legacy: MGHDataGrid (MUI X Premium)
|
|
261
328
|
|
|
262
329
|
```tsx
|
|
@@ -273,6 +340,7 @@ import { MGHDataGrid } from 'mgh-components';
|
|
|
273
340
|
|
|
274
341
|
## Components
|
|
275
342
|
|
|
343
|
+
- `MGHDateRangePicker` (+ `MGHDateRangeCalendar`, `MGH_DATE_RANGE_DEFAULT_SHORTCUTS`, `MGHDateRangeIcons`, `useMGHDateRangePickerStyles`)
|
|
276
344
|
- `MGHGrid` (+ `MGHGridDefaultToolbar`, `MGHGridQuickFilter`, `MGHGridExportMenu`, `MGHGridExportMenuItem`,
|
|
277
345
|
`MGHGridActionsCellItem`, `MGHGridNoRowsOverlay`, `MGHGridIcons`, `useMGHGridApiRef`, `useMGHGridApiContext`)
|
|
278
346
|
- `MGHDataGrid`, `MGHGridToolbar`, `DataGridQuickFilter`, `CustomNoRowsOverlay`, `CustomGridToolbarExport` (legacy, MUI)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { MGHDateRangeCalendarProps } from './MGHDateRangePicker.types';
|
|
3
|
+
/**
|
|
4
|
+
* Pure-React port of MUI X's `DateRangeCalendar`: one to three months side by side with range
|
|
5
|
+
* highlighting, hover preview, keyboard navigation and min/max/disabled-day validation.
|
|
6
|
+
*/
|
|
7
|
+
export declare function MGHDateRangeCalendar({ value: valueProp, defaultValue, onChange, rangePosition: rangePositionProp, onRangePositionChange, calendars, referenceDate, minDate, maxDate, disablePast, disableFuture, shouldDisableDate, disabled, readOnly, showDaysOutsideCurrentMonth, fixedWeekNumber, dayOfWeekFormatter, disableHighlightToday, showRangePreview, autoFocus, onMonthChange, localeText, themeMode, className, style, }: MGHDateRangeCalendarProps): React.JSX.Element;
|
|
8
|
+
export default MGHDateRangeCalendar;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { MGHDateRangePickerLocaleText, MGHDateRangePickerProps } from './MGHDateRangePicker.types';
|
|
3
|
+
export declare const MGH_DATE_RANGE_PICKER_DEFAULT_LOCALE_TEXT: MGHDateRangePickerLocaleText;
|
|
4
|
+
/**
|
|
5
|
+
* MUI-free port of MUI X Pro's `DateRangePicker` (desktop variant): an outlined text field with a
|
|
6
|
+
* calendar button that opens a two-month range calendar, optional shortcuts and an action bar.
|
|
7
|
+
* Values are dayjs `[start, end]` tuples so existing `DateRange<Dayjs>` state works unchanged.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MGHDateRangePicker: React.ForwardRefExoticComponent<MGHDateRangePickerProps & React.RefAttributes<HTMLDivElement>>;
|
|
10
|
+
export default MGHDateRangePicker;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const MGH_DATE_RANGE_PICKER_STYLE_ID = "mgh-drp-styles";
|
|
2
|
+
/**
|
|
3
|
+
* Stylesheet for MGHDateRangePicker. Injected once into <head> the first time a picker mounts.
|
|
4
|
+
* Colours are CSS variables (`--mgh-drp-*`). When the picker is rendered inside an MGHGrid card
|
|
5
|
+
* it inherits the grid palette (`--mgh-grid-*`) automatically.
|
|
6
|
+
*/
|
|
7
|
+
export declare const MGH_DATE_RANGE_PICKER_CSS = "\n.mgh-drp, .mgh-drp-popover, .mgh-drp-calendars {\n --mgh-drp-font-family: var(--mgh-grid-font-family, inherit);\n --mgh-drp-font-size: var(--mgh-grid-font-size, 14px);\n --mgh-drp-paper: var(--mgh-grid-paper, #ffffff);\n --mgh-drp-fg: var(--mgh-grid-fg, rgba(0, 0, 0, 0.87));\n --mgh-drp-fg-secondary: var(--mgh-grid-fg-secondary, rgba(0, 0, 0, 0.6));\n --mgh-drp-fg-disabled: var(--mgh-grid-fg-disabled, rgba(0, 0, 0, 0.38));\n --mgh-drp-border: var(--mgh-grid-border, rgba(224, 224, 224, 1));\n --mgh-drp-input-border: var(--mgh-grid-input-border, rgba(0, 0, 0, 0.23));\n --mgh-drp-hover: var(--mgh-grid-hover, rgba(0, 0, 0, 0.04));\n --mgh-drp-primary: var(--mgh-grid-primary, #1976d2);\n --mgh-drp-primary-contrast: var(--mgh-grid-primary-contrast, #ffffff);\n --mgh-drp-focus: var(--mgh-grid-focus, #1976d2);\n --mgh-drp-selected: var(--mgh-grid-selected, rgba(25, 118, 210, 0.12));\n --mgh-drp-preview: rgba(0, 0, 0, 0.06);\n --mgh-drp-error: #d32f2f;\n --mgh-drp-radius: 4px;\n --mgh-drp-popover-radius: 8px;\n --mgh-drp-popover-shadow: var(--mgh-grid-popover-shadow, 0 5px 5px -3px rgba(0,0,0,.2), 0 8px 10px 1px rgba(0,0,0,.14), 0 3px 14px 2px rgba(0,0,0,.12));\n --mgh-drp-day-size: 36px;\n --mgh-drp-day-margin: 2px;\n font-family: var(--mgh-drp-font-family);\n font-size: var(--mgh-drp-font-size);\n color: var(--mgh-drp-fg);\n line-height: 1.5;\n}\n/* Dark values also apply to a calendar nested inside a dark picker / popover, so the calendar's own\n defaults (declared above for standalone use) never reset the colours back to light. */\n.mgh-drp[data-mgh-theme=\"dark\"], .mgh-drp-popover[data-mgh-theme=\"dark\"], .mgh-drp-calendars[data-mgh-theme=\"dark\"],\n.mgh-drp[data-mgh-theme=\"dark\"] .mgh-drp-calendars, .mgh-drp-popover[data-mgh-theme=\"dark\"] .mgh-drp-calendars {\n --mgh-drp-paper: #212b36;\n --mgh-drp-fg: #ffffff;\n --mgh-drp-fg-secondary: rgba(255, 255, 255, 0.7);\n --mgh-drp-fg-disabled: rgba(255, 255, 255, 0.5);\n --mgh-drp-border: rgba(81, 81, 81, 1);\n --mgh-drp-input-border: rgba(255, 255, 255, 0.23);\n --mgh-drp-hover: rgba(255, 255, 255, 0.08);\n --mgh-drp-primary: #90caf9;\n --mgh-drp-primary-contrast: rgba(0, 0, 0, 0.87);\n --mgh-drp-focus: #90caf9;\n --mgh-drp-selected: rgba(144, 202, 249, 0.16);\n --mgh-drp-preview: rgba(255, 255, 255, 0.08);\n --mgh-drp-error: #f44336;\n}\n[class*=\"mgh-drp\"], [class*=\"mgh-drp\"]::before, [class*=\"mgh-drp\"]::after { box-sizing: border-box; }\n\n/* ---- root / multi-input layout ---- */\n.mgh-drp { display: inline-flex; flex-direction: column; vertical-align: top; min-width: 0; }\n.mgh-drp--full-width { display: flex; width: 100%; }\n.mgh-drp-fields { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }\n.mgh-drp-fields .mgh-drp-field { flex: 1 1 0; min-width: 0; }\n.mgh-drp-fields-separator { align-self: center; color: var(--mgh-drp-fg-secondary); padding: 0 2px; }\n\n/* ---- outlined text field (MUI TextField look-alike) ---- */\n.mgh-drp-field { position: relative; display: inline-flex; align-items: center; min-width: 0; width: 100%; border-radius: var(--mgh-drp-radius); color: var(--mgh-drp-fg); }\n.mgh-drp-field--medium { height: 56px; }\n.mgh-drp-field--small { height: 40px; }\n.mgh-drp-field:not(.mgh-drp-field--full-width) { width: 260px; }\n.mgh-drp-field--multi:not(.mgh-drp-field--full-width) { width: 170px; }\n.mgh-drp-field--full-width { width: 100%; }\n.mgh-drp-input { flex: 1 1 auto; min-width: 0; width: 100%; height: 100%; margin: 0; border: 0; outline: none; background: transparent; color: inherit; font: inherit; font-size: 1rem; letter-spacing: inherit; padding: 16.5px 14px; }\n.mgh-drp-field--small .mgh-drp-input { padding: 8.5px 14px; }\n.mgh-drp-field--adorned .mgh-drp-input { padding-right: 0; }\n.mgh-drp-input::placeholder { color: var(--mgh-drp-fg-disabled); opacity: 1; }\n.mgh-drp-input::-webkit-calendar-picker-indicator { display: none; }\n.mgh-drp-field--disabled .mgh-drp-input { color: var(--mgh-drp-fg-disabled); -webkit-text-fill-color: var(--mgh-drp-fg-disabled); }\n.mgh-drp-field--disabled { cursor: default; }\n.mgh-drp-field--clickable .mgh-drp-input { cursor: pointer; }\n.mgh-drp-adornment { display: flex; align-items: center; gap: 0; margin-right: 6px; flex: 0 0 auto; color: var(--mgh-drp-fg-secondary); }\n.mgh-drp-field--small .mgh-drp-adornment { margin-right: 4px; }\n\n/* notched outline */\n.mgh-drp-outline { position: absolute; inset: -5px 0 0 0; margin: 0; padding: 0 8px; pointer-events: none; border: 1px solid var(--mgh-drp-input-border); border-radius: inherit; min-width: 0; overflow: hidden; text-align: left; transition: border-color .15s; }\n.mgh-drp-outline legend { float: unset; width: auto; padding: 0; height: 11px; font-size: 0.75em; visibility: hidden; max-width: 0.01px; white-space: nowrap; transition: max-width 50ms cubic-bezier(0.0, 0, 0.2, 1) 0ms; overflow: hidden; display: block; line-height: 11px; }\n.mgh-drp-outline legend > span { padding-left: 5px; padding-right: 5px; display: inline-block; opacity: 0; visibility: visible; }\n.mgh-drp-field--shrink .mgh-drp-outline legend { max-width: 100%; transition: max-width 100ms cubic-bezier(0.0, 0, 0.2, 1) 50ms; }\n.mgh-drp-field--no-label .mgh-drp-outline legend { display: none; }\n.mgh-drp-field:hover:not(.mgh-drp-field--disabled) .mgh-drp-outline { border-color: var(--mgh-drp-fg); }\n.mgh-drp-field--focused .mgh-drp-outline, .mgh-drp-field--focused:hover .mgh-drp-outline { border-color: var(--mgh-drp-focus); border-width: 2px; }\n.mgh-drp-field--error .mgh-drp-outline, .mgh-drp-field--error:hover .mgh-drp-outline { border-color: var(--mgh-drp-error); }\n.mgh-drp-field--disabled .mgh-drp-outline { border-color: var(--mgh-drp-fg-disabled); }\n\n/* floating label */\n.mgh-drp-label { position: absolute; left: 0; top: 0; transform-origin: top left; transform: translate(14px, 16px) scale(1); color: var(--mgh-drp-fg-secondary); font-size: 1rem; line-height: 1.4375em; pointer-events: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: calc(100% - 24px); transition: color .15s, transform .15s cubic-bezier(0.0, 0, 0.2, 1), max-width .15s; z-index: 1; }\n.mgh-drp-field--small .mgh-drp-label { transform: translate(14px, 9px) scale(1); }\n.mgh-drp-field--shrink .mgh-drp-label, .mgh-drp-field--small.mgh-drp-field--shrink .mgh-drp-label { transform: translate(14px, -9px) scale(0.75); max-width: calc(133% - 32px); }\n.mgh-drp-field--focused .mgh-drp-label { color: var(--mgh-drp-focus); }\n.mgh-drp-field--error .mgh-drp-label { color: var(--mgh-drp-error); }\n.mgh-drp-field--disabled .mgh-drp-label { color: var(--mgh-drp-fg-disabled); }\n.mgh-drp-label-asterisk { margin-left: 2px; }\n\n.mgh-drp-helper { margin: 3px 14px 0; font-size: 0.75rem; line-height: 1.66; color: var(--mgh-drp-fg-secondary); }\n.mgh-drp-helper--error { color: var(--mgh-drp-error); }\n\n/* ---- buttons ---- */\n.mgh-drp-btn { display: inline-flex; align-items: center; justify-content: center; gap: 6px; border: 0; margin: 0; background: transparent; color: var(--mgh-drp-fg-secondary); font: inherit; font-size: 0.8125rem; font-weight: 500; line-height: 1.75; letter-spacing: .02em; padding: 4px 8px; border-radius: 4px; cursor: pointer; user-select: none; transition: background-color .15s, color .15s; white-space: nowrap; text-transform: uppercase; }\n.mgh-drp-btn:hover { background: var(--mgh-drp-hover); color: var(--mgh-drp-fg); }\n.mgh-drp-btn:focus-visible { outline: 2px solid var(--mgh-drp-focus); outline-offset: 1px; }\n.mgh-drp-btn:disabled { color: var(--mgh-drp-fg-disabled); cursor: default; background: transparent; }\n.mgh-drp-btn--primary { color: var(--mgh-drp-primary); }\n.mgh-drp-btn--primary:hover { background: var(--mgh-drp-selected); color: var(--mgh-drp-primary); }\n.mgh-drp-btn--icon { padding: 8px; border-radius: 50%; width: 40px; height: 40px; text-transform: none; }\n.mgh-drp-btn--icon.mgh-drp-btn--small { width: 32px; height: 32px; padding: 4px; }\n.mgh-drp-icon { flex: 0 0 auto; }\n\n/* ---- popover ---- */\n.mgh-drp-popover { position: fixed; z-index: 1300; display: flex; flex-direction: column; background: var(--mgh-drp-paper); color: var(--mgh-drp-fg); border-radius: var(--mgh-drp-popover-radius); box-shadow: var(--mgh-drp-popover-shadow); outline: none; max-height: calc(100vh - 16px); max-width: calc(100vw - 16px); overflow: auto; animation: mgh-drp-pop .12s ease-out; }\n@keyframes mgh-drp-pop { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: none; } }\n.mgh-drp-layout { display: flex; flex-direction: row; align-items: stretch; }\n.mgh-drp-layout--with-toolbar { flex-direction: column; }\n.mgh-drp-layout-main { display: flex; flex-direction: row; align-items: stretch; }\n.mgh-drp-toolbar { padding: 16px 24px 0; }\n.mgh-drp-toolbar-title { font-size: 0.75rem; text-transform: uppercase; letter-spacing: .08em; color: var(--mgh-drp-fg-secondary); margin: 0 0 8px; }\n.mgh-drp-toolbar-range { display: flex; align-items: center; gap: 8px; font-size: 1.5rem; font-weight: 400; line-height: 1.334; }\n.mgh-drp-toolbar-range button { border: 0; background: transparent; padding: 0 2px; font: inherit; color: var(--mgh-drp-fg-secondary); cursor: pointer; border-radius: 4px; }\n.mgh-drp-toolbar-range button.mgh-drp-toolbar-range--active { color: var(--mgh-drp-fg); }\n.mgh-drp-toolbar-range button:hover { background: var(--mgh-drp-hover); }\n\n/* shortcuts (left column) */\n.mgh-drp-shortcuts { list-style: none; margin: 0; padding: 8px 0; min-width: 140px; max-height: 340px; overflow: auto; border-right: 1px solid var(--mgh-drp-border); display: flex; flex-direction: column; gap: 4px; padding-left: 8px; padding-right: 8px; }\n.mgh-drp-shortcut { display: inline-flex; align-items: center; height: 32px; padding: 0 12px; border-radius: 16px; border: 1px solid var(--mgh-drp-input-border); background: transparent; color: var(--mgh-drp-fg); font: inherit; font-size: 0.8125rem; cursor: pointer; white-space: nowrap; justify-content: flex-start; transition: background-color .15s; }\n.mgh-drp-shortcut:hover { background: var(--mgh-drp-hover); }\n.mgh-drp-shortcut:focus-visible { outline: 2px solid var(--mgh-drp-focus); outline-offset: 1px; }\n.mgh-drp-shortcut:disabled { color: var(--mgh-drp-fg-disabled); cursor: default; }\n.mgh-drp-shortcut--selected { background: var(--mgh-drp-selected); border-color: var(--mgh-drp-primary); color: var(--mgh-drp-primary); }\n\n/* ---- calendars ---- */\n.mgh-drp-calendars { display: flex; flex-direction: row; align-items: flex-start; }\n.mgh-drp-calendar { display: flex; flex-direction: column; width: calc(7 * (var(--mgh-drp-day-size) + 2 * var(--mgh-drp-day-margin)) + 24px); padding: 0 12px 8px; }\n.mgh-drp-calendar + .mgh-drp-calendar { border-left: 1px solid var(--mgh-drp-border); }\n.mgh-drp-calendar-header { display: flex; align-items: center; justify-content: space-between; min-height: 40px; margin: 12px 0 8px; padding: 0 4px; }\n.mgh-drp-calendar-label { flex: 1 1 auto; text-align: center; font-size: 1rem; font-weight: 500; }\n.mgh-drp-calendar-header .mgh-drp-btn--icon { color: var(--mgh-drp-fg-secondary); }\n.mgh-drp-calendar-nav-spacer { width: 40px; height: 40px; flex: 0 0 auto; }\n.mgh-drp-weekdays, .mgh-drp-week { display: flex; justify-content: center; }\n.mgh-drp-weekday { width: var(--mgh-drp-day-size); height: 40px; margin: 0 var(--mgh-drp-day-margin); display: flex; align-items: center; justify-content: center; font-size: 0.75rem; color: var(--mgh-drp-fg-secondary); font-weight: 400; }\n.mgh-drp-weeks { display: flex; flex-direction: column; min-height: calc(6 * (var(--mgh-drp-day-size) + 2 * var(--mgh-drp-day-margin))); }\n.mgh-drp-week { margin: var(--mgh-drp-day-margin) 0; }\n\n/* day cell: the outer wrapper carries the range highlight, the inner button the day itself */\n.mgh-drp-day-wrap { position: relative; width: calc(var(--mgh-drp-day-size) + 2 * var(--mgh-drp-day-margin)); height: var(--mgh-drp-day-size); display: flex; align-items: center; justify-content: center; }\n.mgh-drp-day-wrap--in-range { background: var(--mgh-drp-selected); }\n.mgh-drp-day-wrap--range-start { border-top-left-radius: 50%; border-bottom-left-radius: 50%; }\n.mgh-drp-day-wrap--range-end { border-top-right-radius: 50%; border-bottom-right-radius: 50%; }\n.mgh-drp-day-wrap--preview { border-top: 1px dashed var(--mgh-drp-fg-secondary); border-bottom: 1px dashed var(--mgh-drp-fg-secondary); }\n.mgh-drp-day-wrap--preview-start { border-left: 1px dashed var(--mgh-drp-fg-secondary); border-top-left-radius: 50%; border-bottom-left-radius: 50%; }\n.mgh-drp-day-wrap--preview-end { border-right: 1px dashed var(--mgh-drp-fg-secondary); border-top-right-radius: 50%; border-bottom-right-radius: 50%; }\n.mgh-drp-day-wrap--first-visible.mgh-drp-day-wrap--in-range:not(.mgh-drp-day-wrap--range-start) { border-top-left-radius: 50%; border-bottom-left-radius: 50%; }\n.mgh-drp-day-wrap--last-visible.mgh-drp-day-wrap--in-range:not(.mgh-drp-day-wrap--range-end) { border-top-right-radius: 50%; border-bottom-right-radius: 50%; }\n.mgh-drp-day-wrap--first-visible.mgh-drp-day-wrap--preview:not(.mgh-drp-day-wrap--preview-start) { border-top-left-radius: 50%; border-bottom-left-radius: 50%; border-left: 1px dashed var(--mgh-drp-fg-secondary); }\n.mgh-drp-day-wrap--last-visible.mgh-drp-day-wrap--preview:not(.mgh-drp-day-wrap--preview-end) { border-top-right-radius: 50%; border-bottom-right-radius: 50%; border-right: 1px dashed var(--mgh-drp-fg-secondary); }\n.mgh-drp-day { width: var(--mgh-drp-day-size); height: var(--mgh-drp-day-size); border-radius: 50%; border: 0; margin: 0; padding: 0; background: transparent; color: var(--mgh-drp-fg); font: inherit; font-size: 0.75rem; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; transition: background-color .15s, color .15s; position: relative; }\n.mgh-drp-day:hover { background: var(--mgh-drp-hover); }\n.mgh-drp-day:focus-visible { outline: 2px solid var(--mgh-drp-focus); outline-offset: -1px; }\n.mgh-drp-day--today:not(.mgh-drp-day--selected) { border: 1px solid var(--mgh-drp-fg-secondary); }\n.mgh-drp-day--outside { color: var(--mgh-drp-fg-secondary); opacity: .6; }\n.mgh-drp-day--hidden { visibility: hidden; pointer-events: none; }\n.mgh-drp-day--disabled, .mgh-drp-day--disabled:hover { color: var(--mgh-drp-fg-disabled); cursor: default; background: transparent; }\n.mgh-drp-day--selected, .mgh-drp-day--selected:hover { background: var(--mgh-drp-primary); color: var(--mgh-drp-primary-contrast); font-weight: 500; }\n.mgh-drp-day--selected:focus-visible { outline-offset: 2px; }\n.mgh-drp-calendar--read-only .mgh-drp-day, .mgh-drp-calendar--disabled .mgh-drp-day { cursor: default; }\n.mgh-drp-calendar--disabled .mgh-drp-day { color: var(--mgh-drp-fg-disabled); }\n.mgh-drp-calendar--disabled .mgh-drp-day--selected { opacity: .6; }\n\n/* ---- action bar ---- */\n.mgh-drp-actions { display: flex; align-items: center; justify-content: flex-end; gap: 8px; padding: 8px; border-top: 1px solid var(--mgh-drp-border); }\n.mgh-drp-actions--spread { justify-content: space-between; }\n.mgh-drp-actions-group { display: flex; gap: 8px; }\n\n@media (max-width: 599.95px) {\n .mgh-drp-layout-main { flex-direction: column; }\n .mgh-drp-shortcuts { flex-direction: row; flex-wrap: wrap; border-right: 0; border-bottom: 1px solid var(--mgh-drp-border); max-height: none; }\n .mgh-drp-calendars { flex-direction: column; align-items: center; }\n .mgh-drp-calendar + .mgh-drp-calendar { border-left: 0; border-top: 1px solid var(--mgh-drp-border); }\n}\n";
|
|
8
|
+
/** Injects the MGHDateRangePicker stylesheet once per document. Safe on the server (no-op). */
|
|
9
|
+
export declare const useMGHDateRangePickerStyles: () => void;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { Dayjs } from 'dayjs';
|
|
3
|
+
/** `[start, end]` – the same shape as MUI's `DateRange<Dayjs>`. */
|
|
4
|
+
export type MGHDateRange = [Dayjs | null, Dayjs | null];
|
|
5
|
+
export type MGHDateRangePosition = 'start' | 'end';
|
|
6
|
+
export type MGHDateRangeThemeMode = 'light' | 'dark';
|
|
7
|
+
export type MGHDateRangeValidationError = 'invalidDate' | 'invalidRange' | 'minDate' | 'maxDate' | 'disablePast' | 'disableFuture' | 'shouldDisableDate' | null;
|
|
8
|
+
export type MGHDateRangeAction = 'clear' | 'today' | 'cancel' | 'accept';
|
|
9
|
+
export interface MGHDateRangeShortcut {
|
|
10
|
+
label: string;
|
|
11
|
+
/** Return the range to apply. `isValid` reports whether a range passes the picker's validation. */
|
|
12
|
+
getValue: (context: {
|
|
13
|
+
isValid: (value: MGHDateRange) => boolean;
|
|
14
|
+
}) => MGHDateRange;
|
|
15
|
+
id?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface MGHDateRangeChangeContext {
|
|
18
|
+
validationError: [MGHDateRangeValidationError, MGHDateRangeValidationError];
|
|
19
|
+
/** Set when the change came from a shortcut. */
|
|
20
|
+
shortcut?: MGHDateRangeShortcut;
|
|
21
|
+
}
|
|
22
|
+
export interface MGHDateRangePickerLocaleText {
|
|
23
|
+
start: string;
|
|
24
|
+
end: string;
|
|
25
|
+
/** Placeholder for a single date, e.g. `MM/DD/YYYY`. Defaults to the `format`. */
|
|
26
|
+
datePlaceholder?: string;
|
|
27
|
+
fieldSeparator: string;
|
|
28
|
+
openPicker: string;
|
|
29
|
+
clear: string;
|
|
30
|
+
today: string;
|
|
31
|
+
cancel: string;
|
|
32
|
+
accept: string;
|
|
33
|
+
previousMonth: string;
|
|
34
|
+
nextMonth: string;
|
|
35
|
+
/** Range summary shown in the toolbar, e.g. "Select date range". */
|
|
36
|
+
toolbarTitle: string;
|
|
37
|
+
}
|
|
38
|
+
export interface MGHDateRangeCalendarProps {
|
|
39
|
+
value?: MGHDateRange;
|
|
40
|
+
defaultValue?: MGHDateRange;
|
|
41
|
+
/** Fires on every click in the calendar; `selectedDay` is the day that was clicked. */
|
|
42
|
+
onChange?: (value: MGHDateRange, selectedDay: Dayjs, position: MGHDateRangePosition) => void;
|
|
43
|
+
rangePosition?: MGHDateRangePosition;
|
|
44
|
+
onRangePositionChange?: (position: MGHDateRangePosition) => void;
|
|
45
|
+
/** Number of months displayed side by side. @default 2 */
|
|
46
|
+
calendars?: 1 | 2 | 3;
|
|
47
|
+
/** Month shown in the first calendar when nothing is selected. @default today */
|
|
48
|
+
referenceDate?: Dayjs;
|
|
49
|
+
minDate?: Dayjs | null;
|
|
50
|
+
maxDate?: Dayjs | null;
|
|
51
|
+
disablePast?: boolean;
|
|
52
|
+
disableFuture?: boolean;
|
|
53
|
+
shouldDisableDate?: (day: Dayjs, position: MGHDateRangePosition) => boolean;
|
|
54
|
+
disabled?: boolean;
|
|
55
|
+
readOnly?: boolean;
|
|
56
|
+
/** Render the days of the previous / next month in the empty cells. @default false */
|
|
57
|
+
showDaysOutsideCurrentMonth?: boolean;
|
|
58
|
+
/** Always render this many week rows (MUI: `fixedWeekNumber`). */
|
|
59
|
+
fixedWeekNumber?: number;
|
|
60
|
+
/** Weekday header formatter. @default first letter of the weekday */
|
|
61
|
+
dayOfWeekFormatter?: (day: Dayjs) => string;
|
|
62
|
+
/** Do not outline today's date. @default false */
|
|
63
|
+
disableHighlightToday?: boolean;
|
|
64
|
+
/** Highlight the hovered interval while the end date is being chosen. @default true */
|
|
65
|
+
showRangePreview?: boolean;
|
|
66
|
+
/** Focus the selected (or current) day when the calendar mounts. */
|
|
67
|
+
autoFocus?: boolean;
|
|
68
|
+
/** Called when the visible month changes (the month of the *first* calendar). */
|
|
69
|
+
onMonthChange?: (month: Dayjs) => void;
|
|
70
|
+
localeText?: Partial<Pick<MGHDateRangePickerLocaleText, 'previousMonth' | 'nextMonth'>>;
|
|
71
|
+
themeMode?: MGHDateRangeThemeMode;
|
|
72
|
+
className?: string;
|
|
73
|
+
style?: React.CSSProperties;
|
|
74
|
+
}
|
|
75
|
+
export interface MGHDateRangePickerProps extends Omit<MGHDateRangeCalendarProps, 'onChange' | 'autoFocus' | 'className' | 'style' | 'localeText'> {
|
|
76
|
+
onChange?: (value: MGHDateRange, context: MGHDateRangeChangeContext) => void;
|
|
77
|
+
/** Fires when a complete range is committed (closing after the end date, the Accept action, or a shortcut). */
|
|
78
|
+
onAccept?: (value: MGHDateRange) => void;
|
|
79
|
+
/** Fires when the validation error of either date changes. */
|
|
80
|
+
onError?: (error: [MGHDateRangeValidationError, MGHDateRangeValidationError], value: MGHDateRange) => void;
|
|
81
|
+
open?: boolean;
|
|
82
|
+
onOpen?: () => void;
|
|
83
|
+
onClose?: () => void;
|
|
84
|
+
/** Text field label (single-input field). For the multi-input field use `localeText.start` / `localeText.end`. */
|
|
85
|
+
label?: React.ReactNode;
|
|
86
|
+
/** dayjs display / parse format. @default 'MM/DD/YYYY' */
|
|
87
|
+
format?: string;
|
|
88
|
+
/** `'single'` renders one field "start – end"; `'multi'` renders two fields. @default 'single' */
|
|
89
|
+
fieldVariant?: 'single' | 'multi';
|
|
90
|
+
size?: 'small' | 'medium';
|
|
91
|
+
fullWidth?: boolean;
|
|
92
|
+
/** Show a clear (×) button when a value is set. @default false */
|
|
93
|
+
clearable?: boolean;
|
|
94
|
+
/** Open the calendar when the text field itself is clicked (MUI only opens from the icon button). @default false */
|
|
95
|
+
openOnClick?: boolean;
|
|
96
|
+
/** Hide the calendar icon button. @default false */
|
|
97
|
+
disableOpenPicker?: boolean;
|
|
98
|
+
/** Close the popover as soon as the end date is picked. @default true (false when `actions` includes 'accept') */
|
|
99
|
+
closeOnSelect?: boolean;
|
|
100
|
+
/** Quick ranges listed on the left of the calendars (MUI `slotProps.shortcuts.items`). */
|
|
101
|
+
shortcuts?: MGHDateRangeShortcut[];
|
|
102
|
+
/** Buttons in the action bar under the calendars (MUI `slotProps.actionBar.actions`). @default [] */
|
|
103
|
+
actions?: MGHDateRangeAction[];
|
|
104
|
+
/** Show the "Start – End" summary toolbar above the calendars. @default false */
|
|
105
|
+
showToolbar?: boolean;
|
|
106
|
+
localeText?: Partial<MGHDateRangePickerLocaleText>;
|
|
107
|
+
name?: string;
|
|
108
|
+
id?: string;
|
|
109
|
+
placeholder?: string;
|
|
110
|
+
required?: boolean;
|
|
111
|
+
/** Force the error state. Validation errors from the value are shown automatically. */
|
|
112
|
+
error?: boolean;
|
|
113
|
+
helperText?: React.ReactNode;
|
|
114
|
+
autoFocus?: boolean;
|
|
115
|
+
inputRef?: React.Ref<HTMLInputElement>;
|
|
116
|
+
themeMode?: MGHDateRangeThemeMode;
|
|
117
|
+
/** Override any `--mgh-drp-*` CSS variable for this instance. */
|
|
118
|
+
themeVars?: Record<string, string>;
|
|
119
|
+
className?: string;
|
|
120
|
+
style?: React.CSSProperties;
|
|
121
|
+
popoverClassName?: string;
|
|
122
|
+
popoverStyle?: React.CSSProperties;
|
|
123
|
+
/** Placement of the popover relative to the field. @default 'bottom-start' */
|
|
124
|
+
placement?: 'bottom-start' | 'bottom-end' | 'bottom';
|
|
125
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface MGHDateRangeIconProps extends React.SVGProps<SVGSVGElement> {
|
|
3
|
+
size?: number;
|
|
4
|
+
}
|
|
5
|
+
export declare const DateRangeIcon: React.ForwardRefExoticComponent<Omit<MGHDateRangeIconProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
|
|
6
|
+
export declare const ChevronLeftIcon: React.ForwardRefExoticComponent<Omit<MGHDateRangeIconProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
|
|
7
|
+
export declare const ChevronRightIcon: React.ForwardRefExoticComponent<Omit<MGHDateRangeIconProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
|
|
8
|
+
export declare const CloseIcon: React.ForwardRefExoticComponent<Omit<MGHDateRangeIconProps, "ref"> & React.RefAttributes<SVGSVGElement>>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default } from './MGHDateRangePicker';
|
|
2
|
+
export { MGHDateRangePicker, MGH_DATE_RANGE_PICKER_DEFAULT_LOCALE_TEXT } from './MGHDateRangePicker';
|
|
3
|
+
export { MGHDateRangeCalendar } from './MGHDateRangeCalendar';
|
|
4
|
+
export * from './MGHDateRangePicker.types';
|
|
5
|
+
export { MGH_DATE_RANGE_PICKER_CSS, MGH_DATE_RANGE_PICKER_STYLE_ID, useMGHDateRangePickerStyles, } from './MGHDateRangePicker.styles';
|
|
6
|
+
export { MGH_DATE_RANGE_DEFAULT_SHORTCUTS, validateRange as mghDateRangeValidate, parseDateText as mghDateRangeParseDate, isRangeEqual as mghDateRangeIsEqual, } from './utils';
|
|
7
|
+
export * as MGHDateRangeIcons from './icons';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { Dayjs } from 'dayjs';
|
|
2
|
+
import type { MGHDateRange, MGHDateRangePosition, MGHDateRangeShortcut, MGHDateRangeValidationError } from './MGHDateRangePicker.types';
|
|
3
|
+
export declare const cx: (...classNames: Array<string | false | null | undefined>) => string;
|
|
4
|
+
export declare const clamp: (value: number, min: number, max: number) => number;
|
|
5
|
+
export declare const EMPTY_RANGE: MGHDateRange;
|
|
6
|
+
/** Coerces Dayjs / Date / ISO string / timestamp into a valid Dayjs (or null). */
|
|
7
|
+
export declare const toDayjs: (value: unknown) => Dayjs | null;
|
|
8
|
+
export declare const isSameDay: (a: Dayjs | null | undefined, b: Dayjs | null | undefined) => boolean;
|
|
9
|
+
export declare const isBeforeDay: (a: Dayjs, b: Dayjs) => boolean;
|
|
10
|
+
export declare const isAfterDay: (a: Dayjs, b: Dayjs) => boolean;
|
|
11
|
+
export declare const isWithinRange: (day: Dayjs, start: Dayjs | null, end: Dayjs | null) => boolean;
|
|
12
|
+
export declare const isRangeEqual: (a: MGHDateRange | undefined, b: MGHDateRange | undefined) => boolean;
|
|
13
|
+
export declare const normalizeRange: (value: unknown) => MGHDateRange;
|
|
14
|
+
/** Strict parse with the display format first, then a handful of forgiving fallbacks. */
|
|
15
|
+
export declare const parseDateText: (text: string, format: string) => Dayjs | null;
|
|
16
|
+
export declare const formatDate: (value: Dayjs | null, format: string) => string;
|
|
17
|
+
/** Splits "start – end" text typed into the single-input field. */
|
|
18
|
+
export declare const splitRangeText: (text: string) => [string, string];
|
|
19
|
+
export interface DateValidationOptions {
|
|
20
|
+
minDate?: Dayjs | null;
|
|
21
|
+
maxDate?: Dayjs | null;
|
|
22
|
+
disablePast?: boolean;
|
|
23
|
+
disableFuture?: boolean;
|
|
24
|
+
shouldDisableDate?: (day: Dayjs, position: MGHDateRangePosition) => boolean;
|
|
25
|
+
}
|
|
26
|
+
export declare const validateDay: (day: Dayjs | null, position: MGHDateRangePosition, options: DateValidationOptions, now?: Dayjs) => MGHDateRangeValidationError;
|
|
27
|
+
export declare const validateRange: (value: MGHDateRange, options: DateValidationOptions) => [MGHDateRangeValidationError, MGHDateRangeValidationError];
|
|
28
|
+
export declare const isDayDisabled: (day: Dayjs, position: MGHDateRangePosition, options: DateValidationOptions) => boolean;
|
|
29
|
+
export interface CalendarWeek {
|
|
30
|
+
key: string;
|
|
31
|
+
days: Array<{
|
|
32
|
+
day: Dayjs;
|
|
33
|
+
outsideMonth: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Builds the weeks of `month`. Weeks start on the locale's first day of the week (Sunday for `en`).
|
|
38
|
+
* Pass `fixedWeekNumber` (usually 6) to always render the same number of rows.
|
|
39
|
+
*/
|
|
40
|
+
export declare const getMonthWeeks: (month: Dayjs, fixedWeekNumber?: number) => CalendarWeek[];
|
|
41
|
+
export declare const getWeekDayLabels: (formatter: (day: Dayjs) => string) => {
|
|
42
|
+
key: string;
|
|
43
|
+
label: string;
|
|
44
|
+
full: string;
|
|
45
|
+
}[];
|
|
46
|
+
export declare const MGH_DATE_RANGE_DEFAULT_SHORTCUTS: MGHDateRangeShortcut[];
|