@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.
Files changed (44) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +25 -0
  2. package/dist/assets/bootstrap-icons.48902042.woff +0 -0
  3. package/dist/assets/bootstrap-icons.a29357cb.woff2 +0 -0
  4. package/dist/assets/index.19ed8c21.css +1 -0
  5. package/dist/assets/index.517219b4.js +2 -0
  6. package/dist/assets/index.517219b4.js.map +1 -0
  7. package/dist/assets/vendor.92043335.js +41 -0
  8. package/dist/assets/vendor.92043335.js.map +1 -0
  9. package/dist/index.html +16 -0
  10. package/index.html +13 -0
  11. package/package.json +44 -0
  12. package/src/components/anchor/anchor.scss +15 -0
  13. package/src/components/anchor/components/anchorWithIcon/anchorWithIcon.tsx +45 -0
  14. package/src/components/anchor/components/simpleAnchor/simpleAnchor.tsx +16 -0
  15. package/src/components/anchor/index.tsx +64 -0
  16. package/src/components/anchor/types.ts +26 -0
  17. package/src/components/button/button.scss +12 -0
  18. package/src/components/button/components/buttonWithIcon/buttonWithIcon.tsx +47 -0
  19. package/src/components/button/components/simpleButton/simpleButton.tsx +16 -0
  20. package/src/components/button/index.tsx +83 -0
  21. package/src/components/button/types.ts +40 -0
  22. package/src/components/dropdown/components/dropdownItem/dropdownItem.tsx +30 -0
  23. package/src/components/dropdown/components/dropdownLabel/dropdownLabel.tsx +60 -0
  24. package/src/components/dropdown/dropdown.scss +89 -0
  25. package/src/components/dropdown/index.tsx +141 -0
  26. package/src/components/dropdown/types.ts +11 -0
  27. package/src/components/icon/icon.scss +18 -0
  28. package/src/components/icon/index.tsx +34 -0
  29. package/src/components/primitives/date/index.tsx +103 -0
  30. package/src/components/primitives/multiline/components/multilineWithLabel/index.tsx +93 -0
  31. package/src/components/primitives/multiline/components/multilineWithoutLabel/index.tsx +51 -0
  32. package/src/components/primitives/multiline/index.tsx +28 -0
  33. package/src/components/primitives/text/components/textWithLabel/index.tsx +92 -0
  34. package/src/components/primitives/text/components/textWithoutLabel/index.tsx +49 -0
  35. package/src/components/primitives/text/index.tsx +21 -0
  36. package/src/components/primitives/types.ts +65 -0
  37. package/src/components/search/index.tsx +127 -0
  38. package/src/components/search/search.scss +24 -0
  39. package/src/components/types.ts +41 -0
  40. package/src/hooks/useComponentOutsideClick.ts +48 -0
  41. package/src/main.tsx +9 -0
  42. package/tsconfig.json +48 -0
  43. package/tsconfig.node.json +8 -0
  44. package/vite.config.ts +14 -0
@@ -0,0 +1,65 @@
1
+ import { BaseElementProps } from "../types";
2
+
3
+ /** Base properties for input components */
4
+ export type BaseInputElementProps<TValue> = BaseElementProps & {
5
+ /** Value change handler. Changed value must be stored outside of component */
6
+ onValueChange: (value: TValue) => void;
7
+
8
+ /** Default value of input component*/
9
+ defaultValue?: TValue;
10
+
11
+ /** Input element placeholder */
12
+ placeholder?: string;
13
+
14
+ /** Is component borders are rounded */
15
+ rounded?: boolean;
16
+
17
+ /** Should be component disabled. Selecting is not allowed */
18
+ disabled?: boolean;
19
+
20
+ /** Should be component int read only mode. Selecting is allowed */
21
+ readonly?: boolean;
22
+
23
+ /** Component size */
24
+ size?: InputSize;
25
+
26
+ /** Label configuration */
27
+ label?: InputLabel;
28
+
29
+ /** Displaying loading state of component as spinner in right end of component */
30
+ loading?: boolean;
31
+
32
+ /** Style. Colors the border */
33
+ style?: InputColor;
34
+
35
+ /** Name of element. Required for form elements */
36
+ name?: string;
37
+ };
38
+
39
+ /** Input label configuration */
40
+ export type InputLabel = {
41
+ /** Label textual content to describe input requirements */
42
+ caption: string;
43
+
44
+ /** Should component be on same line with label */
45
+ horizontal?: boolean;
46
+ };
47
+
48
+ /** Input component size variety */
49
+ export type InputSize =
50
+ | 'small'
51
+ | 'normal'
52
+ | 'medium'
53
+ | 'large'
54
+ ;
55
+
56
+ /** Input component border-color type */
57
+ export type InputColor =
58
+ | 'default' /** color: transparent */
59
+ | 'primary' /** color: seawave green */
60
+ | 'link' /** color: blue-violet */
61
+ | 'info' /** color: sky-blue */
62
+ | 'success' /** color: green */
63
+ | 'warning' /** color: yellow */
64
+ | 'danger' /** color: red */
65
+ ;
@@ -0,0 +1,127 @@
1
+ import { ChangeEvent, useCallback, useState } from 'react';
2
+
3
+ import './search.scss';
4
+
5
+ import { generateGuid } from '@bodynarf/utils/guid';
6
+ import { isNullOrUndefined } from '@bodynarf/utils/common';
7
+
8
+ import Button from '@app/components/button';
9
+
10
+ type SearchProps = {
11
+ /** Search caption */
12
+ caption: string;
13
+
14
+ /** Search handler */
15
+ onSearch: (searchPattern: string) => void;
16
+
17
+ /** Initial search value */
18
+ defaultValue?: string;
19
+
20
+ /**
21
+ * Search type: by typing, starts from minimum characters to search
22
+ * or by clicking on button next to search bar.
23
+ * Default is byTyping
24
+ */
25
+ searchType?: 'byTyping' | 'byButton';
26
+
27
+ /** Control name. If empty - will be replaced by random guid */
28
+ name?: string;
29
+
30
+ /** Minimum amount of characters to search */
31
+ minCharsToSearch?: number;
32
+
33
+ /** Size of search bar */
34
+ size?: 'small' | 'normal' | 'medium' | 'large';
35
+
36
+ /** Should search bar be rounded */
37
+ rounded?: boolean;
38
+
39
+ /** Is search bar disabled */
40
+ disabled?: boolean;
41
+
42
+ /** Should loading icon be displayed in search bar*/
43
+ isLoading?: boolean;
44
+ };
45
+
46
+ /** Search component */
47
+ export default function Search(props: SearchProps): JSX.Element {
48
+ const [name] = useState<string>(props.name || generateGuid());
49
+ const [searchValue, setSearchValue] = useState<string>(props.defaultValue || '');
50
+
51
+ const searchType = props.searchType || 'byTyping';
52
+
53
+ const onChange = useCallback(
54
+ (event: ChangeEvent<HTMLInputElement>) => {
55
+ const elementValue: string = event.target.value;
56
+
57
+ if (searchType === 'byTyping') {
58
+ const minCharsToSearch: number =
59
+ isNullOrUndefined(props.minCharsToSearch)
60
+ ? 3
61
+ : props.minCharsToSearch as number;
62
+
63
+ if (elementValue.length >= minCharsToSearch) {
64
+ props.onSearch(elementValue);
65
+ }
66
+ }
67
+
68
+ setSearchValue(elementValue);
69
+ }, [props, searchType]);
70
+
71
+ const onSearchButtonClick = useCallback(
72
+ () => {
73
+ props.onSearch(searchValue);
74
+ }, [props, searchValue]);
75
+
76
+ const className: string = 'app-search control'
77
+ + ` is-${(props.size || 'normal')}`
78
+ + (props.isLoading === true ? ' is-loading' : '')
79
+ + (searchType === 'byButton' ? ' is-expanded': '')
80
+ ;
81
+
82
+ const inputClassName: string = 'input is-unselectable'
83
+ + ` is-${(props.size || 'normal')}`
84
+ + (props.rounded === true ? ' is-rounded' : '')
85
+ ;
86
+
87
+ if (searchType === 'byButton') {
88
+ return (
89
+ <div className="field has-addons">
90
+ <div className={className}>
91
+ <input
92
+ type='search'
93
+ name={name}
94
+ defaultValue={searchValue}
95
+ className={inputClassName}
96
+ disabled={props.disabled}
97
+ onChange={onChange}
98
+ placeholder={props.caption}
99
+ />
100
+ </div>
101
+ <div className="control">
102
+ <Button
103
+ caption="Search"
104
+ type="info"
105
+ onClick={onSearchButtonClick}
106
+ isLoading={props.isLoading}
107
+ />
108
+ </div>
109
+ </div>
110
+ );
111
+ }
112
+ else {
113
+ return (
114
+ <div className={className}>
115
+ <input
116
+ type='search'
117
+ name={name}
118
+ defaultValue={searchValue}
119
+ className={inputClassName}
120
+ disabled={props.disabled}
121
+ onChange={onChange}
122
+ placeholder={props.caption}
123
+ />
124
+ </div>
125
+ );
126
+ }
127
+ }
@@ -0,0 +1,24 @@
1
+ .app-search {
2
+ input[type="search"] {
3
+ transition: 0.15s ease-out;
4
+ transition-property: border-color, color, background-color;
5
+
6
+ &::placeholder {
7
+ user-select: none;
8
+ }
9
+
10
+ &::-webkit-input-placeholder {
11
+ transition: 0.15s ease-out color;
12
+ }
13
+
14
+ &::-webkit-search-cancel-button {
15
+ cursor: pointer;
16
+ font-size: 1.25rem;
17
+ }
18
+ }
19
+
20
+ &.is-loading::after {
21
+ right: 1rem;
22
+ top: 0.725rem;
23
+ }
24
+ }
@@ -0,0 +1,41 @@
1
+ /** Base interface for component props */
2
+ export type BaseElementProps = {
3
+ /** Additional class names */
4
+ className?: string;
5
+
6
+ /** Title */
7
+ title?: string;
8
+
9
+ /** Extra data-* attributes */
10
+ data?: {
11
+ /**Will add data-{key} attribute to element */
12
+ [key: string]: any;
13
+ };
14
+ };
15
+
16
+ export type IconSize =
17
+ | 'small'
18
+ | 'medium' /** default size */
19
+ | 'large';
20
+
21
+ export type IconPosition =
22
+ | 'left'
23
+ | 'right';
24
+
25
+ /** Icon for component configuration */
26
+ export type ElementIcon = {
27
+ /**
28
+ * Class name for icon.
29
+ * Used to display icon from bootstrap-icons
30
+ */
31
+ className: string;
32
+
33
+ /** Icon size */
34
+ size?: IconSize;
35
+
36
+ /**
37
+ * Position
38
+ * Works only with other content
39
+ */
40
+ position?: IconPosition;
41
+ };
@@ -0,0 +1,48 @@
1
+ import { DependencyList, useCallback, useEffect } from "react";
2
+
3
+ import { isNull, isNullOrUndefined } from "@bodynarf/utils/common";
4
+
5
+ /**
6
+ * Subscribe to component outside click
7
+ * @param selector Component root selector
8
+ * @param clickHandleCondition Condition to handle document click
9
+ * @param clickHandleChange Handler of outside component click
10
+ * @param clickListenCondition Condition to start listening document clicks
11
+ * @param dependencies Additional dependencies to update hook
12
+ */
13
+ export const useComponentOutsideClick = (
14
+ selector: string,
15
+ clickHandleCondition: boolean,
16
+ clickHandleChange: () => void,
17
+ clickListenCondition?: boolean,
18
+ dependencies?: DependencyList
19
+ ): void => {
20
+ const onDocumentClick = useCallback(
21
+ (event: MouseEvent): void => {
22
+ if (clickHandleCondition) {
23
+ const target: HTMLElement = event.target as HTMLElement;
24
+
25
+ if (isNullOrUndefined(target)) {
26
+ return;
27
+ }
28
+
29
+ const relatedComponent: Element | null =
30
+ target.closest(selector);
31
+
32
+ if (isNull(relatedComponent)) {
33
+ clickHandleChange();
34
+ }
35
+ }
36
+ // eslint-disable-next-line react-hooks/exhaustive-deps
37
+ }, [clickHandleCondition, selector, clickHandleChange, dependencies]);
38
+
39
+ useEffect(() => {
40
+ if (isNullOrUndefined(clickListenCondition) || clickListenCondition === true) {
41
+ document.addEventListener('click', onDocumentClick);
42
+
43
+ return (): void => document.removeEventListener('click', onDocumentClick);
44
+ }
45
+
46
+ return () => { };
47
+ }, [clickListenCondition, onDocumentClick]);
48
+ };
package/src/main.tsx ADDED
@@ -0,0 +1,9 @@
1
+ import { createRoot } from "react-dom/client";
2
+
3
+ import "bootstrap-icons/font/bootstrap-icons.css";
4
+ import "bulma/bulma.sass";
5
+
6
+ const container = document.getElementById("root");
7
+
8
+ const root = createRoot(container!); // createRoot(container!) if you use TypeScript
9
+ root.render(<div> Components </div>);
package/tsconfig.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "useDefineForClassFields": true,
5
+ "lib": [
6
+ "DOM",
7
+ "DOM.Iterable",
8
+ "ESNext"
9
+ ],
10
+ "allowJs": false,
11
+ "skipLibCheck": false,
12
+ "esModuleInterop": false,
13
+ "allowSyntheticDefaultImports": true,
14
+ "strict": true,
15
+ "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
16
+ "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
17
+ "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
18
+ "forceConsistentCasingInFileNames": true,
19
+ "module": "ESNext",
20
+ "moduleResolution": "Node",
21
+ "resolveJsonModule": true,
22
+ // "isolatedModules": true,
23
+ "noEmit": true,
24
+ "jsx": "react-jsx",
25
+ "downlevelIteration": true,
26
+ "noUnusedLocals": true, /* Report errors on unused locals. */
27
+ "noUnusedParameters": true, /* Report errors on unused parameters. */
28
+ "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
29
+ "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
30
+ "paths": {
31
+ "@app/components/*": [
32
+ "src/components/*",
33
+ "src/components/primitives/*"
34
+ ],
35
+ "@app/hooks/*": [
36
+ "src/hooks/*"
37
+ ],
38
+ },
39
+ },
40
+ "include": [
41
+ "src"
42
+ ],
43
+ "references": [
44
+ {
45
+ "path": "./tsconfig.node.json"
46
+ }
47
+ ],
48
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "module": "esnext",
5
+ "moduleResolution": "node"
6
+ },
7
+ "include": ["vite.config.ts"]
8
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { defineConfig, splitVendorChunkPlugin } from 'vite';
2
+
3
+ import react from '@vitejs/plugin-react';
4
+
5
+ // https://vitejs.dev/config/
6
+ export default defineConfig({
7
+ plugins: [
8
+ splitVendorChunkPlugin(),
9
+ react(),
10
+ ],
11
+ build: {
12
+ sourcemap: true,
13
+ }
14
+ });