@majoo-ui/react 2.2.0 → 2.3.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.
@@ -82,7 +82,19 @@ function useMapSearch({
82
82
  }, abortControllerRef.current.signal);
83
83
  // getFullAddress returns null if aborted
84
84
  if (fullAddress) {
85
- handleSelect(fullAddress);
85
+ // Keep the chosen item's coordinates; the reverse geocode only supplies the address.
86
+ const {
87
+ display_name,
88
+ place_id,
89
+ address
90
+ } = fullAddress;
91
+ handleSelect({
92
+ lat: item.lat,
93
+ lon: item.lon,
94
+ display_name,
95
+ place_id,
96
+ address
97
+ });
86
98
  }
87
99
  }), [getFullAddress, handleSelect]);
88
100
  const handleSearchLatLng = useCallback(latLng => __awaiter(this, void 0, void 0, function* () {
@@ -90,19 +102,26 @@ function useMapSearch({
90
102
  // Cancel any previous pending request
91
103
  (_b = abortControllerRef.current) === null || _b === void 0 ? void 0 : _b.abort();
92
104
  abortControllerRef.current = new AbortController();
105
+ // Move the center now, so a re-render while the address loads (or a failed lookup)
106
+ // does not send the map back to the previous point.
107
+ setCenter({
108
+ lat: Number(latLng.lat),
109
+ lng: Number(latLng.lng)
110
+ });
93
111
  const fullAddress = yield getFullAddress(latLng, abortControllerRef.current.signal);
94
112
  // getFullAddress returns null if aborted
95
113
  if (fullAddress) {
114
+ // Nominatim answers with the coordinates of the OSM object it matched (for a large
115
+ // polygon such as a golf course, its centroid), not the point that was asked about.
116
+ // Keep the requested point; the reverse geocode only supplies the address.
96
117
  const {
97
- lat,
98
- lon,
99
118
  display_name,
100
119
  place_id,
101
120
  address
102
121
  } = fullAddress;
103
122
  handleSelect({
104
- lat,
105
- lon,
123
+ lat: String(latLng.lat),
124
+ lon: String(latLng.lng),
106
125
  display_name,
107
126
  place_id,
108
127
  address
@@ -1,9 +1,10 @@
1
- import { useFloating, offset, shift, flip, useInteractions, useClick, useDismiss, FloatingFocusManager, FloatingPortal } from '@floating-ui/react';
1
+ import { useFloating, autoUpdate, useInteractions, useClick, useDismiss, FloatingFocusManager, FloatingPortal } from '@floating-ui/react';
2
2
  import PropTypes from 'prop-types';
3
3
  import { useState, useEffect, useContext, cloneElement, createContext } from 'react';
4
4
  import Flex from '../../layout/flex/Flex.esm.js';
5
5
  import { foundations } from '@majoo-ui/core';
6
6
  import { ConditionalWrapper } from '../../wrapper/ConditionalWrapper.esm.js';
7
+ import { createPopoverMiddleware } from './middleware.esm.js';
7
8
  import { jsx, Fragment } from 'react/jsx-runtime';
8
9
 
9
10
  const {
@@ -17,7 +18,7 @@ const Popover = ({
17
18
  placement,
18
19
  sideOffset = 5,
19
20
  hideOnScroll = false,
20
- flip: flip$1 = true,
21
+ flip = true,
21
22
  onClose
22
23
  }) => {
23
24
  const [openState, setOpenState] = useState(false);
@@ -27,9 +28,17 @@ const Popover = ({
27
28
  useEffect(() => {
28
29
  if (onOpenChange) onOpenChange(openState);
29
30
  }, [openState, onOpenChange]);
31
+
32
+ // floating-ui measures the element PopoverContent renders, so its children must
33
+ // be in-flow (see createPopoverMiddleware). autoUpdate recomputes when content
34
+ // height changes after mount (async options, a menu toggling display).
30
35
  const floatingHook = useFloating({
31
36
  open: openState,
32
- middleware: [offset(sideOffset), shift(), flip$1 && flip()],
37
+ whileElementsMounted: autoUpdate,
38
+ middleware: createPopoverMiddleware({
39
+ sideOffset,
40
+ flip
41
+ }),
33
42
  onOpenChange: isOpen => {
34
43
  setOpenState(isOpen);
35
44
  !isOpen && onClose?.();
@@ -0,0 +1,43 @@
1
+ import { offset, flip, shift, size } from '@floating-ui/react';
2
+
3
+ /**
4
+ * The floating-ui middleware every majoo-ui dropdown shares, so Popover and the
5
+ * components that drive floating-ui themselves (InputSelectCheck) place content
6
+ * the same way.
7
+ *
8
+ * - `flip` treats `placement` as a preference: the other side is used when the
9
+ * preferred one lacks room.
10
+ * - `size` writes two CSS variables on the floating element, which consumers opt
11
+ * into: `--majoo-popover-available-height` (room left on the chosen side) and
12
+ * `--majoo-popover-reference-width` (width of the trigger).
13
+ *
14
+ * floating-ui measures the floating element itself, so its content must be
15
+ * in-flow — an all-absolute child measures 0px and flip/shift never fire.
16
+ */
17
+ // Floor for the reported available height. Without it, flip and size lock each
18
+ // other: a menu that opens while still `display: none` measures 0px, flip keeps a
19
+ // side with almost no room, size reports ~0px, the menu's max-height collapses to
20
+ // 0 and never changes size again — so autoUpdate never re-runs flip. With a floor
21
+ // the box always has real height, flip moves it to the roomier side, and size then
22
+ // grows it from there.
23
+ const MIN_AVAILABLE_HEIGHT = 120;
24
+ const createPopoverMiddleware = ({
25
+ sideOffset = 5,
26
+ flip: flip$1 = true
27
+ } = {}) => [offset(sideOffset), flip$1 && flip({
28
+ padding: 8
29
+ }), shift({
30
+ padding: 8
31
+ }), size({
32
+ padding: 8,
33
+ apply({
34
+ availableHeight,
35
+ rects,
36
+ elements
37
+ }) {
38
+ elements.floating.style.setProperty('--majoo-popover-available-height', `${Math.max(MIN_AVAILABLE_HEIGHT, availableHeight)}px`);
39
+ elements.floating.style.setProperty('--majoo-popover-reference-width', `${rects.reference.width}px`);
40
+ }
41
+ })].filter(Boolean);
42
+
43
+ export { createPopoverMiddleware };
@@ -18,7 +18,7 @@ import Text from '../text/Text.esm.js';
18
18
  import { ConditionalWrapper } from '../../wrapper/ConditionalWrapper.esm.js';
19
19
  import { ContextMenuTrigger } from '../contextMenu/ContextMenu.esm.js';
20
20
  import Paragraph from '../paragraph/Paragraph.esm.js';
21
- import { useTranslation } from 'react-i18next';
21
+ import { defaultTableLocaleText } from './locale.esm.js';
22
22
 
23
23
  const TableContainer = styled(Flex, {
24
24
  position: 'relative',
@@ -88,12 +88,10 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
88
88
  footerData,
89
89
  footerColumnIds,
90
90
  headerRowSpan,
91
- keyId = 'id'
91
+ keyId = 'id',
92
+ sortColumnTitle = defaultTableLocaleText.sortColumnTitle
92
93
  }, ref) => {
93
94
  var _a, _b, _c, _d;
94
- const {
95
- t
96
- } = useTranslation('MajooUI/table');
97
95
  const {
98
96
  getTableProps,
99
97
  getTableBodyProps,
@@ -190,7 +188,7 @@ const MobileTable = /*#__PURE__*/React__default.forwardRef(({
190
188
  children: headerGroup.headers.filter((column, index) => filterMobileHeader(column, index)).map(column => {
191
189
  const headerRender = column.render('Header');
192
190
  return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(isHeaderSortable(column) ? column.getSortByToggleProps({
193
- title: t('sortColumnTitle', 'Urutkan') || 'Urutkan'
191
+ title: sortColumnTitle
194
192
  }) : {
195
193
  onClick: column.onToggle
196
194
  }), {
@@ -44,8 +44,8 @@ import { alignCss, headerAlignCss, resolveCellCss } from './utils/columnStyles.e
44
44
  import { footerValueOf } from './utils/footerValue.esm.js';
45
45
  import { HeaderTooltip } from './utils/HeaderTooltip.esm.js';
46
46
  import { isHeaderSortable, getAriaSort } from './utils/headerSort.esm.js';
47
+ import { useTableLocaleText, i18nTableLocaleText } from './locale.esm.js';
47
48
  import { useSticky, getStickyValue, getMarginRight } from 'react-table-sticky';
48
- import { useTranslation } from 'react-i18next';
49
49
  import { useVirtual } from 'react-virtual';
50
50
  import { PlusPrimitive } from '@majoo-ui/icons';
51
51
  import '../scrollSync/ScrollSync.esm.js';
@@ -524,9 +524,9 @@ const formattingData = (rawData, rowGroup) => {
524
524
  }
525
525
  return rawData;
526
526
  };
527
- const defaultEmptyDataProps = (t, isMobile) => ({
527
+ const emptyStateProps = (asset, title, description, isMobile) => ({
528
528
  doodleImage: jsx(TableEmptyStateDoodle, {
529
- asset: "emptyData",
529
+ asset: asset,
530
530
  "aria-hidden": "true"
531
531
  }),
532
532
  title: jsx(Paragraph, {
@@ -536,48 +536,31 @@ const defaultEmptyDataProps = (t, isMobile) => ({
536
536
  css: {
537
537
  marginTop: isMobile ? 0 : '$spacing-05'
538
538
  },
539
- children: t('dataNotAvailableTitle', 'Data tidak tersedia')
539
+ children: title
540
540
  }),
541
- description: !isMobile ? jsxs(Paragraph, {
541
+ description: !isMobile ? jsx(Paragraph, {
542
542
  paragraph: "shortContentRegular",
543
543
  color: "secondary",
544
544
  align: "center",
545
- children: [t('dataNotAvailableDescription_1', 'Belum ada data yang dapat ditampilkan'), ' ', t('dataNotAvailableDescription_2', 'di halaman ini')]
546
- }) : jsxs(Text, {
545
+ children: description
546
+ }) : jsx(Text, {
547
547
  variant: "caption",
548
548
  color: "secondary",
549
549
  align: "center",
550
- children: [t('dataNotAvailableDescription_1', 'Belum ada data yang dapat ditampilkan'), jsx("br", {}), t('dataNotAvailableDescription_2', 'di halaman ini')]
551
- })
552
- });
553
- const defaultEmptySearchProps = (t, isMobile, query) => ({
554
- doodleImage: jsx(TableEmptyStateDoodle, {
555
- asset: "emptySearchData",
556
- "aria-hidden": "true"
557
- }),
558
- title: jsx(Paragraph, {
559
- paragraph: "shortContentBold",
560
- color: "primary",
561
- align: "center",
562
- css: {
563
- marginTop: isMobile ? 0 : '$spacing-05'
564
- },
565
- children: t('dataNotAvailableTitleSearch', `Oops... ${query} tidak ditemukan`, {
566
- query
567
- })
568
- }),
569
- description: !isMobile ? jsxs(Paragraph, {
570
- paragraph: "shortContentRegular",
571
- color: "secondary",
572
- align: "center",
573
- children: [t('dataNotAvailableDescriptionSearch_1', 'Coba lagi dengan memasukkan'), ' ', t('dataNotAvailableDescriptionSearch_2', 'kata kunci yang tepat')]
574
- }) : jsxs(Text, {
575
- variant: "caption",
576
- color: "secondary",
577
- align: "center",
578
- children: [t('dataNotAvailableDescriptionSearch_1', 'Coba lagi dengan memasukkan'), jsx("br", {}), t('dataNotAvailableDescriptionSearch_2', 'kata kunci yang tepat')]
550
+ children: description
579
551
  })
580
552
  });
553
+ /**
554
+ * Builds the default empty state from resolved table texts.
555
+ * Passing an i18next `t` function as the first argument is deprecated.
556
+ */
557
+ const defaultEmptyDataProps = (localeText, isMobile) => {
558
+ const text = typeof localeText === 'function' ? i18nTableLocaleText(localeText) : localeText;
559
+ return emptyStateProps('emptyData', text.emptyDataTitle, text.emptyDataDescription, isMobile);
560
+ };
561
+ const defaultEmptySearchProps = (localeText, isMobile, query) => emptyStateProps('emptySearchData', localeText.emptySearchTitle({
562
+ query
563
+ }), localeText.emptySearchDescription, isMobile);
581
564
  function HeaderMenuOption(props) {
582
565
  const {
583
566
  open,
@@ -587,7 +570,8 @@ function HeaderMenuOption(props) {
587
570
  headerGroups,
588
571
  id,
589
572
  filterHeader: items,
590
- rowData
573
+ rowData,
574
+ title
591
575
  } = props;
592
576
  const {
593
577
  headers = []
@@ -601,7 +585,7 @@ function HeaderMenuOption(props) {
601
585
  asChild: true,
602
586
  children: children
603
587
  }), jsx(ModalDialogTitle, {
604
- children: "Tampilkan Kolom"
588
+ children: title
605
589
  }), jsx(ModalDialogContent, {
606
590
  css: {
607
591
  overflowY: visibleHeaders.filter(val => typeof val.Header === 'string').length > 8 ? 'scroll' : 'none'
@@ -710,8 +694,9 @@ function Table({
710
694
  fetchData,
711
695
  hideDataInfo = false,
712
696
  rowsPerPageOptions = [10, 25, 50, 100],
713
- emptyDataMessage = 'Tidak ada data',
697
+ localeText,
714
698
  customEmptyDataProps: emptyDataProps,
699
+ customEmptySearchProps: emptySearchProps,
715
700
  isLoading = false,
716
701
  css,
717
702
  headerCss,
@@ -766,11 +751,11 @@ function Table({
766
751
  const [filteredHeader, setFilteredHeader] = useState([]);
767
752
  const [hasDarkParent, setHasDarkParent] = useState(false);
768
753
  const [hoveredRowId, setHoveredRowId] = useState(null);
769
- const {
770
- t
771
- } = useTranslation('MajooUI/table');
754
+ const tableLocaleText = useTableLocaleText(paginationLabel && {
755
+ pagination: paginationLabel
756
+ }, localeText);
772
757
  const isMobile = useMediaQuery(isMobileLayout === undefined ? '(max-width: 1024px)' : isMobileLayout ? '(max-width: 8000px)' : '(max-width: 0px)');
773
- const customEmptyDataProps = searchQuery ? defaultEmptySearchProps(t, isMobile, searchQuery) : emptyDataProps || defaultEmptyDataProps(t, isMobile);
758
+ const customEmptyDataProps = searchQuery ? emptySearchProps || defaultEmptySearchProps(tableLocaleText, isMobile, searchQuery) : emptyDataProps || defaultEmptyDataProps(tableLocaleText, isMobile);
774
759
  const getInitialState = () => {
775
760
  const initialState = {};
776
761
  if (fetchData) {
@@ -903,56 +888,37 @@ function Table({
903
888
  });
904
889
  if (fetchData) fetchData(fetchParams);
905
890
  };
906
- const renderFooter = () => {
907
- var _a, _b, _c, _d, _e, _f, _g, _h, _j;
908
- return jsx(Pagination, {
909
- hidePagination: hidePagination || isExpandableTree,
910
- hideInputPageSection: false,
911
- limit: pageSize,
912
- currentPage: pageIndex + 1,
913
- totalData: totalData,
914
- totalPage: totalPage,
915
- onLimitChange: selectedItem => {
916
- if (selectedItem) {
917
- // always reset page when limit changed
918
- gotoPage(0);
919
- doFetchData(0, selectedItem);
920
- setPageSize(selectedItem);
921
- setLimit(selectedItem);
922
- }
923
- },
924
- hideDataInfo: hideDataInfo || isExpandableTree,
925
- onPageChange: page => {
926
- doFetchData(page - 1, pageSize);
927
- gotoPage(page - 1);
928
- },
929
- rowsPerPageOptions: rowsPerPageOptions,
930
- showingText: (_a = paginationLabel === null || paginationLabel === void 0 ? void 0 : paginationLabel.showingText) !== null && _a !== void 0 ? _a : t('pagination.showingText', 'Tampilkan:'),
931
- showingCountText: (_b = paginationLabel === null || paginationLabel === void 0 ? void 0 : paginationLabel.showingCountText) !== null && _b !== void 0 ? _b : ({
932
- from,
933
- to,
934
- total
935
- }) => t('pagination.showingCountText', `Ditampilkan ${from} - ${to} dari ${total} data`, {
936
- from,
937
- to,
938
- total
939
- }),
940
- dialogShowingText: (_c = paginationLabel === null || paginationLabel === void 0 ? void 0 : paginationLabel.dialogShowingText) !== null && _c !== void 0 ? _c : t('pagination.dialogShowingText', 'Tampilkan Data'),
941
- previousButton: (_d = paginationLabel === null || paginationLabel === void 0 ? void 0 : paginationLabel.previousButton) !== null && _d !== void 0 ? _d : t('pagination.previousButton', 'Sebelumnya'),
942
- nextButton: (_e = paginationLabel === null || paginationLabel === void 0 ? void 0 : paginationLabel.nextButton) !== null && _e !== void 0 ? _e : t('pagination.nextButton', 'Selanjutnya'),
943
- gotoPageText: (_f = paginationLabel === null || paginationLabel === void 0 ? void 0 : paginationLabel.gotoPageText) !== null && _f !== void 0 ? _f : t('pagination.gotoPageText', 'Ke Halaman'),
944
- goToPagePlaceholder: (_g = paginationLabel === null || paginationLabel === void 0 ? void 0 : paginationLabel.goToPagePlaceholder) !== null && _g !== void 0 ? _g : t('pagination.goToPagePlaceholder', 'Contoh: 10'),
945
- btnCancelText: (_h = paginationLabel === null || paginationLabel === void 0 ? void 0 : paginationLabel.btnCancelText) !== null && _h !== void 0 ? _h : t('pagination.btnCancelText', 'Batal'),
946
- btnSubmitText: (_j = paginationLabel === null || paginationLabel === void 0 ? void 0 : paginationLabel.btnSubmitText) !== null && _j !== void 0 ? _j : t('pagination.btnSubmitText', 'Lanjutkan'),
947
- css: {
948
- backgroundColor: '$bgWhite',
949
- padding: '$spacing-04 0',
950
- '@md': {
951
- padding: '$spacing-06 $spacing-03'
952
- }
891
+ const renderFooter = () => jsx(Pagination, Object.assign({
892
+ hidePagination: hidePagination || isExpandableTree,
893
+ hideInputPageSection: false,
894
+ limit: pageSize,
895
+ currentPage: pageIndex + 1,
896
+ totalData: totalData,
897
+ totalPage: totalPage,
898
+ onLimitChange: selectedItem => {
899
+ if (selectedItem) {
900
+ // always reset page when limit changed
901
+ gotoPage(0);
902
+ doFetchData(0, selectedItem);
903
+ setPageSize(selectedItem);
904
+ setLimit(selectedItem);
953
905
  }
954
- });
955
- };
906
+ },
907
+ hideDataInfo: hideDataInfo || isExpandableTree,
908
+ onPageChange: page => {
909
+ doFetchData(page - 1, pageSize);
910
+ gotoPage(page - 1);
911
+ },
912
+ rowsPerPageOptions: rowsPerPageOptions
913
+ }, tableLocaleText.pagination, {
914
+ css: {
915
+ backgroundColor: '$bgWhite',
916
+ padding: '$spacing-04 0',
917
+ '@md': {
918
+ padding: '$spacing-06 $spacing-03'
919
+ }
920
+ }
921
+ }));
956
922
  const renderInfiniteScrollWrapper = children => jsx(ConditionalWrapper, {
957
923
  condition: isInfiniteScroll && rawData.length > 0,
958
924
  wrapper: children => jsx(InfiniteScroll, {
@@ -1059,7 +1025,7 @@ function Table({
1059
1025
  children: visibleHeaders.map((column, index) => {
1060
1026
  const headerRender = column.render('Header');
1061
1027
  return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(props => [props, isHeaderSortable(column) ? column.getSortByToggleProps({
1062
- title: t('sortColumnTitle', 'Urutkan') || 'Urutkan'
1028
+ title: tableLocaleText.sortColumnTitle
1063
1029
  }) : {
1064
1030
  onClick: column.onToggle
1065
1031
  }, {
@@ -1303,7 +1269,7 @@ function Table({
1303
1269
  }, index) => {
1304
1270
  const headerRender = column.render('Header');
1305
1271
  return jsx(TableHeadCell, Object.assign({}, column.getHeaderProps(props => [props, isHeaderSortable(column) ? column.getSortByToggleProps({
1306
- title: t('sortColumnTitle', 'Urutkan') || 'Urutkan'
1272
+ title: tableLocaleText.sortColumnTitle
1307
1273
  }) : {
1308
1274
  onClick: column.onToggle
1309
1275
  }, {
@@ -1621,6 +1587,7 @@ function Table({
1621
1587
  onChange: handleFilterHeaderChange,
1622
1588
  headerGroups: headerGroups,
1623
1589
  filterHeader: filterHeader,
1590
+ title: tableLocaleText.showColumnTitle,
1624
1591
  children: jsx(IconButton, {
1625
1592
  css: {
1626
1593
  zIndex: '$docked',
@@ -1695,7 +1662,8 @@ function Table({
1695
1662
  footerData: footerData,
1696
1663
  footerColumnIds: (footerData === null || footerData === void 0 ? void 0 : footerData.length) ? new Set(visibleColumns.filter(column => column.Footer !== emptyRenderer).map(column => column.id)) : undefined,
1697
1664
  headerRowSpan: headerRowSpan,
1698
- keyId: keyId
1665
+ keyId: keyId,
1666
+ sortColumnTitle: tableLocaleText.sortColumnTitle
1699
1667
  })]
1700
1668
  });
1701
1669
  }
@@ -3,7 +3,6 @@ import 'core-js/modules/es.iterator.map.js';
3
3
  import React__default, { useState } from 'react';
4
4
  import { EllipsisHorizontalOutline } from '@majoo-ui/icons';
5
5
  import Box from '../../../../layout/box/Box.esm.js';
6
- import { useTranslation } from 'react-i18next';
7
6
  import { jsxs, jsx } from 'react/jsx-runtime';
8
7
  import { DropdownMenu, DropdownMenuTrigger, DropdownMenuPortal, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator } from '../../../dropdownMenu/DropdownMenu.esm.js';
9
8
  import IconButton from '../../../iconButton/IconButton.esm.js';
@@ -13,11 +12,7 @@ const rowMenuColumn = (options, icon, config) => ({
13
12
  options,
14
13
  Header: () => null,
15
14
  Cell: props => {
16
- const {
17
- t
18
- } = useTranslation('MajooUI/table');
19
15
  const defaultConfig = {
20
- mobileTitle: t('rowMenuMobileLabel', 'Aksi'),
21
16
  cssContent: {},
22
17
  cssMenuItem: {},
23
18
  align: 'end'
@@ -0,0 +1,91 @@
1
+ import 'core-js/modules/es.iterator.constructor.js';
2
+ import 'core-js/modules/es.iterator.filter.js';
3
+ import 'core-js/modules/es.iterator.reduce.js';
4
+ import { useContext, useMemo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { LocaleTextContext } from '../../config/locale/LocaleTextContext.esm.js';
7
+
8
+ const defaultTableLocaleText = {
9
+ sortColumnTitle: 'Urutkan',
10
+ showColumnTitle: 'Tampilkan Kolom',
11
+ emptyDataTitle: 'Data tidak tersedia',
12
+ emptyDataDescription: 'Belum ada data yang dapat ditampilkan di halaman ini',
13
+ emptySearchTitle: ({
14
+ query
15
+ }) => `Oops... ${query} tidak ditemukan`,
16
+ emptySearchDescription: 'Coba lagi dengan memasukkan kata kunci yang tepat',
17
+ pagination: {
18
+ showingText: 'Tampilkan:',
19
+ showingCountText: ({
20
+ from,
21
+ to,
22
+ total
23
+ }) => `Ditampilkan ${from} - ${to} dari ${total} data`,
24
+ dialogShowingText: 'Tampilkan Data',
25
+ previousButton: 'Sebelumnya',
26
+ nextButton: 'Selanjutnya',
27
+ gotoPageText: 'Ke Halaman',
28
+ goToPagePlaceholder: 'Contoh: 10',
29
+ btnCancelText: 'Batal',
30
+ btnSubmitText: 'Lanjutkan'
31
+ }
32
+ };
33
+ // Transitional: keeps apps that still ship `MajooUI/table` locale files translated until they pass
34
+ // `localeText`. Remove together with the react-i18next dependency of the table family.
35
+ const i18nTableLocaleText = t => {
36
+ const d = defaultTableLocaleText;
37
+ return {
38
+ sortColumnTitle: t('sortColumnTitle', d.sortColumnTitle),
39
+ showColumnTitle: t('customColumnTitle', d.showColumnTitle),
40
+ emptyDataTitle: t('dataNotAvailableTitle', d.emptyDataTitle),
41
+ emptyDataDescription: `${t('dataNotAvailableDescription_1', 'Belum ada data yang dapat ditampilkan')} ${t('dataNotAvailableDescription_2', 'di halaman ini')}`,
42
+ emptySearchTitle: ({
43
+ query
44
+ }) => t('dataNotAvailableTitleSearch', `Oops... ${query} tidak ditemukan`, {
45
+ query
46
+ }),
47
+ emptySearchDescription: `${t('dataNotAvailableDescriptionSearch_1', 'Coba lagi dengan memasukkan')} ${t('dataNotAvailableDescriptionSearch_2', 'kata kunci yang tepat')}`,
48
+ pagination: {
49
+ showingText: t('pagination.showingText', d.pagination.showingText),
50
+ showingCountText: ({
51
+ from,
52
+ to,
53
+ total
54
+ }) => t('pagination.showingCountText', `Ditampilkan ${from} - ${to} dari ${total} data`, {
55
+ from,
56
+ to,
57
+ total
58
+ }),
59
+ dialogShowingText: t('pagination.dialogShowingText', d.pagination.dialogShowingText),
60
+ previousButton: t('pagination.previousButton', d.pagination.previousButton),
61
+ nextButton: t('pagination.nextButton', d.pagination.nextButton),
62
+ gotoPageText: t('pagination.gotoPageText', d.pagination.gotoPageText),
63
+ goToPagePlaceholder: t('pagination.goToPagePlaceholder', d.pagination.goToPagePlaceholder),
64
+ btnCancelText: t('pagination.btnCancelText', d.pagination.btnCancelText),
65
+ btnSubmitText: t('pagination.btnSubmitText', d.pagination.btnSubmitText)
66
+ }
67
+ };
68
+ };
69
+ const definedEntries = obj => obj ? Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== undefined)) : {};
70
+ /** Later sources win; an `undefined` value never erases an earlier one. */
71
+ const mergeTableLocaleText = (base, ...overrides) => overrides.reduce((merged, override) => Object.assign(Object.assign(Object.assign({}, merged), definedEntries(override)), {
72
+ pagination: Object.assign(Object.assign({}, merged.pagination), definedEntries(override === null || override === void 0 ? void 0 : override.pagination))
73
+ }), base);
74
+ /**
75
+ * Resolves the table texts. Precedence: the table's own `localeText`, then `MajooProvider`'s
76
+ * `localeText.table`, then the app's `MajooUI/table` translations, then the Indonesian defaults.
77
+ * Several table-level overrides may be passed; later ones win. Pass the same number on every render.
78
+ */
79
+ const useTableLocaleText = (...localeTexts) => {
80
+ const {
81
+ t
82
+ } = useTranslation('MajooUI/table');
83
+ const {
84
+ table: appLocaleText
85
+ } = useContext(LocaleTextContext);
86
+ return useMemo(() => mergeTableLocaleText(i18nTableLocaleText(t), appLocaleText, ...localeTexts),
87
+ // eslint-disable-next-line react-hooks/exhaustive-deps
88
+ [t, appLocaleText, ...localeTexts]);
89
+ };
90
+
91
+ export { defaultTableLocaleText, i18nTableLocaleText, mergeTableLocaleText, useTableLocaleText };
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+
3
+ const LocaleTextContext = /*#__PURE__*/React.createContext({});
4
+
5
+ export { LocaleTextContext };
package/index.esm.css CHANGED
@@ -1,7 +1,3 @@
1
- .recharts-pie-sector {
2
- outline: none;
3
- }
4
-
5
1
  .rdw-option-wrapper {
6
2
  padding: 5px;
7
3
  min-width: 25px;
@@ -1293,3 +1289,7 @@
1293
1289
  height: 24px;
1294
1290
  }
1295
1291
  }
1292
+
1293
+ .recharts-pie-sector {
1294
+ outline: none;
1295
+ }
package/index.esm.js CHANGED
@@ -42,6 +42,7 @@ export { NativeTimePicker, TimePicker } from './components/datePicker/timePicker
42
42
  export { InputTimePicker } from './components/datePicker/timePicker/InputTimePicker.esm.js';
43
43
  export { DatePickerMulti } from './components/datePicker/DatePickerMulti.esm.js';
44
44
  export { Cell, Table, TableBody, TableCell, TableColumn, TableContent, TableFoot, TableFootCell, TableFootRow, TableHead, TableHeadCellContent, TableHeaderCellContainer, TableRow, addOnCellStyle, defaultEmptyDataProps, defaultFetchDataParams } from './components/table/Table.esm.js';
45
+ export { defaultTableLocaleText } from './components/table/locale.esm.js';
45
46
  export { default as MobileTable } from './components/table/MobileTable.esm.js';
46
47
  export { PageDialog, PageDialogContent, PageDialogFooter, PageDialogTitle, PageDialogTrigger } from './components/pageDialog/PageDialog.esm.js';
47
48
  export { default as DevicePreview, ImageDevicePreview } from './components/devicePreview/DevicePreview.esm.js';
@@ -5,14 +5,17 @@ import { theme } from '../config/stitches.config.esm.js';
5
5
  import PropTypes from 'prop-types';
6
6
  import { useTranslation } from 'react-i18next';
7
7
  import moment from 'moment';
8
+ import { LocaleTextContext } from '../config/locale/LocaleTextContext.esm.js';
8
9
  import { jsx } from 'react/jsx-runtime';
9
10
 
11
+ const EMPTY_LOCALE_TEXT = {};
10
12
  function MajooProvider({
11
13
  children,
12
14
  name,
13
15
  theme: theme$1 = theme,
14
16
  extendStyle,
15
- disableGlobalStyle
17
+ disableGlobalStyle,
18
+ localeText = EMPTY_LOCALE_TEXT
16
19
  }) {
17
20
  !disableGlobalStyle && applyGlobalStyles(extendStyle, theme$1);
18
21
  const {
@@ -22,7 +25,10 @@ function MajooProvider({
22
25
  return /*#__PURE__*/jsx(ThemeProvider, {
23
26
  name: name,
24
27
  theme: theme$1,
25
- children: children
28
+ children: /*#__PURE__*/jsx(LocaleTextContext.Provider, {
29
+ value: localeText,
30
+ children: children
31
+ })
26
32
  });
27
33
  }
28
34
  MajooProvider.propTypes = {
@@ -30,7 +36,14 @@ MajooProvider.propTypes = {
30
36
  theme: PropTypes.object.isRequired,
31
37
  children: PropTypes.node.isRequired,
32
38
  extendStyle: PropTypes.object,
33
- disableGlobalStyle: PropTypes.bool
39
+ disableGlobalStyle: PropTypes.bool,
40
+ /**
41
+ * Component texts for the whole app, e.g. `{ table: { emptyDataTitle: 'No data' } }`.
42
+ * Memoize it: every new object re-renders all components that read it.
43
+ */
44
+ localeText: PropTypes.shape({
45
+ table: PropTypes.object
46
+ })
34
47
  };
35
48
  MajooProvider.defaultProps = {
36
49
  name: 'default',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@majoo-ui/react",
3
- "version": "2.2.0",
3
+ "version": "2.3.0",
4
4
  "main": "./index.esm.js",
5
5
  "sideEffects": [
6
6
  "*.css",
@@ -37,7 +37,7 @@
37
37
  "@floating-ui/react": "^0.19.2",
38
38
  "@majoo-ui/core": "1.32.0",
39
39
  "@majoo-ui/helpers": "1.30.9",
40
- "@majoo-ui/icons": "1.40.3",
40
+ "@majoo-ui/icons": "1.40.4",
41
41
  "@monsonjeremy/react-leaflet": "^3.2.2",
42
42
  "@radix-ui/react-accordion": "^1.0.0",
43
43
  "@radix-ui/react-alert-dialog": "^1.0.3",
@@ -18,6 +18,7 @@ interface EditableMobileProps<Data extends object> {
18
18
  hiddenColumns?: string[];
19
19
  customEmptyDataProps?: EmptyDataProps;
20
20
  virtualized?: boolean;
21
+ sortColumnTitle?: string;
21
22
  }
22
23
  export default function EditableTableMobile(props: EditableMobileProps<any>): JSX.Element;
23
24
  export {};