wini-web-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 (62) hide show
  1. package/README.md +70 -0
  2. package/package.json +62 -0
  3. package/public/favicon.ico +0 -0
  4. package/public/index.html +43 -0
  5. package/public/logo192.png +0 -0
  6. package/public/logo512.png +0 -0
  7. package/public/manifest.json +25 -0
  8. package/public/robots.txt +3 -0
  9. package/src/App.css +38 -0
  10. package/src/App.js +25 -0
  11. package/src/App.test.js +8 -0
  12. package/src/component/calendar/calendar.css +133 -0
  13. package/src/component/calendar/calendar.tsx +448 -0
  14. package/src/component/checkbox/checkbox.css +46 -0
  15. package/src/component/checkbox/checkbox.tsx +59 -0
  16. package/src/component/component-status.tsx +53 -0
  17. package/src/component/date-picker/date-picker.css +114 -0
  18. package/src/component/date-picker/date-picker.tsx +363 -0
  19. package/src/component/dialog/dialog.css +125 -0
  20. package/src/component/dialog/dialog.tsx +91 -0
  21. package/src/component/export-component.js +48 -0
  22. package/src/component/import-file/import-file.css +86 -0
  23. package/src/component/import-file/import-file.tsx +154 -0
  24. package/src/component/infinite-scroll/infinite-scroll.css +37 -0
  25. package/src/component/infinite-scroll/infinite-scroll.tsx +33 -0
  26. package/src/component/input-multi-select/input-multi-select.css +127 -0
  27. package/src/component/input-multi-select/input-multi-select.tsx +261 -0
  28. package/src/component/pagination/pagination.css +69 -0
  29. package/src/component/pagination/pagination.tsx +65 -0
  30. package/src/component/popup/popup.css +90 -0
  31. package/src/component/popup/popup.tsx +105 -0
  32. package/src/component/progress-bar/progress-bar.css +47 -0
  33. package/src/component/progress-bar/progress-bar.tsx +34 -0
  34. package/src/component/progress-circle/progress-circle.css +47 -0
  35. package/src/component/progress-circle/progress-circle.tsx +24 -0
  36. package/src/component/radio-button/radio-button.css +49 -0
  37. package/src/component/radio-button/radio-button.tsx +59 -0
  38. package/src/component/rating/rating.css +11 -0
  39. package/src/component/rating/rating.tsx +65 -0
  40. package/src/component/select1/select1.css +155 -0
  41. package/src/component/select1/select1.tsx +241 -0
  42. package/src/component/slider/slider.css +16 -0
  43. package/src/component/slider/slider.tsx +87 -0
  44. package/src/component/switch/switch.css +53 -0
  45. package/src/component/switch/switch.tsx +67 -0
  46. package/src/component/table/table.css +74 -0
  47. package/src/component/table/table.tsx +99 -0
  48. package/src/component/text/text.css +16 -0
  49. package/src/component/text/text.tsx +21 -0
  50. package/src/component/text-area/text-area.css +63 -0
  51. package/src/component/text-area/text-area.tsx +55 -0
  52. package/src/component/text-field/text-field.css +64 -0
  53. package/src/component/text-field/text-field.tsx +63 -0
  54. package/src/component/toast-noti/toast-noti.css +0 -0
  55. package/src/component/toast-noti/toast-noti.tsx +21 -0
  56. package/src/index.css +13 -0
  57. package/src/index.js +17 -0
  58. package/src/logo.svg +1 -0
  59. package/src/reportWebVitals.js +13 -0
  60. package/src/setupTests.js +5 -0
  61. package/src/skin/color.css +17 -0
  62. package/src/skin/typography.css +378 -0
@@ -0,0 +1,261 @@
1
+ import { faChevronDown, faChevronUp, faClose, faSearch } from '@fortawesome/free-solid-svg-icons'
2
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
3
+ import React, { CSSProperties } from 'react'
4
+ import ReactDOM from 'react-dom'
5
+ import './input-multi-select.css'
6
+ import { Checkbox, Text } from '../export-component'
7
+
8
+ interface SelectMultipleProps {
9
+ value?: Array<string | number>,
10
+ options: Required<Array<{ id: string | number, name: string }>>,
11
+ onChange?: (value?: Array<string | number>) => void,
12
+ placeholder?: string,
13
+ disabled: boolean,
14
+ className?: string,
15
+ helperText?: string,
16
+ helperTextColor?: string,
17
+ style?: CSSProperties,
18
+ handleSearch?: (e: string) => Promise<Array<{ id: string | number, name: string }>>
19
+ }
20
+
21
+ interface SelectMultipleState {
22
+ value: Array<string | number>,
23
+ offset: DOMRect,
24
+ isOpen: boolean,
25
+ onSelect: any,
26
+ search?: Array<{ id: string | number, name: string }>,
27
+ style?: Object
28
+ };
29
+
30
+ export class SelectMultiple extends React.Component<SelectMultipleProps, SelectMultipleState> {
31
+ constructor(props: SelectMultipleProps) {
32
+ super(props)
33
+ this.state = {
34
+ value: props.value ?? [],
35
+ offset: {
36
+ x: 0,
37
+ y: 0,
38
+ height: 0,
39
+ width: 0,
40
+ bottom: 0,
41
+ left: 0,
42
+ right: 0,
43
+ top: 0,
44
+ toJSON: function () {
45
+ throw new Error('Function not implemented.')
46
+ }
47
+ },
48
+ isOpen: false,
49
+ onSelect: null,
50
+ }
51
+ this.onCheck = this.onCheck.bind(this)
52
+ this.search = this.search.bind(this);
53
+ }
54
+
55
+ private onCheck(item: { id: string | number, name: string }) {
56
+ let newValue: Array<string | number> = []
57
+ if (this.state.value.includes(item.id)) {
58
+ newValue = this.state.value.filter(vl => vl !== item.id)
59
+ } else {
60
+ newValue = [...this.state.value, item.id]
61
+ }
62
+ this.setState({
63
+ ...this.state,
64
+ value: newValue
65
+ })
66
+ if (this.props.onChange) this.props.onChange(newValue)
67
+ }
68
+
69
+ private async search(ev: React.ChangeEvent<HTMLInputElement>) {
70
+ if (ev.target.value.trim().length) {
71
+ if (this.props?.handleSearch) {
72
+ const res = await this.props.handleSearch(ev.target.value.trim())
73
+ this.setState({
74
+ ...this.state,
75
+ search: res
76
+ })
77
+ } else {
78
+ this.setState({
79
+ ...this.state,
80
+ search: this.props.options.filter(e => e.name.toLowerCase().includes(ev.target.value.trim().toLowerCase()))
81
+ })
82
+ }
83
+ } else {
84
+ this.setState({
85
+ ...this.state,
86
+ search: undefined
87
+ })
88
+ }
89
+ }
90
+
91
+ componentDidUpdate(prevProps: SelectMultipleProps, prevState: SelectMultipleState) {
92
+ if (prevProps.value !== this.props.value) {
93
+ this.setState({
94
+ ...this.state,
95
+ value: this.props.value ?? []
96
+ })
97
+ }
98
+ if (prevState.isOpen !== this.state.isOpen && this.state.isOpen) {
99
+ const thisPopupRect = document.body.querySelector('.select-multi-popup')?.getBoundingClientRect()
100
+ if (thisPopupRect) {
101
+ let style: { top?: string, left?: string, right?: string, bottom?: string, width?: string, height?: string } | undefined;
102
+ if (thisPopupRect.right > document.body.offsetWidth) {
103
+ style = {
104
+ top: this.state.offset.y + this.state.offset.height + 2 + 'px',
105
+ width: `${this.state.offset.width}px`,
106
+ right: document.body.offsetWidth - this.state.offset.right + 'px'
107
+ }
108
+ }
109
+ if (thisPopupRect.bottom - 12 > document.body.offsetHeight) {
110
+ style = style ? {
111
+ ...style,
112
+ top: undefined,
113
+ bottom: document.body.offsetHeight - this.state.offset.bottom + 'px'
114
+ } : {
115
+ left: this.state.offset.x + 'px',
116
+ width: `${this.state.offset.width}px`,
117
+ bottom: document.body.offsetHeight - this.state.offset.bottom + 'px'
118
+ }
119
+ }
120
+ if (style) this.setState({
121
+ ...this.state,
122
+ style: style
123
+ })
124
+ }
125
+ }
126
+ }
127
+
128
+ render() {
129
+ return <div
130
+ className={`select-multi-container row ${this.props.disabled ? 'disabled' : ''} ${this.props.helperText?.length && 'helper-text'}`}
131
+ helper-text={this.props.helperText}
132
+ style={this.props.style ? { ...({ '--helper-text-color': this.props.helperTextColor ?? '#e14337' } as CSSProperties), ...this.props.style } : ({ '--helper-text-color': this.props.helperTextColor ?? '#e14337' } as CSSProperties)}
133
+ onClick={ev => {
134
+ if (!this.state.isOpen) {
135
+ this.setState({
136
+ ...this.state,
137
+ isOpen: true,
138
+ style: undefined,
139
+ offset: ((ev.target as HTMLElement).closest('.select-multi-container') ?? (ev.target as HTMLElement)).getBoundingClientRect(),
140
+ })
141
+ }
142
+ }}
143
+ >
144
+ <div className='row' style={{ overflow: 'auto hidden', flex: 1, width: '100%', gap: '0.8rem' }}>
145
+ {this.state.value?.length ? (
146
+ this.state.value.map(item => {
147
+ const optionItem = this.props.options.find(e => e.id === item)
148
+ return <div
149
+ key={item}
150
+ className='selected-item-value row'
151
+ onClick={(ev) => {
152
+ ev.stopPropagation()
153
+ let newValue = this.state.value.filter(vl => vl !== item)
154
+ this.setState({
155
+ ...this.state,
156
+ value: newValue,
157
+ isOpen: true,
158
+ style: undefined,
159
+ offset: ((ev.target as HTMLElement).closest('.select-multi-container') ?? (ev.target as HTMLElement)).getBoundingClientRect(),
160
+ })
161
+ if (this.props.onChange) this.props.onChange(newValue)
162
+ }}
163
+ >
164
+ <div className='selected-value-title'>{optionItem?.name}</div>
165
+ <FontAwesomeIcon icon={faClose} style={{ color: '#161D24E5', fontSize: '1.2rem' }} />
166
+ </div>
167
+ })
168
+ ) : (
169
+ <div className='select-multi-placeholder'>
170
+ {this.props.placeholder}
171
+ </div>
172
+ )}
173
+ </div>
174
+ <FontAwesomeIcon
175
+ icon={this.state.isOpen ? faChevronUp : faChevronDown}
176
+ style={{ fontSize: '1.2rem', color: '#888' }}
177
+ />
178
+ {this.state.isOpen &&
179
+ ReactDOM.createPortal(
180
+ <div
181
+ className='select-multi-popup col'
182
+ style={this.state.style ?? {
183
+ top: this.state.offset.y + this.state.offset.height + 2 + 'px',
184
+ left: this.state.offset.x + 'px',
185
+ width: this.state.offset.width,
186
+ }}
187
+ onMouseOver={ev => {
188
+ this.setState({
189
+ ...this.state,
190
+ onSelect: ev.target
191
+ })
192
+ }}
193
+ onMouseOut={() =>
194
+ this.setState({
195
+ ...this.state,
196
+ onSelect: null
197
+ })
198
+ }
199
+ >
200
+ <div className='row header-search'>
201
+ <input
202
+ autoFocus={true}
203
+ placeholder={'Tìm kiếm'}
204
+ onChange={this.search}
205
+ onBlur={ev => {
206
+ if (this.state.onSelect) {
207
+ ev.target.focus()
208
+ } else {
209
+ this.setState({
210
+ ...this.state,
211
+ isOpen: false,
212
+ onSelect: null
213
+ })
214
+ }
215
+ }}
216
+ />
217
+ <FontAwesomeIcon icon={faSearch} style={{ fontSize: '1.2rem', color: '#161D24D9' }} />
218
+ </div>
219
+ <div style={{ padding: '1.2rem 1.6rem', width: '100%', borderTop: '1px solid #161D2414', borderBottom: '1px solid #161D2414' }}>
220
+ {(() => {
221
+ const _list = (this.state.search ?? this.props.options ?? [])
222
+ const isSelectedAll = _list.every(item => this.state.value.some(vl => vl === item.id))
223
+ return <Text onClick={() => {
224
+ let newValue: Array<string | number> = []
225
+ if (_list.length) {
226
+ if (isSelectedAll) {
227
+ newValue = this.state.value.filter(vl => _list.every(item => vl !== item.id))
228
+ } else {
229
+ newValue = [...this.state.value, ..._list.filter(item => this.state.value.every(vl => vl !== item.id)).map(e => e.id)]
230
+ }
231
+ }
232
+ this.setState({ ...this.state, value: newValue })
233
+ if (this.props.onChange) this.props.onChange(newValue)
234
+ }} className='button-text-3' style={{ color: _list.length ? 'var(--primary-color)' : '#00204D99', }}>{_list.length && isSelectedAll ? 'Bỏ chọn tất cả' : 'Chọn tất cả'}</Text>
235
+ })()}
236
+ </div>
237
+ <div className='col select-body'>
238
+ {(this.state.search ?? this.props.options ?? []).map(
239
+ item => (
240
+ <div key={item.id} className='select-tile row'>
241
+ <Checkbox
242
+ value={this.state.value.some(vl => vl === item.id)}
243
+ onChange={() => { this.onCheck(item) }}
244
+ size={'2rem'}
245
+ />
246
+ <div className='label-3'>
247
+ {item.name}
248
+ </div>
249
+ </div>
250
+ )
251
+ )}
252
+ {(this.state.search?.length === 0 || this.props.options?.length === 0) && (
253
+ <div className='no-results-found'>No result found</div>
254
+ )}
255
+ </div>
256
+ </div>,
257
+ document.body
258
+ )}
259
+ </div>
260
+ }
261
+ }
@@ -0,0 +1,69 @@
1
+ .custom-pagination {
2
+ width: 100%;
3
+ height: 100%;
4
+ justify-content: space-between;
5
+ gap: 1.6rem;
6
+ }
7
+
8
+ .custom-pagination>.row {
9
+ gap: 1.6rem;
10
+ height: 100%;
11
+ }
12
+
13
+ .items-per-page-container {
14
+ gap: 8px;
15
+ height: 100%;
16
+ }
17
+
18
+ .pagination {
19
+ display: flex;
20
+ flex-direction: row !important;
21
+ height: 4rem !important;
22
+ background-color: white;
23
+ overflow: hidden !important;
24
+ border: 1px solid rgba(88, 88, 91, 0.24);
25
+ box-sizing: border-box;
26
+ border-radius: 8px;
27
+ padding: 0;
28
+ margin: 0;
29
+ order: 2;
30
+ }
31
+
32
+ .pagination>* {
33
+ width: 3.4rem !important;
34
+ height: 100% !important;
35
+ background-color: white;
36
+ box-sizing: border-box;
37
+ padding: 0;
38
+ border: 0;
39
+ color: #1C2430;
40
+ font: 400 1.4rem/normal 'Inter' !important;
41
+ white-space: nowrap;
42
+ list-style: none;
43
+
44
+ }
45
+
46
+ .pagination>*>* {
47
+ width: 100%;
48
+ height: 100%;
49
+ display: flex;
50
+ align-items: center;
51
+ justify-content: center;
52
+ }
53
+
54
+ .pagination>*.active {
55
+ background-color: var(--infor-color) !important;
56
+ }
57
+
58
+ .pagination>*.active>* {
59
+ color: white;
60
+ }
61
+
62
+ .pagination>*:not(:last-child) {
63
+ border-right: 1px solid rgba(88, 88, 91, 0.24);
64
+ }
65
+
66
+ .pagination>:first-child,
67
+ .pagination>:last-child {
68
+ width: 8rem !important;
69
+ }
@@ -0,0 +1,65 @@
1
+ import React, { CSSProperties } from "react";
2
+ import ReactPaginate from "react-paginate";
3
+ import './pagination.css';
4
+ import { Select1, Text } from "../export-component";
5
+
6
+ export function Pagination({ currentPage, itemPerPage, totalItem, onChangePage, hiddenPageSize = false, hiddenTotal = false, style }: { currentPage: number, itemPerPage: number, totalItem: number, onChangePage: Function, hiddenPageSize: boolean, hiddenTotal: boolean, style: CSSProperties }) {
7
+ if (currentPage > 1 && (totalItem === 0 || (Math.floor(totalItem / itemPerPage) + (totalItem % itemPerPage === 0 ? 0 : 1)) < currentPage)) {
8
+ onChangePage(1, itemPerPage);
9
+ return <div></div>;
10
+ }
11
+ if (totalItem > 0) {
12
+ return (
13
+ <div className="row custom-pagination" style={style}>
14
+ {hiddenTotal ? null : <Text className="regular2">
15
+ Hiển thị {itemPerPage * (currentPage - 1) + 1}-{((itemPerPage * (currentPage - 1) + itemPerPage) > totalItem) ? totalItem : (itemPerPage * (currentPage - 1) + itemPerPage)} trong tổng số {totalItem} bản ghi
16
+ </Text>}
17
+ <div className="row ">
18
+ {hiddenPageSize ? null : <div className="row items-per-page-container" >
19
+ <Text className="regular2">Items/page</Text>
20
+ <div className="row">
21
+ <Select1
22
+ searchPlaceholder={'Search'}
23
+ style={{ width: '8.6rem' }}
24
+ placeholder={itemPerPage.toString()}
25
+ options={[10, 20, 50, 100, 200].map((item, _) => { return { id: item, name: item } })}
26
+ onChange={(ev: any) => {
27
+ onChangePage(currentPage, isNaN(parseInt(ev.id)) ? itemPerPage : parseInt(ev.id));
28
+ }}
29
+ />
30
+ </div>
31
+ </div>}
32
+ <ReactPaginate
33
+ breakLabel="..."
34
+ nextLabel="Next"
35
+ onPageChange={(ev) => {
36
+ onChangePage(ev.selected + 1, itemPerPage);
37
+ }}
38
+ forcePage={currentPage - 1}
39
+ // initialPage={currentPage - 1}
40
+ pageCount={Math.ceil(totalItem / itemPerPage)}
41
+ previousLabel="Previous"
42
+ containerClassName="pagination row"
43
+ pageClassName=""
44
+ pageLinkClassName="nav-link"
45
+ previousClassName="nav-link"
46
+ previousLinkClassName="nav-link"
47
+ nextClassName="nav-link regular2"
48
+ nextLinkClassName="nav-link"
49
+ activeClassName="active"
50
+ hrefBuilder={(pageIndex: number) =>
51
+ pageIndex >= 1 && pageIndex <= Math.ceil(totalItem / itemPerPage) ? `/page/${pageIndex}` : '#'
52
+ }
53
+ renderOnZeroPageCount={null}
54
+ />
55
+ </div>
56
+ </div>
57
+ )
58
+ } else {
59
+ return (
60
+ <div >
61
+
62
+ </div>
63
+ )
64
+ }
65
+ }
@@ -0,0 +1,90 @@
1
+ .popup-overlay {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ width: 100%;
6
+ height: 100%;
7
+ background-color: rgba(0, 0, 0, 0.5);
8
+ display: flex;
9
+ justify-content: center;
10
+ align-items: center;
11
+ z-index: 10;
12
+ }
13
+
14
+ .popup-container {
15
+ position: relative;
16
+ background-color: #ffffff;
17
+ border-radius: 0.8rem;
18
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
19
+ overflow: visible;
20
+ max-width: 86%;
21
+ max-height: 80%;
22
+ }
23
+
24
+ .popup-container .view-form {
25
+ border-radius: 0;
26
+ }
27
+
28
+ .popup-header {
29
+ align-items: center;
30
+ justify-content: start;
31
+ padding: 1.6rem 2.4rem;
32
+ border-bottom: 1px solid rgba(0, 53, 128, 0.08);
33
+ }
34
+
35
+ .popup-footer {
36
+ column-gap: 0.8rem;
37
+ justify-content: end;
38
+ padding: 1.2rem 2.4rem;
39
+ box-shadow: 0 -1px 6px 0 #2D32390F;
40
+ }
41
+
42
+ .popup-footer>.popup-action {
43
+ height: 4rem;
44
+ padding: 0 1.6rem;
45
+ border-radius: 0.8rem;
46
+ background-color: #f2f5f8;
47
+ align-items: center;
48
+ }
49
+
50
+ .popup-footer>.popup-submit {
51
+ color: #ffffff;
52
+ background-color: var(--infor-color);
53
+ }
54
+
55
+ .popup-container>.popup-close-btn {
56
+ position: absolute;
57
+ right: 0;
58
+ top: 0;
59
+ transform: translate(50%, -50%);
60
+ width: 3.2rem;
61
+ height: 3.2rem;
62
+ padding: 0.6rem;
63
+ border-radius: 50%;
64
+ align-items: center;
65
+ justify-content: center;
66
+ background-color: #ffffff;
67
+ box-shadow: 0px 1px 6px 0px #2d32390f;
68
+ }
69
+
70
+ .popup-body {
71
+ width: 100%;
72
+ max-height: 100%;
73
+ flex: 1;
74
+ overflow: auto;
75
+ }
76
+
77
+ .popup-overlay.hidden-overlay {
78
+ background-color: rgba(0, 0, 0, 0.01);
79
+ }
80
+
81
+ .popup-overlay.hidden-overlay>.popup-container {
82
+ position: absolute;
83
+ width: max-content;
84
+ height: max-content;
85
+ background-color: transparent;
86
+ }
87
+
88
+ .popup-overlay.hidden-overlay>.popup-container .popup-close-btn {
89
+ display: none !important;
90
+ }
@@ -0,0 +1,105 @@
1
+ import React, { CSSProperties, ReactNode } from 'react'
2
+ import ReactDOM from 'react-dom'
3
+ import './popup.css'
4
+
5
+ interface PopupState {
6
+ readonly open?: boolean,
7
+ heading?: ReactNode,
8
+ content?: ReactNode,
9
+ footer?: ReactNode,
10
+ clickOverlayClosePopup?: boolean,
11
+ style?: CSSProperties,
12
+ hideButtonClose?: boolean,
13
+ }
14
+
15
+ export const showPopup = ({ ref, heading, content, footer, clickOverlayClosePopup, style, hideButtonClose }: {
16
+ ref: React.MutableRefObject<Popup | undefined>,
17
+ heading?: ReactNode,
18
+ content?: ReactNode,
19
+ footer?: ReactNode,
20
+ clickOverlayClosePopup?: boolean,
21
+ style?: CSSProperties,
22
+ hideButtonClose?: boolean
23
+ }) => {
24
+ ref?.current?.onOpen({
25
+ heading: heading,
26
+ content: content,
27
+ footer: footer,
28
+ clickOverlayClosePopup: clickOverlayClosePopup,
29
+ style: style,
30
+ hideButtonClose: hideButtonClose
31
+ })
32
+ }
33
+
34
+ export const closePopup = (ref: React.MutableRefObject<Popup>) => {
35
+ ref.current.onClose()
36
+ }
37
+
38
+ export class Popup extends React.Component<Object, PopupState> {
39
+ private ref: React.RefObject<HTMLDivElement>;
40
+ constructor(props: Object | Readonly<Object>) {
41
+ super(props);
42
+ this.ref = React.createRef();
43
+ }
44
+ state: Readonly<PopupState> = {
45
+ open: false,
46
+ }
47
+
48
+ onOpen(data: PopupState) {
49
+ this.setState({ open: true, ...data })
50
+ }
51
+
52
+ onClose() {
53
+ this.setState({ open: false })
54
+ }
55
+
56
+ componentDidUpdate(prevProps: Readonly<Object>, prevState: Readonly<PopupState>) {
57
+ if (prevState.open !== this.state.open && this.state.open && this.state.style) {
58
+ const thisPopupRect = this.ref.current?.getBoundingClientRect()
59
+ if (thisPopupRect) {
60
+ let style: CSSProperties | undefined;
61
+ if (thisPopupRect.right > document.body.offsetWidth) {
62
+ style = { ...this.state.style, right: '0.4rem' }
63
+ delete style.left
64
+ }
65
+ if (thisPopupRect.bottom > document.body.offsetHeight) {
66
+ style = style ? {
67
+ ...style,
68
+ bottom: '0.4rem'
69
+ } : {
70
+ ...this.state.style,
71
+ bottom: '0.4rem'
72
+ }
73
+ delete style.top
74
+ }
75
+ if (style) this.setState({ ...this.state, style: style })
76
+ }
77
+ }
78
+ }
79
+
80
+ render() {
81
+ return (
82
+ <>
83
+ {this.state.open &&
84
+ ReactDOM.createPortal(
85
+ <div className={`popup-overlay ${this.state.clickOverlayClosePopup ? 'hidden-overlay' : ''}`} onClick={this.state.clickOverlayClosePopup ? (ev) => {
86
+ if ((ev.target as HTMLElement).classList.contains('popup-overlay'))
87
+ this.onClose()
88
+ } : undefined}>
89
+ <div ref={this.ref} className='popup-container col' onClick={e => e.stopPropagation()} style={this.state.style} >
90
+ {this.state.heading}
91
+ {this.state.content}
92
+ {this.state.footer}
93
+ {this.state.hideButtonClose ? null : <button type='button' onClick={() => this.onClose()} className='popup-close-btn row' >
94
+ <svg width='100%' height='100%' viewBox='0 0 20 20' fill='none' xmlns='http://www.w3.org/2000/svg' style={{ width: '2rem', height: '2rem' }} >
95
+ <path fillRule='evenodd' clipRule='evenodd' d='M16.4223 4.7559C16.7477 4.43047 16.7477 3.90283 16.4223 3.57739C16.0968 3.25195 15.5692 3.25195 15.2438 3.57739L9.99967 8.82147L4.7556 3.57739C4.43016 3.25195 3.90252 3.25195 3.57709 3.57739C3.25165 3.90283 3.25165 4.43047 3.57709 4.7559L8.82116 9.99998L3.57709 15.2441C3.25165 15.5695 3.25165 16.0971 3.57709 16.4226C3.90252 16.748 4.43016 16.748 4.7556 16.4226L9.99967 11.1785L15.2438 16.4226C15.5692 16.748 16.0968 16.748 16.4223 16.4226C16.7477 16.0971 16.7477 15.5695 16.4223 15.2441L11.1782 9.99998L16.4223 4.7559Z' fill='#00204D' fillOpacity={0.6} />
96
+ </svg>
97
+ </button>}
98
+ </div>
99
+ </div>,
100
+ document.body
101
+ )}
102
+ </>
103
+ )
104
+ }
105
+ }
@@ -0,0 +1,47 @@
1
+ .progress-bar-container {
2
+ row-gap: 1.2rem;
3
+ width: 38.2rem;
4
+ }
5
+
6
+ .progress-bar-container>.progress-bar-title {
7
+ justify-content: space-between;
8
+ width: 100%;
9
+ }
10
+
11
+ .progress-bar-container>.progress-bar-title>.heading-text {
12
+ font: 600 1.4rem/2.2rem 'Inter';
13
+ color: #00204D;
14
+ }
15
+
16
+ .progress-bar-container>.progress-bar-tile {
17
+ column-gap: 1.6rem;
18
+ width: 100%;
19
+ height: 2.2rem;
20
+ }
21
+
22
+ .progress-bar-container>.progress-bar-tile>.progress-bar-value {
23
+ border-radius: 10rem;
24
+ width: 100%;
25
+ flex: 1;
26
+ height: 0.6rem;
27
+ position: relative;
28
+ overflow: hidden;
29
+ background-color: var(--full-color);
30
+ }
31
+
32
+ .progress-bar-container>.progress-bar-tile>.progress-bar-value::after {
33
+ content: '';
34
+ position: absolute;
35
+ top: 0;
36
+ left: 0;
37
+ width: var(--percent);
38
+ height: 0.6rem;
39
+ border-radius: 10rem;
40
+ background-color: var(--percent-color);
41
+ transition: width ease-in-out 1s;
42
+ }
43
+
44
+ .progress-bar-container>.progress-bar-tile>.text-value {
45
+ font: 400 1.4rem/2.2rem 'Inter';
46
+ color: #00204DE5;
47
+ }
@@ -0,0 +1,34 @@
1
+ import { CSSProperties, ReactNode, useState } from 'react'
2
+ import './progress-bar.css'
3
+ import React from 'react'
4
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
5
+ import { faChevronDown, faChevronUp } from '@fortawesome/free-solid-svg-icons'
6
+ import { ComponentStatus, getStatusIcon } from '../export-component'
7
+
8
+ export function ProgressBar({ status = ComponentStatus.INFOR, percent = 100, titleText, title, hideTitle = false, progressBarOnly = false, fullColor = 'var(--background)', percentColor = 'var(--infor-color)', style, progressBarStyle }: {
9
+ percent: number,
10
+ titleText?: string,
11
+ title?: ReactNode,
12
+ hideTitle: boolean,
13
+ progressBarOnly: boolean,
14
+ fullColor: string,
15
+ percentColor: string,
16
+ style?: CSSProperties,
17
+ status: ComponentStatus,
18
+ progressBarStyle?: CSSProperties
19
+ }) {
20
+ const [openDetails, setOpenDetails] = useState(true)
21
+
22
+ return <div className="progress-bar-container col" style={style ? { padding: progressBarOnly ? '0' : '1.6rem 2.4rem', ...style } : { padding: progressBarOnly ? '0' : '1.6rem 2.4rem' }}>
23
+ {(hideTitle || progressBarOnly) ? null : (title ?? <div className="progress-bar-title row">
24
+ <div className="heading-text">{titleText}</div>
25
+ <button type="button" className="suffix-action" onClick={() => { setOpenDetails(!openDetails) }}><FontAwesomeIcon icon={openDetails ? faChevronDown : faChevronUp} /></button>
26
+ </div>)}
27
+ {openDetails ? <div className='progress-bar-tile row' >
28
+ <div className="progress-bar-value" style={{ '--percent-color': percentColor, '--full-color': fullColor, '--percent': `${percent}%`, ...(progressBarStyle ?? {}) } as CSSProperties}></div>
29
+ {progressBarOnly || status === ComponentStatus.INFOR ? null : <div className='status-icon'>{getStatusIcon(status)}</div>}
30
+ {progressBarOnly ? null : <div className='text-value'>{percent}/100</div>}
31
+ </div> : null}
32
+
33
+ </div>
34
+ }