@12-apps/ui 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components-catalog.md +8 -0
- package/package.json +2 -1
- package/src/components/data-display/DataViews/data-views-day-input.tsx +43 -22
- package/src/components/form/DateRangePicker/DateRangePicker.dates.ts +149 -0
- package/src/components/form/DateRangePicker/DateRangePicker.hooks.ts +87 -0
- package/src/components/form/DateRangePicker/DateRangePicker.md +228 -0
- package/src/components/form/DateRangePicker/DateRangePicker.quick.ts +97 -0
- package/src/components/form/DateRangePicker/DateRangePicker.range.ts +105 -0
- package/src/components/form/DateRangePicker/DateRangePicker.tsx +146 -0
- package/src/components/form/DateRangePicker/DateRangePicker.types.ts +193 -0
- package/src/components/form/DateRangePicker/DateRangePickerFields.tsx +90 -0
- package/src/components/form/DateRangePicker/DateRangePickerQuickList.tsx +123 -0
- package/src/components/form/DateRangePicker/index.ts +25 -0
- package/src/components/form/index.ts +1 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The quick-pick column beside the calendar.
|
|
3
|
+
*
|
|
4
|
+
* These are BUTTONS, not links or list rows: each one sets the range and the
|
|
5
|
+
* pressed one stays visibly pressed, which is what `aria-pressed` says out
|
|
6
|
+
* loud. (A listbox with `aria-selected` would model it too — the rule is to
|
|
7
|
+
* pick one and use it for every entry, so a reader's screen reader describes
|
|
8
|
+
* the whole column the same way.)
|
|
9
|
+
*
|
|
10
|
+
* An over-cap entry is `aria-disabled` and keeps its place in the tab order,
|
|
11
|
+
* with the reason rendered under the label. Removing it, or quietly shortening
|
|
12
|
+
* the window it hands back, both answer "why can't I have this year?" with
|
|
13
|
+
* silence.
|
|
14
|
+
*/
|
|
15
|
+
import { Box } from '@mui/material';
|
|
16
|
+
import type { SxProps, Theme } from '@mui/material';
|
|
17
|
+
import React, { useId } from 'react';
|
|
18
|
+
|
|
19
|
+
import type { ResolvedQuickRange } from './DateRangePicker.types';
|
|
20
|
+
|
|
21
|
+
const optionSx = (active: boolean, overMax: boolean): SxProps<Theme> => ({
|
|
22
|
+
display: 'block',
|
|
23
|
+
// Full-width rows in the side column; chips that flow onto as many lines as
|
|
24
|
+
// they need once the column has become a row under the calendar. A fixed
|
|
25
|
+
// full width in both would make the narrow layout nine stacked lines.
|
|
26
|
+
width: { xs: 'auto', md: '100%' },
|
|
27
|
+
textAlign: 'left',
|
|
28
|
+
font: 'inherit',
|
|
29
|
+
fontSize: '0.875rem',
|
|
30
|
+
fontWeight: active ? 600 : 400,
|
|
31
|
+
lineHeight: 1.4,
|
|
32
|
+
px: 1.5,
|
|
33
|
+
py: 0.75,
|
|
34
|
+
border: '1px solid',
|
|
35
|
+
borderColor: active ? 'primary.main' : 'transparent',
|
|
36
|
+
borderRadius: 1,
|
|
37
|
+
cursor: overMax ? 'not-allowed' : 'pointer',
|
|
38
|
+
color: overMax ? 'text.disabled' : active ? 'primary.main' : 'text.primary',
|
|
39
|
+
backgroundColor: active ? 'action.selected' : 'transparent',
|
|
40
|
+
'&:hover': { backgroundColor: overMax ? 'transparent' : 'action.hover' },
|
|
41
|
+
'&:focus-visible': { outline: '2px solid', outlineColor: 'primary.main', outlineOffset: 1 },
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const REASON_SX: SxProps<Theme> = {
|
|
45
|
+
display: 'block',
|
|
46
|
+
fontSize: '0.6875rem',
|
|
47
|
+
lineHeight: 1.3,
|
|
48
|
+
mt: 0.25,
|
|
49
|
+
color: 'text.disabled',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export interface DateRangeQuickListProps {
|
|
53
|
+
options: ResolvedQuickRange[];
|
|
54
|
+
/** The entry the current draft equals, if any. */
|
|
55
|
+
activeId: string | undefined;
|
|
56
|
+
onPick: (option: ResolvedQuickRange) => void;
|
|
57
|
+
/** Why an entry is refused, rendered under it. */
|
|
58
|
+
reasonFor: (option: ResolvedQuickRange) => string;
|
|
59
|
+
/** Accessible name of the group. */
|
|
60
|
+
label: string;
|
|
61
|
+
dataTestId: string;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function DateRangeQuickList({
|
|
65
|
+
options,
|
|
66
|
+
activeId,
|
|
67
|
+
onPick,
|
|
68
|
+
reasonFor,
|
|
69
|
+
label,
|
|
70
|
+
dataTestId,
|
|
71
|
+
}: DateRangeQuickListProps): React.JSX.Element {
|
|
72
|
+
const reasonPrefix = useId();
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Box
|
|
76
|
+
role="group"
|
|
77
|
+
aria-label={label}
|
|
78
|
+
data-testid={`${dataTestId}-quick-list`}
|
|
79
|
+
sx={{
|
|
80
|
+
display: 'flex',
|
|
81
|
+
// A column beside the calendar; a wrapping row above it once there is
|
|
82
|
+
// no width for both. Checked at 390px: the calendar keeps its full
|
|
83
|
+
// 280px grid and the entries flow onto as many lines as they need.
|
|
84
|
+
flexDirection: { xs: 'row', md: 'column' },
|
|
85
|
+
flexWrap: 'wrap',
|
|
86
|
+
alignContent: 'flex-start',
|
|
87
|
+
gap: 0.5,
|
|
88
|
+
minWidth: { md: 168 },
|
|
89
|
+
maxWidth: { md: 200 },
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{options.map((option) => {
|
|
93
|
+
const active = option.id === activeId;
|
|
94
|
+
const reasonId = `${reasonPrefix}-${option.id}`;
|
|
95
|
+
return (
|
|
96
|
+
<Box
|
|
97
|
+
component="button"
|
|
98
|
+
type="button"
|
|
99
|
+
key={option.id}
|
|
100
|
+
aria-pressed={active}
|
|
101
|
+
// `aria-disabled`, never the `disabled` attribute: a disabled button
|
|
102
|
+
// is skipped by the tab order, so the reason under it is unreachable
|
|
103
|
+
// by exactly the reader most likely to need it.
|
|
104
|
+
aria-disabled={option.overMax || undefined}
|
|
105
|
+
aria-describedby={option.overMax ? reasonId : undefined}
|
|
106
|
+
data-testid={`${dataTestId}-quick-${option.id}`}
|
|
107
|
+
onClick={() => {
|
|
108
|
+
if (!option.overMax) onPick(option);
|
|
109
|
+
}}
|
|
110
|
+
sx={optionSx(active, option.overMax)}
|
|
111
|
+
>
|
|
112
|
+
{option.label}
|
|
113
|
+
{option.overMax && (
|
|
114
|
+
<Box component="span" id={reasonId} sx={REASON_SX}>
|
|
115
|
+
{reasonFor(option)}
|
|
116
|
+
</Box>
|
|
117
|
+
)}
|
|
118
|
+
</Box>
|
|
119
|
+
);
|
|
120
|
+
})}
|
|
121
|
+
</Box>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export { DateRangePicker, DateRangePicker as default } from './DateRangePicker';
|
|
2
|
+
export {
|
|
3
|
+
createQuickRanges,
|
|
4
|
+
DEFAULT_QUICK_RANGE_LABELS,
|
|
5
|
+
DEFAULT_QUICK_RANGES,
|
|
6
|
+
QUICK_RANGE_IDS,
|
|
7
|
+
type QuickRangeId,
|
|
8
|
+
} from './DateRangePicker.quick';
|
|
9
|
+
// The verdict the picker shows, exported so a caller's confirm button reads the
|
|
10
|
+
// SAME answer instead of re-deriving it from the two strings.
|
|
11
|
+
export { resolveDayRange } from './DateRangePicker.range';
|
|
12
|
+
export type {
|
|
13
|
+
DateRangeChangeMeta,
|
|
14
|
+
DateRangeDraft,
|
|
15
|
+
DateRangePickerMessages,
|
|
16
|
+
DateRangePickerProps,
|
|
17
|
+
DateRangeProblem,
|
|
18
|
+
DateRangeStatus,
|
|
19
|
+
DayString,
|
|
20
|
+
DayWindow,
|
|
21
|
+
QuickRange,
|
|
22
|
+
QuickRangeContext,
|
|
23
|
+
ResolvedQuickRange,
|
|
24
|
+
WeekStart,
|
|
25
|
+
} from './DateRangePicker.types';
|