@bodynarf/react.components 0.1.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.
- package/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
- package/dist/assets/bootstrap-icons.48902042.woff +0 -0
- package/dist/assets/bootstrap-icons.a29357cb.woff2 +0 -0
- package/dist/assets/index.19ed8c21.css +1 -0
- package/dist/assets/index.517219b4.js +2 -0
- package/dist/assets/index.517219b4.js.map +1 -0
- package/dist/assets/vendor.92043335.js +41 -0
- package/dist/assets/vendor.92043335.js.map +1 -0
- package/dist/index.html +16 -0
- package/index.html +13 -0
- package/package.json +44 -0
- package/src/components/anchor/anchor.scss +15 -0
- package/src/components/anchor/components/anchorWithIcon/anchorWithIcon.tsx +45 -0
- package/src/components/anchor/components/simpleAnchor/simpleAnchor.tsx +16 -0
- package/src/components/anchor/index.tsx +64 -0
- package/src/components/anchor/types.ts +26 -0
- package/src/components/button/button.scss +12 -0
- package/src/components/button/components/buttonWithIcon/buttonWithIcon.tsx +47 -0
- package/src/components/button/components/simpleButton/simpleButton.tsx +16 -0
- package/src/components/button/index.tsx +83 -0
- package/src/components/button/types.ts +40 -0
- package/src/components/dropdown/components/dropdownItem/dropdownItem.tsx +30 -0
- package/src/components/dropdown/components/dropdownLabel/dropdownLabel.tsx +60 -0
- package/src/components/dropdown/dropdown.scss +89 -0
- package/src/components/dropdown/index.tsx +141 -0
- package/src/components/dropdown/types.ts +11 -0
- package/src/components/icon/icon.scss +18 -0
- package/src/components/icon/index.tsx +34 -0
- package/src/components/primitives/date/index.tsx +103 -0
- package/src/components/primitives/multiline/components/multilineWithLabel/index.tsx +93 -0
- package/src/components/primitives/multiline/components/multilineWithoutLabel/index.tsx +51 -0
- package/src/components/primitives/multiline/index.tsx +28 -0
- package/src/components/primitives/text/components/textWithLabel/index.tsx +92 -0
- package/src/components/primitives/text/components/textWithoutLabel/index.tsx +49 -0
- package/src/components/primitives/text/index.tsx +21 -0
- package/src/components/primitives/types.ts +65 -0
- package/src/components/search/index.tsx +127 -0
- package/src/components/search/search.scss +24 -0
- package/src/components/types.ts +41 -0
- package/src/hooks/useComponentOutsideClick.ts +48 -0
- package/src/main.tsx +9 -0
- package/tsconfig.json +48 -0
- package/tsconfig.node.json +8 -0
- package/vite.config.ts +14 -0
package/dist/index.html
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<base href="/"/>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
|
+
<title>bodynarf/react-components</title>
|
|
8
|
+
<script type="module" crossorigin src="/assets/index.517219b4.js"></script>
|
|
9
|
+
<link rel="modulepreload" href="/assets/vendor.92043335.js">
|
|
10
|
+
<link rel="stylesheet" href="/assets/index.19ed8c21.css">
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
<div id="root"></div>
|
|
14
|
+
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
package/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<base href="/"/>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
|
7
|
+
<title>bodynarf/react-components</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="root"></div>
|
|
11
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bodynarf/react.components",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"author": {
|
|
5
|
+
"name": "Artem",
|
|
6
|
+
"email": "bodynar@gmail.com"
|
|
7
|
+
},
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/bodynar/bodynarf.react-components",
|
|
10
|
+
"type": "git"
|
|
11
|
+
},
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/bodynar/bodynarf.react-components/issues",
|
|
14
|
+
"email": "bodynar@gmail.com"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"ts",
|
|
18
|
+
"typescript",
|
|
19
|
+
"react",
|
|
20
|
+
"react component",
|
|
21
|
+
"bulma"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"dev": "vite",
|
|
25
|
+
"build": "tsc && vite build",
|
|
26
|
+
"preview": "vite preview"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"bootstrap-icons": "^1.8.1",
|
|
30
|
+
"react": "^18.0.0",
|
|
31
|
+
"react-dom": "^18.0.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@bodynarf/utils": "^1.0.0",
|
|
35
|
+
"@types/node": "^17.0.25",
|
|
36
|
+
"@types/react": "^18.0.5",
|
|
37
|
+
"@types/react-dom": "^18.0.1",
|
|
38
|
+
"@vitejs/plugin-react": "^1.3.0",
|
|
39
|
+
"bulma": "^0.9.3",
|
|
40
|
+
"sass": "^1.50.1",
|
|
41
|
+
"typescript": "^4.6.3",
|
|
42
|
+
"vite": "^2.9.2"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { isNullOrEmpty } from '@bodynarf/utils/common';
|
|
2
|
+
|
|
3
|
+
import Icon from '@app/components/icon';
|
|
4
|
+
|
|
5
|
+
import { AnchorWithIconProps } from "../../types";
|
|
6
|
+
|
|
7
|
+
/** Anchor with icon component */
|
|
8
|
+
export const AnchorWithIcon = ({ href, className, onClick, caption, title, target, icon }: AnchorWithIconProps): JSX.Element => {
|
|
9
|
+
const iconPosition = icon.position || 'left';
|
|
10
|
+
|
|
11
|
+
const iconClassName: string = isNullOrEmpty(caption)
|
|
12
|
+
? icon.className
|
|
13
|
+
: iconPosition === 'left'
|
|
14
|
+
? `${icon.className} app-icon--left`
|
|
15
|
+
: `${icon.className} app-icon--right`;
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
if (iconPosition === 'left') {
|
|
19
|
+
return (
|
|
20
|
+
<a
|
|
21
|
+
href={href}
|
|
22
|
+
className={className}
|
|
23
|
+
title={title}
|
|
24
|
+
target={target}
|
|
25
|
+
onClick={onClick}
|
|
26
|
+
>
|
|
27
|
+
<Icon {...icon} className={iconClassName} />
|
|
28
|
+
{caption}
|
|
29
|
+
</a>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<a
|
|
35
|
+
href={href}
|
|
36
|
+
className={className}
|
|
37
|
+
title={title}
|
|
38
|
+
target={target}
|
|
39
|
+
onClick={onClick}
|
|
40
|
+
>
|
|
41
|
+
{caption}
|
|
42
|
+
<Icon {...icon} className={iconClassName} />
|
|
43
|
+
</a>
|
|
44
|
+
);
|
|
45
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SimpleAnchorProps } from "../../types";
|
|
2
|
+
|
|
3
|
+
/** Simple anchor component, without icon */
|
|
4
|
+
export const SimpleAnchor = ({ href, className, onClick, caption, title, target }: SimpleAnchorProps): JSX.Element => {
|
|
5
|
+
return (
|
|
6
|
+
<a
|
|
7
|
+
className={className}
|
|
8
|
+
href={href}
|
|
9
|
+
title={title}
|
|
10
|
+
target={target}
|
|
11
|
+
onClick={onClick}
|
|
12
|
+
>
|
|
13
|
+
{caption}
|
|
14
|
+
</a>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { isNullOrEmpty, isNullOrUndefined } from '@bodynarf/utils/common';
|
|
2
|
+
|
|
3
|
+
import './anchor.scss';
|
|
4
|
+
|
|
5
|
+
import { ElementIcon } from '../types';
|
|
6
|
+
|
|
7
|
+
import { SimpleAnchor } from './components/simpleAnchor/simpleAnchor';
|
|
8
|
+
import { AnchorWithIcon } from './components/anchorWithIcon/anchorWithIcon';
|
|
9
|
+
|
|
10
|
+
export type AnchorProps = {
|
|
11
|
+
/** Link destination */
|
|
12
|
+
href?: string;
|
|
13
|
+
|
|
14
|
+
/** Link caption */
|
|
15
|
+
caption?: string;
|
|
16
|
+
|
|
17
|
+
/** Click handler */
|
|
18
|
+
onClick?: () => void;
|
|
19
|
+
|
|
20
|
+
/** Configuration od inner icon */
|
|
21
|
+
icon?: ElementIcon;
|
|
22
|
+
|
|
23
|
+
/** Title of anchor */
|
|
24
|
+
title?: string;
|
|
25
|
+
|
|
26
|
+
/** Where to open the linked document */
|
|
27
|
+
target?: '_blank' | '_top';
|
|
28
|
+
|
|
29
|
+
/** Additional class names */
|
|
30
|
+
className?: string;
|
|
31
|
+
|
|
32
|
+
/** Should css hovering effects be disabled */
|
|
33
|
+
disableHovering?: boolean;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/** Anchor component */
|
|
37
|
+
export default function Anchor(props: AnchorProps): JSX.Element {
|
|
38
|
+
if (isNullOrUndefined(props.caption) && isNullOrUndefined(props.icon)) {
|
|
39
|
+
throw new Error("No anchor content provided");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const className: string = 'app-anchor'
|
|
43
|
+
+ (!isNullOrEmpty(props.className) ? ` ${props.className}` : '')
|
|
44
|
+
+ (props.disableHovering === true ? ' app-anchor--unhoverable' : '');
|
|
45
|
+
|
|
46
|
+
if (isNullOrUndefined(props.icon)) {
|
|
47
|
+
return (
|
|
48
|
+
<SimpleAnchor
|
|
49
|
+
{...props}
|
|
50
|
+
className={className}
|
|
51
|
+
onClick={props.onClick}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<AnchorWithIcon
|
|
58
|
+
{...props}
|
|
59
|
+
className={className}
|
|
60
|
+
onClick={props.onClick}
|
|
61
|
+
icon={props.icon as ElementIcon}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ElementIcon } from "../types";
|
|
2
|
+
|
|
3
|
+
export type SimpleAnchorProps = {
|
|
4
|
+
/** Link destination */
|
|
5
|
+
href?: string;
|
|
6
|
+
|
|
7
|
+
/** Class names */
|
|
8
|
+
className: string;
|
|
9
|
+
|
|
10
|
+
/** Click handler */
|
|
11
|
+
onClick?: () => void;
|
|
12
|
+
|
|
13
|
+
/** Link caption */
|
|
14
|
+
caption?: string;
|
|
15
|
+
|
|
16
|
+
/** Title of anchor */
|
|
17
|
+
title?: string;
|
|
18
|
+
|
|
19
|
+
/** Where to open the linked document */
|
|
20
|
+
target?: '_blank' | '_top';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type AnchorWithIconProps = SimpleAnchorProps & {
|
|
24
|
+
/** Configuration of icon */
|
|
25
|
+
icon: ElementIcon;
|
|
26
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
|
|
2
|
+
import { isNullOrEmpty } from '@bodynarf/utils/common';
|
|
3
|
+
|
|
4
|
+
import Icon from '@app/components/icon';
|
|
5
|
+
|
|
6
|
+
import { ButtonWithIconProps } from '../../types';
|
|
7
|
+
|
|
8
|
+
/** Button with icon component */
|
|
9
|
+
export const ButtonWithIcon = ({ className, disabled, onClick, caption, title, icon }: ButtonWithIconProps): JSX.Element => {
|
|
10
|
+
const iconPosition = icon.position || 'left';
|
|
11
|
+
|
|
12
|
+
const iconClassName: string = isNullOrEmpty(caption)
|
|
13
|
+
? icon.className
|
|
14
|
+
: iconPosition === 'left'
|
|
15
|
+
? `${icon.className} app-icon--left`
|
|
16
|
+
: `${icon.className} app-icon--right`;
|
|
17
|
+
|
|
18
|
+
className = isNullOrEmpty(caption)
|
|
19
|
+
? `${className} button--icon-only`
|
|
20
|
+
: className;
|
|
21
|
+
|
|
22
|
+
if (iconPosition === 'left') {
|
|
23
|
+
return (
|
|
24
|
+
<button
|
|
25
|
+
className={className}
|
|
26
|
+
disabled={disabled}
|
|
27
|
+
onClick={onClick}
|
|
28
|
+
title={title}
|
|
29
|
+
>
|
|
30
|
+
<Icon {...icon} className={iconClassName} />
|
|
31
|
+
{caption}
|
|
32
|
+
</button>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<button
|
|
38
|
+
className={className}
|
|
39
|
+
disabled={disabled}
|
|
40
|
+
onClick={onClick}
|
|
41
|
+
title={title}
|
|
42
|
+
>
|
|
43
|
+
{caption}
|
|
44
|
+
<Icon {...icon} className={iconClassName} />
|
|
45
|
+
</button>
|
|
46
|
+
);
|
|
47
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
import { SimpleButtonProps } from '../../types';
|
|
3
|
+
|
|
4
|
+
/** Simple button component, without icon */
|
|
5
|
+
export const SimpleButton = ({ className, disabled, onClick, caption, title }: SimpleButtonProps): JSX.Element => {
|
|
6
|
+
return (
|
|
7
|
+
<button
|
|
8
|
+
className={className}
|
|
9
|
+
disabled={disabled}
|
|
10
|
+
onClick={onClick}
|
|
11
|
+
title={title}
|
|
12
|
+
>
|
|
13
|
+
{caption}
|
|
14
|
+
</button>
|
|
15
|
+
);
|
|
16
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { isNullOrEmpty, isNullOrUndefined, isStringEmpty } from '@bodynarf/utils/common';
|
|
2
|
+
|
|
3
|
+
import './button.scss';
|
|
4
|
+
|
|
5
|
+
import { ElementIcon, IconSize } from '../types';
|
|
6
|
+
|
|
7
|
+
import { ButtonType } from './types';
|
|
8
|
+
import { ButtonWithIcon } from './components/buttonWithIcon/buttonWithIcon';
|
|
9
|
+
import { SimpleButton } from './components/simpleButton/simpleButton';
|
|
10
|
+
|
|
11
|
+
type ButtonProps = {
|
|
12
|
+
/** Button displaying text */
|
|
13
|
+
caption?: string;
|
|
14
|
+
|
|
15
|
+
/** Type of button (color) */
|
|
16
|
+
type: ButtonType;
|
|
17
|
+
|
|
18
|
+
/** Configuration of inner icon */
|
|
19
|
+
icon?: ElementIcon;
|
|
20
|
+
|
|
21
|
+
/** Button size */
|
|
22
|
+
size?: IconSize; // TODO: fix this type using
|
|
23
|
+
|
|
24
|
+
/** Title on hover */
|
|
25
|
+
title?: string;
|
|
26
|
+
|
|
27
|
+
/** Is button uses light version of color */
|
|
28
|
+
light?: boolean;
|
|
29
|
+
|
|
30
|
+
/** Is button outlined */
|
|
31
|
+
outlined?: boolean;
|
|
32
|
+
|
|
33
|
+
/** Should button corners be rounded */
|
|
34
|
+
rounded?: boolean;
|
|
35
|
+
|
|
36
|
+
/** Display loading icon */
|
|
37
|
+
isLoading?: boolean;
|
|
38
|
+
|
|
39
|
+
/** Is button disabled */
|
|
40
|
+
disabled?: boolean;
|
|
41
|
+
|
|
42
|
+
/** Click action handler */
|
|
43
|
+
onClick?: () => void;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Button component
|
|
48
|
+
* @throws Caption is not defined and icon configuration is not defined at the same time
|
|
49
|
+
*/
|
|
50
|
+
export default function Button(props: ButtonProps): JSX.Element {
|
|
51
|
+
if ((isNullOrEmpty(props.caption))
|
|
52
|
+
&& (isNullOrUndefined(props.icon) || isStringEmpty(props.icon?.className as string))
|
|
53
|
+
) {
|
|
54
|
+
throw new Error("No button content provided.");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const className: string =
|
|
58
|
+
`button is-${props.type}`
|
|
59
|
+
+ (props.light === true ? ' is-light' : '')
|
|
60
|
+
+ (!isNullOrUndefined(props.size) ? ` is-${props.size}` : '')
|
|
61
|
+
+ (props.outlined === true ? ' is-outlined' : '')
|
|
62
|
+
+ (props.rounded === true ? ' is-rounded' : '')
|
|
63
|
+
+ (props.isLoading === true ? ' is-loading' : '');
|
|
64
|
+
|
|
65
|
+
if (!isNullOrUndefined(props.icon)) {
|
|
66
|
+
return (
|
|
67
|
+
<ButtonWithIcon
|
|
68
|
+
{...props}
|
|
69
|
+
className={className}
|
|
70
|
+
onClick={props.onClick}
|
|
71
|
+
icon={props.icon as ElementIcon}
|
|
72
|
+
/>
|
|
73
|
+
);
|
|
74
|
+
} else {
|
|
75
|
+
return (
|
|
76
|
+
<SimpleButton
|
|
77
|
+
{...props}
|
|
78
|
+
className={className}
|
|
79
|
+
onClick={props.onClick}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ElementIcon } from "../types";
|
|
2
|
+
|
|
3
|
+
/** Button types according to Bulma framework */
|
|
4
|
+
export type ButtonType =
|
|
5
|
+
'default' /** color: transparent */
|
|
6
|
+
| 'primary' /** color: seawave green */
|
|
7
|
+
| 'link' /** color: blue-violet */
|
|
8
|
+
| 'info' /** color: sky-blue */
|
|
9
|
+
| 'success' /** color: green */
|
|
10
|
+
| 'warning' /** color: yellow */
|
|
11
|
+
| 'danger' /** color: red */
|
|
12
|
+
| 'white' /** color: white */
|
|
13
|
+
| 'light' /** color: light-gray */
|
|
14
|
+
| 'dark' /** color: dark-gray */
|
|
15
|
+
| 'black' /** color: black */
|
|
16
|
+
| 'text' /** Underline text with color: gray */
|
|
17
|
+
| 'ghost' /** Blue underline text with color: transparent */
|
|
18
|
+
;
|
|
19
|
+
|
|
20
|
+
export type SimpleButtonProps = {
|
|
21
|
+
/** Button class name*/
|
|
22
|
+
className: string;
|
|
23
|
+
|
|
24
|
+
/** Button click handler */
|
|
25
|
+
onClick?: () => void;
|
|
26
|
+
|
|
27
|
+
/** Button caption */
|
|
28
|
+
caption?: string;
|
|
29
|
+
|
|
30
|
+
/** Disabled attribute value*/
|
|
31
|
+
disabled?: boolean;
|
|
32
|
+
|
|
33
|
+
/** Title on hover */
|
|
34
|
+
title?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type ButtonWithIconProps = SimpleButtonProps & {
|
|
38
|
+
/** Icon configuration */
|
|
39
|
+
icon: ElementIcon;
|
|
40
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { SelectableItem } from "../../types";
|
|
2
|
+
|
|
3
|
+
/** Dropdown item props */
|
|
4
|
+
interface DropdownItemProps {
|
|
5
|
+
/** Item to present in dropdown */
|
|
6
|
+
item: SelectableItem;
|
|
7
|
+
|
|
8
|
+
/** Is item selected*/
|
|
9
|
+
selected: boolean;
|
|
10
|
+
|
|
11
|
+
/** Item click handler */
|
|
12
|
+
onClick: (event: React.MouseEvent<HTMLLIElement>) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Single item in dropdown component */
|
|
16
|
+
const DropdownItem = ({ item, selected, onClick }: DropdownItemProps): JSX.Element => {
|
|
17
|
+
return (
|
|
18
|
+
<li
|
|
19
|
+
key={item.id}
|
|
20
|
+
className={`app-dropdown-item dropdown-item${selected ? " is-active" : ""}`}
|
|
21
|
+
onClick={onClick}
|
|
22
|
+
data-dropdown-item-value={item.value}
|
|
23
|
+
title={item.displayValue}
|
|
24
|
+
>
|
|
25
|
+
{item.displayValue}
|
|
26
|
+
</li>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export default DropdownItem;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { MouseEvent } from 'react';
|
|
2
|
+
|
|
3
|
+
import { isNullOrEmpty, isNullOrUndefined } from "@bodynarf/utils/common";
|
|
4
|
+
|
|
5
|
+
import Icon from '@app/components/icon';
|
|
6
|
+
|
|
7
|
+
import { SelectableItem } from "../../types";
|
|
8
|
+
|
|
9
|
+
interface DropdownLabelProps {
|
|
10
|
+
/** Caption when no items selected */
|
|
11
|
+
caption: string;
|
|
12
|
+
|
|
13
|
+
/** Can user deselect */
|
|
14
|
+
deselectable: boolean;
|
|
15
|
+
|
|
16
|
+
/** Selected item */
|
|
17
|
+
selectedItem?: SelectableItem;
|
|
18
|
+
|
|
19
|
+
/** Click handler*/
|
|
20
|
+
onClick: (event: MouseEvent<HTMLLabelElement>) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Label component */
|
|
24
|
+
const DropdownLabel = ({ caption, selectedItem, onClick, deselectable }: DropdownLabelProps): JSX.Element => {
|
|
25
|
+
const itemSelected = !isNullOrUndefined(selectedItem);
|
|
26
|
+
|
|
27
|
+
const text = itemSelected
|
|
28
|
+
? selectedItem?.displayValue
|
|
29
|
+
: caption;
|
|
30
|
+
|
|
31
|
+
const deselectVisible = deselectable && itemSelected;
|
|
32
|
+
|
|
33
|
+
const className = [
|
|
34
|
+
"dropdown-trigger",
|
|
35
|
+
"app-dropdown__label",
|
|
36
|
+
itemSelected ? "" : "app-dropdown__label--default",
|
|
37
|
+
"button"
|
|
38
|
+
].filter(x => !isNullOrEmpty(x))
|
|
39
|
+
.join(" ");
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<label
|
|
43
|
+
className={className}
|
|
44
|
+
onClick={onClick}
|
|
45
|
+
>
|
|
46
|
+
{deselectVisible &&
|
|
47
|
+
<Icon className="plus-lg" />
|
|
48
|
+
}
|
|
49
|
+
<span
|
|
50
|
+
className={deselectVisible ? "mx-2" : "mr-2"}
|
|
51
|
+
title={itemSelected ? text : undefined}
|
|
52
|
+
>
|
|
53
|
+
{text}
|
|
54
|
+
</span>
|
|
55
|
+
<Icon className="arrow-up" />
|
|
56
|
+
</label>
|
|
57
|
+
);
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default DropdownLabel;
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
.app-dropdown {
|
|
2
|
+
min-width: 7.5rem;
|
|
3
|
+
width: fit-content;
|
|
4
|
+
|
|
5
|
+
&__list {
|
|
6
|
+
position: absolute;
|
|
7
|
+
|
|
8
|
+
opacity: 0;
|
|
9
|
+
visibility: collapse;
|
|
10
|
+
|
|
11
|
+
transition: 0.15s ease-in-out opacity;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
& .dropdown-menu {
|
|
15
|
+
min-width: 11.75rem;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
span.dropdown-item {
|
|
19
|
+
color: gray;
|
|
20
|
+
text-align: center;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
&__label {
|
|
24
|
+
cursor: pointer;
|
|
25
|
+
user-select: none;
|
|
26
|
+
display: flex;
|
|
27
|
+
justify-content: space-between;
|
|
28
|
+
|
|
29
|
+
span {
|
|
30
|
+
white-space: nowrap;
|
|
31
|
+
text-overflow: ellipsis;
|
|
32
|
+
overflow-x: hidden;
|
|
33
|
+
max-width: 15rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
&--default span {
|
|
37
|
+
color: gray;
|
|
38
|
+
font-style: italic;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.app-icon {
|
|
42
|
+
&:hover {
|
|
43
|
+
color: #0d6efd;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
&.bi-arrow-up::before {
|
|
47
|
+
transition: 0.25s ease-in-out transform;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&.bi-plus-lg::before {
|
|
51
|
+
transform: rotate(45deg);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&-item {
|
|
57
|
+
cursor: pointer;
|
|
58
|
+
user-select: none;
|
|
59
|
+
white-space: normal;
|
|
60
|
+
|
|
61
|
+
transition: 0.25s ease-in-out;
|
|
62
|
+
transition-property: color, background-color;
|
|
63
|
+
|
|
64
|
+
&:hover {
|
|
65
|
+
background-color: rgba(0, 0, 0, 0.05);
|
|
66
|
+
color: #0a0a0a;
|
|
67
|
+
}
|
|
68
|
+
&:active {
|
|
69
|
+
background-color: rgba(0, 0, 0, 0.15);
|
|
70
|
+
color: #0a0a0a;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
&.is-active {
|
|
74
|
+
background-color: #485fc7;
|
|
75
|
+
color: #fff;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&.is-active {
|
|
80
|
+
.app-dropdown__label .app-icon.bi-arrow-up::before {
|
|
81
|
+
transform: rotate(180deg);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.app-dropdown__list {
|
|
85
|
+
opacity: 1;
|
|
86
|
+
visibility: visible;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|