jamespot-react-components 1.0.3 → 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.
- package/build/jamespot-react-components.js +208 -192
- package/build/jamespot-react-components.js.map +1 -1
- package/build/src/components/Common/JRCConditionalWrapper.d.ts +4 -4
- package/build/src/components/Form/Input/JRCInputCheckbox/JRCInputCheckbox.d.ts +5 -2
- package/build/src/components/Form/Input/JRCInputDate/JRCInputDate.d.ts +11 -0
- package/build/src/components/Form/Input/JRCInputDate/JRCInputDate.stories.d.ts +5 -0
- package/build/src/components/Form/Input/JRCInputText/JRCInputText.d.ts +1 -1
- package/build/src/components/Form/Input/JRCInputTextarea/JRCInputTextarea.d.ts +15 -0
- package/build/src/components/Form/Input/JRCInputTextarea/JRCInputTextarea.stories.d.ts +5 -0
- package/build/src/components/JRCButton/JRCButton.d.ts +25 -0
- package/build/src/components/JRCButton/JRCButtonConfig.d.ts +1 -2
- package/build/src/components/JRCEllipsis/JRCEllipsis.d.ts +5 -0
- package/build/src/components/JRCEllipsis/JRCEllipsis.stories.d.ts +5 -0
- package/build/src/components/JRCHref/JRCHref.d.ts +18 -10
- package/build/src/components/JRCImg/url.util.d.ts +3 -3
- package/build/src/components/JRCList/JRCList.d.ts +2 -0
- package/build/src/components/JRCModal/JRCModal.d.ts +5 -1
- package/build/src/components/JRCModal/JRCModal.styles.d.ts +0 -1
- package/build/src/components/JRCTag/JRCTag.d.ts +2 -0
- package/build/src/hooks/UseDidMountEffect.d.ts +1 -2
- package/build/src/index.d.ts +8 -5
- package/build/src/styles/theme.d.ts +1 -0
- package/build/src/types.d.ts +4 -0
- package/externals.json +2 -1
- package/package.json +2 -2
- package/src/components/Common/JRCConditionalWrapper.tsx +6 -13
- package/src/components/Form/Input/JRCFormColor/JRCFormColor.tsx +12 -12
- package/src/components/Form/Input/JRCFormEmail/JRCInputEmail.tsx +4 -8
- package/src/components/Form/Input/JRCFormSelect/JRCFormSelect.tsx +5 -1
- package/src/components/Form/Input/JRCFormSelect/JRCFormSelectTag.tsx +3 -1
- package/src/components/Form/Input/JRCInputCheckbox/JRCInputCheckbox.stories.tsx +1 -4
- package/src/components/Form/Input/JRCInputCheckbox/JRCInputCheckbox.tsx +12 -10
- package/src/components/Form/Input/JRCInputDate/JRCInputDate.stories.tsx +50 -0
- package/src/components/Form/Input/JRCInputDate/JRCInputDate.tsx +26 -0
- package/src/components/Form/Input/JRCInputText/JRCInputText.tsx +2 -2
- package/src/components/Form/Input/JRCInputTextarea/JRCInputTextarea.stories.tsx +52 -0
- package/src/components/Form/Input/JRCInputTextarea/JRCInputTextarea.tsx +36 -0
- package/src/components/Form/Input/JRCSelect/JRCInputSelect.tsx +1 -1
- package/src/components/JRCButton/JRCButton.stories.tsx +1 -1
- package/src/components/JRCButton/JRCButton.tsx +9 -3
- package/src/components/JRCButton/JRCButtonConfig.tsx +1 -1
- package/src/components/JRCButton/JRCValidationButton.tsx +10 -4
- package/src/components/JRCButtonDropdown/JRCButtonDropdown.tsx +2 -2
- package/src/components/JRCEllipsis/JRCEllipsis.stories.tsx +18 -0
- package/src/components/JRCEllipsis/JRCEllipsis.tsx +22 -0
- package/src/components/JRCHref/JRCHref.stories.tsx +2 -0
- package/src/components/JRCHref/JRCHref.tsx +42 -15
- package/src/components/JRCIcon/JRCIcon.tsx +1 -1
- package/src/components/JRCIconButton/JRCIconButton.tsx +1 -4
- package/src/components/JRCImg/JRCImg.tsx +4 -2
- package/src/components/JRCImg/url.util.ts +7 -6
- package/src/components/JRCImg/url.utils.test.ts +7 -1
- package/src/components/JRCList/JRCList.styles.tsx +16 -2
- package/src/components/JRCList/JRCList.tsx +5 -5
- package/src/components/JRCList/JRCListMockData.stories.tsx +1 -1
- package/src/components/JRCModal/JRCModal.styles.tsx +0 -6
- package/src/components/JRCModal/JRCModal.tsx +77 -62
- package/src/components/JRCTag/JRCTag.tsx +29 -9
- package/src/components/JRCThemeProvider/animation.css +19 -0
- package/src/components/JRCThemeProvider/gabarit.css +4 -0
- package/src/components/JRCTooltip/JRCTooltip.tsx +5 -2
- package/src/hooks/UseDidMountEffect.tsx +1 -3
- package/src/index.tsx +9 -5
- package/src/styles/theme.tsx +3 -2
- package/src/translation/lang.json +3 -2
- package/src/types.ts +4 -0
- package/src/variables.scss +0 -67
|
@@ -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
|
|
7
|
+
export type JRCInputTextProps = DataCy & React.ComponentPropsWithoutRef<'input'>;
|
|
8
8
|
|
|
9
|
-
const RenderInput = React.forwardRef((props:
|
|
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
|
|
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
|
() =>
|
|
@@ -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
|
|
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
|
-
|
|
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 =
|
|
102
|
+
const themeButton = BUTTON_CONFIG[variant][disabled ? 'disabled' : color];
|
|
97
103
|
|
|
98
104
|
return (
|
|
99
105
|
<Button
|
|
@@ -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(
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
* @
|
|
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
|
-
|
|
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
|
-
<
|
|
58
|
+
<Href href={href ? href : `/article/${idObject}`} {...props}>
|
|
32
59
|
{children}
|
|
33
|
-
</
|
|
60
|
+
</Href>
|
|
34
61
|
);
|
|
35
62
|
|
|
36
63
|
export const JRCLinkToUser = ({ idObject, href, children, ...props }: JRCLinkToProps) => (
|
|
37
|
-
<
|
|
64
|
+
<Href href={href ? href : `/user/${idObject}`} {...props}>
|
|
38
65
|
{children}
|
|
39
|
-
</
|
|
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
|
-
|
|
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)
|
|
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(
|
|
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', () => {
|
|
@@ -4,6 +4,7 @@ import { DndProvider, useDrag, useDrop } from 'react-dnd';
|
|
|
4
4
|
import { HTML5Backend } from 'react-dnd-html5-backend';
|
|
5
5
|
import { JRCIconButton } from '../JRCIconButton/JRCIconButton';
|
|
6
6
|
import { JRCConditionalWrapper } from '../Common/JRCConditionalWrapper';
|
|
7
|
+
import { useIntl } from 'react-intl';
|
|
7
8
|
import { DropTargetMonitor } from 'react-dnd/dist/types/types';
|
|
8
9
|
|
|
9
10
|
const TableCss = css`
|
|
@@ -19,13 +20,20 @@ export const Table = styled.table`
|
|
|
19
20
|
border-collapse: collapse;
|
|
20
21
|
`;
|
|
21
22
|
|
|
22
|
-
export const Thead = styled.thead
|
|
23
|
+
export const Thead = styled.thead`
|
|
24
|
+
position: sticky;
|
|
25
|
+
top: 0;
|
|
26
|
+
background-color: white;
|
|
27
|
+
border-bottom: 2px solid ${(props) => props.theme.color.grey2};
|
|
28
|
+
z-index: 1;
|
|
29
|
+
`;
|
|
23
30
|
|
|
24
31
|
export const TBody = styled.tbody``;
|
|
25
32
|
|
|
26
33
|
export const Th = styled.th<{ width?: number | string }>`
|
|
27
34
|
${TableCss};
|
|
28
35
|
font-size: 12px;
|
|
36
|
+
padding: 0 4px;
|
|
29
37
|
font-weight: normal;
|
|
30
38
|
${(props) => props.width && props.width > 0 && `width: ${props.width}px`};
|
|
31
39
|
`;
|
|
@@ -75,6 +83,7 @@ export const Tr = styled.tr<{ dragging?: boolean }>`
|
|
|
75
83
|
|
|
76
84
|
export const Td = styled.td`
|
|
77
85
|
${TableCss};
|
|
86
|
+
padding: 0 4px;
|
|
78
87
|
${(props) => props.width && `width: ${props.width}`};
|
|
79
88
|
`;
|
|
80
89
|
|
|
@@ -137,6 +146,8 @@ export const DraggableTBody = ({ draggable, children }: DraggableTBodyProps) =>
|
|
|
137
146
|
const DND_ITEM_TYPE = 'row';
|
|
138
147
|
|
|
139
148
|
export const DraggableTr = ({ index, onDrag, onDrop, draggable, children }: DraggableTrProps) => {
|
|
149
|
+
const intl = useIntl();
|
|
150
|
+
|
|
140
151
|
const dropRef = React.useRef<HTMLTableRowElement>(null);
|
|
141
152
|
const dragRef = React.useRef<HTMLTableCellElement>(null);
|
|
142
153
|
let dragging = false;
|
|
@@ -199,7 +210,10 @@ export const DraggableTr = ({ index, onDrag, onDrop, draggable, children }: Drag
|
|
|
199
210
|
<Tr ref={dropRef} dragging={dragging}>
|
|
200
211
|
{draggable && (
|
|
201
212
|
<DraggableTd ref={dragRef}>
|
|
202
|
-
<JRCIconButton
|
|
213
|
+
<JRCIconButton
|
|
214
|
+
icon="icon-fs-dw"
|
|
215
|
+
tooltip={{ description: intl.formatMessage({ id: 'GLOBAL_Move' }), position: 'right' }}
|
|
216
|
+
/>
|
|
203
217
|
|
|
204
218
|
</DraggableTd>
|
|
205
219
|
)}
|