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,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,24 @@
1
+ import { CSSProperties } from 'react'
2
+ import './progress-circle.css'
3
+ import React from 'react'
4
+
5
+ export function ProgressCircle({ percent, style, fillColor, percentColor, strokeWidth, strokeColor, textStyle }: {
6
+ /** value: 0 - 100 (%)*/
7
+ percent?: number,
8
+ style?: CSSProperties,
9
+ fillColor?: string,
10
+ percentColor?: string,
11
+ strokeWidth?: number,
12
+ strokeColor?: string,
13
+ textStyle?: CSSProperties
14
+ }) {
15
+ const radius = 30 - (strokeWidth ?? 4)
16
+ const diameter = Math.PI * 2 * radius;
17
+ const strokeOffset = (1 - ((percent ?? 0) / 100)) * diameter;
18
+ return <svg width="100%" height="100%" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg" style={{ width: 60, height: 60, ...(style ?? {}) }} >
19
+ <path d={`M 30,30 m 0,-${radius} a ${radius},${radius} 0 1 1 0,${2 * radius} a ${radius},${radius} 0 1 1 0,-${2 * radius}`} style={{ fill: "none", stroke: strokeColor ?? "#E6EAF0", strokeWidth: strokeWidth ?? '4px', }} />
20
+ <path d={`M 30,30 m 0,-${radius} a ${radius},${radius} 0 1 1 0,${2 * radius} a ${radius},${radius} 0 1 1 0,-${2 * radius}`} style={{ fill: fillColor ?? "none", stroke: percentColor ?? "var(--infor-color)", strokeWidth: strokeWidth ?? '4px', strokeLinecap: 'round', strokeDasharray: `${diameter}px ${diameter}px`, strokeDashoffset: `${strokeOffset}px` }} />
21
+ <text x="50%" y="50%" fill="#00204D" dy=".3em" textAnchor="middle" fontSize={16} fontWeight={'600'} style={textStyle}>{percent ?? 0}%</text>
22
+ </svg>
23
+ }
24
+
@@ -0,0 +1,49 @@
1
+ .radio-btn-container {
2
+ height: max(var(--size), 14px);
3
+ width: max(var(--size), 14px);
4
+ aspect-ratio: 1 / 1 !important;
5
+ border-radius: 50%;
6
+ box-sizing: border-box;
7
+ border: 2px solid var(--off-color);
8
+ background-color: transparent !important;
9
+ transition: border-color ease-in-out 0.5s;
10
+ justify-content: center;
11
+ cursor: pointer;
12
+ }
13
+
14
+ .radio-btn-container>input {
15
+ position: absolute;
16
+ opacity: 0;
17
+ cursor: pointer;
18
+ }
19
+
20
+ .radio-btn-container>.checkmark {
21
+ height: calc(max(var(--size), 14px) / 2);
22
+ width: calc(max(var(--size), 14px) / 2);
23
+ border-radius: 50%;
24
+ aspect-ratio: 1 / 1;
25
+ visibility: hidden;
26
+ opacity: 0;
27
+ transition: visibility, opacity ease-in-out 0.5s;
28
+ }
29
+
30
+ .radio-btn-container>input:checked~.checkmark {
31
+ visibility: visible;
32
+ opacity: 1;
33
+ background-color: var(--active-color);
34
+ }
35
+
36
+ .radio-btn-container:has(> input:checked) {
37
+ border-color: var(--active-color) !important;
38
+ }
39
+
40
+ .radio-btn-container:has(input:disabled) {
41
+ pointer-events: none !important;
42
+ border-color: var(--off-color) !important;
43
+ background-color: #0035800A !important;
44
+ }
45
+
46
+ .radio-btn-container>input:disabled~.checkmark {
47
+ display: block !important;
48
+ background-color: #00204D40;
49
+ }
@@ -0,0 +1,59 @@
1
+ import React, { CSSProperties } from 'react';
2
+ import './radio-button.css';
3
+ import { UseFormRegister } from 'react-hook-form';
4
+
5
+ interface RadioButtonProps {
6
+ onChange?: React.ChangeEventHandler<HTMLInputElement>,
7
+ value?: string | number | readonly string[],
8
+ disabled: false,
9
+ style: CSSProperties,
10
+ size?: number | string,
11
+ defaultChecked?: boolean,
12
+ name?: string,
13
+ activeColor?: string,
14
+ offColor?: string,
15
+ className?: string,
16
+ register?: UseFormRegister<{}>,
17
+ }
18
+
19
+
20
+ export class RadioButton extends React.Component<RadioButtonProps> {
21
+ render(): React.ReactNode {
22
+ let convertStyle: CSSProperties = {
23
+ '--off-color': this.props.offColor ?? '#ccd7e6',
24
+ '--active-color': this.props.activeColor ?? 'var(--infor-color)',
25
+ '--size': this.props.size ? (typeof this.props.size === 'number') ? `${this.props.size}px` : this.props.size : '20px'
26
+ } as CSSProperties
27
+ if (this.props.style) {
28
+ delete this.props.style.width
29
+ delete this.props.style.minWidth
30
+ delete this.props.style.maxWidth
31
+ delete this.props.style.height
32
+ delete this.props.style.minHeight
33
+ delete this.props.style.maxHeight
34
+ convertStyle = {
35
+ ...this.props.style,
36
+ ...convertStyle,
37
+ }
38
+ }
39
+
40
+ return <label className={`radio-btn-container row ${this.props.className ?? ''}`} style={convertStyle} >
41
+ {this.props.register ?
42
+ <input
43
+ {...this.props.register}
44
+ type="radio"
45
+ value={this.props.value}
46
+ disabled={this.props.disabled}
47
+ /> :
48
+ <input type="radio"
49
+ name={this.props.name}
50
+ value={this.props.value}
51
+ defaultChecked={this.props.defaultChecked}
52
+ disabled={this.props.disabled}
53
+ onChange={this.props.onChange}
54
+ />
55
+ }
56
+ <span className="checkmark"></span>
57
+ </label>
58
+ }
59
+ }
@@ -0,0 +1,11 @@
1
+ .rating-container {
2
+ gap: 0.8rem;
3
+ }
4
+
5
+ .rating-container svg {
6
+ cursor: pointer;
7
+ }
8
+
9
+ .rating-container svg > * {
10
+ pointer-events: none !important;
11
+ }
@@ -0,0 +1,65 @@
1
+ import React, { CSSProperties } from "react"
2
+ import './rating.css'
3
+
4
+ interface RatingProps {
5
+ /**
6
+ value: 0-5
7
+ */
8
+ value?: number,
9
+ size?: number | string,
10
+ onChange?: (e: number) => {}
11
+ className?: string,
12
+ style?: CSSProperties,
13
+ strokeColor?: string,
14
+ fillColor?: string,
15
+ }
16
+
17
+ interface RatingState {
18
+ value: number,
19
+ }
20
+
21
+ const autoKeyId = () => (`${1e7 + -1e3 + -4e3 + -8e3 + -1e11}`).replace(/[018]/g, c =>
22
+ (parseFloat(c) ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (parseFloat(c) / 4)))).toString(16))
23
+
24
+ export class Rating extends React.Component<RatingProps, RatingState> {
25
+ state: Readonly<RatingState> = {
26
+ value: this.props.value ?? 0
27
+ }
28
+
29
+ componentDidUpdate(prevProps: Readonly<RatingProps>): void {
30
+ if (prevProps.value !== this.props.value) {
31
+ this.setState({ value: this.props.value ?? 0 })
32
+ }
33
+ }
34
+ render(): React.ReactNode {
35
+ return <div className={`row rating-container ${this.props.className ?? ''}`} style={this.props.style}>
36
+ {Array.from({ length: 5 }).map((_, i) => {
37
+ let uniqueId = 'rating-star-grad-0'
38
+ let stopValue = 0
39
+ if (this.state.value >= 5) {
40
+ uniqueId = 'rating-star-grad-5'
41
+ stopValue = 100
42
+ } else if (this.state.value >= i) {
43
+ uniqueId = autoKeyId()
44
+ stopValue = (this.state.value - i) * 100
45
+ }
46
+ return <svg onClick={() => {
47
+ if (this.props.onChange) {
48
+ this.setState({ value: i + 1 })
49
+ this.props.onChange(i + 1)
50
+ }
51
+ }} key={'rate-' + i} width={"100%"} height={"100%"} style={{ width: this.props.size ?? '2rem', height: this.props.size ?? '2rem' }} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
52
+ <defs>
53
+ <linearGradient id={uniqueId} x1="0%" x2="100%" y1="0%" y2="0%">
54
+ <stop offset="0%" stopColor={this.props.fillColor ?? '#FC6B03'} />
55
+ <stop offset={`${stopValue}%`} stopColor={this.props.fillColor ?? '#FC6B03'} />
56
+ <stop offset={`${stopValue}%`} stopColor="#00000000" />
57
+ </linearGradient>
58
+ </defs>
59
+ <path fillRule="evenodd" clipRule="evenodd" d="M10 1.66667C10.2884 1.66667 10.5518 1.82993 10.6794 2.0878L12.844 6.46194L17.6847 7.16325C17.97 7.20459 18.2071 7.4039 18.2962 7.67736C18.3853 7.95082 18.311 8.25101 18.1045 8.45172L14.6018 11.8563L15.4285 16.6636C15.4772 16.947 15.3604 17.2334 15.127 17.4024C14.8937 17.5714 14.5844 17.5937 14.3292 17.4599L10 15.1897L5.67081 17.4599C5.41557 17.5937 5.10627 17.5714 4.87295 17.4024C4.63964 17.2334 4.52278 16.947 4.57151 16.6636L5.39815 11.8563L1.89545 8.45172C1.68896 8.25101 1.61465 7.95082 1.70377 7.67736C1.79288 7.4039 2.02996 7.20459 2.31533 7.16325L7.15599 6.46194L9.32063 2.0878C9.44825 1.82993 9.71162 1.66667 10 1.66667ZM10 4.12915L8.33846 7.48665C8.22811 7.70963 8.01479 7.86418 7.76802 7.89993L4.05223 8.43827L6.74094 11.0517C6.91947 11.2252 7.00094 11.4752 6.95881 11.7203L6.3243 15.4102L9.64738 13.6676C9.86813 13.5519 10.1319 13.5519 10.3526 13.6676L13.6757 15.4102L13.0412 11.7203C12.9991 11.4752 13.0805 11.2252 13.2591 11.0517L15.9478 8.43827L12.232 7.89993C11.9852 7.86418 11.7719 7.70963 11.6615 7.48665L10 4.12915Z" fill={this.props.strokeColor ?? '#667994'} />
60
+ <path d="M17.738 7.18949L12.8212 6.47499L10.6249 2.02268C10.5611 1.91426 10.47 1.82438 10.3608 1.76194C10.2515 1.6995 10.1279 1.66666 10.0021 1.66666C9.87623 1.66666 9.75259 1.6995 9.64335 1.76194C9.53411 1.82438 9.44306 1.91426 9.37921 2.02268L7.17875 6.47499L2.26191 7.18949C2.13368 7.208 2.0132 7.26201 1.91406 7.34542C1.81493 7.42882 1.74111 7.5383 1.70095 7.66147C1.66078 7.78463 1.65588 7.91658 1.68678 8.04239C1.71769 8.1682 1.78317 8.28286 1.87583 8.3734L5.43449 11.8411L4.59499 16.7385C4.57311 16.8662 4.58739 16.9975 4.63622 17.1175C4.68505 17.2375 4.76648 17.3414 4.8713 17.4175C4.97612 17.4937 5.10016 17.539 5.22938 17.5483C5.3586 17.5577 5.48785 17.5306 5.60252 17.4704L9.99998 15.1588L14.3974 17.4704C14.5121 17.5306 14.6414 17.5577 14.7706 17.5483C14.8998 17.539 15.0238 17.4937 15.1286 17.4175C15.2335 17.3414 15.3149 17.2375 15.3637 17.1175C15.4126 16.9975 15.4268 16.8662 15.405 16.7385L14.5655 11.8411L18.1241 8.3734C18.2168 8.28295 18.2823 8.16841 18.3132 8.0427C18.3442 7.91699 18.3394 7.78512 18.2994 7.66199C18.2594 7.53886 18.1858 7.42937 18.0868 7.34587C17.9879 7.26238 17.8675 7.20822 17.7394 7.18949H17.738Z" fill={`url(#${uniqueId})`} />
61
+ </svg>
62
+ })}
63
+ </div>
64
+ }
65
+ }
@@ -0,0 +1,155 @@
1
+ .select1-container {
2
+ border-radius: 0.4rem;
3
+ align-items: center;
4
+ justify-content: start;
5
+ width: 100%;
6
+ height: 4rem;
7
+ max-height: 100%;
8
+ box-sizing: border-box;
9
+ padding: 0.8rem;
10
+ gap: 0.8rem;
11
+ width: 16rem;
12
+ position: relative;
13
+ border: 0.5px solid #00358033;
14
+ cursor: context-menu;
15
+ }
16
+
17
+ .select1-container.disabled {
18
+ background-color: var(--disabled-background) !important;
19
+ pointer-events: none !important;
20
+ }
21
+
22
+ .select1-container.helper-text {
23
+ overflow: visible !important;
24
+ border-color: var(--helper-text-color) !important;
25
+ }
26
+
27
+ .select1-container.helper-text::after {
28
+ content: attr(helper-text);
29
+ color: var(--helper-text-color);
30
+ position: absolute;
31
+ left: 0;
32
+ bottom: -0.4rem;
33
+ width: max-content;
34
+ font: 400 1.2rem/1.6rem 'Inter';
35
+ transform: translate(-7.5%, 92.5%) scale(0.85);
36
+ }
37
+
38
+ .select1-container::-webkit-scrollbar {
39
+ display: none;
40
+ }
41
+
42
+ .selected-item-value>* {
43
+ pointer-events: none;
44
+ }
45
+
46
+ .select1-container>.select1-placeholder {
47
+ font: inherit;
48
+ font-size: inherit;
49
+ font-family: inherit;
50
+ font-weight: inherit;
51
+ line-height: inherit;
52
+ text-align: inherit;
53
+ text-overflow: inherit;
54
+ color: #c5c5c5 !important;
55
+ flex: 1;
56
+ }
57
+
58
+ .select1-container>.select1-value-name {
59
+ font: inherit;
60
+ color: inherit;
61
+ font-size: inherit;
62
+ font-family: inherit;
63
+ font-weight: inherit;
64
+ line-height: inherit;
65
+ text-align: inherit;
66
+ text-overflow: inherit;
67
+ flex: 1;
68
+ }
69
+
70
+ .select1-container>.selected-item-value {
71
+ border-radius: 4px;
72
+ padding: 4px 8px;
73
+ background-color: #c5c5c5;
74
+ column-gap: 4px;
75
+ }
76
+
77
+ .select1-container>.selected-item-value>.selected-value-title {
78
+ font: 400 1.2rem/1.6rem "Inter";
79
+ color: #1C2430;
80
+ }
81
+
82
+ .select1-popup {
83
+ position: absolute;
84
+ z-index: 10;
85
+ background-color: white;
86
+ border-radius: 8px;
87
+ height: fit-content;
88
+ box-shadow: 0 0 12px 0 #00204d1f;
89
+ align-items: center;
90
+ padding: 8px 0;
91
+ overflow: hidden;
92
+ /* border: 0.5px solid #00358033; */
93
+ box-shadow: -20px 20px 40px -4px rgba(145, 158, 171, 0.24), 0px 0px 2px 0px rgba(145, 158, 171, 0.24);
94
+ }
95
+
96
+ .select1-popup>.header-search {
97
+ width: 100%;
98
+ padding: 0.8rem 1.6rem;
99
+ box-sizing: border-box;
100
+ align-items: center;
101
+ border-bottom: var(--border-grey1);
102
+ }
103
+
104
+ .select1-popup>.header-search>input {
105
+ outline: none;
106
+ padding: 0;
107
+ border: 0;
108
+ font: 400 1.4rem/2.2rem "Inter";
109
+ color: #1C2430;
110
+ min-width: 0;
111
+ flex: 1;
112
+ max-width: 100%;
113
+ }
114
+
115
+ .select1-popup>.select1-body {
116
+ width: 100%;
117
+ align-items: start;
118
+ row-gap: 2px;
119
+ overflow: hidden;
120
+ }
121
+
122
+ .select1-popup>.select1-body>.select1-scroll-view {
123
+ overflow: hidden auto;
124
+ max-height: 26rem;
125
+ min-height: 8rem;
126
+ width: 100%;
127
+ align-items: start;
128
+ }
129
+
130
+ .select1-popup>.select1-body .select1-tile * {
131
+ pointer-events: none;
132
+ }
133
+
134
+ .select1-popup>.select1-body .select1-tile {
135
+ justify-content: start;
136
+ align-items: center;
137
+ padding: 1.2rem 1.6rem;
138
+ gap: 0.8rem;
139
+ width: 100%;
140
+ border: none;
141
+ outline: none;
142
+ background-color: transparent;
143
+ text-align: start;
144
+ }
145
+
146
+ .select1-popup>.select1-body .select1-tile:hover {
147
+ background-color: var(--infor-color) !important;
148
+ color: white;
149
+ }
150
+
151
+ .no-results-found {
152
+ padding: 8px;
153
+ font: 400 1.4rem/2rem "Inter";
154
+ color: #1C2430;
155
+ }
@@ -0,0 +1,241 @@
1
+ import React, { CSSProperties, FocusEvent, ReactNode } from 'react'
2
+ import {
3
+ faChevronDown,
4
+ faChevronUp,
5
+ faSearch,
6
+ } from '@fortawesome/free-solid-svg-icons'
7
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
8
+ import ReactDOM from 'react-dom'
9
+ import './select1.css'
10
+
11
+ interface OptionsItem {
12
+ id: string | number,
13
+ name: string | ReactNode,
14
+ title?: string | ReactNode
15
+ }
16
+
17
+ interface Select1Props {
18
+ value?: any,
19
+ options: Required<Array<OptionsItem>>,
20
+ onChange?: (value?: OptionsItem) => void,
21
+ placeholder?: string,
22
+ disabled?: boolean,
23
+ className?: string,
24
+ helperText?: string,
25
+ helperTextColor?: string,
26
+ style?: CSSProperties,
27
+ searchPlaceholder?: string,
28
+ hideSearch?: boolean,
29
+ handleSearch?: (e: string) => Promise<Array<OptionsItem>>
30
+ };
31
+
32
+ interface Select1State {
33
+ value?: string | number,
34
+ offset: DOMRect,
35
+ isOpen: boolean,
36
+ onSelect: any,
37
+ search?: Array<OptionsItem>,
38
+ style?: Object
39
+ };
40
+
41
+ export class Select1 extends React.Component<Select1Props, Select1State> {
42
+ constructor(props: Select1Props) {
43
+ super(props)
44
+ this.state = {
45
+ value: this.props.value,
46
+ offset: {
47
+ x: 0,
48
+ y: 0,
49
+ height: 0,
50
+ width: 0,
51
+ bottom: 0,
52
+ left: 0,
53
+ right: 0,
54
+ top: 0,
55
+ toJSON: function () {
56
+ throw new Error('Function not implemented.')
57
+ }
58
+ },
59
+ isOpen: false,
60
+ onSelect: null,
61
+ }
62
+ this.parseValue = this.parseValue.bind(this)
63
+ this.onChangeValue = this.onChangeValue.bind(this)
64
+ this.search = this.search.bind(this)
65
+ }
66
+
67
+ private parseValue(value: any) {
68
+ if (value === null || value === undefined || value === '') {
69
+ return null
70
+ }
71
+ switch (typeof this.props.options[0].id) {
72
+ case 'number':
73
+ return parseFloat(value)
74
+ default:
75
+ return value
76
+ }
77
+ }
78
+
79
+ componentDidUpdate(prevProps: Select1Props, prevState: Select1State) {
80
+ if (prevProps.value !== this.props.value) {
81
+ this.setState({
82
+ ...this.state,
83
+ value: this.props.value
84
+ })
85
+ }
86
+ if (prevState.isOpen !== this.state.isOpen && this.state.isOpen) {
87
+ const thisPopupRect = document.body.querySelector('.select1-popup')?.getBoundingClientRect()
88
+ if (thisPopupRect) {
89
+ let style: { top?: string, left?: string, right?: string, bottom?: string, width?: string, height?: string } | undefined;
90
+ if (thisPopupRect.right > document.body.offsetWidth) {
91
+ style = {
92
+ top: this.state.offset.y + this.state.offset.height + 2 + 'px',
93
+ width: `${this.state.offset.width}px`,
94
+ right: document.body.offsetWidth - this.state.offset.right + 'px'
95
+ }
96
+ }
97
+ if (thisPopupRect.bottom > document.body.offsetHeight) {
98
+ style = style ? {
99
+ ...style,
100
+ top: undefined,
101
+ bottom: document.body.offsetHeight - this.state.offset.bottom + 'px'
102
+ } : {
103
+ left: this.state.offset.x + 'px',
104
+ width: `${this.state.offset.width}px`,
105
+ bottom: document.body.offsetHeight - this.state.offset.bottom + 'px'
106
+ }
107
+ }
108
+ if (style) this.setState({ ...this.state, style: style })
109
+ }
110
+ }
111
+ }
112
+
113
+ private onChangeValue(ev: FocusEvent) {
114
+ if (this.state.onSelect?.classList?.contains('select1-tile')) {
115
+ const item = this.props.options.find(e => e.id === this.parseValue(this.state.onSelect.id))
116
+ this.setState({
117
+ ...this.state,
118
+ isOpen: false,
119
+ onSelect: null,
120
+ value: item?.id
121
+ })
122
+ if (this.props.onChange) this.props.onChange(item)
123
+ } else if (this.state.onSelect) {
124
+ (ev.target as HTMLInputElement).focus()
125
+ } else {
126
+ this.setState({
127
+ ...this.state,
128
+ isOpen: false,
129
+ onSelect: null
130
+ })
131
+ }
132
+ }
133
+
134
+ private search(ev: React.ChangeEvent<HTMLInputElement>) {
135
+ if (ev.target.value.trim().length) {
136
+ if (this.props.handleSearch) {
137
+ this.props.handleSearch(ev.target.value.trim()).then(res => {
138
+ this.setState({
139
+ ...this.state,
140
+ search: res
141
+ })
142
+ })
143
+ } else {
144
+ this.setState({
145
+ ...this.state,
146
+ search: this.props.options.filter(e => typeof e.name === 'string' && e.name.toLowerCase().includes(ev.target.value.trim().toLowerCase()))
147
+ })
148
+ }
149
+ } else {
150
+ this.setState({
151
+ ...this.state,
152
+ search: undefined
153
+ })
154
+ }
155
+ }
156
+
157
+ render() {
158
+ const selectedValue: OptionsItem | undefined = (this.props.options ?? []).find(e => e.id === this.state.value)
159
+ return <div
160
+ className={`select1-container row ${this.props.className ?? 'placeholder-2'} ${this.props.disabled ? 'disabled' : ''} ${this.props.helperText?.length && 'helper-text'}`}
161
+ helper-text={this.props.helperText}
162
+ 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)}
163
+ onClick={ev => {
164
+ if (!this.state.isOpen) {
165
+ let _offset = ((ev.target as HTMLElement).closest('.select1-container') ?? (ev.target as HTMLElement)).getBoundingClientRect()
166
+ if (_offset.bottom + 32 > document.body.offsetHeight) {
167
+ this.setState({
168
+ ...this.state,
169
+ isOpen: true,
170
+ offset: _offset,
171
+ style: {
172
+ left: _offset.x + 'px',
173
+ bottom: (document.body.offsetHeight - _offset.y) + 'px',
174
+ width: `${_offset.width / 10}rem`
175
+ }
176
+ })
177
+ } else {
178
+ this.setState({
179
+ ...this.state,
180
+ isOpen: true,
181
+ offset: _offset,
182
+ style: undefined
183
+ })
184
+ }
185
+ }
186
+ }}
187
+ >
188
+ {selectedValue?.name ? (<div className='select1-value-name'>{selectedValue.name}</div>) : (<div className='select1-placeholder'>{this.props.placeholder}</div>)}
189
+ <FontAwesomeIcon
190
+ icon={this.state.isOpen ? faChevronUp : faChevronDown}
191
+ style={{ fontSize: '1.2rem', color: '#888' }}
192
+ />
193
+ {this.state.isOpen &&
194
+ ReactDOM.createPortal(
195
+ <div
196
+ className='select1-popup col'
197
+ style={this.state.style ?? {
198
+ top: this.state.offset.y + this.state.offset.height + 2 + 'px',
199
+ left: this.state.offset.x + 'px',
200
+ width: this.state.offset.width,
201
+ }}
202
+ onMouseOver={ev => {
203
+ this.setState({
204
+ ...this.state,
205
+ onSelect: ev.target
206
+ })
207
+ }}
208
+ onMouseOut={() =>
209
+ this.setState({
210
+ ...this.state,
211
+ onSelect: null
212
+ })
213
+ }
214
+ >
215
+ <div className='row header-search' style={this.props.hideSearch ? { marginTop: '-4rem' } : undefined}>
216
+ <input autoFocus={true} placeholder={this.props.searchPlaceholder ?? 'Tìm kiếm'}
217
+ onChange={this.search}
218
+ onBlur={ev => {
219
+ this.onChangeValue(ev)
220
+ }}
221
+ />
222
+ <FontAwesomeIcon icon={faSearch} style={{ fontSize: '1.2rem', color: '#161D24D9' }} />
223
+ </div>
224
+ <div className='col select1-body'>
225
+ <div className='col select1-scroll-view'>
226
+ {(this.state.search ?? this.props.options).map(item => (
227
+ <button type='button' key={item.id} className='select1-tile label-3 row' id={`${item.id}`} style={{ backgroundColor: selectedValue?.id === item.id ? 'var(--selected-background)' : '#00000000' }}>
228
+ {item.title ?? item.name}
229
+ </button>
230
+ ))}
231
+ {(this.state.search?.length === 0 || this.props.options?.length === 0) && (
232
+ <div className='no-results-found'>No result found</div>
233
+ )}
234
+ </div>
235
+ </div>
236
+ </div>,
237
+ document.body
238
+ )}
239
+ </div>
240
+ }
241
+ }
@@ -0,0 +1,16 @@
1
+ .custom-slider-container {
2
+ --content-background-color: #00000000 !important;
3
+ }
4
+
5
+ .custom-slider-container .awssld__content>* {
6
+ width: 100%;
7
+ height: 100%;
8
+ }
9
+
10
+ .custom-slider-container .awssld__controls button {
11
+ padding: 2.4rem;
12
+ width: fit-content;
13
+ height: fit-content;
14
+ top: 50%;
15
+ transform: translateY(-50%);
16
+ }