@majoo-ui/react 2.1.2 → 2.2.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.
@@ -47,9 +47,7 @@ const slideLeftAndFade = keyframes({
47
47
  transform: 'translateX(0)'
48
48
  }
49
49
  });
50
- const StyledContent = styled(DropdownMenuPrimitive.Content, {
51
- width: 143,
52
- maxWidth: 243,
50
+ const contentStyles = {
53
51
  backgroundColor: '$bgWhite',
54
52
  borderRadius: 4,
55
53
  padding: 8,
@@ -75,6 +73,19 @@ const StyledContent = styled(DropdownMenuPrimitive.Content, {
75
73
  }
76
74
  }
77
75
  }
76
+ };
77
+ const StyledContent = styled(DropdownMenuPrimitive.Content, {
78
+ width: 143,
79
+ maxWidth: 243,
80
+ ...contentStyles
81
+ });
82
+
83
+ // Submenus size to their own content instead of inheriting the trigger-sized width above.
84
+ const StyledSubContent = styled(DropdownMenuPrimitive.SubContent, {
85
+ width: 'max-content',
86
+ minWidth: 180,
87
+ maxWidth: 320,
88
+ ...contentStyles
78
89
  });
79
90
  const itemStyles = {
80
91
  all: 'unset',
@@ -97,24 +108,105 @@ const itemStyles = {
97
108
  '&:active': {
98
109
  backgroundColor: '$gray150'
99
110
  },
100
- '&:focus': {
111
+ // Radix moves focus to the highlighted item, but data-highlighted is the attribute it
112
+ // guarantees — keep both so hover and keyboard land on the same style.
113
+ '&:focus, &[data-highlighted]': {
101
114
  backgroundColor: '$gray100'
102
115
  }
103
116
  };
117
+ const selectedItemStyles = {
118
+ backgroundColor: '$bgSelectedLight',
119
+ color: '$textBrand',
120
+ fontWeight: 600
121
+ };
122
+
123
+ // Space reserved for the indicator drawn in the item's left gutter.
124
+ const INDICATOR_GUTTER = 28;
104
125
  const StyledItem = styled(DropdownMenuPrimitive.Item, {
105
- ...itemStyles
126
+ ...itemStyles,
127
+ variants: {
128
+ selected: {
129
+ true: selectedItemStyles
130
+ }
131
+ }
106
132
  });
107
133
  StyledItem.propTypes = {
108
134
  asChild: PropTypes.bool,
109
135
  disabled: PropTypes.bool,
110
- onSelect: PropTypes.func
136
+ onSelect: PropTypes.func,
137
+ selected: PropTypes.bool
111
138
  };
112
139
  const StyledCheckboxItem = styled(DropdownMenuPrimitive.CheckboxItem, {
113
- ...itemStyles
140
+ ...itemStyles,
141
+ paddingLeft: INDICATOR_GUTTER,
142
+ '&[data-state="checked"]': {
143
+ color: '$textPrimary',
144
+ fontWeight: 600
145
+ }
114
146
  });
147
+
148
+ // The ring and dot are drawn here rather than in ItemIndicator because Radix only mounts
149
+ // ItemIndicator while checked — the unchecked ring has to come from the item itself.
115
150
  const StyledRadioItem = styled(DropdownMenuPrimitive.RadioItem, {
116
- ...itemStyles
151
+ ...itemStyles,
152
+ paddingLeft: INDICATOR_GUTTER,
153
+ '&::before': {
154
+ content: '',
155
+ position: 'absolute',
156
+ left: 0,
157
+ top: '50%',
158
+ boxSizing: 'border-box',
159
+ width: 18,
160
+ height: 18,
161
+ borderRadius: '50%',
162
+ border: '1.5px solid $colors$formDefault',
163
+ transform: 'translateY(-50%)',
164
+ transition: 'border-color 120ms ease'
165
+ },
166
+ '&::after': {
167
+ content: '',
168
+ position: 'absolute',
169
+ left: 5,
170
+ top: '50%',
171
+ width: 8,
172
+ height: 8,
173
+ borderRadius: '50%',
174
+ backgroundColor: '$iconBrand',
175
+ transform: 'translateY(-50%) scale(0)',
176
+ transition: 'transform 120ms ease'
177
+ },
178
+ '&[data-state="checked"]': {
179
+ color: '$textPrimary',
180
+ fontWeight: 600,
181
+ '&::before': {
182
+ borderColor: '$formActive'
183
+ },
184
+ '&::after': {
185
+ transform: 'translateY(-50%) scale(1)'
186
+ }
187
+ },
188
+ '&[data-disabled]': {
189
+ '&::before': {
190
+ borderColor: '$formDisable'
191
+ },
192
+ '&::after': {
193
+ backgroundColor: '$iconDisable'
194
+ }
195
+ }
196
+ });
197
+ const StyledSubTrigger = styled(DropdownMenuPrimitive.SubTrigger, {
198
+ ...itemStyles,
199
+ '&[data-state="open"]': selectedItemStyles,
200
+ variants: {
201
+ selected: {
202
+ true: selectedItemStyles
203
+ }
204
+ }
117
205
  });
206
+ StyledSubTrigger.propTypes = {
207
+ disabled: PropTypes.bool,
208
+ selected: PropTypes.bool
209
+ };
118
210
  const StyledLabel = styled(DropdownMenuPrimitive.Label, {
119
211
  padding: '8px 0',
120
212
  fontSize: 12,
@@ -129,7 +221,10 @@ const StyledSeparator = styled(DropdownMenuPrimitive.Separator, {
129
221
  const StyledItemIndicator = styled(DropdownMenuPrimitive.ItemIndicator, {
130
222
  position: 'absolute',
131
223
  left: 0,
132
- width: 25,
224
+ top: '50%',
225
+ width: 18,
226
+ height: 18,
227
+ transform: 'translateY(-50%)',
133
228
  display: 'inline-flex',
134
229
  alignItems: 'center',
135
230
  justifyContent: 'center'
@@ -159,7 +254,7 @@ DropdownMenuContext.propTypes = {
159
254
  align: PropTypes.oneOf(['start', 'center', 'end'])
160
255
  };
161
256
  const DropdownMenuContentContext = ({
162
- theme,
257
+ theme = 'default',
163
258
  ...props
164
259
  }) => {
165
260
  const {
@@ -191,21 +286,43 @@ DropdownMenuContentContext.propTypes = {
191
286
  alignOffset: PropTypes.number,
192
287
  asChild: PropTypes.bool
193
288
  };
194
- const DropdownTrigger = /*#__PURE__*/React__default.forwardRef(({
289
+
290
+ // Radix positions submenus itself, so this only carries the theme class across the portal.
291
+ const DropdownMenuSubContentContext = ({
292
+ theme = 'default',
195
293
  children,
196
294
  ...props
295
+ }) => {
296
+ const themeProvider = useThemeProvider(theme);
297
+ return /*#__PURE__*/jsx(StyledSubContent, {
298
+ className: `dropdown-content ${themeProvider?.theme || ''}`,
299
+ ...props,
300
+ children: children
301
+ });
302
+ };
303
+ DropdownMenuSubContentContext.propTypes = {
304
+ sideOffset: PropTypes.number,
305
+ alignOffset: PropTypes.number,
306
+ asChild: PropTypes.bool
307
+ };
308
+
309
+ /**
310
+ * @type {React.ForwardRefExoticComponent<
311
+ * React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger> & React.RefAttributes<HTMLButtonElement>
312
+ * >}
313
+ */
314
+ const DropdownTrigger = /*#__PURE__*/React__default.forwardRef(({
315
+ children,
316
+ asChild,
317
+ ...rest
197
318
  }, ref) => {
198
- const {
199
- asChild,
200
- ...rest
201
- } = props;
202
319
  return /*#__PURE__*/jsx(DropdownMenuPrimitive.Trigger, {
203
320
  ...rest,
204
321
  asChild: asChild,
205
322
  ref: ref,
206
- children: children && /*#__PURE__*/React__default.cloneElement(children, {
323
+ children: /*#__PURE__*/React__default.isValidElement(children) ? /*#__PURE__*/React__default.cloneElement(children, {
207
324
  ...rest
208
- })
325
+ }) : children
209
326
  });
210
327
  });
211
328
  DropdownTrigger.propTypes = {
@@ -213,11 +330,20 @@ DropdownTrigger.propTypes = {
213
330
  };
214
331
  const RightSlot = styled('div', {
215
332
  marginLeft: 'auto',
216
- ':focus > &': {
217
- color: 'white'
218
- },
333
+ paddingLeft: 16,
334
+ display: 'inline-flex',
335
+ alignItems: 'center',
219
336
  '[data-disabled] &': {
220
337
  color: '$gray400'
338
+ },
339
+ variants: {
340
+ // Secondary text (a hint or shortcut) rather than an icon.
341
+ muted: {
342
+ true: {
343
+ color: '$textDisable',
344
+ fontSize: 12
345
+ }
346
+ }
221
347
  }
222
348
  });
223
349
  const StyledDropdown = styled(Button, {
@@ -261,7 +387,9 @@ const DropdownMenuCheckboxItem = StyledCheckboxItem;
261
387
  const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup;
262
388
  const DropdownMenuRadioItem = StyledRadioItem;
263
389
  const DropdownMenuItemIndicator = StyledItemIndicator;
264
- // export const DropdownMenuTriggerItem = StyledTriggerItem;
390
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
391
+ const DropdownMenuSubTrigger = StyledSubTrigger;
392
+ const DropdownMenuSubContent = DropdownMenuSubContentContext;
265
393
  const DropdownMenuLabel = StyledLabel;
266
394
  const DropdownMenuSeparator = StyledSeparator;
267
395
  const DropdownMenuArrow = StyledArrow;
@@ -273,9 +401,11 @@ DropdownMenuCheckboxItem.displayName = 'DropdownMenuCheckboxItem';
273
401
  DropdownMenuRadioGroup.displayName = 'DropdownMenuRadioGroup';
274
402
  DropdownMenuRadioItem.displayName = 'DropdownMenuRadioItem';
275
403
  DropdownMenuItemIndicator.displayName = 'DropdownMenuItemIndicator';
276
- // DropdownMenuTriggerItem.displayName = 'DropdownMenuTriggerItem';
404
+ DropdownMenuSub.displayName = 'DropdownMenuSub';
405
+ DropdownMenuSubTrigger.displayName = 'DropdownMenuSubTrigger';
406
+ DropdownMenuSubContent.displayName = 'DropdownMenuSubContent';
277
407
  DropdownMenuLabel.displayName = 'DropdownMenuLabel';
278
408
  DropdownMenuSeparator.displayName = 'DropdownMenuSeparator';
279
409
  DropdownMenuArrow.displayName = 'DropdownMenuArrow';
280
410
 
281
- export { ButtonDropdown, DropdownMenu, DropdownMenuArrow, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemIndicator, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, RightSlot, StyledDropdown };
411
+ export { ButtonDropdown, DropdownMenu, DropdownMenuArrow, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemIndicator, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, RightSlot, StyledDropdown };
@@ -0,0 +1,212 @@
1
+ import 'core-js/modules/es.iterator.constructor.js';
2
+ import 'core-js/modules/es.iterator.map.js';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+ import React__default, { useState } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { CaretDownOutline, ChevronRightOutline } from '@majoo-ui/icons';
7
+ import { styled, colors } from '../../config/stitches.config.esm.js';
8
+ import { weekdayNamesFullEng, weekdayNamesFullId } from '../../utils/calendar.esm.js';
9
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSub, DropdownMenuSubTrigger, RightSlot, DropdownMenuSubContent, DropdownMenuLabel, DropdownMenuRadioGroup, DropdownMenuRadioItem } from '../dropdownMenu/DropdownMenu.esm.js';
10
+
11
+ const Trigger = styled('button', {
12
+ all: 'unset',
13
+ boxSizing: 'border-box',
14
+ display: 'inline-flex',
15
+ alignItems: 'center',
16
+ gap: '$spacing-02',
17
+ padding: '0 $spacing-04',
18
+ border: '1px solid $bgOutline',
19
+ borderRadius: '$lg',
20
+ backgroundColor: '$bgWhite',
21
+ color: '$textPrimary',
22
+ fontFamily: 'inherit',
23
+ fontSize: '$short-content-regular',
24
+ lineHeight: '$short-content-regular',
25
+ letterSpacing: '$short-content-regular',
26
+ cursor: 'pointer',
27
+ '&:hover': {
28
+ borderColor: '$primary600'
29
+ },
30
+ '&[data-state="open"]': {
31
+ borderColor: '$primary400'
32
+ },
33
+ variants: {
34
+ size: {
35
+ sm: {
36
+ height: 32
37
+ },
38
+ lg: {
39
+ height: 40
40
+ }
41
+ },
42
+ // Named apart from the DOM attribute so Stitches does not swallow it as a variant —
43
+ // the button still needs a real `disabled` to block the menu from opening.
44
+ isDisabled: {
45
+ true: {
46
+ backgroundColor: '$formDisable',
47
+ color: '$textDisable',
48
+ cursor: 'not-allowed',
49
+ '&:hover': {
50
+ borderColor: '$bgOutline'
51
+ }
52
+ }
53
+ }
54
+ },
55
+ defaultVariants: {
56
+ size: 'lg'
57
+ }
58
+ });
59
+ const TriggerLabel = styled('span', {
60
+ fontWeight: 600
61
+ });
62
+ const TriggerRange = styled('span', {
63
+ color: '$textDisable'
64
+ });
65
+ const MENU_ITEM_CSS = {
66
+ paddingLeft: '$spacing-04',
67
+ paddingRight: '$spacing-04'
68
+ };
69
+ const DEFAULT_PERIODS = ['day', 'week', 'month'];
70
+ // Listed Monday first, the way the week reads in Indonesian — not in Date#getDay order.
71
+ const START_DAY_ORDER = [1, 2, 3, 4, 5, 6, 0];
72
+ const PeriodSelect = /*#__PURE__*/React__default.forwardRef(({
73
+ value,
74
+ onChange,
75
+ startDay,
76
+ onStartDayChange,
77
+ defaultStartDay = 1,
78
+ resetStartDayOnPeriodChange = true,
79
+ periods = DEFAULT_PERIODS,
80
+ showRange = true,
81
+ disabled = false,
82
+ size = 'lg',
83
+ side = 'bottom',
84
+ align = 'start',
85
+ periodLabels,
86
+ startWeekLabel,
87
+ formatWeekRange,
88
+ css
89
+ }, ref) => {
90
+ const {
91
+ t,
92
+ i18n: {
93
+ language
94
+ }
95
+ } = useTranslation(['MajooUI/periodSelect', 'translation']);
96
+ const [open, setOpen] = useState(false);
97
+ const [internalStartDay, setInternalStartDay] = useState(defaultStartDay);
98
+ const activeStartDay = startDay !== null && startDay !== void 0 ? startDay : internalStartDay;
99
+ const weekdayNames = language === 'en' ? weekdayNamesFullEng : weekdayNamesFullId;
100
+ // A week always spans 7 days, so the last one is always 6 days after the first.
101
+ const rangeLabel = (day, place) => {
102
+ const endDay = (day + 6) % 7;
103
+ const range = {
104
+ startDay: day,
105
+ endDay,
106
+ startName: weekdayNames[day],
107
+ endName: weekdayNames[endDay],
108
+ place
109
+ };
110
+ return formatWeekRange ? formatWeekRange(range) : `${range.startName} - ${range.endName}`;
111
+ };
112
+ const showWeekRange = showRange && value === 'week';
113
+ const defaultPeriodLabels = {
114
+ day: t('day', 'Hari'),
115
+ week: t('week', 'Minggu'),
116
+ month: t('month', 'Bulan')
117
+ };
118
+ const periodLabel = period => {
119
+ var _a;
120
+ return (_a = periodLabels === null || periodLabels === void 0 ? void 0 : periodLabels[period]) !== null && _a !== void 0 ? _a : defaultPeriodLabels[period];
121
+ };
122
+ const applyStartDay = day => {
123
+ setInternalStartDay(day);
124
+ onStartDayChange === null || onStartDayChange === void 0 ? void 0 : onStartDayChange(day);
125
+ };
126
+ const selectPeriod = period => {
127
+ onChange(period);
128
+ if (resetStartDayOnPeriodChange && period !== 'week' && activeStartDay !== defaultStartDay) {
129
+ applyStartDay(defaultStartDay);
130
+ }
131
+ setOpen(false);
132
+ };
133
+ const renderPeriodItem = period => {
134
+ if (period !== 'week') {
135
+ return jsx(DropdownMenuItem, {
136
+ selected: value === period,
137
+ onSelect: () => selectPeriod(period),
138
+ css: MENU_ITEM_CSS,
139
+ children: periodLabel(period)
140
+ }, period);
141
+ }
142
+ return jsxs(DropdownMenuSub, {
143
+ children: [jsxs(DropdownMenuSubTrigger, {
144
+ selected: value === 'week',
145
+ onClick: () => selectPeriod('week'),
146
+ css: MENU_ITEM_CSS,
147
+ children: [periodLabel('week'), jsx(RightSlot, {
148
+ children: jsx(ChevronRightOutline, {
149
+ size: "16",
150
+ color: "currentColor"
151
+ })
152
+ })]
153
+ }), jsxs(DropdownMenuSubContent, {
154
+ sideOffset: 8,
155
+ alignOffset: -8,
156
+ css: {
157
+ padding: '$spacing-04 $spacing-05'
158
+ },
159
+ children: [jsx(DropdownMenuLabel, {
160
+ children: startWeekLabel !== null && startWeekLabel !== void 0 ? startWeekLabel : t('startWeekFrom', 'Mulai minggu dari:')
161
+ }), jsx(DropdownMenuRadioGroup, {
162
+ value: String(activeStartDay),
163
+ onValueChange: day => {
164
+ applyStartDay(Number(day));
165
+ onChange('week');
166
+ },
167
+ children: START_DAY_ORDER.map(day => jsx(DropdownMenuRadioItem, {
168
+ value: String(day),
169
+ children: rangeLabel(day, 'option')
170
+ }, day))
171
+ })]
172
+ })]
173
+ }, period);
174
+ };
175
+ return jsxs(DropdownMenu, {
176
+ open: open,
177
+ onOpenChange: setOpen,
178
+ side: side,
179
+ align: align,
180
+ modal: false,
181
+ children: [jsx(DropdownMenuTrigger, {
182
+ asChild: true,
183
+ children: jsxs(Trigger, {
184
+ ref: ref,
185
+ size: size,
186
+ isDisabled: disabled,
187
+ disabled: disabled,
188
+ css: css,
189
+ type: "button",
190
+ children: [jsxs(TriggerLabel, {
191
+ children: [periodLabel(value), showWeekRange && ',']
192
+ }), showWeekRange && jsx(TriggerRange, {
193
+ children: rangeLabel(activeStartDay, 'trigger')
194
+ }), jsx(CaretDownOutline, {
195
+ size: "16",
196
+ color: disabled ? colors.iconDisable : colors.iconPrimary
197
+ })]
198
+ })
199
+ }), jsx(DropdownMenuContent, {
200
+ sideOffset: 4,
201
+ css: {
202
+ width: 'max-content',
203
+ minWidth: 160,
204
+ maxWidth: 'none'
205
+ },
206
+ children: periods.map(renderPeriodItem)
207
+ })]
208
+ });
209
+ });
210
+ PeriodSelect.displayName = 'PeriodSelect';
211
+
212
+ export { PeriodSelect as default };
@@ -10,6 +10,10 @@ import { styled } from '../../config/stitches.config.esm.js';
10
10
  import Box from '../../layout/box/Box.esm.js';
11
11
  import Flex from '../../layout/flex/Flex.esm.js';
12
12
  import { TableContent, TableHead, TableHeaderCellContainer, TableHeadCellContent, TableBody, addOnCellStyle, TableFoot, TableFootRow, TableFootCell } from './Table.esm.js';
13
+ import { resolveCellCss } from './utils/columnStyles.esm.js';
14
+ import { footerValueOf } from './utils/footerValue.esm.js';
15
+ import { HeaderTooltip } from './utils/HeaderTooltip.esm.js';
16
+ import { isHeaderSortable, getAriaSort } from './utils/headerSort.esm.js';
13
17
  import Text from '../text/Text.esm.js';
14
18
  import { ConditionalWrapper } from '../../wrapper/ConditionalWrapper.esm.js';
15
19
  import { ContextMenuTrigger } from '../contextMenu/ContextMenu.esm.js';
@@ -80,7 +84,11 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
80
84
  hideHeader,
81
85
  decoration,
82
86
  rowVirtualizer,
83
- virtualized
87
+ virtualized,
88
+ footerData,
89
+ footerColumnIds,
90
+ headerRowSpan,
91
+ keyId = 'id'
84
92
  }, ref) => {
85
93
  var _a, _b, _c, _d;
86
94
  const {
@@ -178,26 +186,29 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
178
186
  children: jsxs(TableContent, Object.assign({}, getTableProps(), {
179
187
  children: [jsx(TableHead, {
180
188
  hiddenHeader: hideHeader,
181
- children: headerGroups.map(headerGroup => jsx(TableHeadRow, Object.assign({}, headerGroup.getHeaderGroupProps(), {
189
+ children: (headerRowSpan ? headerGroups.slice(-1) : headerGroups).map(headerGroup => jsx(TableHeadRow, Object.assign({}, headerGroup.getHeaderGroupProps(), {
182
190
  children: headerGroup.headers.filter((column, index) => filterMobileHeader(column, index)).map(column => {
183
191
  const headerRender = column.render('Header');
184
- return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(!column.unsortable ? column.getSortByToggleProps({
192
+ return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(isHeaderSortable(column) ? column.getSortByToggleProps({
185
193
  title: t('sortColumnTitle', 'Urutkan') || 'Urutkan'
186
194
  }) : {
187
195
  onClick: column.onToggle
188
196
  }), {
189
- children: typeof headerRender === 'string' ? jsx(TableHeaderCellContainer, {
197
+ "aria-sort": getAriaSort(column),
198
+ children: typeof headerRender === 'string' ? jsxs(TableHeaderCellContainer, {
190
199
  isMobile: true,
191
200
  isSortedDesc: column.isSortedDesc,
192
- unsortable: column.unsortable,
201
+ unsortable: !isHeaderSortable(column),
193
202
  isToggleable: !!column.onToggle,
194
203
  isToggled: column.isToggled,
195
- children: jsx(TableHeadCellContent, {
204
+ children: [jsx(TableHeadCellContent, {
196
205
  isMobile: true,
197
206
  heading: "headerTable",
198
207
  color: "secondary",
199
208
  children: headerRender.toUpperCase()
200
- })
209
+ }), column.headerTooltip && jsx(HeaderTooltip, {
210
+ label: column.headerTooltip
211
+ })]
201
212
  }) : headerRender
202
213
  }));
203
214
  })
@@ -281,6 +292,7 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
281
292
  children: cell.render('Cell')
282
293
  });
283
294
  return jsx(TableCell, Object.assign({}, cell.getCellProps(addOnCellStyle(cell.column.id)), {
295
+ css: resolveCellCss(cell),
284
296
  children: cell.column.accessor ? jsxs(Flex, {
285
297
  justify: "between",
286
298
  align: "center",
@@ -341,12 +353,12 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
341
353
  align: "center",
342
354
  justify: "between",
343
355
  gap: 5,
344
- css: {
356
+ css: Object.assign({
345
357
  backgroundColor: '$bgGray',
346
358
  padding: `$spacing-04 $spacing-05 ${'selection' in row.values ? '$spacing-04 $spacing-07' : ''}`,
347
359
  borderBottom: '1pt solid $bgBorder',
348
360
  alignItems: cell.column.accessor ? getAlign(cellAlign) : 'center'
349
- },
361
+ }, resolveCellCss(cell)),
350
362
  children: [jsx(Heading, {
351
363
  css: {
352
364
  padding: '3px 0px'
@@ -383,6 +395,48 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
383
395
  }) : null]
384
396
  }, row.id);
385
397
  })
398
+ }), footerData === null || footerData === void 0 ? void 0 : footerData.map((footerRow, footerRowIndex) => {
399
+ var _a;
400
+ return jsx(TableRow, {
401
+ children: jsx(TableCell, {
402
+ colSpan: visibleColumns.length,
403
+ css: {
404
+ backgroundColor: '$bgGray'
405
+ },
406
+ children: jsx(Flex, {
407
+ direction: "column",
408
+ children: visibleColumns.map((column, index) => {
409
+ // A column without a Footer of its own would only add an empty line.
410
+ if (column.isSubRow || !(footerColumnIds === null || footerColumnIds === void 0 ? void 0 : footerColumnIds.has(column.id))) return null;
411
+ const isTitle = getIsMobileHeader() ? column.isMobileHeader : index === indexDefaultHeader;
412
+ return jsxs(Flex, {
413
+ align: "center",
414
+ justify: "between",
415
+ gap: 5,
416
+ css: Object.assign({
417
+ padding: '$spacing-04 $spacing-05',
418
+ borderBottom: '1pt solid $bgBorder'
419
+ }, column.footerCss),
420
+ children: [!isTitle && jsx(Heading, {
421
+ css: {
422
+ padding: '3px 0px'
423
+ },
424
+ heading: "headerTable",
425
+ color: "secondary",
426
+ children: typeof column.Header === 'string' ? column.Header.toUpperCase() : column.render('Header')
427
+ }), jsx(Paragraph, {
428
+ paragraph: "shortContentBold",
429
+ children: column.render('Footer', {
430
+ footerRow,
431
+ footerRowIndex,
432
+ value: footerValueOf(column, footerRow, footerRowIndex)
433
+ })
434
+ })]
435
+ }, column.id);
436
+ })
437
+ })
438
+ })
439
+ }, (_a = footerRow[keyId]) !== null && _a !== void 0 ? _a : `footer-${footerRowIndex}`);
386
440
  }), (_d = decoration === null || decoration === void 0 ? void 0 : decoration.footerRender) === null || _d === void 0 ? void 0 : _d.call(decoration), isInfiniteScroll && paddingBottom > 0 && jsx("tr", {
387
441
  children: jsx("td", {
388
442
  style: {
@@ -4,6 +4,7 @@ import 'core-js/modules/es.iterator.constructor.js';
4
4
  import 'core-js/modules/es.iterator.filter.js';
5
5
  import 'core-js/modules/es.iterator.for-each.js';
6
6
  import 'core-js/modules/es.iterator.map.js';
7
+ import 'core-js/modules/es.iterator.some.js';
7
8
  import 'core-js/modules/es.json.parse.js';
8
9
  import 'core-js/modules/es.json.stringify.js';
9
10
  import 'core-js/modules/es.map.get-or-insert.js';
@@ -15,12 +16,14 @@ import 'core-js/modules/es.set.is-subset-of.v2.js';
15
16
  import 'core-js/modules/es.set.is-superset-of.v2.js';
16
17
  import 'core-js/modules/es.set.symmetric-difference.v2.js';
17
18
  import 'core-js/modules/es.set.union.v2.js';
19
+ import 'core-js/modules/es.weak-map.get-or-insert.js';
20
+ import 'core-js/modules/es.weak-map.get-or-insert-computed.js';
18
21
  import { __rest } from 'tslib';
19
22
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
20
23
  import * as React from 'react';
21
24
  import { useState, useCallback, useMemo, useRef, useLayoutEffect, useEffect } from 'react';
22
25
  import InfiniteScroll from 'react-infinite-scroller';
23
- import { useTable, useSortBy, useExpanded, usePagination, useRowSelect, useColumnOrder, useMountedLayoutEffect } from 'react-table';
26
+ import { useTable, useSortBy, useExpanded, usePagination, useRowSelect, useColumnOrder, useMountedLayoutEffect, emptyRenderer } from 'react-table';
24
27
  import { SortableElement, SortableContainer, arrayMove } from 'react-sortable-hoc';
25
28
  import Text from '../text/Text.esm.js';
26
29
  import Paragraph from '../paragraph/Paragraph.esm.js';
@@ -37,6 +40,10 @@ import useMediaQuery from '../../utils/useMediaQuery.esm.js';
37
40
  import { ConditionalWrapper } from '../../wrapper/ConditionalWrapper.esm.js';
38
41
  import { ContextMenuTrigger, ContextMenu, ContextMenuItem, ContextMenuSeparator } from '../contextMenu/ContextMenu.esm.js';
39
42
  import MobileTable from './MobileTable.esm.js';
43
+ import { alignCss, headerAlignCss, resolveCellCss } from './utils/columnStyles.esm.js';
44
+ import { footerValueOf } from './utils/footerValue.esm.js';
45
+ import { HeaderTooltip } from './utils/HeaderTooltip.esm.js';
46
+ import { isHeaderSortable, getAriaSort } from './utils/headerSort.esm.js';
40
47
  import { useSticky, getStickyValue, getMarginRight } from 'react-table-sticky';
41
48
  import { useTranslation } from 'react-i18next';
42
49
  import { useVirtual } from 'react-virtual';
@@ -283,6 +290,10 @@ const TableRow = styled('tr', {
283
290
  borderBottom: '1pt solid $bgBorder',
284
291
  '&:hover': {
285
292
  backgroundColor: '$bgGray',
293
+ // Body cells paint a background of their own (white, or a column's cellCss) over the row's.
294
+ '& > td': {
295
+ backgroundColor: '$bgGray'
296
+ },
286
297
  '& *[data-sticky-td="true"]': {
287
298
  backgroundColor: '$bgGray'
288
299
  }
@@ -389,12 +400,6 @@ const stickyShadowStyling = (column, tableScrollPosition, scrollAreaWidth) => {
389
400
  }
390
401
  return {};
391
402
  };
392
- const resolveCellCss = cell => {
393
- const {
394
- cellCss
395
- } = cell.column;
396
- return typeof cellCss === 'function' ? cellCss(cell) : cellCss;
397
- };
398
403
  /**
399
404
  * react-table-sticky only decorates header and body cells, so footer cells work out the same
400
405
  * sticky offset themselves.
@@ -412,6 +417,23 @@ const getStickyFooterProps = (column, flatHeaders) => {
412
417
  'data-sticky-td': true
413
418
  };
414
419
  };
420
+ /**
421
+ * react-table-sticky offsets a sticky column by react-table's `width` of the columns beside it, which
422
+ * stays at its 150px default unless set, so a numeric `colWidth` is passed on as that width.
423
+ */
424
+ const withStickyWidths = columns => {
425
+ const needsWidth = column => {
426
+ var _a;
427
+ return typeof column.colWidth === 'number' && column.width === undefined || ((_a = column.columns) === null || _a === void 0 ? void 0 : _a.some(needsWidth));
428
+ };
429
+ if (!columns.some(needsWidth)) return columns;
430
+ return columns.map(column => Object.assign(Object.assign(Object.assign({}, column), typeof column.colWidth === 'number' && column.width === undefined && {
431
+ width: column.colWidth
432
+ }), column.columns && {
433
+ columns: withStickyWidths(column.columns)
434
+ }));
435
+ };
436
+ const isMissingRowId = value => value === undefined || value === null || value === '';
415
437
  /**
416
438
  * Cells per header row. With `spanRows`, a column outside any group is drawn once across every
417
439
  * header row instead of under the empty placeholder cells react-table puts above it.
@@ -443,15 +465,19 @@ const buildHeaderRows = (headerGroups, spanRows) => {
443
465
  /**
444
466
  * rowSpan of the body cells whose column sets `rowSpan`, looked up by column id then row id. The
445
467
  * first row of a run of equal keys spans the run; the rows it covers get 0 and render no cell. A
446
- * run never crosses an expanded row, whose sub rows sit in between.
468
+ * run never crosses an expanded row, whose sub rows sit in between. `anchors` maps every row of a
469
+ * run to its first row, the one whose cell a hover anywhere in the run highlights.
447
470
  */
448
471
  const getCellRowSpans = (rows, spanColumns) => {
449
472
  const spans = new Map();
473
+ const anchors = new Map();
450
474
  spanColumns.forEach(column => {
451
475
  // Nested by column: joining ids into one string would let row `0` + column `1.x` collide with
452
476
  // sub row `0.1` + column `x`.
453
477
  const columnSpans = new Map();
478
+ const columnAnchors = new Map();
454
479
  spans.set(column.id, columnSpans);
480
+ anchors.set(column.id, columnAnchors);
455
481
  let anchorId = null;
456
482
  let anchorKey;
457
483
  rows.forEach((row, index) => {
@@ -462,14 +488,19 @@ const getCellRowSpans = (rows, spanColumns) => {
462
488
  if (anchorId !== null && hasKey && key === anchorKey && !((_a = rows[index - 1]) === null || _a === void 0 ? void 0 : _a.isExpanded)) {
463
489
  columnSpans.set(anchorId, columnSpans.get(anchorId) + 1);
464
490
  columnSpans.set(row.id, 0);
491
+ columnAnchors.set(row.id, anchorId);
465
492
  return;
466
493
  }
467
494
  anchorId = hasKey ? row.id : null;
468
495
  anchorKey = key;
469
496
  columnSpans.set(row.id, 1);
497
+ columnAnchors.set(row.id, row.id);
470
498
  });
471
499
  });
472
- return spans;
500
+ return {
501
+ spans,
502
+ anchors
503
+ };
473
504
  };
474
505
  const formattingData = (rawData, rowGroup) => {
475
506
  if ((rowGroup === null || rowGroup === void 0 ? void 0 : rowGroup.groupKey) && !(rowGroup === null || rowGroup === void 0 ? void 0 : rowGroup.isManuallyFormatted)) {
@@ -734,6 +765,7 @@ function Table({
734
765
  }, [filterHeader]);
735
766
  const [filteredHeader, setFilteredHeader] = useState([]);
736
767
  const [hasDarkParent, setHasDarkParent] = useState(false);
768
+ const [hoveredRowId, setHoveredRowId] = useState(null);
737
769
  const {
738
770
  t
739
771
  } = useTranslation('MajooUI/table');
@@ -761,18 +793,47 @@ function Table({
761
793
  sortBy: initialSortBy
762
794
  });
763
795
  }
796
+ // Otherwise react-table's default of 10 rows shows until the pageSize effect below runs.
797
+ const initialPageSize = withoutLimit ? totalData : rowLimit;
798
+ if (typeof initialPageSize === 'number') {
799
+ Object.assign(initialState, {
800
+ pageSize: initialPageSize
801
+ });
802
+ }
764
803
  return Object.keys(initialState).length > 0 ? initialState : undefined;
765
804
  };
766
805
  const records = useMemo(() => formattingData(rawData, rowGroup), [rawData, rowGroup]);
806
+ // A row without a `keyId` is identified by its JSON; identical rows would share that id, so every
807
+ // repeat gets an occurrence suffix.
808
+ const fallbackRowIds = useMemo(() => {
809
+ const ids = new WeakMap();
810
+ const occurrences = new Map();
811
+ const visit = rowList => {
812
+ if (!Array.isArray(rowList)) return;
813
+ rowList.forEach(row => {
814
+ var _a;
815
+ if (!row || typeof row !== 'object') return;
816
+ if (isMissingRowId(row[keyId])) {
817
+ const json = JSON.stringify(row);
818
+ const count = (_a = occurrences.get(json)) !== null && _a !== void 0 ? _a : 0;
819
+ occurrences.set(json, count + 1);
820
+ ids.set(row, count ? `${json}#${count}` : json);
821
+ }
822
+ visit(row[subRows]);
823
+ });
824
+ };
825
+ visit(records);
826
+ return ids;
827
+ }, [records, keyId, subRows]);
767
828
  const filteredColumns = useMemo(() => {
768
829
  if (draggable) {
769
- return [rowDraggableColumn,
830
+ return withStickyWidths([rowDraggableColumn,
770
831
  // remove all action column
771
832
  ...columns.filter(item => !['menu', 'expander', 'multiple-expander', 'selection'].includes(item.id))].map(col => Object.assign(Object.assign({}, col), {
772
833
  unsortable: false
773
- }));
834
+ })));
774
835
  }
775
- return columns;
836
+ return withStickyWidths(columns);
776
837
  }, [draggable, columns]);
777
838
  const instance = useTable({
778
839
  columns: filteredColumns,
@@ -791,7 +852,10 @@ function Table({
791
852
  onExpandedChange,
792
853
  autoResetPage: false,
793
854
  autoResetSelectedRows: false,
794
- getRowId: showDuplicatedData ? undefined : row => row[keyId] || JSON.stringify(row),
855
+ getRowId: showDuplicatedData ? undefined : row => {
856
+ var _a;
857
+ return isMissingRowId(row[keyId]) ? (_a = fallbackRowIds.get(row)) !== null && _a !== void 0 ? _a : JSON.stringify(row) : row[keyId];
858
+ },
795
859
  getSubRows: row => row[subRows] || []
796
860
  }, useSortBy, useExpanded, usePagination, useRowSelect, useSticky, useColumnOrder);
797
861
  const {
@@ -994,7 +1058,7 @@ function Table({
994
1058
  return jsx(TableHeadRow, Object.assign({}, headerGroup.getHeaderGroupProps(), {
995
1059
  children: visibleHeaders.map((column, index) => {
996
1060
  const headerRender = column.render('Header');
997
- return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(props => [props, !column.unsortable ? column.getSortByToggleProps({
1061
+ return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(props => [props, isHeaderSortable(column) ? column.getSortByToggleProps({
998
1062
  title: t('sortColumnTitle', 'Urutkan') || 'Urutkan'
999
1063
  }) : {
1000
1064
  onClick: column.onToggle
@@ -1008,7 +1072,7 @@ function Table({
1008
1072
  css: Object.assign(Object.assign(Object.assign({}, stickyPositionStyling(column)), stickyShadowStyling(column, tableScrollPosition, scrollAreaWidth)), column.headerCss),
1009
1073
  children: typeof headerRender === 'string' ? jsx(TableHeaderCellContainer, {
1010
1074
  isSortedDesc: column.isSortedDesc,
1011
- unsortable: column.unsortable,
1075
+ unsortable: !isHeaderSortable(column),
1012
1076
  isToggleable: !!column.onToggle,
1013
1077
  isToggled: column.isToggled,
1014
1078
  darkTheme: hasDarkParent,
@@ -1040,18 +1104,15 @@ function Table({
1040
1104
  var _a;
1041
1105
  return jsx(TableFootRow, {
1042
1106
  children: footerColumns.map(column => jsx(TableCell, Object.assign({}, getStickyFooterProps(column, flatHeaders), {
1043
- css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1107
+ css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1044
1108
  padding: '20px 8px',
1045
1109
  fontWeight: '600',
1046
1110
  verticalAlign: cellAlign
1047
- }), stickyPositionStyling(column)), stickyShadowStyling(column, tableScrollPosition, scrollAreaWidth)), column.footerCss),
1111
+ }), stickyPositionStyling(column)), stickyShadowStyling(column, tableScrollPosition, scrollAreaWidth)), alignCss(column)), column.footerCss),
1048
1112
  children: column.render('Footer', {
1049
1113
  footerRow,
1050
1114
  footerRowIndex,
1051
- value: column.accessor ? column.accessor(footerRow, footerRowIndex, {
1052
- subRows: [],
1053
- depth: 0
1054
- }) : undefined
1115
+ value: footerValueOf(column, footerRow, footerRowIndex)
1055
1116
  })
1056
1117
  }), column.id))
1057
1118
  }, (_a = footerRow[keyId]) !== null && _a !== void 0 ? _a : footerRowIndex);
@@ -1241,7 +1302,7 @@ function Table({
1241
1302
  rowSpan
1242
1303
  }, index) => {
1243
1304
  const headerRender = column.render('Header');
1244
- return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(props => [props, !column.unsortable ? column.getSortByToggleProps({
1305
+ return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(props => [props, isHeaderSortable(column) ? column.getSortByToggleProps({
1245
1306
  title: t('sortColumnTitle', 'Urutkan') || 'Urutkan'
1246
1307
  }) : {
1247
1308
  onClick: column.onToggle
@@ -1252,27 +1313,33 @@ function Table({
1252
1313
  minWidth: column.colMinWidth
1253
1314
  }
1254
1315
  }]), {
1316
+ "aria-sort": getAriaSort(column),
1255
1317
  rowSpan: rowSpan > 1 ? rowSpan : undefined,
1256
1318
  css: Object.assign(Object.assign(Object.assign(Object.assign({}, stickyPositionStyling(column)), stickyShadowStyling(column, tableScrollPosition, scrollAreaWidth)), column.headerCss), {
1257
1319
  position: 'sticky',
1258
1320
  zIndex: column.sticky ? '5 !important' : 3,
1259
1321
  top: 0
1260
1322
  }),
1261
- children: typeof headerRender === 'string' ? jsx(TableHeaderCellContainer, {
1323
+ children: typeof headerRender === 'string' ? jsxs(TableHeaderCellContainer, {
1262
1324
  isSortedDesc: column.isSortedDesc,
1263
- unsortable: id ? true : column.unsortable,
1325
+ unsortable: !isHeaderSortable(column),
1264
1326
  isToggleable: !!column.onToggle,
1265
1327
  isToggled: column.isToggled,
1266
1328
  darkTheme: hasDarkParent,
1267
- css: {
1329
+ css: Object.assign({
1268
1330
  width: column.colWidth,
1269
1331
  maxWidth: column.colMaxWidth,
1270
1332
  minWidth: column.colMinWidth
1271
- },
1272
- children: jsx(TableHeadCellContent, {
1333
+ }, headerAlignCss(column)),
1334
+ children: [jsx(TableHeadCellContent, {
1273
1335
  heading: "headerTable",
1336
+ css: Object.assign({
1337
+ color: 'inherit'
1338
+ }, alignCss(column)),
1274
1339
  children: headerRender.toUpperCase()
1275
- })
1340
+ }), column.headerTooltip && jsx(HeaderTooltip, {
1341
+ label: column.headerTooltip
1342
+ })]
1276
1343
  }) : headerRender
1277
1344
  }));
1278
1345
  })
@@ -1320,13 +1387,13 @@ function Table({
1320
1387
  const collId = (_b = cell === null || cell === void 0 ? void 0 : cell.column) === null || _b === void 0 ? void 0 : _b.id;
1321
1388
  const exceptionId = (_c = [].concat(additionalExceptionIds)) === null || _c === void 0 ? void 0 : _c.includes(collId);
1322
1389
  return jsx(TableCell, Object.assign({}, cell.getCellProps(addOnCellStyle(cell.column.id)), {
1323
- css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1390
+ css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1324
1391
  padding: !isContextMenu || exceptionId ? collId === 'expander' || collId === 'multiple-expander' ? '20px 4px' : '20px 8px' : '',
1325
1392
  verticalAlign: cell.column.accessor ? cellAlign : 'center',
1326
1393
  cursor: onRowClick ? 'pointer' : 'unset'
1327
1394
  }), stickyPositionStyling(cell.column)), stickyShadowStyling(cell.column, tableScrollPosition, scrollAreaWidth)), id && {
1328
1395
  paddingRight: index + 1 === _row.length ? 30 : null
1329
- }), resolveCellCss(cell)),
1396
+ }), alignCss(cell.column)), resolveCellCss(cell)),
1330
1397
  onClick: () => {
1331
1398
  if (!exceptionId) {
1332
1399
  handleRowClick(row, i);
@@ -1359,6 +1426,7 @@ function Table({
1359
1426
  }, row.id);
1360
1427
  })
1361
1428
  })) : jsxs(TableBody, Object.assign({}, getTableBodyProps(), {
1429
+ onMouseLeave: cellRowSpans ? () => setHoveredRowId(null) : undefined,
1362
1430
  children: [isInfiniteScroll && paddingTop > 0 && jsx("tr", {
1363
1431
  children: jsx("td", {
1364
1432
  style: {
@@ -1378,7 +1446,10 @@ function Table({
1378
1446
  wrapper: child => tableRowData && tableRowData.length > 0 ? jsx(TableRow, {
1379
1447
  css: {
1380
1448
  '&:hover': {
1381
- backgroundColor: 'unset'
1449
+ backgroundColor: 'unset',
1450
+ '& > td': {
1451
+ backgroundColor: 'unset'
1452
+ }
1382
1453
  }
1383
1454
  },
1384
1455
  children: jsx(TableCell, {
@@ -1434,9 +1505,10 @@ function Table({
1434
1505
  })), jsx(TableRow, Object.assign({
1435
1506
  css: customStyle
1436
1507
  }, row && (row === null || row === void 0 ? void 0 : row.getRowProps) ? row === null || row === void 0 ? void 0 : row.getRowProps() : {}, {
1508
+ onMouseEnter: cellRowSpans ? () => setHoveredRowId(row.id) : undefined,
1437
1509
  children: row.cells.filter(cell => filteredHeader.length > 0 || filterHeader.length > 0 ? filteredHeader.includes(cell.column.id) || filterHeader.includes(cell.column.id) : cell).filter(cell => !cell.column.isSubRow).map((cell, index, _row) => {
1438
- var _a, _b, _c, _d;
1439
- const cellRowSpan = (_a = cellRowSpans === null || cellRowSpans === void 0 ? void 0 : cellRowSpans.get(cell.column.id)) === null || _a === void 0 ? void 0 : _a.get(row.id);
1510
+ var _a, _b, _c, _d, _e;
1511
+ const cellRowSpan = (_a = cellRowSpans === null || cellRowSpans === void 0 ? void 0 : cellRowSpans.spans.get(cell.column.id)) === null || _a === void 0 ? void 0 : _a.get(row.id);
1440
1512
  if (cellRowSpan === 0) return null;
1441
1513
  let visibleHeaders = (_b = headerGroups === null || headerGroups === void 0 ? void 0 : headerGroups[0]) === null || _b === void 0 ? void 0 : _b.headers.filter(column => !column.isSubRow);
1442
1514
  if (filterHeader.length > 0) {
@@ -1446,13 +1518,15 @@ function Table({
1446
1518
  const exceptionId = (_d = ['expander', 'multiple-expander', 'selection', 'menu', 'eye'].concat(additionalExceptionIds)) === null || _d === void 0 ? void 0 : _d.includes(collId);
1447
1519
  return jsx(TableCell, Object.assign({}, cell.getCellProps(addOnCellStyle(cell.column.id)), {
1448
1520
  rowSpan: cellRowSpan > 1 ? cellRowSpan : undefined,
1449
- css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1521
+ css: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, cssTableCell), {
1450
1522
  padding: !isContextMenu || exceptionId ? collId === 'expander' || collId === 'multiple-expander' ? '20px 4px' : '20px 8px' : '',
1451
1523
  verticalAlign: cell.column.accessor ? cellAlign : 'center',
1452
1524
  cursor: onRowClick ? 'pointer' : 'unset'
1453
1525
  }), stickyPositionStyling(cell.column)), stickyShadowStyling(cell.column, tableScrollPosition, scrollAreaWidth)), id && {
1454
1526
  paddingRight: index + 1 === _row.length ? 30 : null
1455
- }), resolveCellCss(cell)),
1527
+ }), alignCss(cell.column)), resolveCellCss(cell)), cellRowSpan > 1 && ((_e = cellRowSpans.anchors.get(cell.column.id)) === null || _e === void 0 ? void 0 : _e.get(hoveredRowId)) === row.id && {
1528
+ backgroundColor: '$bgGray'
1529
+ }),
1456
1530
  onClick: () => {
1457
1531
  if (!exceptionId) {
1458
1532
  handleRowClick(row, i);
@@ -1617,7 +1691,11 @@ function Table({
1617
1691
  additionalExceptionIds: additionalExceptionIds,
1618
1692
  hideHeader: hideHeader,
1619
1693
  decoration: decoration,
1620
- rowVirtualizer: rowVirtualizer
1694
+ rowVirtualizer: rowVirtualizer,
1695
+ footerData: footerData,
1696
+ footerColumnIds: (footerData === null || footerData === void 0 ? void 0 : footerData.length) ? new Set(visibleColumns.filter(column => column.Footer !== emptyRenderer).map(column => column.id)) : undefined,
1697
+ headerRowSpan: headerRowSpan,
1698
+ keyId: keyId
1621
1699
  })]
1622
1700
  });
1623
1701
  }
@@ -0,0 +1,26 @@
1
+ import 'react';
2
+ import { CircleQuestionOutline } from '@majoo-ui/icons';
3
+ import Box from '../../../layout/box/Box.esm.js';
4
+ import { jsx } from 'react/jsx-runtime';
5
+ import Tooltip from '../../tooltip/Tooltip.esm.js';
6
+
7
+ const HeaderTooltip = ({
8
+ label
9
+ }) => /*#__PURE__*/jsx(Tooltip, {
10
+ label: label,
11
+ side: "top",
12
+ children: /*#__PURE__*/jsx(Box, {
13
+ as: "span",
14
+ css: {
15
+ display: 'flex',
16
+ cursor: 'help'
17
+ },
18
+ onClick: event => event.stopPropagation(),
19
+ children: /*#__PURE__*/jsx(CircleQuestionOutline, {
20
+ size: 16,
21
+ color: "currentColor"
22
+ })
23
+ })
24
+ });
25
+
26
+ export { HeaderTooltip };
@@ -0,0 +1,22 @@
1
+ const JUSTIFY_CONTENT = {
2
+ left: 'flex-start',
3
+ center: 'center',
4
+ right: 'flex-end'
5
+ };
6
+ /** A column's `cellCss` for one cell, called with the cell when it is a function. */
7
+ const resolveCellCss = cell => {
8
+ const {
9
+ cellCss
10
+ } = cell.column;
11
+ return typeof cellCss === 'function' ? cellCss(cell) : cellCss;
12
+ };
13
+ /** Horizontal alignment of a column's body cells, footer cells and header label text. */
14
+ const alignCss = column => column.align ? {
15
+ textAlign: column.align
16
+ } : undefined;
17
+ /** The header label sits in a flex container, which follows the column's alignment through justify-content. */
18
+ const headerAlignCss = column => column.align ? {
19
+ justifyContent: JUSTIFY_CONTENT[column.align]
20
+ } : undefined;
21
+
22
+ export { alignCss, headerAlignCss, resolveCellCss };
@@ -0,0 +1,7 @@
1
+ /** What a column's accessor reads from a footer row: the `value` its `Footer` receives. */
2
+ const footerValueOf = (column, footerRow, footerRowIndex) => typeof column.accessor === 'function' ? column.accessor(footerRow, footerRowIndex, {
3
+ subRows: [],
4
+ depth: 0
5
+ }) : undefined;
6
+
7
+ export { footerValueOf };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Whether a header click should toggle the sort. react-table's `defaultCanSort` makes every header
3
+ * sortable, but toggling a group header or the placeholder above an ungrouped column throws (neither
4
+ * is in `allColumns`), and a column without an accessor has nothing to sort on unless it brings its
5
+ * own `sortType`.
6
+ */
7
+ const isHeaderSortable = column => !column.unsortable && !column.columns && !column.placeholderOf && (Boolean(column.accessor) || typeof column.sortType === 'function');
8
+ /** `aria-sort` of a header cell; only a header that can sort announces a sort state. */
9
+ const getAriaSort = column => {
10
+ if (!isHeaderSortable(column)) return undefined;
11
+ if (!column.isSorted) return 'none';
12
+ return column.isSortedDesc ? 'descending' : 'ascending';
13
+ };
14
+
15
+ export { getAriaSort, isHeaderSortable };
package/index.esm.js CHANGED
@@ -57,6 +57,7 @@ export { default as Skeleton } from './components/skeleton/Skeleton.esm.js';
57
57
  export { default as TagList } from './components/tagList/TagList.esm.js';
58
58
  export { ScrollSync } from './components/scrollSync/ScrollSync.esm.js';
59
59
  export { ScrollSyncNode } from './components/scrollSync/ScrollSyncNode.esm.js';
60
+ export { default as PeriodSelect } from './components/periodSelect/PeriodSelect.esm.js';
60
61
  export { useListOption } from './hooks/useListOption/useListOption.esm.js';
61
62
  export { useToggle } from './hooks/useToggle/useToggle.esm.js';
62
63
  export { Avatar } from './components/avatar/Avatar.esm.js';
@@ -94,7 +95,7 @@ export { default as Upload } from './components/upload/Upload.esm.js';
94
95
  export { AlertDialog, AlertDialogCancel, AlertDialogConfirm, AlertDialogContent, AlertDialogDescription, AlertDialogFunction, AlertDialogRoot, AlertDialogTitle, AlertDialogTrigger } from './components/alertDialog/AlertDialog.esm.js';
95
96
  export { BOTTOM_LEFT_POSITION, BOTTOM_RIGHT_POSITION, TOP_LEFT_POSITION, TOP_RIGHT_POSITION } from './components/toast/Toast.esm.js';
96
97
  export { BreadcrumbItem } from './components/breadcrumb/Breadcrumb.esm.js';
97
- export { ButtonDropdown, DropdownMenu, DropdownMenuArrow, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemIndicator, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuTrigger, RightSlot, StyledDropdown } from './components/dropdownMenu/DropdownMenu.esm.js';
98
+ export { ButtonDropdown, DropdownMenu, DropdownMenuArrow, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuItem, DropdownMenuItemIndicator, DropdownMenuLabel, DropdownMenuPortal, DropdownMenuRadioGroup, DropdownMenuRadioItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, RightSlot, StyledDropdown } from './components/dropdownMenu/DropdownMenu.esm.js';
98
99
  export { ButtonGroup, ButtonGroupItem } from './components/buttonGroup/ButtonGroup.esm.js';
99
100
  export { default as ColorPicker } from './components/colorPicker/ColorPicker.esm.js';
100
101
  export { Drawer, DrawerBody, DrawerClose, DrawerCloseButton, DrawerContent, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle, DrawerTrigger } from './components/drawer/Drawer.esm.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@majoo-ui/react",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "main": "./index.esm.js",
5
5
  "sideEffects": [
6
6
  "*.css",
@@ -46,3 +46,4 @@ export * from './contextMenu';
46
46
  export * from './skeleton';
47
47
  export * from './tagList';
48
48
  export * from './scrollSync';
49
+ export * from './periodSelect';
@@ -0,0 +1,83 @@
1
+ import React from 'react';
2
+ import { CSS } from '../../config/stitches.config';
3
+ export type PeriodValue = 'day' | 'week' | 'month';
4
+ export interface PeriodSelectProps {
5
+ /**
6
+ * The selected period.
7
+ */
8
+ value: PeriodValue;
9
+ /**
10
+ * Called when the user picks a period. Picking a start day also reports `'week'`.
11
+ */
12
+ onChange: (value: PeriodValue) => void;
13
+ /**
14
+ * First day of the week, indexed like `Date#getDay` (0 = Sunday, 1 = Monday).
15
+ * Leave undefined to let the component hold it internally.
16
+ */
17
+ startDay?: number;
18
+ /**
19
+ * Called when the user picks a different first day of the week.
20
+ */
21
+ onStartDayChange?: (startDay: number) => void;
22
+ /**
23
+ * First day of the week before the user changes anything.
24
+ */
25
+ defaultStartDay?: number;
26
+ /**
27
+ * If true, leaving the weekly period reports `defaultStartDay` back through
28
+ * `onStartDayChange`, so returning to weekly starts from the default again.
29
+ */
30
+ resetStartDayOnPeriodChange?: boolean;
31
+ /**
32
+ * Periods offered, in the order they are listed.
33
+ */
34
+ periods?: PeriodValue[];
35
+ /**
36
+ * If false, the trigger shows the period alone, without the day range.
37
+ */
38
+ showRange?: boolean;
39
+ /**
40
+ * If true, the component is disabled.
41
+ */
42
+ disabled?: boolean;
43
+ /**
44
+ * Specify the trigger size.
45
+ */
46
+ size?: 'sm' | 'lg';
47
+ /**
48
+ * The preferred side of the trigger to render the menu against.
49
+ */
50
+ side?: 'top' | 'right' | 'bottom' | 'left';
51
+ /**
52
+ * The preferred alignment against the trigger.
53
+ */
54
+ align?: 'start' | 'center' | 'end';
55
+ /**
56
+ * Replaces the label of one or more periods. Anything left out keeps its default
57
+ * (`Hari`, `Minggu`, `Bulan`), which already goes through i18n.
58
+ */
59
+ periodLabels?: Partial<Record<PeriodValue, React.ReactNode>>;
60
+ /**
61
+ * Replaces the submenu heading above the start-day options.
62
+ */
63
+ startWeekLabel?: React.ReactNode;
64
+ /**
65
+ * Replaces the default `Senin - Minggu` label, both on the trigger and on each option.
66
+ * `place` says which of the two is being rendered, so the two can read differently.
67
+ * Days are indexed like `Date#getDay`.
68
+ */
69
+ formatWeekRange?: (range: WeekRange) => React.ReactNode;
70
+ /**
71
+ * Additional CSS trigger styles.
72
+ */
73
+ css?: CSS;
74
+ }
75
+ export interface WeekRange {
76
+ startDay: number;
77
+ endDay: number;
78
+ startName: string;
79
+ endName: string;
80
+ place: 'trigger' | 'option';
81
+ }
82
+ declare const PeriodSelect: React.ForwardRefExoticComponent<PeriodSelectProps & React.RefAttributes<HTMLButtonElement>>;
83
+ export default PeriodSelect;
@@ -0,0 +1,2 @@
1
+ export { default as PeriodSelect } from './PeriodSelect';
2
+ export * from './PeriodSelect';
@@ -34,6 +34,10 @@ interface MobileTableProps {
34
34
  decoration?: Decoration;
35
35
  rowVirtualizer: IRowVirtualizer;
36
36
  virtualized?: boolean;
37
+ footerData?: Record<string, any>[];
38
+ footerColumnIds?: Set<string>;
39
+ headerRowSpan?: boolean;
40
+ keyId?: string;
37
41
  }
38
42
  declare const MobileTable: React.ForwardRefExoticComponent<MobileTableProps & React.RefAttributes<HTMLDivElement>>;
39
43
  export default MobileTable;
@@ -0,0 +1,10 @@
1
+ /** A column's `cellCss` for one cell, called with the cell when it is a function. */
2
+ export declare const resolveCellCss: (cell: any) => any;
3
+ /** Horizontal alignment of a column's body cells, footer cells and header label text. */
4
+ export declare const alignCss: (column: any) => {
5
+ textAlign: any;
6
+ } | undefined;
7
+ /** The header label sits in a flex container, which follows the column's alignment through justify-content. */
8
+ export declare const headerAlignCss: (column: any) => {
9
+ justifyContent: any;
10
+ } | undefined;
@@ -0,0 +1,2 @@
1
+ /** What a column's accessor reads from a footer row: the `value` its `Footer` receives. */
2
+ export declare const footerValueOf: (column: any, footerRow: any, footerRowIndex: any) => any;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Whether a header click should toggle the sort. react-table's `defaultCanSort` makes every header
3
+ * sortable, but toggling a group header or the placeholder above an ungrouped column throws (neither
4
+ * is in `allColumns`), and a column without an accessor has nothing to sort on unless it brings its
5
+ * own `sortType`.
6
+ */
7
+ export declare const isHeaderSortable: (column: any) => boolean;
8
+ /** `aria-sort` of a header cell; only a header that can sort announces a sort state. */
9
+ export declare const getAriaSort: (column: any) => "none" | "ascending" | "descending" | undefined;
@@ -6,6 +6,10 @@ const monthNamesFullId = ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni'
6
6
  const monthNamesFullEng = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
7
7
  const weekdayNamesShortId = ['Min', 'Sen', 'Sel', 'Rab', 'Kam', 'Jum', 'Sab'];
8
8
  const weekdayNamesShortEng = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
9
+
10
+ // Indexed like Date#getDay: 0 = Sunday.
11
+ const weekdayNamesFullId = ['Minggu', 'Senin', 'Selasa', 'Rabu', 'Kamis', 'Jumat', 'Sabtu'];
12
+ const weekdayNamesFullEng = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
9
13
  const yearRangesList = rangeStart => {
10
14
  let start = rangeStart;
11
15
  let end = start + 9;
@@ -23,4 +27,4 @@ const getCalendarStartYear = year => year - year % 10 + 1;
23
27
  const getCalendarStartYearRange = year => year - year % 100 + 1;
24
28
  const toDateFormat = (date, locale = 'id') => moment(date).locale(locale).format('DD MMMM YYYY');
25
29
 
26
- export { getCalendarStartYear, getCalendarStartYearRange, monthNamesFullEng, monthNamesFullId, toDateFormat, weekdayNamesShortEng, weekdayNamesShortId, yearRangesList };
30
+ export { getCalendarStartYear, getCalendarStartYearRange, monthNamesFullEng, monthNamesFullId, toDateFormat, weekdayNamesFullEng, weekdayNamesFullId, weekdayNamesShortEng, weekdayNamesShortId, yearRangesList };