@applica-software-guru/react-admin 1.3.149 → 1.3.151

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.
@@ -1,3 +1,4 @@
1
1
  export * from './time';
2
2
  export * from './lang';
3
+ export * from './localizedValue';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,QAAQ,CAAC;AACvB,cAAc,kBAAkB,CAAC"}
@@ -0,0 +1,11 @@
1
+ type ILocalizedValue<T> = {
2
+ [key: string]: T;
3
+ };
4
+ declare function localizedValueHasAllLocales<T>(value?: ILocalizedValue<T>, locales?: Array<{
5
+ name: string;
6
+ locale: string;
7
+ }>, options?: {
8
+ isEmpty?: (v: T) => boolean;
9
+ }): boolean;
10
+ export { localizedValueHasAllLocales };
11
+ //# sourceMappingURL=localizedValue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"localizedValue.d.ts","sourceRoot":"","sources":["../../../src/utils/localizedValue.ts"],"names":[],"mappings":"AACA,KAAK,eAAe,CAAC,CAAC,IAAI;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,CAAA;CAAE,CAAC;AAE/C,iBAAS,2BAA2B,CAAC,CAAC,EACpC,KAAK,GAAE,eAAe,CAAC,CAAC,CAAM,EAC9B,OAAO,GAAE,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAM,EACrD,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,CAAA;CAAO,GAC5C,OAAO,CAIT;AAED,OAAO,EAAE,2BAA2B,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@applica-software-guru/react-admin",
3
- "version": "1.3.149",
3
+ "version": "1.3.151",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,46 @@
1
+ import _ from 'lodash';
2
+ import { TextFieldProps } from 'ra-ui-materialui';
3
+ import { Stack, Typography, useTheme } from '@mui/material';
4
+ import { useLocaleState, useLocales, useRecordContext } from 'ra-core';
5
+ import TextField from './TextField';
6
+ import { FlagOutlined, FlagFilled } from '@ant-design/icons';
7
+ import { useSx } from '../../hooks';
8
+ import { localizedValueHasAllLocales } from '../../utils';
9
+ import { ReactElement, useMemo } from 'react';
10
+ import { Tooltip } from '../@extended';
11
+
12
+ type ILocalizedTextFieldProps = TextFieldProps;
13
+
14
+ function LocalizedTextField(props: ILocalizedTextFieldProps) {
15
+ const theme = useTheme(),
16
+ locales = useLocales(),
17
+ { source = '' } = props,
18
+ record = useRecordContext(props),
19
+ value = record[source],
20
+ isMissingLocalizations = !localizedValueHasAllLocales(value, locales),
21
+ sx = useSx(props, { flex: 1 }),
22
+ [currentLocale] = useLocaleState(),
23
+ IconElement: ReactElement = useMemo(() => {
24
+ return isMissingLocalizations ? <FlagFilled style={{ color: theme.palette.warning.main }} /> : <FlagOutlined />;
25
+ }, [isMissingLocalizations]),
26
+ tooltipTitle = useMemo(() => {
27
+ return (
28
+ <Stack>
29
+ {_.map(locales, (l, index) => {
30
+ return <Typography key={index}>{`${l.locale.toUpperCase()}: ${(value ?? {})[l.locale] ?? ''}`}</Typography>;
31
+ })}
32
+ </Stack>
33
+ );
34
+ }, [value, locales]);
35
+
36
+ return (
37
+ <Tooltip title={tooltipTitle} arrow={false}>
38
+ <Stack alignItems="center" direction="row" gap={1}>
39
+ {IconElement}
40
+ <TextField {...props} source={`${source}.${currentLocale}`} sx={sx} />
41
+ </Stack>
42
+ </Tooltip>
43
+ );
44
+ }
45
+
46
+ export default LocalizedTextField;
@@ -7,6 +7,7 @@ import EmailField from './EmailField';
7
7
  import FileField from './FileField';
8
8
  import FunctionField from './FunctionField';
9
9
  import ImageField from './ImageField';
10
+ import LocalizedTextField from './LocalizedTextField';
10
11
  import ReadonlyField from './ReadonlyField';
11
12
  import ReferenceManyField from './ReferenceManyField';
12
13
  import SizeField from './SizeField';
@@ -21,6 +22,7 @@ export {
21
22
  FileField,
22
23
  FunctionField,
23
24
  ImageField,
25
+ LocalizedTextField,
24
26
  ReadonlyField,
25
27
  ReferenceManyField,
26
28
  SizeField,
@@ -0,0 +1,77 @@
1
+ import _ from 'lodash';
2
+ import { ReactElement, useCallback, useMemo, useState } from 'react';
3
+ import { Box, Chip, ListItemText, Menu, MenuItem, PopoverOrigin, Typography } from '@mui/material';
4
+ import { useInput, useLocaleState, useLocales } from 'ra-core';
5
+ import { TextInputProps } from 'ra-ui-materialui';
6
+ import TextInput from './TextInput';
7
+ import { localizedValueHasAllLocales } from '../../utils';
8
+ import { useWatch } from 'react-hook-form';
9
+ import LabeledInput, { LabeledInputProps } from './LabeledInput';
10
+
11
+ type ILocalizedTextInputProps = TextInputProps & LabeledInputProps;
12
+
13
+ const ANCHOR_ORIGIN: PopoverOrigin = { vertical: 'bottom', horizontal: 'right' },
14
+ TRANSFORM_ORIGIN: PopoverOrigin = { vertical: 'top', horizontal: 'right' };
15
+
16
+ function LocalizedTextInput(props: ILocalizedTextInputProps) {
17
+ const { source } = props,
18
+ { fieldState } = useInput(props),
19
+ value = useWatch({ name: source }),
20
+ { error } = fieldState,
21
+ locales = useLocales(),
22
+ isMissingLocalizations = !localizedValueHasAllLocales(value, locales),
23
+ [currentLocale] = useLocaleState(),
24
+ [locale, setLocale] = useState(currentLocale),
25
+ [anchorEl, setAnchorEl] = useState<null | HTMLDivElement>(null),
26
+ open = Boolean(anchorEl),
27
+ toggle = useCallback((e: React.MouseEvent<HTMLDivElement>) => setAnchorEl(open ? null : e.currentTarget), [open, setAnchorEl]),
28
+ handleClose = useCallback(() => setAnchorEl(null), [setAnchorEl]),
29
+ MenuItems: Array<ReactElement> = useMemo(() => {
30
+ return _.map(locales, (l, index) => {
31
+ return (
32
+ <MenuItem
33
+ key={index}
34
+ onClick={() => {
35
+ setLocale(l.locale);
36
+ handleClose();
37
+ }}
38
+ >
39
+ <ListItemText primary={`${l.locale.toUpperCase()}: ${(value ?? {})[l.locale] ?? ''}`} />
40
+ </MenuItem>
41
+ );
42
+ });
43
+ }, [locales, setLocale, handleClose, value]);
44
+
45
+ return (
46
+ <LabeledInput {...props} helperText={error?.message}>
47
+ <Box flex={1}>
48
+ {_.map(locales, (l, index) => {
49
+ return (
50
+ <TextInput
51
+ key={index}
52
+ {...props}
53
+ source={`${source}.${l.locale}`}
54
+ sx={{ width: '100%', display: l.locale === locale ? 'inline-flex' : 'none' }}
55
+ label={false}
56
+ InputProps={{
57
+ endAdornment: (
58
+ <Chip
59
+ label={<Typography variant="button">{locale}</Typography>}
60
+ size="small"
61
+ onClick={toggle}
62
+ color={isMissingLocalizations ? 'warning' : undefined}
63
+ />
64
+ )
65
+ }}
66
+ />
67
+ );
68
+ })}
69
+ <Menu anchorEl={anchorEl} open={open} anchorOrigin={ANCHOR_ORIGIN} transformOrigin={TRANSFORM_ORIGIN} onClose={handleClose}>
70
+ {MenuItems}
71
+ </Menu>
72
+ </Box>
73
+ </LabeledInput>
74
+ );
75
+ }
76
+
77
+ export default LocalizedTextInput;
@@ -9,6 +9,7 @@ import FileInput from './FileInput';
9
9
  import ImageInput from './ImageInput';
10
10
  import LabeledArrayInput from './LabeledArrayInput';
11
11
  import LabeledInput from './LabeledInput';
12
+ import LocalizedTextInput from './LocalizedTextInput';
12
13
  import NumberInput from './NumberInput';
13
14
  import RecordInput from './RecordInput';
14
15
  import ReferenceArrayInput from './ReferenceArrayInput';
@@ -32,6 +33,7 @@ export {
32
33
  ImageInput,
33
34
  LabeledArrayInput,
34
35
  LabeledInput,
36
+ LocalizedTextInput,
35
37
  NumberInput,
36
38
  RecordInput,
37
39
  ReferenceArrayInput,
@@ -1,2 +1,3 @@
1
1
  export * from './time';
2
2
  export * from './lang';
3
+ export * from './localizedValue';
@@ -0,0 +1,14 @@
1
+ import _ from 'lodash';
2
+ type ILocalizedValue<T> = { [key: string]: T };
3
+
4
+ function localizedValueHasAllLocales<T>(
5
+ value: ILocalizedValue<T> = {},
6
+ locales: Array<{ name: string; locale: string }> = [],
7
+ options: { isEmpty?: (v: T) => boolean } = {}
8
+ ): boolean {
9
+ const { isEmpty = _.isEmpty } = options;
10
+
11
+ return !_.some(locales, (l) => isEmpty((value ?? {})[l.locale]));
12
+ }
13
+
14
+ export { localizedValueHasAllLocales };