goblin-magic 1.7.0 → 1.8.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/package.json +1 -1
- package/widgets/calendar-menu/widget.js +2 -1
- package/widgets/calendar-menu-content/widget.js +1 -0
- package/widgets/checkbox-menu-items/styles.js +28 -0
- package/widgets/checkbox-menu-items/widget.js +122 -0
- package/widgets/magic-datetime-field/widget.js +0 -1
- package/widgets/magic-navigation/widget.js +3 -1
- package/widgets/magic-table/widget.js +2 -5
- package/widgets/magic-wave/styles.js +57 -0
- package/widgets/magic-wave/widget.js +36 -0
- package/widgets/menu/styles.js +1 -1
- package/widgets/menu/widget.js +8 -8
- package/widgets/small-calendar-grid/styles.js +5 -0
- package/widgets/small-calendar-grid/widget.js +8 -6
- package/widgets/year-month/styles.js +63 -0
- package/widgets/year-month/widget.js +163 -0
- package/widgets/year-month-grid/styles.js +57 -0
- package/widgets/year-month-grid/widget.js +63 -0
- package/widgets/year-month-menu/styles.js +22 -0
- package/widgets/year-month-menu/widget.js +117 -0
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@ class CalendarMenu extends Widget {
|
|
|
17
17
|
};
|
|
18
18
|
|
|
19
19
|
render() {
|
|
20
|
-
const {allowEmpty, value, onChange, children} = this.props;
|
|
20
|
+
const {allowEmpty, selectWeek, value, onChange, children} = this.props;
|
|
21
21
|
return (
|
|
22
22
|
<Menu>
|
|
23
23
|
{children}
|
|
@@ -26,6 +26,7 @@ class CalendarMenu extends Widget {
|
|
|
26
26
|
{(menu) => (
|
|
27
27
|
<CalendarMenuContent
|
|
28
28
|
allowEmpty={allowEmpty}
|
|
29
|
+
selectWeek={selectWeek}
|
|
29
30
|
value={value || DateConverters.getNowCanonical()}
|
|
30
31
|
onChange={(date) => this.handleCalendarChange(date, menu)}
|
|
31
32
|
onCancel={menu.close}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default function styles() {
|
|
2
|
+
const items = {
|
|
3
|
+
padding: '3px 3px 2px 3px',
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
const item = {
|
|
7
|
+
display: 'flex',
|
|
8
|
+
flexDirection: 'row',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const hr = {
|
|
12
|
+
width: '100%',
|
|
13
|
+
margin: 0,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const buttons = {
|
|
17
|
+
display: 'flex',
|
|
18
|
+
flexDirection: 'row',
|
|
19
|
+
padding: '1px',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
items,
|
|
24
|
+
item,
|
|
25
|
+
hr,
|
|
26
|
+
buttons,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Widget from 'goblin-laboratory/widgets/widget';
|
|
3
|
+
import * as styles from './styles.js';
|
|
4
|
+
import MagicButton from '../magic-button/widget.js';
|
|
5
|
+
import Icon from '@mdi/react';
|
|
6
|
+
import {
|
|
7
|
+
mdiCheckboxBlankOutline,
|
|
8
|
+
mdiCheckboxIntermediateVariant,
|
|
9
|
+
mdiCheckboxMarked,
|
|
10
|
+
} from '@mdi/js';
|
|
11
|
+
import MagicCheckbox from '../magic-checkbox/widget.js';
|
|
12
|
+
import Menu from '../menu/widget.js';
|
|
13
|
+
import withC from 'goblin-laboratory/widgets/connect-helpers/with-c.js';
|
|
14
|
+
|
|
15
|
+
class CheckboxMenuItemsNC extends Widget {
|
|
16
|
+
constructor() {
|
|
17
|
+
super(...arguments);
|
|
18
|
+
this.styles = styles;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
toggle = (value) => {
|
|
22
|
+
const {checkedValues} = this.props;
|
|
23
|
+
if (!checkedValues) {
|
|
24
|
+
this.props.onChange(this.values.filter((v) => v !== value));
|
|
25
|
+
} else if (checkedValues.includes(value)) {
|
|
26
|
+
this.props.onChange([...checkedValues].filter((v) => v !== value));
|
|
27
|
+
} else {
|
|
28
|
+
this.props.onChange([...checkedValues, value]);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
set = (value, event) => {
|
|
33
|
+
const {checkedValues} = this.props;
|
|
34
|
+
if (event.ctrlKey) {
|
|
35
|
+
this.toggle(value);
|
|
36
|
+
} else if (checkedValues?.length === 1 && checkedValues.includes(value)) {
|
|
37
|
+
this.all();
|
|
38
|
+
} else {
|
|
39
|
+
this.props.onChange([value]);
|
|
40
|
+
}
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
all = () => {
|
|
45
|
+
this.props.onChange(null);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
empty = () => {
|
|
49
|
+
this.props.onChange([]);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
invert = () => {
|
|
53
|
+
const {checkedValues} = this.props;
|
|
54
|
+
if (checkedValues) {
|
|
55
|
+
if (checkedValues.length === 0) {
|
|
56
|
+
this.all();
|
|
57
|
+
} else {
|
|
58
|
+
this.props.onChange(
|
|
59
|
+
this.values.filter((value) => !checkedValues.includes(value))
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
this.empty();
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
renderItem(value, text) {
|
|
68
|
+
const {checkedValues} = this.props;
|
|
69
|
+
return (
|
|
70
|
+
<div key={value} className={this.styles.classNames.item}>
|
|
71
|
+
<MagicCheckbox
|
|
72
|
+
value={!checkedValues || checkedValues.includes(value)}
|
|
73
|
+
onChange={() => this.toggle(value)}
|
|
74
|
+
// disabled
|
|
75
|
+
/>
|
|
76
|
+
<Menu.Item onPointerUp={(event) => this.set(value, event)}>
|
|
77
|
+
{typeof text === 'string' ? <span>{text}</span> : text}
|
|
78
|
+
</Menu.Item>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
render() {
|
|
84
|
+
const {values, checkedValues, renderValue, children} = this.props;
|
|
85
|
+
this.values = values ? [...values] : [];
|
|
86
|
+
const renderedChildren = React.Children.map(children, (child) => {
|
|
87
|
+
if (React.isValidElement(child) && child.type === 'option') {
|
|
88
|
+
const {value, children} = child.props;
|
|
89
|
+
this.values.push(value);
|
|
90
|
+
return this.renderItem(value, children);
|
|
91
|
+
}
|
|
92
|
+
return child;
|
|
93
|
+
});
|
|
94
|
+
return (
|
|
95
|
+
<>
|
|
96
|
+
<div className={this.styles.classNames.items}>
|
|
97
|
+
{values?.map((value) => this.renderItem(value, renderValue(value)))}
|
|
98
|
+
{renderedChildren}
|
|
99
|
+
</div>
|
|
100
|
+
<Menu.Hr className={this.styles.classNames.hr} />
|
|
101
|
+
<div className={this.styles.classNames.buttons}>
|
|
102
|
+
<MagicButton simple onPointerUp={this.all}>
|
|
103
|
+
<Icon path={mdiCheckboxMarked} size="1.2em" />
|
|
104
|
+
Tout
|
|
105
|
+
</MagicButton>
|
|
106
|
+
<MagicButton simple onPointerUp={this.empty}>
|
|
107
|
+
<Icon path={mdiCheckboxBlankOutline} size="1.2em" />
|
|
108
|
+
Aucun
|
|
109
|
+
</MagicButton>
|
|
110
|
+
<MagicButton simple onPointerUp={this.invert}>
|
|
111
|
+
<Icon path={mdiCheckboxIntermediateVariant} size="1.2em" />
|
|
112
|
+
Inverser
|
|
113
|
+
</MagicButton>
|
|
114
|
+
</div>
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const CheckboxMenuItems = withC(CheckboxMenuItemsNC);
|
|
121
|
+
|
|
122
|
+
export default CheckboxMenuItems;
|
|
@@ -46,7 +46,9 @@ let MagicNavigationView = class extends Widget {
|
|
|
46
46
|
return (
|
|
47
47
|
<div className={this.styles.classNames.view} data-visible={visible}>
|
|
48
48
|
<WithModel model={serviceId ? `backend.${serviceId}` : ''}>
|
|
49
|
-
<ViewContext.Provider
|
|
49
|
+
<ViewContext.Provider
|
|
50
|
+
value={view.set('id', viewId).set('visible', visible)}
|
|
51
|
+
>
|
|
50
52
|
<ErrorHandler big>
|
|
51
53
|
<Component id={serviceId} viewId={viewId} {...widgetProps} />
|
|
52
54
|
</ErrorHandler>
|
|
@@ -81,10 +81,8 @@ class MagicTableRow extends Widget {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
componentDidMount() {
|
|
84
|
-
this.observer = new MutationObserver((
|
|
85
|
-
|
|
86
|
-
this.props.onUpdate(mutation.target.data);
|
|
87
|
-
}
|
|
84
|
+
this.observer = new MutationObserver(() => {
|
|
85
|
+
this.props.onUpdate();
|
|
88
86
|
});
|
|
89
87
|
this.observer.observe(this.row.current, {
|
|
90
88
|
subtree: true,
|
|
@@ -291,7 +289,6 @@ class MagicTableContainer extends Widget {
|
|
|
291
289
|
sortOrder,
|
|
292
290
|
sortCustom,
|
|
293
291
|
showMenu,
|
|
294
|
-
dispatch,
|
|
295
292
|
...props
|
|
296
293
|
} = this.props;
|
|
297
294
|
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/******************************************************************************/
|
|
2
|
+
|
|
3
|
+
export default function styles() {
|
|
4
|
+
const waveAnim = {
|
|
5
|
+
'0%': {
|
|
6
|
+
transform: 'scaleY(0.3)',
|
|
7
|
+
},
|
|
8
|
+
'15%': {
|
|
9
|
+
transform: 'scaleY(1.7)',
|
|
10
|
+
},
|
|
11
|
+
'30%': {
|
|
12
|
+
transform: 'scaleY(0.6)',
|
|
13
|
+
},
|
|
14
|
+
'55%': {
|
|
15
|
+
transform: 'scaleY(2)',
|
|
16
|
+
},
|
|
17
|
+
'75%': {
|
|
18
|
+
transform: 'scaleY(0.8)',
|
|
19
|
+
},
|
|
20
|
+
'100%': {
|
|
21
|
+
transform: 'scaleY(0.3)',
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const wave = {
|
|
26
|
+
'display': 'inline-flex',
|
|
27
|
+
'flexDirection': 'row',
|
|
28
|
+
'alignItems': 'center',
|
|
29
|
+
'justifyContent': 'center',
|
|
30
|
+
'gap': '2px',
|
|
31
|
+
|
|
32
|
+
'& span': {
|
|
33
|
+
width: '2px',
|
|
34
|
+
height: 'calc(6px + 16px * var(--level))',
|
|
35
|
+
borderRadius: '50px',
|
|
36
|
+
backgroundColor:
|
|
37
|
+
'color-mix(in srgb, var(--button-accent-color), transparent 70%)',
|
|
38
|
+
borderColor:
|
|
39
|
+
'color-mix(in srgb, var(--button-accent-color), transparent 10%)',
|
|
40
|
+
display: 'inline-block',
|
|
41
|
+
|
|
42
|
+
animationName: waveAnim,
|
|
43
|
+
animationDuration: 'var(--speed, 0.6s)',
|
|
44
|
+
animationTimingFunction: 'cubic-bezier(0.37, 0, 0.63, 1)',
|
|
45
|
+
animationIterationCount: 'infinite',
|
|
46
|
+
animationDirection: 'alternate',
|
|
47
|
+
animationDelay: 'calc(var(--i) * -0.08s)',
|
|
48
|
+
transformOrigin: 'bottom',
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
wave,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/******************************************************************************/
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import * as styles from './styles.js';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import Widget from 'goblin-laboratory/widgets/widget';
|
|
4
|
+
|
|
5
|
+
class MagicWave extends Widget {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
this.styles = styles;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
render() {
|
|
12
|
+
const bars = this.props.bars || 5;
|
|
13
|
+
return (
|
|
14
|
+
<div className={this.styles.classNames.wave}>
|
|
15
|
+
{[...Array(bars)].map((_, i) => {
|
|
16
|
+
const center = (bars - 1) / 2;
|
|
17
|
+
const distance = Math.abs(i - center);
|
|
18
|
+
const level = 0.1 + (1 - distance / center) * 0.1;
|
|
19
|
+
const speed = `${(0.45 + Math.random() * 0.35).toFixed(2)}s`;
|
|
20
|
+
return (
|
|
21
|
+
<span
|
|
22
|
+
key={i}
|
|
23
|
+
style={{
|
|
24
|
+
'--i': i,
|
|
25
|
+
'--level': level.toFixed(2),
|
|
26
|
+
'--speed': speed,
|
|
27
|
+
}}
|
|
28
|
+
/>
|
|
29
|
+
);
|
|
30
|
+
})}
|
|
31
|
+
</div>
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export default MagicWave;
|
package/widgets/menu/styles.js
CHANGED
package/widgets/menu/widget.js
CHANGED
|
@@ -193,10 +193,10 @@ class MenuButton extends Widget {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
componentWillUnmount() {
|
|
196
|
-
window.removeEventListener('click', this.stopPropagation, {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
});
|
|
196
|
+
// window.removeEventListener('click', this.stopPropagation, {
|
|
197
|
+
// capture: true,
|
|
198
|
+
// once: true,
|
|
199
|
+
// });
|
|
200
200
|
}
|
|
201
201
|
|
|
202
202
|
stopPropagation(event) {
|
|
@@ -220,10 +220,10 @@ class MenuButton extends Widget {
|
|
|
220
220
|
handlePointerDown = (event, menu) => {
|
|
221
221
|
event.currentTarget?.focus();
|
|
222
222
|
// Prevent click event on parent elements
|
|
223
|
-
window.addEventListener('click', this.stopPropagation, {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
});
|
|
223
|
+
// window.addEventListener('click', this.stopPropagation, {
|
|
224
|
+
// capture: true,
|
|
225
|
+
// once: true,
|
|
226
|
+
// });
|
|
227
227
|
menu.open(event);
|
|
228
228
|
};
|
|
229
229
|
|
|
@@ -68,15 +68,17 @@ class SmallCalendarGrid extends Widget {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
renderWeeks(currentDay) {
|
|
71
|
-
const {startDate} = this.props;
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
6
|
|
75
|
-
);
|
|
71
|
+
const {startDate, selectWeek} = this.props;
|
|
72
|
+
const monthStart = CalendarHelpers.getMonthStart(startDate);
|
|
73
|
+
const weekStarts = CalendarHelpers.generateWeekStarts(monthStart, 6);
|
|
76
74
|
return weekStarts.map((weekStart) => {
|
|
77
75
|
const days = CalendarHelpers.generateDays(weekStart);
|
|
78
76
|
return (
|
|
79
|
-
<div
|
|
77
|
+
<div
|
|
78
|
+
key={monthStart + '_' + weekStart}
|
|
79
|
+
className={this.styles.classNames.weekRow}
|
|
80
|
+
data-select-week={selectWeek}
|
|
81
|
+
>
|
|
80
82
|
<div className={this.styles.classNames.weekNumber}>
|
|
81
83
|
{CalendarHelpers.getWeekNumber(toJsDate(weekStart))}
|
|
82
84
|
</div>
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export default function styles() {
|
|
2
|
+
const yearMonth = {};
|
|
3
|
+
|
|
4
|
+
const top = {
|
|
5
|
+
'display': 'flex',
|
|
6
|
+
'flexDirection': 'row',
|
|
7
|
+
'gap': '5px',
|
|
8
|
+
'marginBottom': '5px',
|
|
9
|
+
|
|
10
|
+
'& > *': {
|
|
11
|
+
flexBasis: 0,
|
|
12
|
+
flexGrow: 1,
|
|
13
|
+
display: 'flex',
|
|
14
|
+
flexDirection: 'row',
|
|
15
|
+
justifyContent: 'space-between',
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const today = {
|
|
20
|
+
color: 'var(--text-color)',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const group = {
|
|
24
|
+
display: 'flex',
|
|
25
|
+
flexDirection: 'row',
|
|
26
|
+
alignItems: 'center',
|
|
27
|
+
zIndex: 0,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const arrow = {
|
|
31
|
+
'zIndex': -1,
|
|
32
|
+
'padding': '6px 0px 4px 0px',
|
|
33
|
+
'margin': '0 -3px',
|
|
34
|
+
':not(:hover)': {
|
|
35
|
+
opacity: 0.5,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const datePart = {
|
|
40
|
+
'fontSize': '18px',
|
|
41
|
+
'padding': '4px 4px',
|
|
42
|
+
'&[class][class]' /* increase specificity */: {
|
|
43
|
+
opacity: 1,
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const years = {
|
|
48
|
+
columnCount: 2,
|
|
49
|
+
columnRule:
|
|
50
|
+
'1px solid color-mix(in srgb, var(--text-color), transparent 75%)',
|
|
51
|
+
columnGap: '10px',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
yearMonth,
|
|
56
|
+
top,
|
|
57
|
+
today,
|
|
58
|
+
group,
|
|
59
|
+
arrow,
|
|
60
|
+
datePart,
|
|
61
|
+
years,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Widget from 'goblin-laboratory/widgets/widget';
|
|
3
|
+
import * as styles from './styles.js';
|
|
4
|
+
import MagicButton from '../magic-button/widget.js';
|
|
5
|
+
import {mdiCalendarToday, mdiChevronLeft, mdiChevronRight} from '@mdi/js';
|
|
6
|
+
import Icon from '@mdi/react';
|
|
7
|
+
import CalendarHelpers from '../calendar-helpers.js';
|
|
8
|
+
import MaxTextWidth from '../max-text-width/widget.js';
|
|
9
|
+
import YearMonthsGrid from '../year-month-grid/widget.js';
|
|
10
|
+
import {yearMonth as YearMonthConverters} from 'xcraft-core-converters';
|
|
11
|
+
|
|
12
|
+
class YearMonth extends Widget {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
this.styles = styles;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
setToday = (event) => {
|
|
19
|
+
const yearMonth = YearMonthConverters.getNowCanonical();
|
|
20
|
+
this.props.onMonthChange(yearMonth);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
validateToday = (event) => {
|
|
24
|
+
const yearMonth = YearMonthConverters.getNowCanonical();
|
|
25
|
+
this.props.onMonthClick(yearMonth, event);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
addMonth = (event, count) => {
|
|
29
|
+
let newDate = YearMonthConverters.addMonths(this.props.value, count);
|
|
30
|
+
this.props.onMonthChange(newDate);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
previousMonth = (event) => {
|
|
34
|
+
this.addMonth(event, -1);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
nextMonth = (event) => {
|
|
38
|
+
this.addMonth(event, 1);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
previousYear = (event) => {
|
|
42
|
+
let newDate = YearMonthConverters.addYears(this.props.value, -1);
|
|
43
|
+
this.props.onMonthChange(newDate);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
nextYear = (event) => {
|
|
47
|
+
let newDate = YearMonthConverters.addYears(this.props.value, 1);
|
|
48
|
+
this.props.onMonthChange(newDate);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
handleKeyDown = (event) => {
|
|
52
|
+
this.props.onKeyDown?.(event);
|
|
53
|
+
if (event.key === 'Enter') {
|
|
54
|
+
if (event.target === event.currentTarget) {
|
|
55
|
+
this.props.onMonthClick(this.props.value, event);
|
|
56
|
+
event.stopPropagation();
|
|
57
|
+
}
|
|
58
|
+
} else if (event.key === 'ArrowUp') {
|
|
59
|
+
this.addMonth(event, -3);
|
|
60
|
+
event.stopPropagation();
|
|
61
|
+
} else if (event.key === 'ArrowDown') {
|
|
62
|
+
this.addMonth(event, 3);
|
|
63
|
+
event.stopPropagation();
|
|
64
|
+
} else if (event.key === 'ArrowLeft') {
|
|
65
|
+
this.addMonth(event, -1);
|
|
66
|
+
event.stopPropagation();
|
|
67
|
+
} else if (event.key === 'ArrowRight') {
|
|
68
|
+
this.addMonth(event, 1);
|
|
69
|
+
event.stopPropagation();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
render() {
|
|
74
|
+
const {value, onMonthClick} = this.props;
|
|
75
|
+
const [year, month] = YearMonthConverters.parse(value);
|
|
76
|
+
const monthNames = CalendarHelpers.getMonthNames('fr-CH', 'long', {
|
|
77
|
+
upperCase: true,
|
|
78
|
+
});
|
|
79
|
+
const monthName = monthNames[month - 1];
|
|
80
|
+
return (
|
|
81
|
+
<div
|
|
82
|
+
className={this.styles.classNames.yearMonth}
|
|
83
|
+
onKeyDown={this.handleKeyDown}
|
|
84
|
+
>
|
|
85
|
+
<div className={this.styles.classNames.top}>
|
|
86
|
+
<div className={this.styles.classNames.topLeft}>
|
|
87
|
+
<MagicButton
|
|
88
|
+
simple
|
|
89
|
+
onClick={this.setToday}
|
|
90
|
+
onDoubleClick={this.validateToday}
|
|
91
|
+
title="Aujourd'hui"
|
|
92
|
+
className={this.styles.classNames.today}
|
|
93
|
+
>
|
|
94
|
+
<Icon path={mdiCalendarToday} size={0.8} />
|
|
95
|
+
</MagicButton>
|
|
96
|
+
<div className={this.styles.classNames.group}>
|
|
97
|
+
<MagicButton
|
|
98
|
+
simple
|
|
99
|
+
onClick={this.previousMonth}
|
|
100
|
+
className={this.styles.classNames.arrow}
|
|
101
|
+
>
|
|
102
|
+
<Icon path={mdiChevronLeft} size={1.1} />
|
|
103
|
+
</MagicButton>
|
|
104
|
+
<MagicButton
|
|
105
|
+
simple
|
|
106
|
+
disabled
|
|
107
|
+
className={this.styles.classNames.datePart}
|
|
108
|
+
>
|
|
109
|
+
<MaxTextWidth texts={monthNames}>{monthName}</MaxTextWidth>
|
|
110
|
+
</MagicButton>
|
|
111
|
+
<MagicButton
|
|
112
|
+
simple
|
|
113
|
+
onClick={this.nextMonth}
|
|
114
|
+
className={this.styles.classNames.arrow}
|
|
115
|
+
>
|
|
116
|
+
<Icon path={mdiChevronRight} size={1.1} />
|
|
117
|
+
</MagicButton>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
<div className={this.styles.classNames.topRight}>
|
|
121
|
+
<div className={this.styles.classNames.group}>
|
|
122
|
+
<MagicButton
|
|
123
|
+
simple
|
|
124
|
+
onClick={this.previousYear}
|
|
125
|
+
className={this.styles.classNames.arrow}
|
|
126
|
+
>
|
|
127
|
+
<Icon path={mdiChevronLeft} size={1.1} />
|
|
128
|
+
</MagicButton>
|
|
129
|
+
<MagicButton
|
|
130
|
+
simple
|
|
131
|
+
disabled
|
|
132
|
+
className={this.styles.classNames.datePart}
|
|
133
|
+
>
|
|
134
|
+
{year}
|
|
135
|
+
</MagicButton>
|
|
136
|
+
<MagicButton
|
|
137
|
+
simple
|
|
138
|
+
onClick={this.nextYear}
|
|
139
|
+
className={this.styles.classNames.arrow}
|
|
140
|
+
>
|
|
141
|
+
<Icon path={mdiChevronRight} size={1.1} />
|
|
142
|
+
</MagicButton>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
<div className={this.styles.classNames.years}>
|
|
147
|
+
<YearMonthsGrid
|
|
148
|
+
year={year}
|
|
149
|
+
value={value}
|
|
150
|
+
onMonthClick={onMonthClick}
|
|
151
|
+
/>
|
|
152
|
+
<YearMonthsGrid
|
|
153
|
+
year={year + 1}
|
|
154
|
+
value={value}
|
|
155
|
+
onMonthClick={onMonthClick}
|
|
156
|
+
/>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export default YearMonth;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export default function styles() {
|
|
2
|
+
const yearMonthGrid = {
|
|
3
|
+
display: 'flex',
|
|
4
|
+
flexDirection: 'column',
|
|
5
|
+
userSelect: 'none',
|
|
6
|
+
padding: '5px',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const yearHeader = {
|
|
10
|
+
color: 'color-mix(in srgb, var(--text-color), transparent 50%)',
|
|
11
|
+
paddingLeft: '3px',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const monthsGrid = {
|
|
15
|
+
display: 'grid',
|
|
16
|
+
gridTemplateColumns: '1fr 1fr 1fr',
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const month = {
|
|
20
|
+
'display': 'flex',
|
|
21
|
+
'alignItems': 'center',
|
|
22
|
+
'border': '1px solid transparent',
|
|
23
|
+
'minHeight': '33px',
|
|
24
|
+
'padding': '0 3px',
|
|
25
|
+
|
|
26
|
+
'&[data-today=true]': {
|
|
27
|
+
borderColor:
|
|
28
|
+
'color-mix(in srgb, var(--button-accent-color), transparent 80%)',
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
'&[data-selected=true]': {
|
|
32
|
+
borderRadius: '5px',
|
|
33
|
+
backgroundColor:
|
|
34
|
+
'color-mix(in srgb, var(--accent-color), transparent 70%)',
|
|
35
|
+
borderColor:
|
|
36
|
+
'color-mix(in srgb, var(--button-accent-color), transparent 40%)',
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
'&[data-enable-selection=true]': {
|
|
40
|
+
'cursor': 'pointer',
|
|
41
|
+
'&:hover': {
|
|
42
|
+
borderRadius: '5px',
|
|
43
|
+
backgroundColor:
|
|
44
|
+
'color-mix(in srgb, var(--button-accent-color), transparent 65%)',
|
|
45
|
+
borderColor:
|
|
46
|
+
'color-mix(in srgb, var(--button-accent-color), transparent 10%)',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
yearMonthGrid,
|
|
53
|
+
yearHeader,
|
|
54
|
+
monthsGrid,
|
|
55
|
+
month,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Widget from 'goblin-laboratory/widgets/widget';
|
|
3
|
+
import * as styles from './styles.js';
|
|
4
|
+
import CurrentDay from '../time-interval/current-day.js';
|
|
5
|
+
import CalendarHelpers from '../calendar-helpers.js';
|
|
6
|
+
import {yearMonth as YearMonthConverters} from 'xcraft-core-converters';
|
|
7
|
+
|
|
8
|
+
class YearMonthsGrid extends Widget {
|
|
9
|
+
constructor() {
|
|
10
|
+
super(...arguments);
|
|
11
|
+
this.styles = styles;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** @type {React.PointerEventHandler} */
|
|
15
|
+
handleMonthClick = (event) => {
|
|
16
|
+
const yearMonth = event.currentTarget.getAttribute('data-month');
|
|
17
|
+
this.props.onMonthClick?.(yearMonth, event);
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
renderMonth(name, index, currentMonth) {
|
|
21
|
+
const {year, value, onMonthClick} = this.props;
|
|
22
|
+
const yearMonth = YearMonthConverters.from(year, index + 1);
|
|
23
|
+
return (
|
|
24
|
+
<div
|
|
25
|
+
key={index}
|
|
26
|
+
className={this.styles.classNames.month}
|
|
27
|
+
data-selected={yearMonth === value}
|
|
28
|
+
data-today={yearMonth === currentMonth}
|
|
29
|
+
data-enable-selection={Boolean(onMonthClick)}
|
|
30
|
+
onClick={this.handleMonthClick}
|
|
31
|
+
data-month={yearMonth}
|
|
32
|
+
>
|
|
33
|
+
{name}
|
|
34
|
+
</div>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
renderMonths(currentDay) {
|
|
39
|
+
const monthNames = CalendarHelpers.getMonthNames('fr-CH', 'long', {
|
|
40
|
+
upperCase: true,
|
|
41
|
+
});
|
|
42
|
+
const currentMonth = currentDay.split('-', 2).join('-');
|
|
43
|
+
return monthNames.map((name, index) =>
|
|
44
|
+
this.renderMonth(name, index, currentMonth)
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
render() {
|
|
49
|
+
const {year} = this.props;
|
|
50
|
+
return (
|
|
51
|
+
<div role="grid" className={this.styles.classNames.yearMonthGrid}>
|
|
52
|
+
<div className={this.styles.classNames.yearHeader}>{year}</div>
|
|
53
|
+
<div className={this.styles.classNames.monthsGrid}>
|
|
54
|
+
<CurrentDay>
|
|
55
|
+
{(currentDay) => this.renderMonths(currentDay)}
|
|
56
|
+
</CurrentDay>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default YearMonthsGrid;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export default function styles() {
|
|
2
|
+
const yearMonthMenuContent = {
|
|
3
|
+
padding: '5px',
|
|
4
|
+
outline: 'none',
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const clearButton = {
|
|
8
|
+
'&[class]' /* increase specificity */: {
|
|
9
|
+
minWidth: 'unset',
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const buttons = {
|
|
14
|
+
marginTop: '5px',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
yearMonthMenuContent,
|
|
19
|
+
clearButton,
|
|
20
|
+
buttons,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Widget from 'goblin-laboratory/widgets/widget';
|
|
3
|
+
import * as styles from './styles.js';
|
|
4
|
+
import Menu from '../menu/widget.js';
|
|
5
|
+
import MagicButton from '../magic-button/widget.js';
|
|
6
|
+
import Icon from '@mdi/react';
|
|
7
|
+
import {mdiBackspaceOutline, mdiCancel, mdiCheck} from '@mdi/js';
|
|
8
|
+
import DialogButtons from '../dialog-buttons/widget.js';
|
|
9
|
+
import {yearMonth as YearMonthConverters} from 'xcraft-core-converters';
|
|
10
|
+
import YearMonth from '../year-month/widget.js';
|
|
11
|
+
|
|
12
|
+
class YearMonthMenuContent extends Widget {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(...arguments);
|
|
15
|
+
this.styles = styles;
|
|
16
|
+
this.state = {
|
|
17
|
+
yearMonth: this.props.value,
|
|
18
|
+
};
|
|
19
|
+
this.calendarRef = React.createRef();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** @type {React.KeyboardEventHandler} */
|
|
23
|
+
handleKeyDown = (event) => {
|
|
24
|
+
this.calendarRef.current?.handleKeyDown(event);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
handleMonthChange = (yearMonth) => {
|
|
28
|
+
this.setState({yearMonth});
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
handleMonthClick = (value) => {
|
|
32
|
+
this.props.onChange(value);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
clear = () => {
|
|
36
|
+
this.props.onChange(null);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
cancel = () => {
|
|
40
|
+
this.props.onCancel();
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
validate = () => {
|
|
44
|
+
this.props.onChange(this.state.yearMonth);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
render() {
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
className={this.styles.classNames.yearMonthMenuContent}
|
|
51
|
+
tabIndex={0}
|
|
52
|
+
onKeyDown={this.handleKeyDown}
|
|
53
|
+
>
|
|
54
|
+
<YearMonth
|
|
55
|
+
ref={this.calendarRef}
|
|
56
|
+
value={this.state.yearMonth}
|
|
57
|
+
onMonthChange={this.handleMonthChange}
|
|
58
|
+
onMonthClick={this.handleMonthClick}
|
|
59
|
+
/>
|
|
60
|
+
|
|
61
|
+
<DialogButtons className={this.styles.classNames.buttons}>
|
|
62
|
+
{this.props.allowEmpty && (
|
|
63
|
+
<MagicButton
|
|
64
|
+
onClick={this.clear}
|
|
65
|
+
className={this.styles.classNames.clearButton}
|
|
66
|
+
>
|
|
67
|
+
<Icon path={mdiBackspaceOutline} size={0.8} /> Effacer
|
|
68
|
+
</MagicButton>
|
|
69
|
+
)}
|
|
70
|
+
<DialogButtons.Spacing />
|
|
71
|
+
<MagicButton onClick={this.cancel}>
|
|
72
|
+
<Icon path={mdiCancel} size={0.8} /> Annuler
|
|
73
|
+
</MagicButton>
|
|
74
|
+
<MagicButton onClick={this.validate} className="main">
|
|
75
|
+
<Icon path={mdiCheck} size={0.8} />{' '}
|
|
76
|
+
{YearMonthConverters.toLocaleString(this.state.yearMonth, 'fr-CH')}
|
|
77
|
+
</MagicButton>
|
|
78
|
+
</DialogButtons>
|
|
79
|
+
</div>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
class YearMonthMenu extends Widget {
|
|
85
|
+
constructor() {
|
|
86
|
+
super(...arguments);
|
|
87
|
+
this.styles = styles;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
handleChange = (value, menu) => {
|
|
91
|
+
menu.close();
|
|
92
|
+
this.props.onChange(value);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
render() {
|
|
96
|
+
const {allowEmpty, value, onChange, children} = this.props;
|
|
97
|
+
return (
|
|
98
|
+
<Menu>
|
|
99
|
+
{children}
|
|
100
|
+
<Menu.Content position="bottom center" addTabIndex={false}>
|
|
101
|
+
<Menu.Context.Consumer>
|
|
102
|
+
{(menu) => (
|
|
103
|
+
<YearMonthMenuContent
|
|
104
|
+
allowEmpty={allowEmpty}
|
|
105
|
+
value={value || YearMonthConverters.getNowCanonical()}
|
|
106
|
+
onChange={(value) => this.handleChange(value, menu)}
|
|
107
|
+
onCancel={menu.close}
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
</Menu.Context.Consumer>
|
|
111
|
+
</Menu.Content>
|
|
112
|
+
</Menu>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default YearMonthMenu;
|