jamespot-react-components 1.0.1 → 1.0.9

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.
Files changed (77) hide show
  1. package/.github/workflows/increment-npm-version.yml +26 -0
  2. package/build/jamespot-react-components.js +247 -231
  3. package/build/jamespot-react-components.js.LICENSE.txt +0 -16
  4. package/build/jamespot-react-components.js.map +1 -1
  5. package/build/src/components/Common/JRCConditionalWrapper.d.ts +4 -4
  6. package/build/src/components/Form/Input/JRCInputCheckbox/JRCInputCheckbox.d.ts +5 -2
  7. package/build/src/components/Form/Input/JRCInputDate/JRCInputDate.d.ts +11 -0
  8. package/build/src/components/Form/Input/JRCInputDate/JRCInputDate.stories.d.ts +5 -0
  9. package/build/src/components/Form/Input/JRCInputText/JRCInputText.d.ts +1 -1
  10. package/build/src/components/Form/Input/JRCInputTextarea/JRCInputTextarea.d.ts +15 -0
  11. package/build/src/components/Form/Input/JRCInputTextarea/JRCInputTextarea.stories.d.ts +5 -0
  12. package/build/src/components/JRCAppLeftColumn/JRCAppLeftColumn.d.ts +1 -1
  13. package/build/src/components/JRCButton/JRCButton.d.ts +25 -0
  14. package/build/src/components/JRCButton/JRCButtonConfig.d.ts +1 -2
  15. package/build/src/components/JRCEllipsis/JRCEllipsis.d.ts +5 -0
  16. package/build/src/components/JRCEllipsis/JRCEllipsis.stories.d.ts +5 -0
  17. package/build/src/components/JRCHref/JRCHref.d.ts +18 -10
  18. package/build/src/components/JRCImg/url.util.d.ts +3 -3
  19. package/build/src/components/JRCList/JRCList.d.ts +2 -0
  20. package/build/src/components/JRCModal/JRCModal.d.ts +5 -1
  21. package/build/src/components/JRCModal/JRCModal.styles.d.ts +0 -1
  22. package/build/src/components/JRCTag/JRCTag.d.ts +2 -0
  23. package/build/src/hooks/UseDidMountEffect.d.ts +1 -2
  24. package/build/src/index.d.ts +8 -7
  25. package/build/src/styles/theme.d.ts +1 -0
  26. package/build/src/types.d.ts +4 -0
  27. package/externals.d.ts +0 -1
  28. package/externals.json +2 -1
  29. package/package.json +5 -5
  30. package/src/components/Common/JRCConditionalWrapper.tsx +6 -13
  31. package/src/components/Form/Input/JRCFormColor/JRCFormColor.tsx +12 -12
  32. package/src/components/Form/Input/JRCFormEmail/JRCInputEmail.tsx +4 -8
  33. package/src/components/Form/Input/JRCFormSelect/JRCFormSelect.tsx +5 -1
  34. package/src/components/Form/Input/JRCFormSelect/JRCFormSelectTag.tsx +3 -1
  35. package/src/components/Form/Input/JRCInputCheckbox/JRCInputCheckbox.stories.tsx +1 -4
  36. package/src/components/Form/Input/JRCInputCheckbox/JRCInputCheckbox.tsx +12 -10
  37. package/src/components/Form/Input/JRCInputDate/JRCInputDate.stories.tsx +50 -0
  38. package/src/components/Form/Input/JRCInputDate/JRCInputDate.tsx +26 -0
  39. package/src/components/Form/Input/JRCInputText/JRCInputText.tsx +2 -2
  40. package/src/components/Form/Input/JRCInputTextarea/JRCInputTextarea.stories.tsx +52 -0
  41. package/src/components/Form/Input/JRCInputTextarea/JRCInputTextarea.tsx +36 -0
  42. package/src/components/Form/Input/JRCSelect/JRCInputSelect.tsx +1 -1
  43. package/src/components/JRCAppLeftColumn/JRCAppLeftColumn.tsx +1 -1
  44. package/src/components/JRCButton/JRCButton.stories.tsx +1 -1
  45. package/src/components/JRCButton/JRCButton.tsx +9 -3
  46. package/src/components/JRCButton/JRCButtonConfig.tsx +1 -1
  47. package/src/components/JRCButton/JRCValidationButton.tsx +10 -4
  48. package/src/components/JRCButtonDropdown/JRCButtonDropdown.tsx +2 -2
  49. package/src/components/JRCEllipsis/JRCEllipsis.stories.tsx +18 -0
  50. package/src/components/JRCEllipsis/JRCEllipsis.tsx +22 -0
  51. package/src/components/JRCHref/JRCHref.stories.tsx +2 -0
  52. package/src/components/JRCHref/JRCHref.tsx +42 -15
  53. package/src/components/JRCIcon/JRCIcon.tsx +1 -1
  54. package/src/components/JRCIconButton/JRCIconButton.tsx +1 -4
  55. package/src/components/JRCImg/JRCImg.tsx +4 -2
  56. package/src/components/JRCImg/url.util.ts +7 -6
  57. package/src/components/JRCImg/url.utils.test.ts +7 -1
  58. package/src/components/JRCList/JRCList.styles.tsx +16 -2
  59. package/src/components/JRCList/JRCList.tsx +5 -5
  60. package/src/components/JRCList/JRCListMockData.stories.tsx +1 -1
  61. package/src/components/JRCModal/JRCModal.styles.tsx +0 -6
  62. package/src/components/JRCModal/JRCModal.tsx +77 -62
  63. package/src/components/JRCTag/JRCTag.tsx +29 -9
  64. package/src/components/JRCThemeProvider/animation.css +19 -0
  65. package/src/components/JRCThemeProvider/gabarit.css +4 -0
  66. package/src/components/JRCTooltip/JRCTooltip.tsx +5 -2
  67. package/src/hooks/UseDidMountEffect.tsx +1 -3
  68. package/src/index.tsx +9 -10
  69. package/src/styles/theme.tsx +3 -2
  70. package/src/translation/lang.json +3 -2
  71. package/src/types.ts +4 -0
  72. package/tsconfig.json +1 -0
  73. package/build/src/components/Form/Input/JRCFormTextEditor/JRCFormTextEditor.d.ts +0 -10
  74. package/build/src/components/Form/Input/JRCFormTextEditor/JRCFormTextEditor.stories.d.ts +0 -5
  75. package/src/components/Form/Input/JRCFormTextEditor/JRCFormTextEditor.stories.tsx +0 -30
  76. package/src/components/Form/Input/JRCFormTextEditor/JRCFormTextEditor.tsx +0 -36
  77. package/src/variables.scss +0 -67
@@ -12,12 +12,11 @@ import { deepEqual } from '../../../../utils/utils.object';
12
12
  import { DataCy } from '../../../../types/dataAttributes';
13
13
  import { JRCInputFieldProps } from '../Common/JRCFormFieldRenderer.types';
14
14
  import { CheckboxValue } from './JRCInputCheckbox.types';
15
-
16
- type Value<T> = string | number | T;
15
+ import { RefCallBack } from 'react-hook-form';
17
16
 
18
17
  type CheckboxOption<TFieldValues> = {
19
18
  label: string | JSX.Element;
20
- value: Value<TFieldValues>;
19
+ value: string | number | TFieldValues;
21
20
  disabled?: boolean;
22
21
  };
23
22
 
@@ -35,18 +34,19 @@ export type CheckboxProps<TFieldValues> = DataCy & {
35
34
  };
36
35
 
37
36
  export type JRCInputCheckboxProps<TFieldValues> = CheckboxProps<TFieldValues> & JRCInputFieldProps<TFieldValues>;
37
+ export type NativeCheckboxProps<TFieldValues> = CheckboxProps<TFieldValues> &
38
+ Omit<ControllerRenderProps<TFieldValues>, 'value'> & { value: TFieldValues | TFieldValues[] };
38
39
 
39
- function RenderInput<TFieldValues>({
40
- variant = 'default',
41
- ...props
42
- }: CheckboxProps<TFieldValues> &
43
- Omit<ControllerRenderProps<TFieldValues>, 'value'> & { value: TFieldValues | TFieldValues[] }) {
40
+ const RenderInput = React.forwardRef(function <TFieldValues>(
41
+ { variant = 'default', ...props }: NativeCheckboxProps<TFieldValues>,
42
+ ref: React.ForwardedRef<HTMLInputElement>,
43
+ ) {
44
44
  const field = {
45
45
  onChange: props.onChange,
46
46
  onBlur: props.onBlur,
47
47
  value: props.value,
48
48
  name: props.name,
49
- ref: props.ref,
49
+ ref: ref as RefCallBack,
50
50
  };
51
51
 
52
52
  const [valueTypeIsBoolean, setValueTypeIsBoolean] = React.useState(
@@ -98,6 +98,7 @@ function RenderInput<TFieldValues>({
98
98
  {props.checkboxMode === 'radio' ? (
99
99
  <JRCRadio
100
100
  name={props.name}
101
+ ref={(index === 0 && ref) || undefined}
101
102
  data-cy={props.dataCy && `${props.dataCy}-option-${index}`}
102
103
  disabled={props.disabled || option.disabled}
103
104
  checked={checked}
@@ -109,6 +110,7 @@ function RenderInput<TFieldValues>({
109
110
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
110
111
  // @ts-ignore
111
112
  checkboxMode={props.checkboxMode}
113
+ ref={(index === 0 && ref) || undefined}
112
114
  name={props.name}
113
115
  data-cy={props.dataCy && `${props.dataCy}-option-${index}`}
114
116
  disabled={props.disabled || option.disabled}
@@ -147,7 +149,7 @@ function RenderInput<TFieldValues>({
147
149
  })}
148
150
  </DirectionContainer>
149
151
  );
150
- }
152
+ }) as unknown as <T>(props: NativeCheckboxProps<T>) => JSX.Element;
151
153
 
152
154
  export function JRCInputCheckbox<TFieldValues>({
153
155
  name,
@@ -0,0 +1,50 @@
1
+ import * as React from 'react';
2
+ import { JRCInputDate } from './JRCInputDate';
3
+ import { useForm } from 'react-hook-form';
4
+ import { JRCInputFieldProps } from '../Common/JRCFormFieldRenderer.types';
5
+ import { SubmitHandler } from 'react-hook-form/dist/types/form';
6
+ import { Meta, Story } from '@storybook/react';
7
+
8
+ export default {
9
+ title: 'Inputs/new',
10
+ component: JRCInputDate,
11
+ } as Meta;
12
+
13
+ const Template: Story<JRCInputFieldProps<'date'>> = (args) => {
14
+ const { handleSubmit, control, reset } = useForm({
15
+ defaultValues: {
16
+ date: '',
17
+ },
18
+ criteriaMode: 'all',
19
+ });
20
+
21
+ const onSubmit: SubmitHandler<{ date: string }> = (data) => {
22
+ alert(JSON.stringify(data, null, 2));
23
+ };
24
+
25
+ return (
26
+ <form onSubmit={handleSubmit(onSubmit)}>
27
+ <JRCInputDate {...args} name="date" control={control} />
28
+ <input type="submit" />
29
+ <input type="reset" />
30
+ <button type="button" onClick={() => reset()}>
31
+ Custom Reset
32
+ </button>
33
+ </form>
34
+ );
35
+ };
36
+
37
+ export const InputDate = Template.bind({});
38
+ InputDate.args = {
39
+ label: 'Date de création du groupe',
40
+ description:
41
+ "La date de création du groupe doit permettre en un clin d'oeil de connaitre la date de création du groupe",
42
+ helper: { description: "Ajouter une date cohérente ! L'année 218 n'est pas cohérente", title: 'Indication' },
43
+ rules: {
44
+ min: {
45
+ value: '2023-11-11',
46
+ message: 'La date doit être supérieur au 11/11/2023',
47
+ },
48
+ required: true,
49
+ },
50
+ };
@@ -0,0 +1,26 @@
1
+ import * as React from 'react';
2
+ import { StyledInput } from '../JRCStyledInput';
3
+ import { JRCInputFieldProps } from '../Common/JRCFormFieldRenderer.types';
4
+ import { JRCFormFieldRenderer } from '../Common/JRCFormFieldRenderer';
5
+ import { DataCy } from '../../../../types/dataAttributes';
6
+
7
+ export type JRCInputDateProps = DataCy & React.ComponentPropsWithoutRef<'input'>;
8
+
9
+ const RenderInput = React.forwardRef((props: JRCInputDateProps, ref: React.ForwardedRef<HTMLInputElement>) => {
10
+ return <StyledInput type="date" id={props.name} {...props} ref={ref} />;
11
+ });
12
+
13
+ /**
14
+ * Component used as a <input type="date"/>
15
+ * @param props JRCInputFieldProps
16
+ * validation props: required
17
+ * @returns JSX.Element
18
+ */
19
+ export function JRCInputDate<T>(props: JRCInputFieldProps<T>) {
20
+ return (
21
+ <JRCFormFieldRenderer
22
+ {...props}
23
+ renderFunction={({ value, ...field }) => <RenderInput {...props} {...field} value={value as string} />}
24
+ />
25
+ );
26
+ }
@@ -4,9 +4,9 @@ import { JRCInputFieldProps } from '../Common/JRCFormFieldRenderer.types';
4
4
  import { JRCFormFieldRenderer } from '../Common/JRCFormFieldRenderer';
5
5
  import { DataCy } from '../../../../types/dataAttributes';
6
6
 
7
- export type InputTextProps = DataCy & React.ComponentPropsWithoutRef<'input'>;
7
+ export type JRCInputTextProps = DataCy & React.ComponentPropsWithoutRef<'input'>;
8
8
 
9
- const RenderInput = React.forwardRef((props: InputTextProps, ref: React.ForwardedRef<HTMLInputElement>) => {
9
+ const RenderInput = React.forwardRef((props: JRCInputTextProps, ref: React.ForwardedRef<HTMLInputElement>) => {
10
10
  return <StyledInput type="text" id={props.name} {...props} ref={ref} />;
11
11
  });
12
12
 
@@ -0,0 +1,52 @@
1
+ import * as React from 'react';
2
+ import { JRCInputTextarea } from './JRCInputTextarea';
3
+ import { useForm } from 'react-hook-form';
4
+ import { JRCInputFieldProps } from '../Common/JRCFormFieldRenderer.types';
5
+ import { SubmitHandler } from 'react-hook-form/dist/types/form';
6
+ import { Meta, Story } from '@storybook/react';
7
+
8
+ export default {
9
+ title: 'Inputs/new',
10
+ component: JRCInputTextarea,
11
+ } as Meta;
12
+
13
+ const Template: Story<JRCInputFieldProps<'textarea'>> = (args) => {
14
+ const { handleSubmit, control, reset } = useForm({
15
+ defaultValues: {
16
+ textarea: '',
17
+ },
18
+ criteriaMode: 'all',
19
+ });
20
+
21
+ const onSubmit: SubmitHandler<{ textarea: string }> = (data) => {
22
+ alert(JSON.stringify(data, null, 2));
23
+ };
24
+
25
+ return (
26
+ <form onSubmit={handleSubmit(onSubmit)}>
27
+ <JRCInputTextarea {...args} name="textarea" control={control} />
28
+ <input type="submit" />
29
+ <input type="reset" />
30
+ <button type="button" onClick={() => reset()}>
31
+ Custom Reset
32
+ </button>
33
+ </form>
34
+ );
35
+ };
36
+
37
+ export const InputTextarea = Template.bind({});
38
+ InputTextarea.args = {
39
+ label: 'Nom de groupe',
40
+ description: "Le nom du groupe doit permettre en un clin d'oeil de connaitre le sujet du groupe",
41
+ placeholder: 'Présentations',
42
+ helper: { description: 'Ne faites pas commencer votre groupe par "Groupe de"', title: 'Indication' },
43
+ rules: {
44
+ maxLength: {
45
+ value: 25,
46
+ message:
47
+ 'This is an example error. For instance, the text should not be written with more than 25 characters. This message is not the default one.',
48
+ },
49
+ minLength: 3,
50
+ required: true,
51
+ },
52
+ };
@@ -0,0 +1,36 @@
1
+ import * as React from 'react';
2
+ import { cssStyledInput } from '../JRCStyledInput';
3
+ import { JRCInputFieldProps } from '../Common/JRCFormFieldRenderer.types';
4
+ import { JRCFormFieldRenderer } from '../Common/JRCFormFieldRenderer';
5
+ import { DataCy } from '../../../../types/dataAttributes';
6
+ import styled from 'styled-components';
7
+
8
+ export type JRCInputTextareaProps = DataCy & React.ComponentPropsWithoutRef<'textarea'>;
9
+
10
+ export const StyledTextArea = styled.textarea`
11
+ ${cssStyledInput}
12
+ &&&& {
13
+ padding-top: 10px;
14
+ height: 140px;
15
+ resize: vertical;
16
+ }
17
+ `;
18
+
19
+ const RenderInput = React.forwardRef((props: JRCInputTextareaProps, ref: React.ForwardedRef<HTMLTextAreaElement>) => {
20
+ return <StyledTextArea id={props.name} {...props} ref={ref} />;
21
+ });
22
+
23
+ /**
24
+ * Component used as a <input type="text"/>
25
+ * @param props JRCInputFieldProps
26
+ * validation props: required
27
+ * @returns JSX.Element
28
+ */
29
+ export function JRCInputTextarea<T>(props: JRCInputFieldProps<T>) {
30
+ return (
31
+ <JRCFormFieldRenderer
32
+ {...props}
33
+ renderFunction={({ value, ...field }) => <RenderInput {...props} {...field} value={value as string} />}
34
+ />
35
+ );
36
+ }
@@ -56,7 +56,7 @@ export const Select = React.forwardRef(function <T>(
56
56
  if (menuRef.current && selectedOptionRef) scrollIntoView(menuRef.current, selectedOptionRef);
57
57
  }
58
58
 
59
- // Hack to use .focus() outside of this component while beeing able to access the ref within this component
59
+ // Hack to use .focus() outside of this component while being able to access the ref within this component
60
60
  React.useImperativeHandle(
61
61
  ref,
62
62
  () =>
@@ -22,7 +22,7 @@ import { useIntl } from 'react-intl';
22
22
  import { JRCDefaultMenu } from './JRCDefaultMenu';
23
23
 
24
24
  /**
25
- *
25
+ * /!\ This component should be use into JRCAppContainer
26
26
  * @param Component
27
27
  * @param {sections, icon, color, title, description, activeItem, callback} props, @see JRCAppLeftColumnProps interface
28
28
  * @returns JSX.Element
@@ -44,7 +44,7 @@ export const AllButtons = () => {
44
44
  </JRCButton>
45
45
  <JRCButton
46
46
  onClick={action('button-click')}
47
- // icon="icon-check"
47
+ icon="icon-check"
48
48
  type="submit"
49
49
  color="primary"
50
50
  variant="contained">
@@ -2,7 +2,7 @@ import * as React from 'react';
2
2
  import JRCIcon, { JRCIconProps } from '../JRCIcon/JRCIcon';
3
3
  import styled from 'styled-components';
4
4
  import JRCLoader from 'components/JRCLoader/JRCLoader';
5
- import config, { ButtonType } from './JRCButtonConfig';
5
+ import { BUTTON_CONFIG, ButtonType } from './JRCButtonConfig';
6
6
  import { ForwardedRef } from 'react';
7
7
  import { DataCy } from '../../types/dataAttributes';
8
8
 
@@ -39,7 +39,10 @@ export const transformColor = (color?: string): JRCIconProps['color'] => {
39
39
  }
40
40
  };
41
41
 
42
- const Button = styled.button<{ themeButton: ButtonType; hasChildren: boolean } & JRCButtonProps>`
42
+ /**
43
+ * Button styling. May be used as a <a> tag
44
+ */
45
+ export const Button = styled.button<{ themeButton: ButtonType; hasChildren: boolean } & JRCButtonProps>`
43
46
  float: ${(props) => props.float};
44
47
  min-width: ${(props) => props.minWidth};
45
48
  display: inline-flex;
@@ -58,7 +61,10 @@ const Button = styled.button<{ themeButton: ButtonType; hasChildren: boolean } &
58
61
  box-sizing: border-box;
59
62
  border: 2px solid ${(props) => props.theme.color[props.themeButton.border]};
60
63
  transition: color 0.5s, background-color 0.5s, border 0.5s;
64
+ text-decoration: none;
61
65
  &:hover {
66
+ text-decoration: none;
67
+ color: ${(props) => props.theme.color[props.themeButton.color]};
62
68
  background-color: ${(props) => props.theme.color[props.themeButton.hover.background]};
63
69
  border-color: ${(props) => props.theme.color[props.themeButton.hover.border]};
64
70
  }
@@ -93,7 +99,7 @@ const JRCButton = React.forwardRef(
93
99
  ref: ForwardedRef<HTMLButtonElement>,
94
100
  ) => {
95
101
  const disabled = props.disabled || props.loader;
96
- const themeButton = config[variant][disabled ? 'disabled' : color];
102
+ const themeButton = BUTTON_CONFIG[variant][disabled ? 'disabled' : color];
97
103
 
98
104
  return (
99
105
  <Button
@@ -1,4 +1,4 @@
1
- export default {
1
+ export const BUTTON_CONFIG = {
2
2
  contained: {
3
3
  primary: {
4
4
  color: 'white',
@@ -13,7 +13,7 @@ export type JRCValidationButtonProps = JRCButtonProps & {
13
13
  validationMessage?: string;
14
14
  };
15
15
 
16
- /** double click prevented by using a temporary state, changed after a timeout */
16
+ /** double click prevented by using a temporary state (PREVENT_DB_CLICK), changed after a timeout */
17
17
  const state = {
18
18
  NOT_CLICKED: 'NOT_CLICKED',
19
19
  PREVENT_DB_CLICK: 'PREVENT_DB_CLICK',
@@ -44,14 +44,20 @@ export const JRCValidationButton = ({
44
44
 
45
45
  React.useEffect(() => {
46
46
  if (validation === state.PREVENT_DB_CLICK) {
47
- setTimeoutId(setTimeout(() => setValidation(state.NOT_CLICKED), RESET_CLICK_DELAY) as unknown as number);
47
+ setTimeoutId(
48
+ setTimeout(() => {
49
+ setValidation(state.NOT_CLICKED);
50
+ buttonRef?.current?.removeAttribute('style');
51
+ }, RESET_CLICK_DELAY) as unknown as number,
52
+ );
48
53
  }
49
54
  }, [validation]);
50
55
 
51
56
  const validationOnClick = React.useCallback(
52
57
  (e: React.MouseEvent<HTMLButtonElement>) => {
53
58
  if (validationRef.current === state.CLICKED) {
54
- setValidation(state.NOT_CLICKED);
59
+ // Timeout of 0 second to delay the change at the end of the execution pile
60
+ setTimeout(() => setValidation(state.NOT_CLICKED), 0);
55
61
  buttonRef?.current?.removeAttribute('style');
56
62
  onClick && onClick(e);
57
63
  if (timeoutIdRef.current) clearTimeout(timeoutIdRef.current);
@@ -69,7 +75,7 @@ export const JRCValidationButton = ({
69
75
  return (
70
76
  <JRCButton
71
77
  {...props}
72
- type={validation === state.NOT_CLICKED ? 'button' : props.type}
78
+ type={validation === state.CLICKED ? props.type : 'button'}
73
79
  ref={buttonRef}
74
80
  onClick={validationOnClick}
75
81
  icon={validation === state.NOT_CLICKED ? icon : 'icon-fs-question'}
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import styled from 'styled-components';
3
3
  import JRCButton, { transformColor } from '../JRCButton/JRCButton';
4
- import config, { ButtonType } from '../JRCButton/JRCButtonConfig';
4
+ import { BUTTON_CONFIG, ButtonType } from '../JRCButton/JRCButtonConfig';
5
5
  import JRCIcon from '../JRCIcon/JRCIcon';
6
6
  import { ClickAwayListener } from '../Form/Common/ClickAwayListener';
7
7
 
@@ -120,7 +120,7 @@ export const JRCButtonDropdown = ({
120
120
  variant = 'contained',
121
121
  ...props
122
122
  }: JRCButtonDropdownProps) => {
123
- const color = config[variant][props.disabled ? 'disabled' : props.color || 'primary'];
123
+ const color = BUTTON_CONFIG[variant][props.disabled ? 'disabled' : props.color || 'primary'];
124
124
  const [isOpen, setOpenMenu] = React.useState(false);
125
125
  const open = isOpen;
126
126
  const IconRight = ({ color }: any) => {
@@ -0,0 +1,18 @@
1
+ import * as React from 'react';
2
+ import { Meta, Story } from '@storybook/react';
3
+ import { JRCEllipsis, JRCEllipsisProps } from './JRCEllipsis';
4
+
5
+ export default {
6
+ title: 'JRCEllipsis',
7
+ component: JRCEllipsis,
8
+ } as Meta;
9
+
10
+ const Template: Story<JRCEllipsisProps> = (args) => {
11
+ return <JRCEllipsis {...args} />;
12
+ };
13
+
14
+ export const CustomButton = Template.bind({});
15
+ CustomButton.args = {
16
+ label: "Je suis un texte trop long ! Ca marche avec un caractère double tel que : 👋 ! C'est génial !",
17
+ length: 75,
18
+ };
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+ import { JRCConditionalWrapper } from '../Common/JRCConditionalWrapper';
3
+ import JRCTooltip from '../JRCTooltip/JRCTooltip';
4
+
5
+ export type JRCEllipsisProps = {
6
+ label?: string;
7
+ length: number;
8
+ };
9
+
10
+ export const JRCEllipsis = ({ label, length }: JRCEllipsisProps) => {
11
+ if (!label) return <></>;
12
+
13
+ const labelArr = [...label];
14
+
15
+ return (
16
+ <JRCConditionalWrapper
17
+ condition={labelArr.length > length}
18
+ wrapper={() => <JRCTooltip description={label}>{labelArr.slice(0, length).join('')}…</JRCTooltip>}>
19
+ <>{label}</>
20
+ </JRCConditionalWrapper>
21
+ );
22
+ };
@@ -5,6 +5,7 @@ import { Meta, Story } from '@storybook/react';
5
5
 
6
6
  export default {
7
7
  title: 'JRCHref',
8
+ component: JRCStyledHref,
8
9
  } as Meta;
9
10
 
10
11
  const Template: Story<JRCStyledHrefProps> = (args) => <JRCStyledHref {...args}>{args.children}</JRCStyledHref>;
@@ -14,6 +15,7 @@ Default.args = {
14
15
  children: 'Hello',
15
16
  href: '#',
16
17
  dataCy: 'link-to-the-main-content',
18
+ as: 'a',
17
19
  };
18
20
 
19
21
  const UserTemplate: Story<JRCLinkToProps> = (args) => <JRCLinkToUser {...args}>{args.children}</JRCLinkToUser>;
@@ -1,40 +1,67 @@
1
1
  import * as React from 'react';
2
2
  import styled from 'styled-components';
3
3
  import { DataCy } from '../../types/dataAttributes';
4
+ import { Button } from '../JRCButton/JRCButton';
5
+ import { BUTTON_CONFIG } from '../JRCButton/JRCButtonConfig';
4
6
 
5
7
  /**
6
8
  * Props type for JRCLinkToArticle and JRCLinkToUser
7
- * @member id number : id of the record
8
- * @member href string : href for href
9
- * @member children: enclosed React component
9
+ * @member idObject number : id of the record
10
10
  */
11
- export interface JRCLinkToProps extends JRCStyledHrefProps {
12
- idObject?: number;
13
- href?: string;
14
- children: React.ReactNode;
15
- }
11
+ export type JRCLinkToProps = DataCy &
12
+ React.ComponentPropsWithoutRef<'a'> & {
13
+ idObject?: number;
14
+ };
15
+
16
+ type AnchorProps = {
17
+ as?: 'a';
18
+ };
19
+
20
+ type ButtonProps = {
21
+ variant?: 'contained' | 'outlined';
22
+ color?: 'primary' | 'valid' | 'danger' | 'secondary';
23
+ float?: 'left' | 'right';
24
+ children?: React.ReactNode;
25
+ } & {
26
+ as: 'button';
27
+ };
16
28
 
17
29
  /**
18
30
  * Props type for JRCStyledHref
19
- * @see HTML.a default props
31
+ * @member as render the link styled as a button
20
32
  */
21
- export type JRCStyledHrefProps = DataCy & React.ComponentPropsWithoutRef<'a'>;
33
+ export type JRCStyledHrefProps = DataCy & React.ComponentPropsWithoutRef<'a'> & (AnchorProps | ButtonProps);
22
34
 
23
- export const JRCStyledHref = styled.a.attrs<JRCStyledHrefProps>(({ dataCy, ...props }) => ({
35
+ const Href = styled.a.attrs<DataCy>(({ dataCy, ...props }) => ({
24
36
  'data-cy': dataCy,
25
37
  ...props,
26
38
  }))`
27
39
  color: ${(props) => props.theme.font.hrefColor};
28
40
  `;
29
41
 
42
+ export const JRCStyledHref = (props: JRCStyledHrefProps) => {
43
+ if (props.as === 'button') {
44
+ const { color = 'primary', variant = 'contained', ...restProps } = props;
45
+ const themeButton = BUTTON_CONFIG[variant][color];
46
+
47
+ return (
48
+ <Button {...(restProps as any)} hasChildren={!!props.children} themeButton={themeButton} as="a">
49
+ {props.children}
50
+ </Button>
51
+ );
52
+ }
53
+
54
+ return <Href {...props} />;
55
+ };
56
+
30
57
  export const JRCLinkToArticle = ({ idObject, href, children, ...props }: JRCLinkToProps) => (
31
- <JRCStyledHref href={href ? href : `/article/${idObject}`} {...props}>
58
+ <Href href={href ? href : `/article/${idObject}`} {...props}>
32
59
  {children}
33
- </JRCStyledHref>
60
+ </Href>
34
61
  );
35
62
 
36
63
  export const JRCLinkToUser = ({ idObject, href, children, ...props }: JRCLinkToProps) => (
37
- <JRCStyledHref href={href ? href : `/user/${idObject}`} {...props}>
64
+ <Href href={href ? href : `/user/${idObject}`} {...props}>
38
65
  {children}
39
- </JRCStyledHref>
66
+ </Href>
40
67
  );
@@ -45,7 +45,7 @@ const Icon = styled.i<Required<JRCIconProps>>`
45
45
  `;
46
46
 
47
47
  const JRCIcon = ({ color = 'primary', variant = 'default', name, size = 20, className }: JRCIconProps) => (
48
- <Icon className={`react-icon ${name} ${className}`} name={name} size={size} color={color} variant={variant} />
48
+ <Icon className={`react-icon ${name} ${className || ''}`} name={name} size={size} color={color} variant={variant} />
49
49
  );
50
50
 
51
51
  export default JRCIcon;
@@ -81,10 +81,7 @@ const JRCIconButton = (props: JRCIconButtonProps & typeof defaultProps) => {
81
81
  return (
82
82
  <JRCConditionalWrapper
83
83
  condition={!!props.tooltip}
84
- // FIXME TYPESCRIPT-STRICT
85
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
86
- // @ts-ignore
87
- wrapper={(children) => <JRCTooltip {...props.tooltip}>{children}</JRCTooltip>}>
84
+ wrapper={(children) => <JRCTooltip {...(props.tooltip as JRCTooltipProps)}>{children}</JRCTooltip>}>
88
85
  <Button {...props} data-cy={props.dataCy} size={props.size}>
89
86
  <JRCIcon
90
87
  name={props.icon}
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
 
3
- import styled from 'styled-components';
3
+ import styled, { ThemeContext } from 'styled-components';
4
4
  import { formatImgUrl, ImgUrlProps } from './url.util';
5
5
 
6
6
  /**
@@ -43,9 +43,11 @@ const Img = styled.img`
43
43
  `;
44
44
 
45
45
  const JRCImg = (props: JRCImgProps) => {
46
+ const themeContext = React.useContext(ThemeContext);
47
+
46
48
  return (
47
49
  <Img
48
- src={formatImgUrl(props)}
50
+ src={formatImgUrl(props, themeContext.dpr)}
49
51
  loading={props.loading || 'lazy'}
50
52
  alt={props.alt}
51
53
  width={props.width}
@@ -46,7 +46,7 @@ export type ImgUrlProps = {
46
46
  };
47
47
 
48
48
  export function getFrom(arg: Pick<ImgUrlProps, 'from'>) {
49
- return removeIfStartsWith(suffixIfDoesNotExist(arg.from, '/'), '/');
49
+ return removeIfStartsWith(suffixIfDoesNotExist(arg.from, '/'), '/') || '';
50
50
  }
51
51
 
52
52
  export function getTimestamp(arg: Timestamp) {
@@ -58,9 +58,10 @@ export function getUri(arg: UriOrTypeId): string {
58
58
  return `${arg.type}/${arg.recordId}`;
59
59
  }
60
60
 
61
- export function getSize(arg: Size & WidthHeight): string {
61
+ export function getSize(arg: Size & WidthHeight, dpr = 2): string {
62
62
  if ('size' in arg && arg.size) return arg.size;
63
- else if ('width' in arg && arg.width && 'height' in arg && arg.height) return `${arg.width * 2}x${arg.height * 2}`;
63
+ else if ('width' in arg && arg.width && 'height' in arg && arg.height)
64
+ return `${arg.width * dpr}x${arg.height * dpr}`;
64
65
  return '';
65
66
  }
66
67
 
@@ -76,7 +77,7 @@ export function getUrl(url: string): URL | undefined {
76
77
  }
77
78
 
78
79
  try {
79
- return new URL(window.location.origin + url);
80
+ return new URL(window.location.origin + '/' + removeIfStartsWith(url, '/'));
80
81
  } catch (e) {
81
82
  /* silent exception */
82
83
  }
@@ -95,7 +96,7 @@ export function getUrl(url: string): URL | undefined {
95
96
  * The final url is {from}/{size}/{uri}.{format}?_={timestamp}
96
97
  * @param arg
97
98
  */
98
- export function formatImgUrl(arg: ImgUrlProps) {
99
+ export function formatImgUrl(arg: ImgUrlProps, dpr = 2) {
99
100
  if ('url' in arg && arg.url) {
100
101
  const url = getUrl(arg.url);
101
102
  if (url) {
@@ -104,5 +105,5 @@ export function formatImgUrl(arg: ImgUrlProps) {
104
105
  return url.toString();
105
106
  }
106
107
  }
107
- return `/${getFrom(arg)}${getSize(arg)}/${getUri(arg)}.${getFormat(arg)}${getTimestamp(arg)}`;
108
+ return `/${getFrom(arg)}${getSize(arg, dpr)}/${getUri(arg)}.${getFormat(arg)}${getTimestamp(arg)}`;
108
109
  }
@@ -9,6 +9,7 @@ describe('Test utils.url', () => {
9
9
  // @ts-ignore
10
10
  window.location = { origin: 'https://website.com' };
11
11
  });
12
+
12
13
  test('formatImgUrl url', () => {
13
14
  expect(formatImgUrl({ url: '/url/random' })).toStrictEqual('https://website.com/url/random');
14
15
  expect(formatImgUrl({ url: '/url/random?_=12345' })).toStrictEqual('https://website.com/url/random?_=12345');
@@ -24,18 +25,23 @@ describe('Test utils.url', () => {
24
25
  expect(formatImgUrl({ url: 'https://otherwebsite.com/url/random?_=12345', timestamp: 123 })).toStrictEqual(
25
26
  'https://otherwebsite.com/url/random?_=12345',
26
27
  );
28
+ expect(formatImgUrl({ url: 'img/react/bookmark-noresult.png', height: 450 })).toStrictEqual(
29
+ 'https://website.com/img/react/bookmark-noresult.png',
30
+ );
27
31
  });
28
32
 
29
33
  test('getFrom', () => {
30
- expect(getFrom({})).toStrictEqual(undefined);
34
+ expect(getFrom({})).toStrictEqual('');
31
35
  expect(getFrom({ from: 'imagestatic' })).toStrictEqual('imagestatic/');
32
36
  expect(getFrom({ from: 'imagestatic/' })).toStrictEqual('imagestatic/');
33
37
  expect(getFrom({ from: '/imagestatic/' })).toStrictEqual('imagestatic/');
38
+ expect(getFrom({ from: undefined })).toStrictEqual('');
34
39
  });
35
40
 
36
41
  test('getSize', () => {
37
42
  expect(getSize({ size: '32' })).toStrictEqual('32');
38
43
  expect(getSize({ width: 32, height: 32 })).toStrictEqual('64x64');
44
+ expect(getSize({ width: 32, height: 32 }, 3)).toStrictEqual('96x96');
39
45
  });
40
46
 
41
47
  test('getUri', () => {