mirror of
				https://gitlab.nic.cz/turris/reforis/foris-js.git
				synced 2025-11-03 23:00:31 +01:00 
			
		
		
		
	Compare commits
	
		
			729 Commits
		
	
	
		
			v1.1.0
			...
			feature/EH
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					333ab3a40b | ||
| 
						 | 
					9da7bf6bdc | ||
| 
						 | 
					15da3249fc | ||
| 
						 | 
					5a8281393a | ||
| 
						 | 
					d0632a4c82 | ||
| 
						 | 
					4d4d37034a | ||
| 
						 | 
					7afbd07ab4 | ||
| 
						 | 
					ff13566f2a | ||
| 
						 | 
					835a6e6d2b | ||
| 
						 | 
					69b1b38202 | ||
| 
						 | 
					d6fda7d732 | ||
| 
						 | 
					602e3f58dd | ||
| 
						 | 
					4b58e96f71 | ||
| 
						 | 
					a174d6a612 | ||
| 
						 | 
					5d0276a80f | ||
| 
						 | 
					e01295504b | ||
| 
						 | 
					af49bc7a24 | ||
| 
						 | 
					4a60fb23cc | ||
| 
						 | 
					c7282261ef | ||
| 
						 | 
					928758f5c6 | ||
| 
						 | 
					030a563c77 | ||
| 
						 | 
					336fb666cc | ||
| 
						 | 
					debd00d519 | ||
| 
						 | 
					cef75e5748 | ||
| 
						 | 
					027cd6eefb | ||
| 
						 | 
					227a975e5f | ||
| 
						 | 
					819e5a1dd2 | ||
| 
						 | 
					6432073d62 | ||
| 
						 | 
					94f436008d | ||
| 
						 | 
					6f9e44a7b1 | ||
| 
						 | 
					13ca745412 | ||
| 
						 | 
					a25133d786 | ||
| 
						 | 
					0a839bf369 | ||
| 
						 | 
					54a801a580 | ||
| 
						 | 
					377b4279fd | ||
| 
						 | 
					317966e1c9 | ||
| 
						 | 
					326790d80d | ||
| 
						 | 
					700b28c463 | ||
| 
						 | 
					3d725e7e1b | ||
| 
						 | 
					ede4fb0212 | ||
| 
						 | 
					33add77704 | ||
| 
						 | 
					456cbcfeec | ||
| 
						 | 
					bf0b2ce70c | ||
| 
						 | 
					1441f6ff5a | ||
| 
						 | 
					c7d0655771 | ||
| 
						 | 
					7197813cc9 | ||
| 
						 | 
					31cb8e2ae0 | ||
| 
						 | 
					0a75f24a04 | ||
| 
						 | 
					230ae8e35b | ||
| 
						 | 
					eb4ffb0651 | ||
| 
						 | 
					2f249ce3dc | ||
| 
						 | 
					74722b8ff3 | ||
| 
						 | 
					a2f9b3bfab | ||
| 
						 | 
					2ab65be0bf | ||
| 
						 | 
					36a7b4dfda | ||
| 
						 | 
					9fb0871cfc | ||
| 
						 | 
					c7087eabf2 | ||
| 
						 | 
					b315ea2fd0 | ||
| 
						 | 
					d46629a1bd | ||
| 
						 | 
					8ddb590ba8 | ||
| 
						 | 
					1c2a4518d3 | ||
| 
						 | 
					b6312075d2 | ||
| 
						 | 
					2feedec8d1 | ||
| 
						 | 
					dff5f87e91 | ||
| 
						 | 
					38de792390 | ||
| 
						 | 
					c23616811a | ||
| 
						 | 
					f1132c6b22 | ||
| 
						 | 
					e8e81b36dc | ||
| 
						 | 
					53c7bb1a10 | ||
| 
						 | 
					fb1f79c6c1 | ||
| 
						 | 
					eafbc01c73 | ||
| 
						 | 
					73819809f4 | ||
| 
						 | 
					ffa1121d39 | ||
| 
						 | 
					ee6865e3bb | ||
| 
						 | 
					6352060da3 | ||
| 
						 | 
					a63b5bfa4e | ||
| 
						 | 
					4b2e47f8f9 | ||
| 
						 | 
					66f83b24bd | ||
| 
						 | 
					30fd6f91b4 | ||
| 
						 | 
					5a53eca138 | ||
| 
						 | 
					8d2a4dc108 | ||
| 
						 | 
					2481a0c025 | ||
| 
						 | 
					71697424c5 | ||
| 
						 | 
					07f8e3b9de | ||
| 
						 | 
					c9f2b24095 | ||
| 
						 | 
					087ecfa670 | ||
| 
						 | 
					e6365ecac4 | ||
| 
						 | 
					e57722caa0 | ||
| 
						 | 
					babdf92ddd | ||
| 
						 | 
					42294316d9 | ||
| 
						 | 
					b65e034b04 | ||
| 
						 | 
					14b90bbbd4 | ||
| 
						 | 
					85b207b1dd | ||
| 
						 | 
					79e61d9507 | ||
| 
						 | 
					6795c3941b | ||
| 
						 | 
					969e8e6411 | ||
| 
						 | 
					0099759279 | ||
| 
						 | 
					87c81a2a2d | ||
| 
						 | 
					81b71f8153 | ||
| 
						 | 
					c0fd0adbc9 | ||
| 
						 | 
					1ec0a26199 | ||
| 
						 | 
					76e37b738a | ||
| 
						 | 
					8a69d14429 | ||
| 
						 | 
					4d5395c826 | ||
| 
						 | 
					1fb83e08ea | ||
| 
						 | 
					e6cfc6dbb0 | ||
| 
						 | 
					a93a64bf96 | ||
| 
						 | 
					1ab77decfd | ||
| 
						 | 
					b6e1e0adae | ||
| 
						 | 
					a7a4e76cd1 | ||
| 
						 | 
					e7758cab9a | ||
| 
						 | 
					bf88b76613 | ||
| 
						 | 
					3cf85a9516 | ||
| 
						 | 
					7c8442300a | ||
| 
						 | 
					e849397aa2 | ||
| 
						 | 
					c1b44d498c | ||
| 
						 | 
					1b5a5da953 | ||
| 
						 | 
					7f45201f05 | ||
| 
						 | 
					f34d9bbdbd | ||
| 
						 | 
					c7ff3f42f6 | ||
| 
						 | 
					a1036514dd | ||
| 
						 | 
					a352f12279 | ||
| 
						 | 
					acfbeb2c43 | ||
| 
						 | 
					3bef624ce4 | ||
| 
						 | 
					7d0d52666d | ||
| 
						 | 
					52fe5d65a6 | ||
| 
						 | 
					b99add91cf | ||
| 
						 | 
					b7a4613cf4 | ||
| 
						 | 
					9e2278e016 | ||
| 
						 | 
					83a6ff75f6 | ||
| 
						 | 
					02f3803265 | ||
| 
						 | 
					bb559cbe53 | ||
| 
						 | 
					c86e2c8944 | ||
| 
						 | 
					b96ccde81c | ||
| 
						 | 
					cfa6eade17 | ||
| 
						 | 
					380a388a38 | ||
| 
						 | 
					cc19b4b293 | ||
| 
						 | 
					e7ec494bb2 | ||
| 
						 | 
					ea590e443c | ||
| 
						 | 
					b127bf5edf | ||
| 
						 | 
					40e4a9a4e3 | ||
| 
						 | 
					bcb7c43863 | ||
| 
						 | 
					c809817283 | ||
| 
						 | 
					a429b7c1bf | ||
| 
						 | 
					4e6f6e7413 | ||
| 
						 | 
					e6356de57f | ||
| 
						 | 
					d4a71c346c | ||
| 
						 | 
					eb9582db96 | ||
| 
						 | 
					036f191949 | ||
| 
						 | 
					2f73516384 | ||
| 
						 | 
					b97ba379ec | ||
| 
						 | 
					5f1372bb37 | ||
| 
						 | 
					7c9cd9451b | ||
| 
						 | 
					7e0752fc17 | ||
| 
						 | 
					4c5aeed26e | ||
| 
						 | 
					d90e39a570 | ||
| 
						 | 
					2e2f326ade | ||
| 
						 | 
					541ca7a784 | ||
| 
						 | 
					8e0c60a576 | ||
| 
						 | 
					928cf716d6 | ||
| 
						 | 
					bd8d5bc8cb | ||
| 
						 | 
					804e0022eb | ||
| 
						 | 
					d69398ac06 | ||
| 
						 | 
					e297410f16 | ||
| 
						 | 
					17e5a959f7 | ||
| 
						 | 
					3b48510246 | ||
| 
						 | 
					8bac4f18f4 | ||
| 
						 | 
					6cb2a5388e | ||
| 
						 | 
					0b02bead71 | ||
| 
						 | 
					12c6d05ca6 | ||
| 
						 | 
					923bbab6d5 | ||
| 
						 | 
					0ea5d43c75 | ||
| 
						 | 
					c209796118 | ||
| 
						 | 
					bf7e5303e9 | ||
| 
						 | 
					59b3130277 | ||
| 
						 | 
					4ca07dceb0 | ||
| 
						 | 
					912f8facdb | ||
| 
						 | 
					42fb16d066 | ||
| 
						 | 
					cd9eb80e9c | ||
| 
						 | 
					5a77a22755 | ||
| 
						 | 
					3fa5ab7c07 | ||
| 
						 | 
					ff48d6ca36 | ||
| 
						 | 
					39257567d4 | ||
| 
						 | 
					5ed48bf2a3 | ||
| 
						 | 
					c8fbdc5bba | ||
| 
						 | 
					46bd8edcea | ||
| 
						 | 
					42fcc02d83 | ||
| 
						 | 
					96785f0774 | ||
| 
						 | 
					7823bff6d9 | ||
| 
						 | 
					bee4bee300 | ||
| 
						 | 
					0fd7c08188 | ||
| 
						 | 
					32630601c2 | ||
| 
						 | 
					03aad5498a | ||
| 
						 | 
					2f2b812ddb | ||
| 
						 | 
					c0bcd46b2b | ||
| 
						 | 
					68ea8cf460 | ||
| 
						 | 
					79fe68dba3 | ||
| 
						 | 
					683a8736a6 | ||
| 
						 | 
					6631d4847b | ||
| 
						 | 
					8887d0d68e | ||
| 
						 | 
					390e4bdce8 | ||
| 
						 | 
					5232b55cf6 | ||
| 
						 | 
					5823012c6e | ||
| 
						 | 
					e907a3a21f | ||
| 
						 | 
					9e4cb4b417 | ||
| 
						 | 
					55f4d2ff15 | ||
| 
						 | 
					6b464783ed | ||
| 
						 | 
					85ba270135 | ||
| 
						 | 
					80619fab3c | ||
| 
						 | 
					a1a47e0d0f | ||
| 
						 | 
					d49ff0150c | ||
| 
						 | 
					a0f42906f5 | ||
| 
						 | 
					bc1b6e7877 | ||
| 
						 | 
					efb3fa80ce | ||
| 
						 | 
					9c3dcaf6b5 | ||
| 
						 | 
					fb41c9629e | ||
| 
						 | 
					620eee3f53 | ||
| 
						 | 
					f6ec82609c | ||
| 
						 | 
					0b47c38f21 | ||
| 
						 | 
					6183669c9b | ||
| 
						 | 
					f3694bb62c | ||
| 
						 | 
					0f2344a005 | ||
| 
						 | 
					f2ae6c4d0a | ||
| 
						 | 
					f382e743aa | ||
| 
						 | 
					d71f4a7967 | ||
| 
						 | 
					aeabc0bf06 | ||
| 
						 | 
					46fe75d3cf | ||
| 
						 | 
					c469d8dfde | ||
| 
						 | 
					f327428765 | ||
| 
						 | 
					5a359661da | ||
| 
						 | 
					3d30e2720e | ||
| 
						 | 
					badb043554 | ||
| 
						 | 
					ab692e5c4d | ||
| 
						 | 
					2e398388b5 | ||
| 
						 | 
					5e539de03f | ||
| 
						 | 
					87f5557ef6 | ||
| 
						 | 
					f128c5c7d6 | ||
| 
						 | 
					7a633574da | ||
| 
						 | 
					ee40697e2b | ||
| 
						 | 
					4525c6bc66 | ||
| 
						 | 
					1d6987b21b | ||
| 
						 | 
					67fc2d32ce | ||
| 
						 | 
					ab13b7aa08 | ||
| 
						 | 
					2a73502710 | ||
| 
						 | 
					003606cb8c | ||
| 
						 | 
					aeddd9ce74 | ||
| 
						 | 
					a4fd74bf38 | ||
| 
						 | 
					b0e2f62a41 | ||
| 
						 | 
					caf8af44d1 | ||
| 
						 | 
					fd7cd49790 | ||
| 
						 | 
					d95fdf8517 | ||
| 
						 | 
					68c560078b | ||
| 
						 | 
					83caf505d9 | ||
| 
						 | 
					f3a1090dbd | ||
| 
						 | 
					d588291f1c | ||
| 
						 | 
					bc044df7a8 | ||
| 
						 | 
					b4c6a7fb70 | ||
| 
						 | 
					d6563d2ffd | ||
| 
						 | 
					af90d8d09d | ||
| 
						 | 
					006d6ce8d9 | ||
| 
						 | 
					cee08f48ce | ||
| 
						 | 
					2d0ca58057 | ||
| 
						 | 
					db4a6fb763 | ||
| 
						 | 
					caaa154d21 | ||
| 
						 | 
					518fa30306 | ||
| 
						 | 
					fb3562373a | ||
| 
						 | 
					5afbbac74c | ||
| 
						 | 
					f25832432b | ||
| 
						 | 
					926cb2505f | ||
| 
						 | 
					985fd08b46 | ||
| 
						 | 
					da019b6d86 | ||
| 
						 | 
					514f02a5f6 | ||
| 
						 | 
					c6557aae5e | ||
| 
						 | 
					92ed7f1ee7 | ||
| 
						 | 
					46ce6ebbb9 | ||
| 
						 | 
					1a4ba03ff5 | ||
| 
						 | 
					e24cd76009 | ||
| 
						 | 
					ae6b495683 | ||
| 
						 | 
					272c97dc8a | ||
| 
						 | 
					fd25ae04a8 | ||
| 
						 | 
					cc1b0b3f81 | ||
| 
						 | 
					3ba279f42c | ||
| 
						 | 
					0167b7ce66 | ||
| 
						 | 
					9db509ace3 | ||
| 
						 | 
					d42347f169 | ||
| 
						 | 
					82c34e5d42 | ||
| 
						 | 
					7867a1a494 | ||
| 
						 | 
					1bb8abd633 | ||
| 
						 | 
					536ccc0f03 | ||
| 
						 | 
					671c711a33 | ||
| 
						 | 
					e2f3e6857e | ||
| 
						 | 
					fe4ab298d8 | ||
| 
						 | 
					0f7f89dfc0 | ||
| 
						 | 
					be2e3fe3f0 | ||
| 
						 | 
					577ad70c06 | ||
| 
						 | 
					d17638eb6e | ||
| 
						 | 
					13869336db | ||
| 
						 | 
					7c46abcd5d | ||
| 
						 | 
					894d92b683 | ||
| 
						 | 
					ca335ab3a5 | ||
| 
						 | 
					2161fc0b32 | ||
| 
						 | 
					0a23506a38 | ||
| 
						 | 
					d23c7cb790 | ||
| 
						 | 
					129327cfcf | ||
| 
						 | 
					0a143e7de6 | ||
| 
						 | 
					7ec1c46a63 | ||
| 
						 | 
					7ceccd5222 | ||
| 
						 | 
					f868881b3d | ||
| 
						 | 
					188ed87fc0 | ||
| 
						 | 
					c0f64e8c6c | ||
| 
						 | 
					b95cfb664d | ||
| 
						 | 
					52cdaf5bc5 | ||
| 
						 | 
					175a07a865 | ||
| 
						 | 
					f952e25205 | ||
| 
						 | 
					01eeb06f9e | ||
| 
						 | 
					839e227feb | ||
| 
						 | 
					4b239ed195 | ||
| 
						 | 
					2bcbe0ae59 | ||
| 
						 | 
					b1ff608337 | ||
| 
						 | 
					b662ba5b52 | ||
| 
						 | 
					a4115245fe | ||
| 
						 | 
					e1a893874a | ||
| 
						 | 
					dd383ef1b2 | ||
| 
						 | 
					b6e2cb71bb | ||
| 048e686185 | |||
| eacb2f66a3 | |||
| 
						 | 
					2433641f56 | ||
| 
						 | 
					185d2e6436 | ||
| 
						 | 
					7f262d4941 | ||
| 
						 | 
					72356bb6c1 | ||
| 
						 | 
					13c94caeb5 | ||
| 
						 | 
					c24e58fae8 | ||
| 
						 | 
					6329b5a104 | ||
| 
						 | 
					fbac503586 | ||
| 
						 | 
					550af8967c | ||
| 
						 | 
					3640d6a90a | ||
| 
						 | 
					7b2bc43f3f | ||
| 
						 | 
					1e693b0963 | ||
| 
						 | 
					afde04c662 | ||
| 
						 | 
					22fb7dcf58 | ||
| 
						 | 
					b557b67308 | ||
| 
						 | 
					cc6e5e2933 | ||
| 
						 | 
					60f850a095 | ||
| 
						 | 
					a1e9f23620 | ||
| 
						 | 
					579ed5ea8c | ||
| 
						 | 
					c2eda33998 | ||
| 
						 | 
					f49529018c | ||
| 
						 | 
					a66a2f4708 | ||
| 
						 | 
					2e473003bd | ||
| 
						 | 
					43cb5bff50 | ||
| 
						 | 
					c67ea089fd | ||
| 
						 | 
					4b25f6eafc | ||
| 
						 | 
					c1e807bc74 | ||
| 
						 | 
					69da5afffe | ||
| 
						 | 
					1669ac8576 | ||
| 
						 | 
					6e6c349866 | ||
| 
						 | 
					5207029462 | ||
| 
						 | 
					53aec6372d | ||
| 
						 | 
					a7d7e59028 | ||
| 
						 | 
					0beb1f0418 | ||
| 
						 | 
					2644f6fd70 | ||
| 
						 | 
					585fec4e3e | ||
| 
						 | 
					682abc126a | ||
| 
						 | 
					a9f3f77bd5 | ||
| 
						 | 
					4703721c5c | ||
| 
						 | 
					aff1ba7b6d | ||
| 
						 | 
					9eb7197035 | ||
| 
						 | 
					462a86b31d | ||
| 
						 | 
					cbce4c1ec1 | ||
| 
						 | 
					aee19694b5 | ||
| 
						 | 
					f3b1ef741a | ||
| 
						 | 
					c35a4a8236 | ||
| 
						 | 
					67b8386cd0 | ||
| 
						 | 
					f67edc39e1 | ||
| 
						 | 
					6f0f344eb4 | ||
| 
						 | 
					3a39e44c34 | ||
| 
						 | 
					cff5f1e5e1 | ||
| 
						 | 
					b7bab92d5d | ||
| 
						 | 
					75dd0fec92 | ||
| 
						 | 
					3619532124 | ||
| 
						 | 
					ce62fd1043 | ||
| 
						 | 
					c5bac99d8e | ||
| 
						 | 
					f7146e3b14 | ||
| 
						 | 
					18ba90567c | ||
| 
						 | 
					2e9da55df7 | ||
| 
						 | 
					da10a34d64 | ||
| 
						 | 
					764a6c86cd | ||
| 
						 | 
					6059ce9e7b | ||
| 
						 | 
					4368bea2c2 | ||
| 
						 | 
					9dd6bbca90 | ||
| 
						 | 
					d5bb99570c | ||
| 
						 | 
					e1260a5ea1 | ||
| 
						 | 
					02f2c5be4f | ||
| 
						 | 
					ce04f6c27e | ||
| 
						 | 
					80d4dd914d | ||
| 
						 | 
					7f82b2e73c | ||
| 
						 | 
					ac8646a4e7 | ||
| 
						 | 
					7505302875 | ||
| 
						 | 
					adc6bbca14 | ||
| 
						 | 
					86f98148c6 | ||
| 
						 | 
					f623b98acc | ||
| 
						 | 
					3be1213b3b | ||
| 
						 | 
					09007b922e | ||
| 
						 | 
					f6231370b9 | ||
| 
						 | 
					449b93ce41 | ||
| 
						 | 
					764c8dedd8 | ||
| 
						 | 
					9bfd20ef0c | ||
| 
						 | 
					0289c5010f | ||
| 
						 | 
					1733b8609b | ||
| 
						 | 
					d5c3365fdb | ||
| 
						 | 
					0ba4814275 | ||
| 
						 | 
					fca410ec82 | ||
| 
						 | 
					4f09c2da9a | ||
| 
						 | 
					57ef9c4ea0 | ||
| 
						 | 
					b7695cc854 | ||
| 
						 | 
					fd8b8b926a | ||
| 
						 | 
					b91ec527d1 | ||
| 
						 | 
					7369d906b5 | ||
| 
						 | 
					45fee77426 | ||
| 
						 | 
					b12cba893e | ||
| 
						 | 
					09d1698647 | ||
| 
						 | 
					83c05c6c89 | ||
| 
						 | 
					a08de54ca1 | ||
| 
						 | 
					cb5fa4ce34 | ||
| 
						 | 
					fb32c84dc2 | ||
| 
						 | 
					4060b3c916 | ||
| 
						 | 
					7abfd627e4 | ||
| 
						 | 
					0fbc3df247 | ||
| 
						 | 
					bc9c00d3a1 | ||
| 
						 | 
					8d75b5ec6e | ||
| 
						 | 
					c1aa1948b4 | ||
| 
						 | 
					8c110ebf52 | ||
| 
						 | 
					abb5be53aa | ||
| 
						 | 
					af0fb80e45 | ||
| 
						 | 
					688192504f | ||
| 
						 | 
					b8e5dbec8d | ||
| 
						 | 
					bcb5365d08 | ||
| 
						 | 
					037d1993c8 | ||
| 
						 | 
					2287ddc420 | ||
| 
						 | 
					fde751a25f | ||
| 
						 | 
					79006cfb99 | ||
| 
						 | 
					de398901f3 | ||
| 
						 | 
					bea429d6ac | ||
| 
						 | 
					e818120986 | ||
| 
						 | 
					56173d4959 | ||
| 
						 | 
					7c837d041e | ||
| 
						 | 
					473c81f9a4 | ||
| 
						 | 
					ba9abca5cf | ||
| 
						 | 
					15567a7dde | ||
| 
						 | 
					e2695d49a1 | ||
| 
						 | 
					a87e6858bf | ||
| 
						 | 
					e864de5a24 | ||
| 
						 | 
					5469e6ec80 | ||
| 
						 | 
					4898016388 | ||
| 
						 | 
					e0fab75c69 | ||
| 
						 | 
					6480a39cdb | ||
| 
						 | 
					6f05d5d136 | ||
| 
						 | 
					96150fe230 | ||
| 
						 | 
					0892a1534a | ||
| 
						 | 
					1bac60e054 | ||
| 
						 | 
					328e568ab3 | ||
| 
						 | 
					c68389359e | ||
| 
						 | 
					e03e0f44cc | ||
| 
						 | 
					1e04d34645 | ||
| 
						 | 
					187ecc54e5 | ||
| 
						 | 
					ed7cf34e76 | ||
| 
						 | 
					aaf4087c96 | ||
| 
						 | 
					240db88661 | ||
| 
						 | 
					913a7d7b75 | ||
| 
						 | 
					bdc8726791 | ||
| 
						 | 
					1c986519f6 | ||
| 
						 | 
					defc363f01 | ||
| 
						 | 
					ef66fb43cc | ||
| 
						 | 
					69723f6b0b | ||
| 
						 | 
					c32137e29a | ||
| 
						 | 
					03cf73be6e | ||
| 
						 | 
					be7349661f | ||
| 
						 | 
					5186385b9f | ||
| 
						 | 
					002786d073 | ||
| 
						 | 
					4d246540c1 | ||
| 
						 | 
					35b97ec0fe | ||
| 
						 | 
					d2688532af | ||
| 
						 | 
					e1d75d8328 | ||
| 
						 | 
					0f85713483 | ||
| 
						 | 
					c3cdafce13 | ||
| 
						 | 
					b96b434a3e | ||
| 
						 | 
					0ea5f7de84 | ||
| 
						 | 
					0c7997f6c0 | ||
| 
						 | 
					90ce866869 | ||
| 
						 | 
					ad99a2034d | ||
| 
						 | 
					4ff814f0fd | ||
| 
						 | 
					896277b62a | ||
| 
						 | 
					b0365e3b06 | ||
| 
						 | 
					8bd71a08af | ||
| 
						 | 
					1903016f13 | ||
| 
						 | 
					443f14d26c | ||
| 
						 | 
					f1feffb4bb | ||
| 
						 | 
					61b349c6cc | ||
| 
						 | 
					7a98ab0c2d | ||
| 
						 | 
					5de05fe4eb | ||
| 
						 | 
					50943e0b11 | ||
| 
						 | 
					f64419c643 | ||
| 
						 | 
					a0f7a312e5 | ||
| 
						 | 
					f8726e6012 | ||
| 
						 | 
					e41da48b1a | ||
| 
						 | 
					a434ecac18 | ||
| 
						 | 
					5ae129b0f5 | ||
| 
						 | 
					a2acac255d | ||
| 
						 | 
					c1b1d8c079 | ||
| 
						 | 
					e422acc92f | ||
| 
						 | 
					705ed5ac80 | ||
| 
						 | 
					1dd1805ae0 | ||
| 
						 | 
					e858b30994 | ||
| 
						 | 
					8a56d71c51 | ||
| 
						 | 
					d34c465787 | ||
| 
						 | 
					cbf37dd747 | ||
| 
						 | 
					f9cfb248d3 | ||
| 
						 | 
					9be880aeaa | ||
| 
						 | 
					a4bb41d585 | ||
| 
						 | 
					c3b09b01e5 | ||
| 
						 | 
					12b862c568 | ||
| 
						 | 
					54f9f984f1 | ||
| 
						 | 
					5dbc58d44b | ||
| 
						 | 
					e7f9fbca96 | ||
| 
						 | 
					8d40dbb841 | ||
| 
						 | 
					cea8aa0c12 | ||
| 
						 | 
					16a7a6c52d | ||
| 
						 | 
					597b6fcf4c | ||
| 
						 | 
					5eb6b90ed4 | ||
| 
						 | 
					48c323c1a1 | ||
| 
						 | 
					3d57b38808 | ||
| 
						 | 
					ae8baddbdd | ||
| 
						 | 
					67e4abe4d1 | ||
| 
						 | 
					57f1ccced8 | ||
| 
						 | 
					1e95bff7ff | ||
| 
						 | 
					0f253ecc19 | ||
| 
						 | 
					a5e096dc00 | ||
| 
						 | 
					074ddf8a8b | ||
| 
						 | 
					182cbe698f | ||
| 
						 | 
					982eb371ad | ||
| 
						 | 
					2786f856f7 | ||
| 
						 | 
					48b080dc26 | ||
| 
						 | 
					71beeb46f1 | ||
| 
						 | 
					060a0489e1 | ||
| 
						 | 
					ae49b246cd | ||
| 
						 | 
					27c37eb74b | ||
| 
						 | 
					cd708fa294 | ||
| 
						 | 
					8ec0392852 | ||
| 
						 | 
					27a5e62d9a | ||
| 
						 | 
					aeaad4aa72 | ||
| 
						 | 
					256a000d61 | ||
| 
						 | 
					c78ed9a5d0 | ||
| 
						 | 
					bded10211a | ||
| 
						 | 
					25ac6cf1e9 | ||
| 
						 | 
					9a2547a6c2 | ||
| 
						 | 
					7968c7af4a | ||
| 
						 | 
					4b94c470c3 | ||
| 
						 | 
					e1b5a25ddd | ||
| 
						 | 
					95af86c776 | ||
| 
						 | 
					02b5583712 | ||
| 
						 | 
					2f4d757a1a | ||
| 
						 | 
					3c7a67783f | ||
| 
						 | 
					4500e85a40 | ||
| 
						 | 
					ce955095fd | ||
| 
						 | 
					00b861531e | ||
| 
						 | 
					fad5b97a2e | ||
| 
						 | 
					aa639596d4 | ||
| 
						 | 
					1ee41f4f14 | ||
| 
						 | 
					bf8c2d28bf | ||
| 
						 | 
					dbb840d51c | ||
| 
						 | 
					ba772be869 | ||
| 
						 | 
					70da1c3c00 | ||
| 
						 | 
					8e68bbc91f | ||
| 
						 | 
					0af8c4aa28 | ||
| 
						 | 
					a9114caf9e | ||
| 
						 | 
					3c81264024 | ||
| 
						 | 
					0330b39f2e | ||
| 
						 | 
					a7dcced08b | ||
| 
						 | 
					c453a35763 | ||
| 
						 | 
					d97248c6ec | ||
| 
						 | 
					9fbc4e8383 | ||
| 
						 | 
					57bebc92c7 | ||
| 
						 | 
					5939e9dd0e | ||
| 
						 | 
					0665869c30 | ||
| 
						 | 
					199b27d63a | ||
| 
						 | 
					2b28434712 | ||
| 
						 | 
					388860d51e | ||
| 
						 | 
					8b7c459855 | ||
| 
						 | 
					83409b0118 | ||
| 
						 | 
					c1cd90dff6 | ||
| 
						 | 
					01fb897180 | ||
| 
						 | 
					716c323b28 | ||
| 
						 | 
					55dbf8f8bb | ||
| 
						 | 
					85e42980ec | ||
| 
						 | 
					3dee532ea2 | ||
| 
						 | 
					3aac48d2bf | ||
| 
						 | 
					ee33d33738 | ||
| 
						 | 
					605f682356 | ||
| 
						 | 
					a0a775996e | ||
| 
						 | 
					532acf9d86 | ||
| 
						 | 
					cbc3c2f3e7 | ||
| 
						 | 
					556e12c964 | ||
| 
						 | 
					813a865f62 | ||
| 
						 | 
					c495aa97ac | ||
| 
						 | 
					2d375b1690 | ||
| 
						 | 
					e7e389e843 | ||
| 
						 | 
					8679749e0f | ||
| 
						 | 
					6d8e0cec70 | ||
| 
						 | 
					5091eecedf | ||
| 
						 | 
					75bfbb88ae | ||
| 
						 | 
					e5cbbc9019 | ||
| 
						 | 
					7ab1d2aaa4 | ||
| 
						 | 
					e62accc4b3 | ||
| 
						 | 
					03e071d5ee | ||
| 
						 | 
					d83ba3bfd3 | ||
| 
						 | 
					3e2c89cac7 | ||
| 
						 | 
					e3d159d6a3 | ||
| 
						 | 
					afa9b5a402 | ||
| 
						 | 
					b8555247f2 | ||
| 
						 | 
					de8462429b | ||
| 
						 | 
					5fd0d3626a | ||
| 
						 | 
					9dcc689491 | ||
| 
						 | 
					35f307200d | ||
| 
						 | 
					afb5366dd7 | ||
| 
						 | 
					1e6278abdf | ||
| 
						 | 
					6769e84e62 | ||
| 
						 | 
					71b0a9a5fa | ||
| 
						 | 
					418e38de31 | ||
| 
						 | 
					56a4c47948 | ||
| 
						 | 
					c67ad164ce | ||
| 
						 | 
					6374fd5adf | ||
| 
						 | 
					cc13e9c164 | ||
| 
						 | 
					bb90800945 | ||
| 
						 | 
					6d4bff2b4f | ||
| 
						 | 
					92f560b69f | ||
| 
						 | 
					a318f12352 | ||
| 
						 | 
					32e3a57bd7 | ||
| 
						 | 
					dd27802056 | ||
| 
						 | 
					bd4e1953e3 | ||
| 
						 | 
					68e4368ae3 | ||
| 
						 | 
					51ba380cf0 | ||
| 
						 | 
					4eae1ed8d2 | ||
| 
						 | 
					3d290114fa | ||
| 
						 | 
					3f87e9e4b4 | ||
| 
						 | 
					6d5cb6a951 | ||
| 
						 | 
					8d3be8df67 | ||
| 
						 | 
					90509f2a23 | ||
| 
						 | 
					73f84a2d81 | ||
| 
						 | 
					cea7325427 | ||
| 
						 | 
					a8d8c872f9 | ||
| 
						 | 
					cda7898a96 | ||
| 
						 | 
					26bea9c7c4 | ||
| 
						 | 
					fd1518265f | ||
| 
						 | 
					61d10e91e0 | ||
| 
						 | 
					aac6c6bf2a | ||
| 
						 | 
					d55615abcc | ||
| 
						 | 
					9d322811c3 | ||
| 
						 | 
					f30685d9c2 | ||
| 
						 | 
					5bb298270b | ||
| 
						 | 
					8d0c640994 | ||
| 
						 | 
					25ddb5949c | ||
| 
						 | 
					7b739f55a0 | ||
| 
						 | 
					03a020f87c | ||
| 
						 | 
					c6fd9bbadb | ||
| 
						 | 
					acaaab0654 | ||
| 
						 | 
					04a667eb6f | ||
| 
						 | 
					d71f638bd5 | ||
| 
						 | 
					1064277cd9 | ||
| 
						 | 
					cffa0a2b80 | ||
| 
						 | 
					8d1d5b57fd | ||
| 
						 | 
					7579fc3b8c | ||
| 
						 | 
					6601cd55e0 | ||
| 
						 | 
					fd01bc6f56 | ||
| 
						 | 
					e27a23600f | ||
| 
						 | 
					daf787e2df | ||
| 
						 | 
					512c65c213 | ||
| 
						 | 
					3925fb6439 | ||
| 
						 | 
					21bbfb6d2e | ||
| 
						 | 
					1dcba1dfa8 | ||
| 
						 | 
					fd8acd1ceb | ||
| 
						 | 
					48d5cf0119 | ||
| 
						 | 
					1a82f8e225 | ||
| 
						 | 
					638821d025 | ||
| 
						 | 
					fbaa73e378 | ||
| 
						 | 
					e54db2c577 | ||
| 
						 | 
					a6866a0673 | ||
| 
						 | 
					429814ebb5 | ||
| 
						 | 
					73e213c467 | ||
| 
						 | 
					2e68e56e44 | ||
| 
						 | 
					b3bb4de646 | ||
| 
						 | 
					b30f9f59b4 | ||
| 
						 | 
					158bd1bb24 | ||
| 
						 | 
					d935f78d6e | ||
| 
						 | 
					73f4ab48c3 | ||
| 
						 | 
					7075592f24 | ||
| 
						 | 
					23029470b9 | ||
| 
						 | 
					7e6e6f8c87 | ||
| 
						 | 
					b831d664a9 | ||
| 
						 | 
					0f5b35a3ba | ||
| 
						 | 
					6e02f1d194 | ||
| 
						 | 
					13ff8221ca | ||
| 
						 | 
					a51ba0630d | ||
| 
						 | 
					ee5cf07614 | ||
| 
						 | 
					8b39bf4193 | ||
| 
						 | 
					644726a0fc | ||
| 
						 | 
					654ae6914a | ||
| 
						 | 
					81dedf59cd | ||
| 
						 | 
					7f8aaea7b8 | ||
| 
						 | 
					dfa80e64ec | ||
| 
						 | 
					031b53f03c | ||
| 
						 | 
					ca23b2d335 | ||
| 
						 | 
					0984c45161 | ||
| 
						 | 
					6835bc7a28 | ||
| 
						 | 
					0915d477fe | ||
| 
						 | 
					c114579b7f | ||
| 
						 | 
					22b2bc9c09 | ||
| 
						 | 
					cab2cfa068 | ||
| 
						 | 
					a137a0d4cf | ||
| 
						 | 
					54cf7e3c06 | ||
| 
						 | 
					18eb28f368 | ||
| 
						 | 
					88f3836485 | ||
| 
						 | 
					a88fbf63e9 | ||
| 
						 | 
					ff9e8fdeb1 | ||
| 
						 | 
					8cd4ac8b44 | ||
| 
						 | 
					760e6d9243 | ||
| 
						 | 
					2429f4662c | ||
| 
						 | 
					b320e6a860 | ||
| 
						 | 
					1e3e9433ec | ||
| 
						 | 
					e3a795e40d | 
							
								
								
									
										65
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								.eslintrc.js
									
									
									
									
									
								
							@@ -1,66 +1,3 @@
 | 
			
		||||
const path = require("path");
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
    "env": {
 | 
			
		||||
        "browser": true,
 | 
			
		||||
        "node": true,
 | 
			
		||||
        "es6": true,
 | 
			
		||||
        "jest": true
 | 
			
		||||
    },
 | 
			
		||||
    "extends": [
 | 
			
		||||
        "airbnb",
 | 
			
		||||
        "airbnb/hooks"
 | 
			
		||||
    ],
 | 
			
		||||
    "globals": {
 | 
			
		||||
        "_": "readonly",
 | 
			
		||||
        "babel": "readonly",
 | 
			
		||||
        "ForisTranslations": "readonly",
 | 
			
		||||
        "ngettext": "readonly",
 | 
			
		||||
        "ForisPlugins": "readonly"
 | 
			
		||||
    },
 | 
			
		||||
    "parser": "babel-eslint",
 | 
			
		||||
    "rules": {
 | 
			
		||||
        "quotes": ["error", "double"],
 | 
			
		||||
        "indent": ["error", 4],
 | 
			
		||||
        "react/jsx-indent": ["error", 4],
 | 
			
		||||
        "react/jsx-indent-props": ["error", 4],
 | 
			
		||||
        "react/prop-types": "warn",
 | 
			
		||||
        "react/no-array-index-key": "warn",
 | 
			
		||||
        "react/button-has-type": "warn",
 | 
			
		||||
        "import/prefer-default-export": "off",
 | 
			
		||||
        "import/no-unresolved": [
 | 
			
		||||
            "error",
 | 
			
		||||
            // Ignore imports used only in tests
 | 
			
		||||
            { ignore: ["customTestRender"] }
 | 
			
		||||
        ],
 | 
			
		||||
        "import/no-cycle": "warn",
 | 
			
		||||
        "no-console": "error",
 | 
			
		||||
        "no-use-before-define": ["error", {
 | 
			
		||||
            functions: false,
 | 
			
		||||
            classes: true,
 | 
			
		||||
            variables: true
 | 
			
		||||
        }],
 | 
			
		||||
        "no-restricted-syntax": "warn",
 | 
			
		||||
        // Should be enabled in the future
 | 
			
		||||
        "camelcase": "off",
 | 
			
		||||
        "no-param-reassign": "off",
 | 
			
		||||
        "react/jsx-props-no-spreading": "off",
 | 
			
		||||
        "react/require-default-props": "off",
 | 
			
		||||
        "react/default-props-match-prop-types": "off",
 | 
			
		||||
        "react/forbid-prop-types": "off",
 | 
			
		||||
        // Permanently disabled
 | 
			
		||||
        "react/jsx-filename-extension": "off",
 | 
			
		||||
        "no-plusplus": "off",
 | 
			
		||||
        "consistent-return": "off",
 | 
			
		||||
        "radix": "off",
 | 
			
		||||
        "no-continue": "off",
 | 
			
		||||
        "react/no-danger": "off",
 | 
			
		||||
    },
 | 
			
		||||
    "settings": {
 | 
			
		||||
        "import/resolver": {
 | 
			
		||||
            "node": {
 | 
			
		||||
                "paths": ["src"]
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    extends: "eslint-config-reforis",
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -4,6 +4,9 @@
 | 
			
		||||
logs
 | 
			
		||||
*.log
 | 
			
		||||
 | 
			
		||||
# Python
 | 
			
		||||
venv/
 | 
			
		||||
 | 
			
		||||
# NodeJS
 | 
			
		||||
## Logs
 | 
			
		||||
npm-debug.log*
 | 
			
		||||
 
 | 
			
		||||
@@ -1,43 +1,44 @@
 | 
			
		||||
image: node:8-alpine
 | 
			
		||||
image: registry.nic.cz/turris/reforis/reforis/reforis-image
 | 
			
		||||
 | 
			
		||||
stages:
 | 
			
		||||
  - test
 | 
			
		||||
  - build
 | 
			
		||||
  - publish
 | 
			
		||||
    - test
 | 
			
		||||
    - build
 | 
			
		||||
    - publish
 | 
			
		||||
 | 
			
		||||
before_script:
 | 
			
		||||
  - npm install
 | 
			
		||||
    - apt-get update && apt-get install -y make
 | 
			
		||||
    - npm install
 | 
			
		||||
 | 
			
		||||
test:
 | 
			
		||||
  stage: test
 | 
			
		||||
  script:
 | 
			
		||||
    - npm test
 | 
			
		||||
    stage: test
 | 
			
		||||
    script:
 | 
			
		||||
        - make test
 | 
			
		||||
 | 
			
		||||
lint:
 | 
			
		||||
  stage: test
 | 
			
		||||
  script:
 | 
			
		||||
    - npm run lint
 | 
			
		||||
    stage: test
 | 
			
		||||
    script:
 | 
			
		||||
        - make lint
 | 
			
		||||
 | 
			
		||||
build:
 | 
			
		||||
  stage: build
 | 
			
		||||
  script:
 | 
			
		||||
    - npm pack
 | 
			
		||||
  artifacts:
 | 
			
		||||
    paths:
 | 
			
		||||
      - foris-*.tgz
 | 
			
		||||
    stage: build
 | 
			
		||||
    script:
 | 
			
		||||
        - make pack
 | 
			
		||||
    artifacts:
 | 
			
		||||
        paths:
 | 
			
		||||
            - dist/foris-*.tgz
 | 
			
		||||
 | 
			
		||||
publish_beta:
 | 
			
		||||
  stage: publish
 | 
			
		||||
  only:
 | 
			
		||||
    refs:
 | 
			
		||||
      - dev
 | 
			
		||||
  script:
 | 
			
		||||
    - sh scripts/publish.sh beta
 | 
			
		||||
    stage: publish
 | 
			
		||||
    only:
 | 
			
		||||
        refs:
 | 
			
		||||
            - dev
 | 
			
		||||
    script:
 | 
			
		||||
        - make publish-beta
 | 
			
		||||
 | 
			
		||||
publish_latest:
 | 
			
		||||
  stage: publish
 | 
			
		||||
  only:
 | 
			
		||||
    refs:
 | 
			
		||||
      - master
 | 
			
		||||
  script:
 | 
			
		||||
    - sh scripts/publish.sh latest
 | 
			
		||||
    stage: publish
 | 
			
		||||
    only:
 | 
			
		||||
        refs:
 | 
			
		||||
            - master
 | 
			
		||||
    script:
 | 
			
		||||
        - make publish-latest
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.weblate
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.weblate
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
[weblate]
 | 
			
		||||
url = https://hosted.weblate.org/api/
 | 
			
		||||
translation = turris/foris-js
 | 
			
		||||
							
								
								
									
										566
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										566
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,566 @@
 | 
			
		||||
# Changelog
 | 
			
		||||
 | 
			
		||||
All notable changes to this project will be documented in this file.
 | 
			
		||||
 | 
			
		||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 | 
			
		||||
and this project adheres to
 | 
			
		||||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 | 
			
		||||
 | 
			
		||||
## [Unreleased]
 | 
			
		||||
 | 
			
		||||
## [6.7.2] - 2025-04-22
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added Turris logo to enhanced QR code display
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Replaced deprecated QRCode component with QRCodeSVG
 | 
			
		||||
- Refactored button click handlers to simplify event handling in WiFiQRCode
 | 
			
		||||
- Re-resolved and re-locked all npm dependencies in package-lock.json
 | 
			
		||||
- Overridden markdown-to-jsx version in order to solve audit issues
 | 
			
		||||
- docs: Enhanced styleguide configuration with new font and layout options
 | 
			
		||||
- docs: Refactored development and introduction sections
 | 
			
		||||
- docs: Fixed code snippets syntax highlighting & some refactoring
 | 
			
		||||
- docs: Updated SubmitButton component
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [6.7.1] - 2025-04-04
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added & updated Weblate translations
 | 
			
		||||
 | 
			
		||||
## [6.7.0] - 2025-03-11
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added encryption property to guest WiFi settings in tests
 | 
			
		||||
- Added global fuzzy search and columns visibility to RichTable
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Made thead of RichTable lighter
 | 
			
		||||
- Updated dependencies in package.json to latest versions
 | 
			
		||||
- Enhanced ActionButtonWithModal to support dynamic methods
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [6.6.2] - 2025-02-20
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Enhanced SubmitButton component to accept a custom label prop
 | 
			
		||||
- Refactored RichTable component to remove forwardRef and simplify data handling
 | 
			
		||||
 | 
			
		||||
## [6.6.1] - 2025-02-17
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Refactored RichTable component to use forwardRef
 | 
			
		||||
 | 
			
		||||
## [6.6.0] - 2025-02-07
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added & updated Weblate translations
 | 
			
		||||
- Added Wi-Fi and LAN settings URLs to ForisURLs
 | 
			
		||||
- Added Wi-Fi modes VHT/HE 80+80
 | 
			
		||||
- Added encryption selection to WiFiGuestForm
 | 
			
		||||
- Added optional close button to ModalHeader component
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Updated Wi-Fi API
 | 
			
		||||
- Enhanced NumberInput component with keyboard & touch accessibility
 | 
			
		||||
- Refactored pagination condition in RichTable component
 | 
			
		||||
 | 
			
		||||
## [6.5.0] - 2024-11-13
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added & updated Weblate translations
 | 
			
		||||
- Added RichTable component with pagination and sorting
 | 
			
		||||
- Added @tanstack/react-table v8.20.5 to dependencies
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Updated documentation
 | 
			
		||||
- Replaced RebootButton with ActionButtonWithModal component
 | 
			
		||||
- Fixed import path for CustomizationContextMock in customTestRender.js
 | 
			
		||||
 | 
			
		||||
## [6.4.0] - 2024-10-02
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Refactored Alert component to include dismiss animation and timeout
 | 
			
		||||
- Refactored ThreeDotsMenu component to include additional props
 | 
			
		||||
 | 
			
		||||
## [6.3.0] - 2024-09-27
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added ThreeDotsMenu component
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Refactored EmailInput description
 | 
			
		||||
- Refactored RadioSet & ignore Radio component
 | 
			
		||||
- Refactored npm package badge in introduction.md
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [6.2.1] - 2024-09-25
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added & updated Weblate translations
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Refactored CopyInput component
 | 
			
		||||
- Refactored ForisURLs to include new URLs for Overview page
 | 
			
		||||
 | 
			
		||||
## [6.2.0] - 2024-09-20
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added useFocusTrap hook
 | 
			
		||||
- Added extendSession endpoint
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Refactored Spinner.css to use CSS variable for color
 | 
			
		||||
- Refactored Modal component to use useFocusTrap hook
 | 
			
		||||
- Refactored Alert component to use useFocusTrap hook
 | 
			
		||||
 | 
			
		||||
## [6.1.1] - 2024-08-30
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added & updated Weblate translations
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Updated icon color classes to use "text-secondary" instead of "text-dark"
 | 
			
		||||
- Updated Wi-Fi QRCodeModal component to use new styles & added close button
 | 
			
		||||
- Refactored WiFiGuestForm component to get rid of obsolete div element
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [6.1.0] - 2024-08-23
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added & updated Weblate translations
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Migrated to Font Awesome v6
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [6.0.3] - 2024-07-26
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Updated WiFiQRCode component
 | 
			
		||||
 | 
			
		||||
## [6.0.2] - 2024-06-28
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added className prop to CheckBox and Radio components
 | 
			
		||||
 | 
			
		||||
## [6.0.1] - 2024-06-26
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added className prop to Switch component
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Updated dependencies in package.json
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [6.0.0] - 2024-06-11
 | 
			
		||||
 | 
			
		||||
### Added
 | 
			
		||||
 | 
			
		||||
- Added CHANGELOG.md
 | 
			
		||||
- Added JS_DIR variable to Makefile
 | 
			
		||||
- Added support for shared reForis ESLint configuration
 | 
			
		||||
 | 
			
		||||
### Changed
 | 
			
		||||
 | 
			
		||||
- Updated dependencies in package.json
 | 
			
		||||
- Updated Spinner.css styles for better positioning and responsiveness
 | 
			
		||||
- Migrated to Bootstrap 5
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
- Other small improvements
 | 
			
		||||
 | 
			
		||||
## [5.6.1] - 2024-01-19
 | 
			
		||||
 | 
			
		||||
- Added & updated Weblate translations
 | 
			
		||||
- Fixed loading state & button's layout
 | 
			
		||||
- Updated bootstrap library to version 4.6.2
 | 
			
		||||
- Used custom reforis-image in GitLab CI/CD
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.6.0] - 2022-12-29
 | 
			
		||||
 | 
			
		||||
- Add & update Weblate translations
 | 
			
		||||
- Add CustomizationContext and custom hook
 | 
			
		||||
- Update caniuse-lite
 | 
			
		||||
- Remove testUtils from .gitignore
 | 
			
		||||
- Make ieee80211w_disabled as optional in WiFiForm
 | 
			
		||||
- Move contexts in a context folder
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.5.0] - 2022-12-02
 | 
			
		||||
 | 
			
		||||
- Add & update translations
 | 
			
		||||
- Add a switch to disable Management Frame Protection (802.11w)
 | 
			
		||||
- Improved Foris JS documentation
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.4.1] - 2022-06-03
 | 
			
		||||
 | 
			
		||||
- Add Weblate translations
 | 
			
		||||
- Update PropType peer dependency
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.4.0] - 2022-05-20
 | 
			
		||||
 | 
			
		||||
- Add & update translations
 | 
			
		||||
- Add CopyInput bootstrap component
 | 
			
		||||
- Update WiFiForm labels and description for wifi ax
 | 
			
		||||
- Make WS path in lighttpd mode configurable
 | 
			
		||||
- Fix Wi-Fi password helptext string
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.3.0] - 2022-02-21
 | 
			
		||||
 | 
			
		||||
- Added & update translations
 | 
			
		||||
- Added rest of the props to DownloadButton component
 | 
			
		||||
- Added hostname validation
 | 
			
		||||
- Added wifi 802.11ax HE modes
 | 
			
		||||
- Set best Wi-Fi HT mode depending on the checked frequency
 | 
			
		||||
- Improved domain name RegEx pattern
 | 
			
		||||
- Removed customOrder prop in Select component
 | 
			
		||||
- Fixed Wi-Fi translation strings
 | 
			
		||||
- Fixed autocomplete attribute in PasswordInput
 | 
			
		||||
- Fixed WiFi password max length check
 | 
			
		||||
- Fixed documentation build
 | 
			
		||||
- Fixed access token in publish script
 | 
			
		||||
- Refined & restructure Makefile
 | 
			
		||||
- Updated GitLab CI image to Node.js v16
 | 
			
		||||
- NPM update (several dependencies)
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.2.0] - 2021-12-15
 | 
			
		||||
 | 
			
		||||
- Remove login page
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.1.16] - 2021-11-18
 | 
			
		||||
 | 
			
		||||
- Revert bad NPM audit fix
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.1.15] - 2021-11-03
 | 
			
		||||
 | 
			
		||||
- Add WPA3 option
 | 
			
		||||
- Add custom order ability of Select options
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.1.14] - 2021-07-30
 | 
			
		||||
 | 
			
		||||
- Add & update translations
 | 
			
		||||
- Fix infinity redirect loop when WS error occurs
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.1.13] - 2021-06-30
 | 
			
		||||
 | 
			
		||||
- Add sentinelAgreement endpoint to forisUrls
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.1.12] - 2021-05-14
 | 
			
		||||
 | 
			
		||||
- Add & update translations
 | 
			
		||||
- Add & fix obsolete links
 | 
			
		||||
- Expend library with the ResetWifiSettings function
 | 
			
		||||
- Fix switching Wi-Fi modes depending on bands in WiFiForm
 | 
			
		||||
- Fix translation sources in WiFiForm
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
- Other small improvements
 | 
			
		||||
 | 
			
		||||
## [5.1.11] - 2021-01-04
 | 
			
		||||
 | 
			
		||||
- Remove duplicated file for Norwegian language
 | 
			
		||||
- Fix translations inconsistency
 | 
			
		||||
 | 
			
		||||
## [5.1.10] - 2021-12-29
 | 
			
		||||
 | 
			
		||||
- Add and update translations
 | 
			
		||||
 | 
			
		||||
## [5.1.9] - 2021-12-20
 | 
			
		||||
 | 
			
		||||
- Increase bottom margin of formFieldsSize
 | 
			
		||||
- Change formFieldsSize of ResetWiFiSettings card
 | 
			
		||||
- Fix trailing space in Modal classes
 | 
			
		||||
 | 
			
		||||
## [5.1.8] - 2020-12-19
 | 
			
		||||
 | 
			
		||||
- Add isPluginInstalled function
 | 
			
		||||
 | 
			
		||||
## [5.1.7] - 2020-11-27
 | 
			
		||||
 | 
			
		||||
## [5.1.6] - 2020-11-25
 | 
			
		||||
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
- Add displayCard function to utils
 | 
			
		||||
- Add optional sizes to Modal
 | 
			
		||||
- Add information about optional sizes to docs
 | 
			
		||||
- Remove redundant merge.py
 | 
			
		||||
 | 
			
		||||
## [5.1.5] - 2020-09-25
 | 
			
		||||
 | 
			
		||||
- Fix DateTime import
 | 
			
		||||
- Fix extra empty space in Switch's classes
 | 
			
		||||
 | 
			
		||||
## [5.1.4] - 2020-09-25
 | 
			
		||||
 | 
			
		||||
- Add inline option to Wi-Fi's RadioSet
 | 
			
		||||
- Fix Alert's dismissible class condition
 | 
			
		||||
- Add closing bootstrap modal using ESC
 | 
			
		||||
- Change reboot modal's heading to "Warning!"
 | 
			
		||||
 | 
			
		||||
## [5.1.3] - 2020-09-11
 | 
			
		||||
 | 
			
		||||
- Add SSID validation for 32 bytes length
 | 
			
		||||
- Add helpText for SSID input
 | 
			
		||||
 | 
			
		||||
## [5.1.2] - 2020-09-08
 | 
			
		||||
 | 
			
		||||
- Fix infinity loop caused by WebSockets
 | 
			
		||||
- Resolve small issues
 | 
			
		||||
 | 
			
		||||
## [5.1.1] - 2020-08-31
 | 
			
		||||
 | 
			
		||||
- Add "inline" option to RadioSet
 | 
			
		||||
- NPM audit fix
 | 
			
		||||
 | 
			
		||||
## [5.1.0] - 2020-08-25
 | 
			
		||||
 | 
			
		||||
- Add new Switch component
 | 
			
		||||
- Swap checkboxes for switches on Wi-Fi page
 | 
			
		||||
- Decrease button width on different breakpoints
 | 
			
		||||
- Add integration of Prettier + ESLint + reForis Style Guide
 | 
			
		||||
- Add appropriate links to dropdown headers
 | 
			
		||||
- Add semantic & accessibility structure for headings
 | 
			
		||||
- NPM audit & Update packages
 | 
			
		||||
- GitLab CI: image update to node 10
 | 
			
		||||
 | 
			
		||||
## [5.0.3] - 2020-09-23
 | 
			
		||||
 | 
			
		||||
- Fixes issue with WebSockets
 | 
			
		||||
 | 
			
		||||
## [5.0.2] - 2020-09-22
 | 
			
		||||
 | 
			
		||||
- Fix infinity loop caused by WebSockets
 | 
			
		||||
 | 
			
		||||
## [5.0.1] - 2020-07-21
 | 
			
		||||
 | 
			
		||||
- Fix Wi-Fi Form
 | 
			
		||||
- NPM audit fix & update of packages
 | 
			
		||||
 | 
			
		||||
## [5.0.0] - 2020-05-07
 | 
			
		||||
 | 
			
		||||
- I've realized that it should be major update due to broken API.
 | 
			
		||||
 | 
			
		||||
## [4.5.1] - 2020-05-07
 | 
			
		||||
 | 
			
		||||
- Add initialData to ForisForm children.
 | 
			
		||||
- Update translations .pot file.
 | 
			
		||||
 | 
			
		||||
## [4.5.0] - 2020-03-25
 | 
			
		||||
 | 
			
		||||
- Use exposed pdfmake.
 | 
			
		||||
- NPM audit fix & update of packages.
 | 
			
		||||
 | 
			
		||||
## [4.4.0] - 2020-03-13
 | 
			
		||||
 | 
			
		||||
- Update domain validation.
 | 
			
		||||
 | 
			
		||||
## [4.3.1] - 2020-03-06
 | 
			
		||||
 | 
			
		||||
- Add logout link.
 | 
			
		||||
 | 
			
		||||
## [4.3.0] - 2020-02-26
 | 
			
		||||
 | 
			
		||||
- Allow RadioSet accept elements as children.
 | 
			
		||||
- Add option to make modal scrollable.
 | 
			
		||||
 | 
			
		||||
## [4.2.0] - 2020-02-21
 | 
			
		||||
 | 
			
		||||
- Add translations.
 | 
			
		||||
- Improve datatime localization.
 | 
			
		||||
 | 
			
		||||
## [4.1.0] - 2020-02-20
 | 
			
		||||
 | 
			
		||||
- Added date and time utilities.
 | 
			
		||||
 | 
			
		||||
## [4.0.0] - 2020-02-20
 | 
			
		||||
 | 
			
		||||
- Throw an error if unhandled exception happens during API request.
 | 
			
		||||
 | 
			
		||||
## [3.4.0] - 2020-02-17
 | 
			
		||||
 | 
			
		||||
- Display actual GET error response within the form.
 | 
			
		||||
- Added styles extracted from reForis.
 | 
			
		||||
- Added reference to form element (for programmatically submitting it).
 | 
			
		||||
 | 
			
		||||
## [3.2.0] - 2020-01-17
 | 
			
		||||
 | 
			
		||||
- Swapped react-router with react-router-dom. Prepared Foris JS for using
 | 
			
		||||
  react-router-dom exposed by reForis.
 | 
			
		||||
- Added controller ID filter to WebSocket hook.
 | 
			
		||||
- Updated translation messages after moving WiFi form.
 | 
			
		||||
- Increased request timeout to 30.5 sec.
 | 
			
		||||
 | 
			
		||||
## [3.1.1] - 2020-01-10
 | 
			
		||||
 | 
			
		||||
- Fixed package dependencies related to exposing libraries via reForis
 | 
			
		||||
 | 
			
		||||
## [3.1.0] - 2020-01-09
 | 
			
		||||
 | 
			
		||||
- Added Wi-Fi settings form
 | 
			
		||||
- Fixed path to index.js file in package.json
 | 
			
		||||
 | 
			
		||||
## [3.0.0] - 2020-01-07
 | 
			
		||||
 | 
			
		||||
- Removal of Babel compiler
 | 
			
		||||
- Fixed width of ForisForm, removed default sizing for form widgets (like
 | 
			
		||||
  buttons)
 | 
			
		||||
 | 
			
		||||
## [2.1.1] - 2020-01-06
 | 
			
		||||
 | 
			
		||||
- Display date and time picker above input element
 | 
			
		||||
 | 
			
		||||
## [2.1.0] - 2019-12-19
 | 
			
		||||
 | 
			
		||||
- Set WebSocket logging to debug level
 | 
			
		||||
- Added hook that detects clicking outside of component
 | 
			
		||||
- Added Radio to list of publicly available components
 | 
			
		||||
- Fixed link to git repository in package.json
 | 
			
		||||
 | 
			
		||||
## [2.0.0] - 2019-12-09
 | 
			
		||||
 | 
			
		||||
- Added dynamic suffix for API URLs (allowing to use one hook for different
 | 
			
		||||
  resources with e.g. PUT)
 | 
			
		||||
- Added unsubscribe method to WebSocket client
 | 
			
		||||
- Added custom class to SpinnerElement
 | 
			
		||||
- Improved documentation
 | 
			
		||||
- Published README.md
 | 
			
		||||
 | 
			
		||||
## [1.4.0] - 2019-11-29
 | 
			
		||||
 | 
			
		||||
- Add reboot button.
 | 
			
		||||
- Fix Foris URLs prefixes
 | 
			
		||||
 | 
			
		||||
## [1.3.3] - 2019-11-22
 | 
			
		||||
 | 
			
		||||
- Add translations from Weblate.
 | 
			
		||||
 | 
			
		||||
## [1.3.2] - 2019-11-20
 | 
			
		||||
 | 
			
		||||
- Expose only AlertContext.
 | 
			
		||||
- Add hook for API pooling.
 | 
			
		||||
 | 
			
		||||
## [1.3.1] - 2019-11-14
 | 
			
		||||
 | 
			
		||||
## [1.2.0] - 2019-10-24
 | 
			
		||||
 | 
			
		||||
## [1.1.0] - 2019-10-22
 | 
			
		||||
 | 
			
		||||
## [1.0.0] - 2019-10-07
 | 
			
		||||
 | 
			
		||||
## [0.0.7] - 2019-09-02
 | 
			
		||||
 | 
			
		||||
[unreleased]:
 | 
			
		||||
    https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.7.2...dev
 | 
			
		||||
[6.7.2]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.7.1...v6.7.2
 | 
			
		||||
[6.7.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.7.0...v6.7.1
 | 
			
		||||
[6.7.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.6.2...v6.7.0
 | 
			
		||||
[6.6.2]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.6.1...v6.6.2
 | 
			
		||||
[6.6.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.6.0...v6.6.1
 | 
			
		||||
[6.6.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.5.0...v6.6.0
 | 
			
		||||
[6.5.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.4.0...v6.5.0
 | 
			
		||||
[6.4.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.3.0...v6.4.0
 | 
			
		||||
[6.3.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.2.1...v6.3.0
 | 
			
		||||
[6.2.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.2.0...v6.2.1
 | 
			
		||||
[6.2.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.1.1...v6.2.0
 | 
			
		||||
[6.1.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.1.0...v6.1.1
 | 
			
		||||
[6.1.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.0.3...v6.1.0
 | 
			
		||||
[6.0.3]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.0.2...v6.0.3
 | 
			
		||||
[6.0.2]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.0.1...v6.0.2
 | 
			
		||||
[6.0.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v6.0.0...v6.0.1
 | 
			
		||||
[6.0.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.6.1...v6.0.0
 | 
			
		||||
[5.6.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.6.0...v5.6.1
 | 
			
		||||
[5.6.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.5.0...v5.6.0
 | 
			
		||||
[5.5.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.4.1...v5.5.0
 | 
			
		||||
[5.4.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.4.0...v5.4.1
 | 
			
		||||
[5.4.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.3.0...v5.4.0
 | 
			
		||||
[5.3.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.2.0...v5.3.0
 | 
			
		||||
[5.2.0]:
 | 
			
		||||
    https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.16...v5.2.0
 | 
			
		||||
[5.1.16]:
 | 
			
		||||
    https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.15...v5.1.16
 | 
			
		||||
[5.1.15]:
 | 
			
		||||
    https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.14...v5.1.15
 | 
			
		||||
[5.1.14]:
 | 
			
		||||
    https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.13...v5.1.14
 | 
			
		||||
[5.1.13]:
 | 
			
		||||
    https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.12...v5.1.13
 | 
			
		||||
[5.1.12]:
 | 
			
		||||
    https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.11...v5.1.12
 | 
			
		||||
[5.1.11]:
 | 
			
		||||
    https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.10...v5.1.11
 | 
			
		||||
[5.1.10]:
 | 
			
		||||
    https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.9...v5.1.10
 | 
			
		||||
[5.1.9]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.8...v5.1.9
 | 
			
		||||
[5.1.8]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.7...v5.1.8
 | 
			
		||||
[5.1.7]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.6...v5.1.7
 | 
			
		||||
[5.1.6]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.5...v5.1.6
 | 
			
		||||
[5.1.5]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.4...v5.1.5
 | 
			
		||||
[5.1.4]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.3...v5.1.4
 | 
			
		||||
[5.1.3]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.2...v5.1.3
 | 
			
		||||
[5.1.2]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.1...v5.1.2
 | 
			
		||||
[5.1.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.1.0...v5.1.1
 | 
			
		||||
[5.1.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.0.3...v5.1.0
 | 
			
		||||
[5.0.3]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.0.2...v5.0.3
 | 
			
		||||
[5.0.2]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.0.1...v5.0.2
 | 
			
		||||
[5.0.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v5.5.0...v5.0.1
 | 
			
		||||
[5.0.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v4.5.1...v5.0.0
 | 
			
		||||
[4.5.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v4.5.0...v4.5.1
 | 
			
		||||
[4.5.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v4.4.0...v4.5.0
 | 
			
		||||
[4.4.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v4.3.1...v4.4.0
 | 
			
		||||
[4.3.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v4.3.0...v4.3.1
 | 
			
		||||
[4.3.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v4.2.0...v4.3.0
 | 
			
		||||
[4.2.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v4.1.0...v4.2.0
 | 
			
		||||
[4.1.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v4.0.0...v4.1.0
 | 
			
		||||
[4.0.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v3.4.0...v4.0.0
 | 
			
		||||
[3.4.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v3.2.0...v3.4.0
 | 
			
		||||
[3.2.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v3.1.1...v3.2.0
 | 
			
		||||
[3.1.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v3.1.0...v3.1.1
 | 
			
		||||
[3.1.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v3.0.0...v3.1.0
 | 
			
		||||
[3.0.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v2.1.1...v3.0.0
 | 
			
		||||
[2.1.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v2.1.0...v2.1.1
 | 
			
		||||
[2.1.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v2.0.0...v2.1.0
 | 
			
		||||
[2.0.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v1.4.0...v2.0.0
 | 
			
		||||
[1.4.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v1.3.3...v1.4.0
 | 
			
		||||
[1.3.3]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v1.3.2...v1.3.3
 | 
			
		||||
[1.3.2]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v1.3.1...v1.3.2
 | 
			
		||||
[1.3.1]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v1.2.0...v1.3.1
 | 
			
		||||
[1.2.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v1.1.0...v1.2.0
 | 
			
		||||
[1.1.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v1.0.0...v1.1.0
 | 
			
		||||
[1.0.0]: https://gitlab.nic.cz/turris/reforis/foris-js/-/compare/v0.0.7...v1.0.0
 | 
			
		||||
[0.0.7]: https://gitlab.nic.cz/turris/reforis/foris-js/-/tags/v0.0.7
 | 
			
		||||
							
								
								
									
										115
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								Makefile
									
									
									
									
									
								
							@@ -1,16 +1,31 @@
 | 
			
		||||
.PHONY: all install-js watch-js build-js lint-js test-js create-messages update-messages docs clean
 | 
			
		||||
# Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
#
 | 
			
		||||
# This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
# See /LICENSE for more information.
 | 
			
		||||
 | 
			
		||||
PROJECT="Foris JS"
 | 
			
		||||
# Retrieve Foris JS version from package.json 
 | 
			
		||||
VERSION= $(shell sed -En "s/.*version['\"]: ['\"](.+)['\"].*/\1/p" package.json)
 | 
			
		||||
COPYRIGHT_HOLDER="CZ.NIC, z.s.p.o. (https://www.nic.cz/)"
 | 
			
		||||
MSGID_BUGS_ADDRESS="tech.support@turris.cz"
 | 
			
		||||
 | 
			
		||||
DEV_PYTHON=python3
 | 
			
		||||
VENV_NAME?=venv
 | 
			
		||||
JS_DIR=js
 | 
			
		||||
VENV_BIN=$(shell pwd)/$(VENV_NAME)/bin
 | 
			
		||||
 | 
			
		||||
.PHONY: all
 | 
			
		||||
all:
 | 
			
		||||
	@echo "make install-js"
 | 
			
		||||
	@echo "    Install dependencies"
 | 
			
		||||
	@echo "make watch-js"
 | 
			
		||||
	@echo "    Compile JS in watch mode."
 | 
			
		||||
	@echo "make build-js"
 | 
			
		||||
	@echo "    Compile JS."
 | 
			
		||||
	@echo "make lint-js"
 | 
			
		||||
	@echo "    Run linter"
 | 
			
		||||
	@echo "make test-js"
 | 
			
		||||
	@echo "    Run tests"
 | 
			
		||||
	@echo "    Install npm dependencies."
 | 
			
		||||
	@echo "make lint"
 | 
			
		||||
	@echo "    Run linter on the project."
 | 
			
		||||
	@echo "make test"
 | 
			
		||||
	@echo "    Run tests on the project."
 | 
			
		||||
	@echo "make test-js-watch"
 | 
			
		||||
	@echo "    Run tests on the project in watch mode."
 | 
			
		||||
	@echo "make test-js-update-snapshots"
 | 
			
		||||
	@echo "    Update snapshots."
 | 
			
		||||
	@echo "make create-messages"
 | 
			
		||||
	@echo "    Create locale messages (.pot)."
 | 
			
		||||
	@echo "make update-messages"
 | 
			
		||||
@@ -22,29 +37,93 @@ all:
 | 
			
		||||
	@echo "make clean"
 | 
			
		||||
	@echo "    Remove python artifacts and virtualenv."
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Preparation
 | 
			
		||||
 | 
			
		||||
.PHONY: venv
 | 
			
		||||
venv: $(VENV_NAME)/bin/activate
 | 
			
		||||
$(VENV_NAME)/bin/activate:
 | 
			
		||||
	test -d $(VENV_NAME) || $(DEV_PYTHON) -m virtualenv -p $(DEV_PYTHON) $(VENV_NAME)
 | 
			
		||||
	$(VENV_BIN)/$(DEV_PYTHON) -m pip install -r requirements.txt
 | 
			
		||||
	touch $(VENV_NAME)/bin/activate
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Installation
 | 
			
		||||
 | 
			
		||||
.PHONY: install-js
 | 
			
		||||
install-js: package.json
 | 
			
		||||
	npm install --save-dev
 | 
			
		||||
 | 
			
		||||
watch-js:
 | 
			
		||||
	npm run build:watch
 | 
			
		||||
build-js:
 | 
			
		||||
	npm run build
 | 
			
		||||
 | 
			
		||||
# Publishing
 | 
			
		||||
 | 
			
		||||
.PHONY: collect-files
 | 
			
		||||
collect-files:
 | 
			
		||||
	sh scripts/collect_files.sh
 | 
			
		||||
 | 
			
		||||
.PHONY: pack
 | 
			
		||||
pack: collect-files
 | 
			
		||||
	cd dist && npm pack
 | 
			
		||||
 | 
			
		||||
.PHONY: publish-beta
 | 
			
		||||
publish-beta: collect-files
 | 
			
		||||
	sh scripts/publish.sh beta
 | 
			
		||||
 | 
			
		||||
.PHONY: publish-latest
 | 
			
		||||
publish-latest: collect-files
 | 
			
		||||
	sh scripts/publish.sh latest
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Linting
 | 
			
		||||
 | 
			
		||||
.PHONY: lint
 | 
			
		||||
lint:
 | 
			
		||||
	npm run lint
 | 
			
		||||
 | 
			
		||||
.PHONY: lint-js-fix
 | 
			
		||||
lint-js-fix:
 | 
			
		||||
	npm run lint:fix
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Testing
 | 
			
		||||
 | 
			
		||||
.PHONY: test
 | 
			
		||||
test:
 | 
			
		||||
	npm test
 | 
			
		||||
 | 
			
		||||
create-messages:
 | 
			
		||||
	pybabel extract -F babel.cfg -o ./translations/forisjs.pot .
 | 
			
		||||
update-messages:
 | 
			
		||||
	pybabel update -i translations/forisjs.pot -d translations
 | 
			
		||||
.PHONY: test-js-watch
 | 
			
		||||
test-js-watch:
 | 
			
		||||
	cd $(JS_DIR); npm test -- --watch
 | 
			
		||||
 | 
			
		||||
.PHONY: test-js-update-snapshots
 | 
			
		||||
test-js-update-snapshots:
 | 
			
		||||
	npm test -- -u
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Translations
 | 
			
		||||
 | 
			
		||||
.PHONY: create-messages
 | 
			
		||||
create-messages: venv
 | 
			
		||||
	$(VENV_BIN)/pybabel extract -F babel.cfg -o ./translations/forisjs.pot . --project=$(PROJECT) --version=$(VERSION) --copyright-holder=$(COPYRIGHT_HOLDER) --msgid-bugs-address=$(MSGID_BUGS_ADDRESS)
 | 
			
		||||
 | 
			
		||||
.PHONY: update-messages
 | 
			
		||||
update-messages: venv
 | 
			
		||||
	$(VENV_BIN)/pybabel update -i ./translations/forisjs.pot -d ./translations -D forisjs --update-header-comment
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Documentation
 | 
			
		||||
 | 
			
		||||
.PHONY: docs
 | 
			
		||||
docs:
 | 
			
		||||
	npm run-script docs
 | 
			
		||||
 | 
			
		||||
.PHONY: docs-watch
 | 
			
		||||
docs-watch:
 | 
			
		||||
	npm run-script docs:watch
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Other
 | 
			
		||||
 | 
			
		||||
.PHONY: clean
 | 
			
		||||
clean:
 | 
			
		||||
	rm -rf node_modules dist
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										39
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								README.md
									
									
									
									
									
								
							@@ -1,17 +1,52 @@
 | 
			
		||||
# foris-js
 | 
			
		||||
 | 
			
		||||
Set of utils and common React elements for reForis.
 | 
			
		||||
 | 
			
		||||
## Publishing package
 | 
			
		||||
 | 
			
		||||
### Beta versions
 | 
			
		||||
 | 
			
		||||
Each commit to `dev` branch will result in publishing a new version of library
 | 
			
		||||
tagged `beta`. Versions names are based on commit SHA, e.g. 
 | 
			
		||||
tagged `beta`. Versions names are based on commit SHA, e.g.
 | 
			
		||||
`foris@0.1.0-beta.d9073aa4`.
 | 
			
		||||
 | 
			
		||||
### Preparing a release
 | 
			
		||||
 | 
			
		||||
1. Crete a merge request to `dev` branch with version bumped
 | 
			
		||||
2. When merging add `[skip ci]` to commit message to prevent publishing
 | 
			
		||||
unnecessary version
 | 
			
		||||
   unnecessary version
 | 
			
		||||
3. Create a merge request from `dev` to `master` branch
 | 
			
		||||
4. New version should be published automatically
 | 
			
		||||
 | 
			
		||||
## Manually managed dependencies
 | 
			
		||||
 | 
			
		||||
Because of `<ForisForm />` component it's required to use exposed
 | 
			
		||||
`ReactRouterDOM` object from `react-router-dom` library. `ReactRouterDOM` is
 | 
			
		||||
exposed by
 | 
			
		||||
[reForis](https://gitlab.labs.nic.cz/turris/reforis/reforis/blob/master/js/webpack.config.js).
 | 
			
		||||
It can be done by following steps:
 | 
			
		||||
 | 
			
		||||
1. Setting `react-router-dom` as `peerDependencies` and `devDependencies` in
 | 
			
		||||
   `package.json`.
 | 
			
		||||
2. Adding the following rules to `externals` in `webpack.conf.js` of the plugin:
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
externals: {
 | 
			
		||||
    ...
 | 
			
		||||
    "react-router-dom": "ReactRouterDOM",
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Docs
 | 
			
		||||
 | 
			
		||||
Build or watch docs to get more info about library:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
make docs
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
or
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
make docs-watch
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,4 @@
 | 
			
		||||
module.exports = {
 | 
			
		||||
    presets: [
 | 
			
		||||
        "@babel/preset-env",
 | 
			
		||||
        "@babel/preset-react",
 | 
			
		||||
    ],
 | 
			
		||||
    plugins: [
 | 
			
		||||
        "@babel/plugin-transform-runtime",
 | 
			
		||||
        "@babel/plugin-syntax-export-default-from",
 | 
			
		||||
        ["module-resolver", {
 | 
			
		||||
            root: ["./src"],
 | 
			
		||||
            alias: {
 | 
			
		||||
                test: "./test",
 | 
			
		||||
                underscore: "lodash",
 | 
			
		||||
            },
 | 
			
		||||
        }],
 | 
			
		||||
    ],
 | 
			
		||||
    presets: ["@babel/preset-env", "@babel/preset-react"],
 | 
			
		||||
    plugins: ["@babel/plugin-transform-runtime"],
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								docs/components/Logo.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								docs/components/Logo.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import Styled from "rsg-components/Styled";
 | 
			
		||||
import logo from "./logo.svg";
 | 
			
		||||
 | 
			
		||||
const styles = ({ fontFamily }) => ({
 | 
			
		||||
    logo: {
 | 
			
		||||
        display: "flex",
 | 
			
		||||
        alignItems: "center",
 | 
			
		||||
        margin: 0,
 | 
			
		||||
        fontFamily: fontFamily.base,
 | 
			
		||||
        fontSize: 18,
 | 
			
		||||
        fontWeight: "normal",
 | 
			
		||||
    },
 | 
			
		||||
    image: {
 | 
			
		||||
        height: "1.3em",
 | 
			
		||||
        marginLeft: "-0.2em",
 | 
			
		||||
        marginRight: "0.2em",
 | 
			
		||||
    },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export function LogoRenderer({ classes, children }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <h1 className={classes.logo}>
 | 
			
		||||
            <img className={classes.image} src={logo} alt="React logo" />
 | 
			
		||||
            {children}
 | 
			
		||||
        </h1>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
LogoRenderer.propTypes = {
 | 
			
		||||
    classes: PropTypes.object.isRequired,
 | 
			
		||||
    children: PropTypes.node,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default Styled(styles)(LogoRenderer);
 | 
			
		||||
							
								
								
									
										3
									
								
								docs/components/logo.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								docs/components/logo.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,3 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" width="1000" height="1000">
 | 
			
		||||
    <path d="M288.258 240.0394L717.5586-.44c.803 62.277-1.8207 124.502-1.4996 186.7266 1.8208 7.6343-7.2288 10.1966-12.102 13.4908L286.4375 432.1518l1.8206-192.1124zm2.284 277.645L711 278.3176l-.8416 192.7742L457.357 614.514l-1.842 289.03-167.7097 95.8926 2.7365-481.753z"/>
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 349 B  | 
							
								
								
									
										27
									
								
								docs/development.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								docs/development.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
At some point, you'll likely need to modify the library. When that happens, your
 | 
			
		||||
best friend will be [`npm link`](https://docs.npmjs.com/cli/link).
 | 
			
		||||
 | 
			
		||||
**Important Note:** Simply linking from the repo root won't work because the
 | 
			
		||||
source files are in `./src`. Instead, you'll need to:
 | 
			
		||||
 | 
			
		||||
1. First package the library using `make pack`
 | 
			
		||||
2. Then link it from the `./dist` directory
 | 
			
		||||
 | 
			
		||||
While this isn't the most developer-friendly workflow, you can improve it by
 | 
			
		||||
creating a script that:
 | 
			
		||||
 | 
			
		||||
- Symlinks all files/directories from `./src` to another location
 | 
			
		||||
- Also links `package.json` and `package-lock.json`
 | 
			
		||||
 | 
			
		||||
## Quick Start Guide
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
# Package and link the library
 | 
			
		||||
make pack
 | 
			
		||||
cd dist
 | 
			
		||||
npm link
 | 
			
		||||
 | 
			
		||||
# Link to your project
 | 
			
		||||
cd /path/to/your/project/js  # Navigate to your project's JS directory
 | 
			
		||||
npm link foris
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										4
									
								
								docs/forisjs-npm.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								docs/forisjs-npm.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
<svg xmlns="http://www.w3.org/2000/svg" width="500" height="500" fill="white">
 | 
			
		||||
    <path d="M49.5 171.722222222222h400v133.333333333333h-200v22.222222222223h-88.888888888889v-22.222222222223H49.5V171.722222222222zm22.222222222222 111.111111111111h44.444444444445v-66.666666666667h22.222222222222v66.666666666667h22.222222222222v-88.888888888889H71.722222222222v88.888888888889zm111.111111111111-88.888888888889v111.111111111111h44.444444444445v-22.222222222222h44.444444444444v-88.888888888889h-88.888888888889zm44.444444444445 22.222222222222H249.5v44.444444444445h-22.222222222222v-44.444444444445zm66.666666666666-22.222222222222v88.888888888889h44.444444444445v-66.666666666667h22.222222222222v66.666666666667h22.222222222222v-66.666666666667h22.222222222222v66.666666666667h22.222222222223v-88.888888888889H293.944444444444z" fill="#cb3837" />
 | 
			
		||||
    <path d="M71.722222222222 282.833333333333h44.444444444444v-66.666666666667h22.222222222223v66.666666666667h22.222222222222v-88.888888888889H71.722222222222zm111.111111111111-88.888888888889v111.111111111111h44.444444444444v-22.222222222222h44.444444444445v-88.888888888889h-88.888888888889zM249.5 260.611111111111h-22.222222222223v-44.444444444445H249.5v44.444444444445zm44.444444444444-66.666666666667v88.888888888889h44.444444444444v-66.666666666667h22.222222222223v66.666666666667h22.222222222222v-66.666666666667h22.222222222222v66.666666666667h22.222222222222v-88.888888888889z" />
 | 
			
		||||
</svg>
 | 
			
		||||
| 
		 After Width: | Height: | Size: 1.4 KiB  | 
@@ -1 +0,0 @@
 | 
			
		||||
Foris JS library is set of componets and utils for Foris JS application and plugins.
 | 
			
		||||
							
								
								
									
										38
									
								
								docs/introduction.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								docs/introduction.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
Welcome to the official Foris JS documentation!
 | 
			
		||||
 | 
			
		||||
## About Foris JS
 | 
			
		||||
 | 
			
		||||
Foris JS is a library containing reusable components and utilities designed
 | 
			
		||||
specifically for the reForis application and its plugins.
 | 
			
		||||
 | 
			
		||||
**Note:** All components and utilities in this library are actively used in
 | 
			
		||||
reForis and its plugins. To see practical examples of how they're implemented,
 | 
			
		||||
we recommend searching through those repositories.
 | 
			
		||||
 | 
			
		||||
## Getting Started
 | 
			
		||||
 | 
			
		||||
### Prerequisites
 | 
			
		||||
 | 
			
		||||
Before installing, ensure you have [Node.js](https://nodejs.org/en/) installed
 | 
			
		||||
on your system.
 | 
			
		||||
 | 
			
		||||
We recommend using the current Long Term Support (LTS) version for optimal
 | 
			
		||||
compatibility. Check the
 | 
			
		||||
[release schedule](https://github.com/nodejs/Release#release-schedule) for
 | 
			
		||||
details.
 | 
			
		||||
 | 
			
		||||
### Installation
 | 
			
		||||
 | 
			
		||||
Install the latest version with:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npm install foris
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Or install a specific version by running:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
npm install foris@version
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
[](https://badge.fury.io/js/foris)
 | 
			
		||||
@@ -12,20 +12,18 @@ module.exports = {
 | 
			
		||||
        "<rootDir>/src/testUtils",
 | 
			
		||||
        "<rootDir>/src/",
 | 
			
		||||
    ],
 | 
			
		||||
    moduleNameMapper: {
 | 
			
		||||
        "\\.(css|less)$": "<rootDir>/src/__mocks__/styleMock.js",
 | 
			
		||||
    },
 | 
			
		||||
    clearMocks: true,
 | 
			
		||||
    collectCoverageFrom: ["src/**/*.{js,jsx}"],
 | 
			
		||||
    coverageDirectory: "coverage",
 | 
			
		||||
    testPathIgnorePatterns: ["/node_modules/", "/__fixtures__/"],
 | 
			
		||||
    testPathIgnorePatterns: ["/node_modules/", "/__fixtures__/", "/dist/"],
 | 
			
		||||
    testEnvironment: "jsdom",
 | 
			
		||||
    verbose: false,
 | 
			
		||||
    setupFilesAfterEnv: [
 | 
			
		||||
        "@testing-library/react/cleanup-after-each",
 | 
			
		||||
        "<rootDir>/src/testUtils/setup",
 | 
			
		||||
    ],
 | 
			
		||||
    setupFilesAfterEnv: ["<rootDir>/src/testUtils/setup"],
 | 
			
		||||
    globals: {
 | 
			
		||||
        TZ: "utc",
 | 
			
		||||
    },
 | 
			
		||||
    transform: {
 | 
			
		||||
        "^.+\\.js$": "babel-jest",
 | 
			
		||||
        "^.+\\.css$": "jest-transform-css",
 | 
			
		||||
    },
 | 
			
		||||
    transformIgnorePatterns: ["node_modules/(?!(react-datetime)/)"],
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31376
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										31376
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										159
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										159
									
								
								package.json
									
									
									
									
									
								
							@@ -1,84 +1,77 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "foris",
 | 
			
		||||
  "version": "1.1.0",
 | 
			
		||||
  "description": "Set of components and utils for Foris and its plugins.",
 | 
			
		||||
  "author": "CZ.NIC, z.s.p.o.",
 | 
			
		||||
  "repository": {
 | 
			
		||||
    "type": "git",
 | 
			
		||||
    "url": "https://gitlab.labs.nic.cz/turris/reforis/forisjs.git"
 | 
			
		||||
  },
 | 
			
		||||
  "keywords": [
 | 
			
		||||
    "foris",
 | 
			
		||||
    "reforis"
 | 
			
		||||
  ],
 | 
			
		||||
  "license": "GPL-3.0",
 | 
			
		||||
  "main": "./dist/index.js",
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "axios": "^0.19.0",
 | 
			
		||||
    "immutability-helper": "^3.0.0",
 | 
			
		||||
    "jest-transform-css": "^2.0.0",
 | 
			
		||||
    "moment": "^2.24.0",
 | 
			
		||||
    "moment-timezone": "^0.5.25",
 | 
			
		||||
    "prop-types": "^15.7.2",
 | 
			
		||||
    "react-datetime": "^2.16.3",
 | 
			
		||||
    "react-router": "^5.0.1",
 | 
			
		||||
    "react-uid": "^2.2.0"
 | 
			
		||||
  },
 | 
			
		||||
  "peerDependencies": {
 | 
			
		||||
    "react": "^16.9.0",
 | 
			
		||||
    "react-dom": "^16.9.0"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/cli": "^7.4.4",
 | 
			
		||||
    "@babel/core": "^7.4.5",
 | 
			
		||||
    "@babel/plugin-proposal-class-properties": "^7.4.4",
 | 
			
		||||
    "@babel/plugin-syntax-export-default-from": "^7.2.0",
 | 
			
		||||
    "@babel/plugin-transform-runtime": "^7.4.4",
 | 
			
		||||
    "@babel/preset-env": "^7.4.5",
 | 
			
		||||
    "@babel/preset-react": "^7.0.0",
 | 
			
		||||
    "@fortawesome/fontawesome-free": "^5.11.2",
 | 
			
		||||
    "@testing-library/react": "^8.0.9",
 | 
			
		||||
    "babel-eslint": "^9.0.0",
 | 
			
		||||
    "babel-jest": "^24.8.0",
 | 
			
		||||
    "babel-loader": "^8.0.6",
 | 
			
		||||
    "babel-plugin-module-resolver": "^3.2.0",
 | 
			
		||||
    "babel-plugin-react-transform": "^3.0.0",
 | 
			
		||||
    "babel-polyfill": "^6.26.0",
 | 
			
		||||
    "bootstrap": "^4.3.1",
 | 
			
		||||
    "copy-webpack-plugin": "^5.0.4",
 | 
			
		||||
    "css-loader": "^3.2.0",
 | 
			
		||||
    "eslint": "^6.1.0",
 | 
			
		||||
    "eslint-config-airbnb": "^18.0.1",
 | 
			
		||||
    "eslint-plugin-import": "^2.18.2",
 | 
			
		||||
    "eslint-plugin-jsx-a11y": "^6.2.3",
 | 
			
		||||
    "eslint-plugin-react": "^7.14.3",
 | 
			
		||||
    "eslint-plugin-react-hooks": "^1.7.0",
 | 
			
		||||
    "file-loader": "^4.2.0",
 | 
			
		||||
    "jest": "^24.8.0",
 | 
			
		||||
    "jest-mock-axios": "^3.0.0",
 | 
			
		||||
    "moment": "^2.24.0",
 | 
			
		||||
    "moment-timezone": "^0.5.25",
 | 
			
		||||
    "react": "^16.9.0",
 | 
			
		||||
    "react-dom": "^16.9.0",
 | 
			
		||||
    "react-styleguidist": "^9.1.16",
 | 
			
		||||
    "snapshot-diff": "^0.5.1",
 | 
			
		||||
    "style-loader": "^1.0.0",
 | 
			
		||||
    "webpack": "^4.41.0"
 | 
			
		||||
  },
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "build": "rm -rf dist; babel src --out-dir dist --ignore '**/__tests__' --source-maps inline --copy-files",
 | 
			
		||||
    "build:watch": "babel src --verbose --watch --out-dir dist --ignore '**/__tests__' --source-maps inline --copy-files",
 | 
			
		||||
    "prepare": "rm -rf ./dist && npm run build",
 | 
			
		||||
    "lint": "eslint src",
 | 
			
		||||
    "test": "jest",
 | 
			
		||||
    "test:watch": "jest --watch",
 | 
			
		||||
    "test:coverage": "jest --coverage --colors",
 | 
			
		||||
    "test:update-snapshots": "jest -u",
 | 
			
		||||
    "docs": "npx styleguidist build ",
 | 
			
		||||
    "docs:watch": "styleguidist server"
 | 
			
		||||
  },
 | 
			
		||||
  "files": [
 | 
			
		||||
    "dist/**",
 | 
			
		||||
    "translations"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
    "name": "foris",
 | 
			
		||||
    "version": "6.7.2",
 | 
			
		||||
    "description": "Foris JS library is a set of components and utils for reForis application and plugins.",
 | 
			
		||||
    "author": "CZ.NIC, z.s.p.o.",
 | 
			
		||||
    "repository": {
 | 
			
		||||
        "type": "git",
 | 
			
		||||
        "url": "https://gitlab.nic.cz/turris/reforis/foris-js.git"
 | 
			
		||||
    },
 | 
			
		||||
    "keywords": [
 | 
			
		||||
        "foris",
 | 
			
		||||
        "reforis"
 | 
			
		||||
    ],
 | 
			
		||||
    "license": "GPL-3.0",
 | 
			
		||||
    "main": "./src/index.js",
 | 
			
		||||
    "dependencies": {
 | 
			
		||||
        "@fortawesome/fontawesome-svg-core": "^6.7.2",
 | 
			
		||||
        "@fortawesome/free-regular-svg-icons": "^6.7.2",
 | 
			
		||||
        "@fortawesome/free-solid-svg-icons": "^6.7.2",
 | 
			
		||||
        "@fortawesome/react-fontawesome": "^0.2.2",
 | 
			
		||||
        "@tanstack/match-sorter-utils": "^8.19.4",
 | 
			
		||||
        "@tanstack/react-table": "^8.21.2",
 | 
			
		||||
        "axios": "^1.7.9",
 | 
			
		||||
        "immutability-helper": "^3.1.1",
 | 
			
		||||
        "moment": "^2.30.1",
 | 
			
		||||
        "qrcode.react": "^4.2.0",
 | 
			
		||||
        "react-datetime": "^3.3.1",
 | 
			
		||||
        "react-uid": "^2.4.0"
 | 
			
		||||
    },
 | 
			
		||||
    "peerDependencies": {
 | 
			
		||||
        "bootstrap": "^5.3.3",
 | 
			
		||||
        "prop-types": "15.8.1",
 | 
			
		||||
        "react": "16.9.0",
 | 
			
		||||
        "react-dom": "16.9.0",
 | 
			
		||||
        "react-router-dom": "^5.1.2"
 | 
			
		||||
    },
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "@babel/cli": "^7.26.4",
 | 
			
		||||
        "@babel/core": "^7.26.9",
 | 
			
		||||
        "@babel/plugin-transform-runtime": "^7.26.9",
 | 
			
		||||
        "@babel/preset-env": "^7.26.9",
 | 
			
		||||
        "@babel/preset-react": "^7.26.3",
 | 
			
		||||
        "@testing-library/react": "^12.1.5",
 | 
			
		||||
        "babel-loader": "^9.2.1",
 | 
			
		||||
        "babel-polyfill": "^6.26.0",
 | 
			
		||||
        "bootstrap": "^5.3.3",
 | 
			
		||||
        "css-loader": "^7.1.2",
 | 
			
		||||
        "eslint": "^8.57.0",
 | 
			
		||||
        "eslint-config-reforis": "^2.2.1",
 | 
			
		||||
        "file-loader": "^6.0.0",
 | 
			
		||||
        "jest": "^29.7.0",
 | 
			
		||||
        "jest-environment-jsdom": "^29.7.0",
 | 
			
		||||
        "jest-mock-axios": "^4.8.0",
 | 
			
		||||
        "moment-timezone": "^0.5.47",
 | 
			
		||||
        "prettier": "^3.5.3",
 | 
			
		||||
        "prop-types": "15.8.1",
 | 
			
		||||
        "react": "16.9.0",
 | 
			
		||||
        "react-dom": "16.9.0",
 | 
			
		||||
        "react-router-dom": "^5.1.2",
 | 
			
		||||
        "react-styleguidist": "^12.0.1",
 | 
			
		||||
        "snapshot-diff": "^0.10.0",
 | 
			
		||||
        "style-loader": "^4.0.0",
 | 
			
		||||
        "webpack": "^5.98.0"
 | 
			
		||||
    },
 | 
			
		||||
    "overrides": {
 | 
			
		||||
        "markdown-to-jsx": "^7.7.4"
 | 
			
		||||
    },
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "lint": "eslint src",
 | 
			
		||||
        "lint:fix": "eslint --fix src",
 | 
			
		||||
        "test": "jest",
 | 
			
		||||
        "test:watch": "jest --watch",
 | 
			
		||||
        "test:coverage": "jest --coverage --colors",
 | 
			
		||||
        "docs": "npx styleguidist build ",
 | 
			
		||||
        "docs:watch": "styleguidist server"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								prettier.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								prettier.config.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
module.exports = require("eslint-config-reforis/prettier.config");
 | 
			
		||||
							
								
								
									
										1
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								requirements.txt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
Babel
 | 
			
		||||
							
								
								
									
										13
									
								
								scripts/collect_files.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								scripts/collect_files.sh
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
# Collect files
 | 
			
		||||
mkdir -p dist
 | 
			
		||||
cp -rf ./src/* dist
 | 
			
		||||
cp package.json package-lock.json README.md dist
 | 
			
		||||
sed -i 's/\/src//g' dist/package.json # remove ./src from main js file path
 | 
			
		||||
 | 
			
		||||
cp -rf translations dist
 | 
			
		||||
 | 
			
		||||
# Remove unwanted files
 | 
			
		||||
find dist -type d -name __tests__ -exec rm -r {} +
 | 
			
		||||
rm -rf dist/__mocks__
 | 
			
		||||
@@ -5,8 +5,8 @@ then
 | 
			
		||||
    echo "\$NPM_TOKEN is not set"
 | 
			
		||||
    exit 1
 | 
			
		||||
else
 | 
			
		||||
    # Need to replace "_" with "_" as GitLab CI won't accept secret vars with "-"
 | 
			
		||||
    echo "//registry.npmjs.org/:_authToken=$(echo "$NPM_TOKEN" | tr _ -)" > .npmrc
 | 
			
		||||
    cd dist
 | 
			
		||||
    echo "//registry.npmjs.org/:_authToken=$(echo "$NPM_TOKEN")" > .npmrc
 | 
			
		||||
    echo "unsafe-perm = true" >> ~/.npmrc
 | 
			
		||||
    if test "$1" = "beta"
 | 
			
		||||
    then
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								src/__mocks__/styleMock.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								src/__mocks__/styleMock.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
module.exports = {};
 | 
			
		||||
@@ -1,35 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useState } from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { Alert } from "bootstrap/Alert";
 | 
			
		||||
 | 
			
		||||
const AlertContext = React.createContext();
 | 
			
		||||
 | 
			
		||||
AlertContextProvider.propTypes = {
 | 
			
		||||
    children: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
        PropTypes.node,
 | 
			
		||||
    ]),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function AlertContextProvider({ children }) {
 | 
			
		||||
    const [alert, setAlert] = useState(null);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            {alert && <Alert type="danger" message={alert} onDismiss={() => setAlert(null)} />}
 | 
			
		||||
            <AlertContext.Provider value={setAlert}>
 | 
			
		||||
                { children }
 | 
			
		||||
            </AlertContext.Provider>
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { AlertContext, AlertContextProvider };
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
			
		||||
 | 
			
		||||
exports[`AlertContext should render alert 1`] = `
 | 
			
		||||
<div>
 | 
			
		||||
  <div
 | 
			
		||||
    class="alert alert-dismissible alert-danger"
 | 
			
		||||
  >
 | 
			
		||||
    <button
 | 
			
		||||
      class="close"
 | 
			
		||||
      type="button"
 | 
			
		||||
    >
 | 
			
		||||
      ×
 | 
			
		||||
    </button>
 | 
			
		||||
    Alert content
 | 
			
		||||
  </div>
 | 
			
		||||
  <button>
 | 
			
		||||
    Set alert
 | 
			
		||||
  </button>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`AlertContext should render component without alert 1`] = `
 | 
			
		||||
<div>
 | 
			
		||||
  <button>
 | 
			
		||||
    Set alert
 | 
			
		||||
  </button>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { useReducer, useCallback } from "react";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    API_ACTIONS, TIMEOUT, HEADERS, APIReducer, getErrorMessage,
 | 
			
		||||
} from "./utils";
 | 
			
		||||
 | 
			
		||||
export function useAPIDelete(url) {
 | 
			
		||||
    const [state, dispatch] = useReducer(APIReducer, {
 | 
			
		||||
        isSending: false,
 | 
			
		||||
        isError: false,
 | 
			
		||||
        isSuccess: false,
 | 
			
		||||
        data: null,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const requestDelete = useCallback(async () => {
 | 
			
		||||
        dispatch({ type: API_ACTIONS.INIT });
 | 
			
		||||
        try {
 | 
			
		||||
            await axios.delete(url, {
 | 
			
		||||
                timeout: TIMEOUT,
 | 
			
		||||
                headers: HEADERS,
 | 
			
		||||
            });
 | 
			
		||||
            dispatch({ type: API_ACTIONS.SUCCESS });
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            dispatch({
 | 
			
		||||
                type: API_ACTIONS.FAILURE,
 | 
			
		||||
                payload: getErrorMessage(error),
 | 
			
		||||
                status: error.response.status,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }, [url]);
 | 
			
		||||
 | 
			
		||||
    return [state, requestDelete];
 | 
			
		||||
}
 | 
			
		||||
@@ -1,65 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { useReducer, useCallback } from "react";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
import { ForisURLs } from "forisUrls";
 | 
			
		||||
import { API_ACTIONS, TIMEOUT } from "./utils";
 | 
			
		||||
 | 
			
		||||
const APIGetReducer = (state, action) => {
 | 
			
		||||
    switch (action.type) {
 | 
			
		||||
    case API_ACTIONS.INIT:
 | 
			
		||||
        return {
 | 
			
		||||
            ...state,
 | 
			
		||||
            isLoading: true,
 | 
			
		||||
            isError: false,
 | 
			
		||||
        };
 | 
			
		||||
    case API_ACTIONS.SUCCESS:
 | 
			
		||||
        return {
 | 
			
		||||
            ...state,
 | 
			
		||||
            isLoading: false,
 | 
			
		||||
            isError: false,
 | 
			
		||||
            data: action.payload,
 | 
			
		||||
        };
 | 
			
		||||
    case API_ACTIONS.FAILURE:
 | 
			
		||||
        if (action.status === 403) window.location.assign(ForisURLs.login);
 | 
			
		||||
        return {
 | 
			
		||||
            ...state,
 | 
			
		||||
            isLoading: false,
 | 
			
		||||
            isError: true,
 | 
			
		||||
            data: action.payload,
 | 
			
		||||
        };
 | 
			
		||||
    default:
 | 
			
		||||
        throw new Error();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function useAPIGet(url) {
 | 
			
		||||
    const [state, dispatch] = useReducer(APIGetReducer, {
 | 
			
		||||
        isLoading: false,
 | 
			
		||||
        isError: false,
 | 
			
		||||
        data: null,
 | 
			
		||||
    });
 | 
			
		||||
    const get = useCallback(async () => {
 | 
			
		||||
        dispatch({ type: API_ACTIONS.INIT });
 | 
			
		||||
        try {
 | 
			
		||||
            const result = await axios.get(url, {
 | 
			
		||||
                timeout: TIMEOUT,
 | 
			
		||||
            });
 | 
			
		||||
            dispatch({ type: API_ACTIONS.SUCCESS, payload: result.data });
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            dispatch({
 | 
			
		||||
                type: API_ACTIONS.FAILURE,
 | 
			
		||||
                payload: error.response.data,
 | 
			
		||||
                status: error.response.status,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }, [url]);
 | 
			
		||||
 | 
			
		||||
    return [state, get];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										143
									
								
								src/api/hooks.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/api/hooks.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { useCallback, useEffect, useReducer, useState } from "react";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    API_ACTIONS,
 | 
			
		||||
    API_METHODS,
 | 
			
		||||
    API_STATE,
 | 
			
		||||
    getErrorPayload,
 | 
			
		||||
    HEADERS,
 | 
			
		||||
    TIMEOUT,
 | 
			
		||||
} from "./utils";
 | 
			
		||||
 | 
			
		||||
const DATA_METHODS = ["POST", "PATCH", "PUT"];
 | 
			
		||||
 | 
			
		||||
function createAPIHook(method) {
 | 
			
		||||
    return (urlRoot, contentType) => {
 | 
			
		||||
        const [state, dispatch] = useReducer(APIReducer, {
 | 
			
		||||
            state: API_STATE.INIT,
 | 
			
		||||
            data: null,
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        const sendRequest = useCallback(
 | 
			
		||||
            async ({ data, suffix } = {}) => {
 | 
			
		||||
                const headers = { ...HEADERS };
 | 
			
		||||
                if (contentType) {
 | 
			
		||||
                    headers["Content-Type"] = contentType;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                dispatch({ type: API_ACTIONS.INIT });
 | 
			
		||||
                try {
 | 
			
		||||
                    // Prepare request
 | 
			
		||||
                    const request = API_METHODS[method];
 | 
			
		||||
                    const config = {
 | 
			
		||||
                        timeout: TIMEOUT,
 | 
			
		||||
                        headers,
 | 
			
		||||
                    };
 | 
			
		||||
                    const url = suffix ? `${urlRoot}/${suffix}` : urlRoot;
 | 
			
		||||
 | 
			
		||||
                    // Make request
 | 
			
		||||
                    let result;
 | 
			
		||||
                    if (DATA_METHODS.includes(method)) {
 | 
			
		||||
                        result = await request(url, data, config);
 | 
			
		||||
                    } else {
 | 
			
		||||
                        result = await request(url, config);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    // Process request result
 | 
			
		||||
                    dispatch({
 | 
			
		||||
                        type: API_ACTIONS.SUCCESS,
 | 
			
		||||
                        payload: result.data,
 | 
			
		||||
                    });
 | 
			
		||||
                } catch (error) {
 | 
			
		||||
                    const errorPayload = getErrorPayload(error);
 | 
			
		||||
                    dispatch({
 | 
			
		||||
                        type: API_ACTIONS.FAILURE,
 | 
			
		||||
                        status: error.response && error.response.status,
 | 
			
		||||
                        payload: errorPayload,
 | 
			
		||||
                    });
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            [urlRoot, contentType]
 | 
			
		||||
        );
 | 
			
		||||
        return [state, sendRequest];
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function APIReducer(state, action) {
 | 
			
		||||
    switch (action.type) {
 | 
			
		||||
        case API_ACTIONS.INIT:
 | 
			
		||||
            return {
 | 
			
		||||
                ...state,
 | 
			
		||||
                state: API_STATE.SENDING,
 | 
			
		||||
            };
 | 
			
		||||
        case API_ACTIONS.SUCCESS:
 | 
			
		||||
            return {
 | 
			
		||||
                state: API_STATE.SUCCESS,
 | 
			
		||||
                data: action.payload,
 | 
			
		||||
            };
 | 
			
		||||
        case API_ACTIONS.FAILURE:
 | 
			
		||||
            if (action.status === 401) {
 | 
			
		||||
                window.location.reload();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Not an API error - should be rethrown.
 | 
			
		||||
            if (
 | 
			
		||||
                action.payload &&
 | 
			
		||||
                action.payload.stack &&
 | 
			
		||||
                action.payload.message
 | 
			
		||||
            ) {
 | 
			
		||||
                throw action.payload;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return {
 | 
			
		||||
                state: API_STATE.ERROR,
 | 
			
		||||
                data: action.payload,
 | 
			
		||||
            };
 | 
			
		||||
        default:
 | 
			
		||||
            throw new Error();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const useAPIGet = createAPIHook("GET");
 | 
			
		||||
const useAPIPost = createAPIHook("POST");
 | 
			
		||||
const useAPIPatch = createAPIHook("PATCH");
 | 
			
		||||
const useAPIPut = createAPIHook("PUT");
 | 
			
		||||
const useAPIDelete = createAPIHook("DELETE");
 | 
			
		||||
 | 
			
		||||
/* eslint-disable default-param-last */
 | 
			
		||||
function useAPIPolling(endpoint, delay = 1000, until) {
 | 
			
		||||
    // delay ms
 | 
			
		||||
    const [state, setState] = useState({ state: API_STATE.INIT });
 | 
			
		||||
    const [getResponse, get] = useAPIGet(endpoint);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (getResponse.state !== API_STATE.INIT) {
 | 
			
		||||
            setState(getResponse);
 | 
			
		||||
        }
 | 
			
		||||
    }, [getResponse]);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (until) {
 | 
			
		||||
            const interval = setInterval(get, delay);
 | 
			
		||||
            return () => clearInterval(interval);
 | 
			
		||||
        }
 | 
			
		||||
    }, [until, delay, get]);
 | 
			
		||||
 | 
			
		||||
    return [state];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
    useAPIGet,
 | 
			
		||||
    useAPIPost,
 | 
			
		||||
    useAPIPatch,
 | 
			
		||||
    useAPIPut,
 | 
			
		||||
    useAPIDelete,
 | 
			
		||||
    useAPIPolling,
 | 
			
		||||
};
 | 
			
		||||
@@ -1,40 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { useReducer } from "react";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    API_ACTIONS, TIMEOUT, HEADERS, APIReducer, getErrorMessage,
 | 
			
		||||
} from "./utils";
 | 
			
		||||
 | 
			
		||||
export function useAPIPatch(url) {
 | 
			
		||||
    const [state, dispatch] = useReducer(APIReducer, {
 | 
			
		||||
        isSending: false,
 | 
			
		||||
        isError: false,
 | 
			
		||||
        isSuccess: false,
 | 
			
		||||
        data: null,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const patch = async (data) => {
 | 
			
		||||
        dispatch({ type: API_ACTIONS.INIT });
 | 
			
		||||
        try {
 | 
			
		||||
            const result = await axios.patch(url, data, {
 | 
			
		||||
                timeout: TIMEOUT,
 | 
			
		||||
                headers: HEADERS,
 | 
			
		||||
            });
 | 
			
		||||
            dispatch({ type: API_ACTIONS.SUCCESS, payload: result.data });
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            dispatch({
 | 
			
		||||
                type: API_ACTIONS.FAILURE,
 | 
			
		||||
                payload: getErrorMessage(error),
 | 
			
		||||
                status: error.response.status,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    return [state, patch];
 | 
			
		||||
}
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { useReducer } from "react";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    API_ACTIONS, TIMEOUT, HEADERS, APIReducer, getErrorMessage,
 | 
			
		||||
} from "./utils";
 | 
			
		||||
 | 
			
		||||
export function useAPIPost(url, contentType) {
 | 
			
		||||
    const [state, dispatch] = useReducer(APIReducer, {
 | 
			
		||||
        isSending: false,
 | 
			
		||||
        isError: false,
 | 
			
		||||
        isSuccess: false,
 | 
			
		||||
        data: null,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const headers = { ...HEADERS };
 | 
			
		||||
    if (contentType) {
 | 
			
		||||
        headers["Content-Type"] = contentType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const post = async (data) => {
 | 
			
		||||
        dispatch({ type: API_ACTIONS.INIT });
 | 
			
		||||
        try {
 | 
			
		||||
            const result = await axios.post(url, data, {
 | 
			
		||||
                timeout: TIMEOUT,
 | 
			
		||||
                headers,
 | 
			
		||||
            });
 | 
			
		||||
            dispatch({ type: API_ACTIONS.SUCCESS, payload: result.data });
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            dispatch({
 | 
			
		||||
                type: API_ACTIONS.FAILURE,
 | 
			
		||||
                payload: getErrorMessage(error),
 | 
			
		||||
                status: error.response.status,
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
    return [state, post];
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										103
									
								
								src/api/utils.js
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								src/api/utils.js
									
									
									
									
									
								
							@@ -1,11 +1,41 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2021 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { ForisURLs } from "forisUrls";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
export const HEADERS = {
 | 
			
		||||
    Accept: "application/json",
 | 
			
		||||
    "Content-Type": "application/json",
 | 
			
		||||
    "X-CSRFToken": getCookie("_csrf_token"),
 | 
			
		||||
    "X-Requested-With": "json",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const TIMEOUT = 30500;
 | 
			
		||||
 | 
			
		||||
export const API_ACTIONS = {
 | 
			
		||||
    INIT: 1,
 | 
			
		||||
    SUCCESS: 2,
 | 
			
		||||
    FAILURE: 3,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const API_STATE = {
 | 
			
		||||
    INIT: "init",
 | 
			
		||||
    SENDING: "sending",
 | 
			
		||||
    SUCCESS: "success",
 | 
			
		||||
    ERROR: "error",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const API_METHODS = {
 | 
			
		||||
    GET: axios.get,
 | 
			
		||||
    POST: axios.post,
 | 
			
		||||
    PATCH: axios.patch,
 | 
			
		||||
    PUT: axios.put,
 | 
			
		||||
    DELETE: axios.delete,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function getCookie(name) {
 | 
			
		||||
    let cookieValue = null;
 | 
			
		||||
@@ -14,8 +44,10 @@ function getCookie(name) {
 | 
			
		||||
        for (let i = 0; i < cookies.length; i++) {
 | 
			
		||||
            const cookie = cookies[i].trim();
 | 
			
		||||
            // Does this cookie string begin with the name we want?
 | 
			
		||||
            if (cookie.substring(0, name.length + 1) === (`${name}=`)) {
 | 
			
		||||
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
 | 
			
		||||
            if (cookie.substring(0, name.length + 1) === `${name}=`) {
 | 
			
		||||
                cookieValue = decodeURIComponent(
 | 
			
		||||
                    cookie.substring(name.length + 1)
 | 
			
		||||
                );
 | 
			
		||||
                break;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -23,55 +55,26 @@ function getCookie(name) {
 | 
			
		||||
    return cookieValue;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const HEADERS = {
 | 
			
		||||
    Accept: "application/json",
 | 
			
		||||
    "Content-Type": "application/json",
 | 
			
		||||
    "X-CSRFToken": getCookie("_csrf_token"),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const TIMEOUT = 5000;
 | 
			
		||||
 | 
			
		||||
export const API_ACTIONS = {
 | 
			
		||||
    INIT: 1,
 | 
			
		||||
    SUCCESS: 2,
 | 
			
		||||
    FAILURE: 3,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function APIReducer(state, action) {
 | 
			
		||||
    switch (action.type) {
 | 
			
		||||
    case API_ACTIONS.INIT:
 | 
			
		||||
        return {
 | 
			
		||||
            ...state,
 | 
			
		||||
            isSending: true,
 | 
			
		||||
            isError: false,
 | 
			
		||||
            isSuccess: false,
 | 
			
		||||
        };
 | 
			
		||||
    case API_ACTIONS.SUCCESS:
 | 
			
		||||
        return {
 | 
			
		||||
            ...state,
 | 
			
		||||
            isSending: false,
 | 
			
		||||
            isError: false,
 | 
			
		||||
            isSuccess: true,
 | 
			
		||||
            data: action.payload,
 | 
			
		||||
        };
 | 
			
		||||
    case API_ACTIONS.FAILURE:
 | 
			
		||||
        if (action.status === 403) window.location.assign(ForisURLs.login);
 | 
			
		||||
        return {
 | 
			
		||||
            ...state,
 | 
			
		||||
            isSending: false,
 | 
			
		||||
            isError: true,
 | 
			
		||||
            isSuccess: false,
 | 
			
		||||
            data: action.payload,
 | 
			
		||||
        };
 | 
			
		||||
    default:
 | 
			
		||||
        throw new Error();
 | 
			
		||||
export function getErrorPayload(error) {
 | 
			
		||||
    if (error.response) {
 | 
			
		||||
        if (error.response.status === 401) {
 | 
			
		||||
            return _("The session is expired. Please log in again.");
 | 
			
		||||
        }
 | 
			
		||||
        return getJSONErrorMessage(error);
 | 
			
		||||
    }
 | 
			
		||||
    if (error.code === "ECONNABORTED") {
 | 
			
		||||
        return _("Timeout error occurred.");
 | 
			
		||||
    }
 | 
			
		||||
    if (error.request) {
 | 
			
		||||
        return _("No response received.");
 | 
			
		||||
    }
 | 
			
		||||
    // Return original error because it's not directly related to API request/response.
 | 
			
		||||
    return error;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function getErrorMessage(error) {
 | 
			
		||||
    let payload = "An unknown error occurred";
 | 
			
		||||
export function getJSONErrorMessage(error) {
 | 
			
		||||
    if (error.response.headers["content-type"] === "application/json") {
 | 
			
		||||
        payload = error.response.data;
 | 
			
		||||
        return error.response.data;
 | 
			
		||||
    }
 | 
			
		||||
    return payload;
 | 
			
		||||
    return _("An unknown API error occurred.");
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,30 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
import React, { useRef, useEffect, useState } from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { useFocusTrap } from "../utils/hooks";
 | 
			
		||||
 | 
			
		||||
export const ALERT_TYPES = Object.freeze({
 | 
			
		||||
    PRIMARY: "primary",
 | 
			
		||||
    SECONDARY: "secondary",
 | 
			
		||||
    SUCCESS: "success",
 | 
			
		||||
    DANGER: "danger",
 | 
			
		||||
    WARNING: "warning",
 | 
			
		||||
    INFO: "info",
 | 
			
		||||
    LIGHT: "light",
 | 
			
		||||
    DARK: "dark",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
Alert.propTypes = {
 | 
			
		||||
    /** Type of the alert it adds as `alert-${type}` class. */
 | 
			
		||||
    type: PropTypes.string.isRequired,
 | 
			
		||||
    /** Alert message. */
 | 
			
		||||
    message: PropTypes.string,
 | 
			
		||||
    type: PropTypes.oneOf(Object.values(ALERT_TYPES)),
 | 
			
		||||
    /** Alert content. */
 | 
			
		||||
    children: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
@@ -22,14 +34,48 @@ Alert.propTypes = {
 | 
			
		||||
    onDismiss: PropTypes.func,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function Alert({
 | 
			
		||||
    type, message, onDismiss, children,
 | 
			
		||||
}) {
 | 
			
		||||
Alert.defaultProps = {
 | 
			
		||||
    type: ALERT_TYPES.DANGER,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function Alert({ type, onDismiss, children }) {
 | 
			
		||||
    const alertRef = useRef();
 | 
			
		||||
    const [isVisible, setIsVisible] = useState(true);
 | 
			
		||||
    useFocusTrap(alertRef, !!onDismiss);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (onDismiss) {
 | 
			
		||||
            const timeout = setTimeout(() => setIsVisible(false), 7000);
 | 
			
		||||
            return () => clearTimeout(timeout);
 | 
			
		||||
        }
 | 
			
		||||
    }, [onDismiss]);
 | 
			
		||||
 | 
			
		||||
    const handleAnimationEnd = () => {
 | 
			
		||||
        if (!isVisible && onDismiss) {
 | 
			
		||||
            onDismiss();
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={`alert alert-dismissible alert-${type}`}>
 | 
			
		||||
            {onDismiss ? <button type="button" className="close" onClick={onDismiss}>×</button> : false}
 | 
			
		||||
            {message}
 | 
			
		||||
        <div
 | 
			
		||||
            ref={alertRef}
 | 
			
		||||
            className={`alert alert-${type} ${isVisible ? "alert-fade-in" : "alert-slide-out-top"} ${
 | 
			
		||||
                onDismiss ? "alert-dismissible" : ""
 | 
			
		||||
            }`.trim()}
 | 
			
		||||
            role="alert"
 | 
			
		||||
            onAnimationEnd={handleAnimationEnd}
 | 
			
		||||
        >
 | 
			
		||||
            {onDismiss && (
 | 
			
		||||
                <button
 | 
			
		||||
                    type="button"
 | 
			
		||||
                    className="btn-close"
 | 
			
		||||
                    onClick={() => setIsVisible(false)}
 | 
			
		||||
                    aria-label={_("Close")}
 | 
			
		||||
                />
 | 
			
		||||
            )}
 | 
			
		||||
            {children}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Alert;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,20 +1,21 @@
 | 
			
		||||
Bootstrap alert component.
 | 
			
		||||
```jsx
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
 | 
			
		||||
function AlertExample(){
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
 | 
			
		||||
function AlertExample() {
 | 
			
		||||
    const [alert, setAlert] = useState(true);
 | 
			
		||||
    if (alert)
 | 
			
		||||
        return <Alert 
 | 
			
		||||
            type='warning' 
 | 
			
		||||
            message='Some warning out there!' 
 | 
			
		||||
            onDismiss={()=>setAlert(false)}
 | 
			
		||||
        />;
 | 
			
		||||
    return <button 
 | 
			
		||||
        className='btn btn-secondary' 
 | 
			
		||||
        onClick={()=>setAlert(true)}
 | 
			
		||||
    >Show alert again</button>;
 | 
			
		||||
};
 | 
			
		||||
<AlertExample/>
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <Alert type="warning" onDismiss={() => setAlert(false)}>
 | 
			
		||||
                Some warning out there!
 | 
			
		||||
            </Alert>
 | 
			
		||||
        );
 | 
			
		||||
    return (
 | 
			
		||||
        <button className="btn btn-secondary" onClick={() => setAlert(true)}>
 | 
			
		||||
            Show alert again
 | 
			
		||||
        </button>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
<AlertExample />;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,13 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
const OFFSET = 8;
 | 
			
		||||
const SIZE = 3;
 | 
			
		||||
const SIZE_CLASS = ` offset-lg-${OFFSET} col-lg-${SIZE}`;
 | 
			
		||||
const SIZE_CLASS_SM = " col-sm-12";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
Button.propTypes = {
 | 
			
		||||
    /** Additional class name. */
 | 
			
		||||
@@ -29,22 +25,28 @@ Button.propTypes = {
 | 
			
		||||
    ]).isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function Button({
 | 
			
		||||
    className, loading, forisFormSize, children, ...props
 | 
			
		||||
}) {
 | 
			
		||||
    className = className ? `btn ${className}` : "btn btn-primary ";
 | 
			
		||||
    if (forisFormSize) className += SIZE_CLASS + SIZE_CLASS_SM;
 | 
			
		||||
 | 
			
		||||
    const span = loading
 | 
			
		||||
        ? <span className="spinner-border spinner-border-sm" role="status" aria-hidden="true" /> : null;
 | 
			
		||||
function Button({ className, loading, forisFormSize, children, ...props }) {
 | 
			
		||||
    let buttonClass = className ? `btn ${className}` : "btn btn-primary";
 | 
			
		||||
    if (forisFormSize) {
 | 
			
		||||
        buttonClass = `${buttonClass} col-12 col-md-3 col-lg-2`;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <button type="button" className={className} {...props}>
 | 
			
		||||
            {span}
 | 
			
		||||
            {" "}
 | 
			
		||||
            {span ? " " : null}
 | 
			
		||||
            {" "}
 | 
			
		||||
            {children}
 | 
			
		||||
        <button
 | 
			
		||||
            type="button"
 | 
			
		||||
            className={`${buttonClass} d-inline-flex justify-content-center align-items-center`}
 | 
			
		||||
            {...props}
 | 
			
		||||
        >
 | 
			
		||||
            {loading && (
 | 
			
		||||
                <span
 | 
			
		||||
                    className="spinner-border spinner-border-sm me-1"
 | 
			
		||||
                    role="status"
 | 
			
		||||
                    aria-hidden="true"
 | 
			
		||||
                />
 | 
			
		||||
            )}
 | 
			
		||||
            <span>{children}</span>
 | 
			
		||||
        </button>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Button;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,5 +11,7 @@ Can be used without parameters:
 | 
			
		||||
Using loading spinner:
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
<Button loading disabled>Loading...</Button>
 | 
			
		||||
<Button loading disabled>
 | 
			
		||||
    Loading...
 | 
			
		||||
</Button>
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,50 +1,52 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import { useUID } from "react-uid";
 | 
			
		||||
 | 
			
		||||
import { formFieldsSize } from "./constants";
 | 
			
		||||
 | 
			
		||||
CheckBox.propTypes = {
 | 
			
		||||
    /** Label message */
 | 
			
		||||
    label: PropTypes.string.isRequired,
 | 
			
		||||
    /** Help text message */
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
    /** Apply default size (full-width) */
 | 
			
		||||
    useDefaultSize: PropTypes.bool,
 | 
			
		||||
    /** Control if checkbox is clickable */
 | 
			
		||||
    disabled: PropTypes.bool,
 | 
			
		||||
    /** Additional class name */
 | 
			
		||||
    className: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CheckBox.defaultProps = {
 | 
			
		||||
    useDefaultSize: true,
 | 
			
		||||
    disabled: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function CheckBox({
 | 
			
		||||
    label, helpText, useDefaultSize, disabled, ...props
 | 
			
		||||
}) {
 | 
			
		||||
function CheckBox({ label, helpText, disabled, className, ...props }) {
 | 
			
		||||
    const uid = useUID();
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={useDefaultSize ? formFieldsSize : ""} style={{ marginBottom: "1rem" }}>
 | 
			
		||||
            <div className="custom-control custom-checkbox" style={{ marginBottom: "0" }}>
 | 
			
		||||
                <input
 | 
			
		||||
                    className="custom-control-input"
 | 
			
		||||
                    type="checkbox"
 | 
			
		||||
                    id={uid}
 | 
			
		||||
                    disabled={disabled}
 | 
			
		||||
 | 
			
		||||
                    {...props}
 | 
			
		||||
                />
 | 
			
		||||
                <label className="custom-control-label" htmlFor={uid} style={helpText ? { marginBottom: "0" } : null}>{label}</label>
 | 
			
		||||
            </div>
 | 
			
		||||
            {helpText ? <small className="form-text text-muted">{helpText}</small> : null}
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={`${className || "mb-3"} form-check`.trim()}>
 | 
			
		||||
            <input
 | 
			
		||||
                className="form-check-input"
 | 
			
		||||
                type="checkbox"
 | 
			
		||||
                id={uid}
 | 
			
		||||
                disabled={disabled}
 | 
			
		||||
                {...props}
 | 
			
		||||
            />
 | 
			
		||||
            <label className="form-check-label" htmlFor={uid}>
 | 
			
		||||
                {label}
 | 
			
		||||
            </label>
 | 
			
		||||
            {helpText && (
 | 
			
		||||
                <div className="form-text">
 | 
			
		||||
                    <small>{helpText}</small>
 | 
			
		||||
                </div>
 | 
			
		||||
            )}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default CheckBox;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,17 @@
 | 
			
		||||
Checkbox with label Bootstrap component with predefined sizes and structure for using in foris forms.
 | 
			
		||||
Checkbox with label Bootstrap component with predefined sizes and structure for
 | 
			
		||||
using in foris forms.
 | 
			
		||||
 | 
			
		||||
All additional `props` are passed to the `<input type="checkbox">` HTML component.
 | 
			
		||||
All additional `props` are passed to the `<input type="checkbox">` HTML
 | 
			
		||||
component.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
const [value, setValue] = useState(false);
 | 
			
		||||
 | 
			
		||||
<CheckBox
 | 
			
		||||
    value={value}
 | 
			
		||||
    label="Some label" 
 | 
			
		||||
    label="Some label"
 | 
			
		||||
    helpText="Read the small text!"
 | 
			
		||||
    onChange={event =>setValue(event.target.value)}
 | 
			
		||||
/>
 | 
			
		||||
    onChange={(event) => setValue(event.target.value)}
 | 
			
		||||
/>;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								src/bootstrap/CopyInput.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								src/bootstrap/CopyInput.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useState, useRef } from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import Input from "./Input";
 | 
			
		||||
 | 
			
		||||
CopyInput.propTypes = {
 | 
			
		||||
    /** Field label. */
 | 
			
		||||
    label: PropTypes.string.isRequired,
 | 
			
		||||
    /** Field value. */
 | 
			
		||||
    value: PropTypes.string,
 | 
			
		||||
    /** Help text message. */
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
    /** Disable input field */
 | 
			
		||||
    disabled: PropTypes.bool,
 | 
			
		||||
    /** Readonly input field */
 | 
			
		||||
    readOnly: PropTypes.bool,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function CopyInput({ value, ...props }) {
 | 
			
		||||
    const inputTextRef = useRef();
 | 
			
		||||
    const [isCopied, setIsCopied] = useState(false);
 | 
			
		||||
 | 
			
		||||
    const handleCopyClick = async () => {
 | 
			
		||||
        // Clipboard API works only in a secure (HTTPS) context.
 | 
			
		||||
        if (navigator.clipboard) {
 | 
			
		||||
            await navigator.clipboard.writeText(value);
 | 
			
		||||
        } else {
 | 
			
		||||
            // Fallback to the "classic" copy to clipboard implementation.
 | 
			
		||||
            inputTextRef.current.focus();
 | 
			
		||||
            inputTextRef.current.select();
 | 
			
		||||
            document.execCommand("copy");
 | 
			
		||||
            inputTextRef.current.blur();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        setIsCopied(true);
 | 
			
		||||
        setTimeout(() => {
 | 
			
		||||
            setIsCopied(false);
 | 
			
		||||
        }, 1500);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Input type="text" value={value} ref={inputTextRef} {...props}>
 | 
			
		||||
            <button
 | 
			
		||||
                className="btn btn-outline-secondary"
 | 
			
		||||
                type="button"
 | 
			
		||||
                onClick={handleCopyClick}
 | 
			
		||||
            >
 | 
			
		||||
                <span>{isCopied ? _("Copied!") : _("Copy")}</span>
 | 
			
		||||
            </button>
 | 
			
		||||
        </Input>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default CopyInput;
 | 
			
		||||
							
								
								
									
										17
									
								
								src/bootstrap/CopyInput.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/bootstrap/CopyInput.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
CopyInput Bootstrap component contains input with a label, predefined sizes, and
 | 
			
		||||
structure for use in ForisForm and the "Copy" button (copy to clipboard). It can
 | 
			
		||||
be used with `readOnly` and `disabled` parameters, please see an example.
 | 
			
		||||
 | 
			
		||||
All additional `props` are passed to the `<input type="text">` HTML component.
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
import React, { useState } from "react";
 | 
			
		||||
const [value, setValue] = useState("Text to appear in clipboard.");
 | 
			
		||||
 | 
			
		||||
<CopyInput
 | 
			
		||||
    label="Copy me"
 | 
			
		||||
    value={value}
 | 
			
		||||
    helpText="Read the small text!"
 | 
			
		||||
    readOnly
 | 
			
		||||
/>;
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										4
									
								
								src/bootstrap/DataTimeInput.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								src/bootstrap/DataTimeInput.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,4 @@
 | 
			
		||||
/* Override defaults from "react-datetime" - display picker above input */
 | 
			
		||||
.rdtPicker {
 | 
			
		||||
    bottom: 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,17 +1,19 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import Datetime from "react-datetime/DateTime";
 | 
			
		||||
import moment from "moment/moment";
 | 
			
		||||
import "react-datetime/css/react-datetime.css";
 | 
			
		||||
 | 
			
		||||
import { Input } from "./Input";
 | 
			
		||||
import moment from "moment/moment";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import Datetime from "react-datetime";
 | 
			
		||||
import "react-datetime/css/react-datetime.css";
 | 
			
		||||
import "./DataTimeInput.css";
 | 
			
		||||
 | 
			
		||||
import Input from "./Input";
 | 
			
		||||
 | 
			
		||||
DataTimeInput.propTypes = {
 | 
			
		||||
    /** Field label. */
 | 
			
		||||
@@ -36,25 +38,32 @@ DataTimeInput.propTypes = {
 | 
			
		||||
const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
 | 
			
		||||
const DEFAULT_TIME_FORMAT = "HH:mm:ss";
 | 
			
		||||
 | 
			
		||||
export function DataTimeInput({
 | 
			
		||||
    value, onChange, isValidDate, dateFormat, timeFormat, children, ...props
 | 
			
		||||
function DataTimeInput({
 | 
			
		||||
    value,
 | 
			
		||||
    onChange,
 | 
			
		||||
    isValidDate,
 | 
			
		||||
    dateFormat,
 | 
			
		||||
    timeFormat,
 | 
			
		||||
    children,
 | 
			
		||||
    ...props
 | 
			
		||||
}) {
 | 
			
		||||
    function renderInput(datetimeProps) {
 | 
			
		||||
    const renderInput = (datetimeProps) => {
 | 
			
		||||
        return (
 | 
			
		||||
            <Input
 | 
			
		||||
                {...props}
 | 
			
		||||
                {...datetimeProps}
 | 
			
		||||
            >
 | 
			
		||||
            <Input {...props} {...datetimeProps}>
 | 
			
		||||
                {children}
 | 
			
		||||
            </Input>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Datetime
 | 
			
		||||
            locale={ForisTranslations.locale}
 | 
			
		||||
            dateFormat={dateFormat !== undefined ? dateFormat : DEFAULT_DATE_FORMAT}
 | 
			
		||||
            timeFormat={timeFormat !== undefined ? timeFormat : DEFAULT_TIME_FORMAT}
 | 
			
		||||
            dateFormat={
 | 
			
		||||
                dateFormat !== undefined ? dateFormat : DEFAULT_DATE_FORMAT
 | 
			
		||||
            }
 | 
			
		||||
            timeFormat={
 | 
			
		||||
                timeFormat !== undefined ? timeFormat : DEFAULT_TIME_FORMAT
 | 
			
		||||
            }
 | 
			
		||||
            value={value}
 | 
			
		||||
            onChange={onChange}
 | 
			
		||||
            isValidDate={isValidDate}
 | 
			
		||||
@@ -62,3 +71,5 @@ export function DataTimeInput({
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default DataTimeInput;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +1,26 @@
 | 
			
		||||
Adopted from `react-datetime/DateTime` datatime picker component.
 | 
			
		||||
It uses `momentjs` see example.
 | 
			
		||||
Adopted from `react-datetime/DateTime` datatime picker component. It uses
 | 
			
		||||
`momentjs` see example.
 | 
			
		||||
 | 
			
		||||
It requires `ForisTranslations.locale` to be defined in order to use right locale.
 | 
			
		||||
It requires `ForisTranslations.locale` to be defined in order to use right
 | 
			
		||||
locale.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
ForisTranslations={locale:'en'};
 | 
			
		||||
```jsx
 | 
			
		||||
ForisTranslations = { locale: "en" };
 | 
			
		||||
 | 
			
		||||
import {useState, useEffect} from 'react';
 | 
			
		||||
import moment from 'moment/moment';
 | 
			
		||||
import { useState, useEffect } from "react";
 | 
			
		||||
import moment from "moment/moment";
 | 
			
		||||
 | 
			
		||||
const [dataTime, setDataTime] = useState(moment());
 | 
			
		||||
const [error, setError] = useState();
 | 
			
		||||
useEffect(()=>{
 | 
			
		||||
   dataTime.isValid() ? setError(null) : setError('Invalid value!');
 | 
			
		||||
},[dataTime]);
 | 
			
		||||
 
 | 
			
		||||
useEffect(() => {
 | 
			
		||||
    dataTime.isValid() ? setError(null) : setError("Invalid value!");
 | 
			
		||||
}, [dataTime]);
 | 
			
		||||
 | 
			
		||||
<DataTimeInput
 | 
			
		||||
    label='Time to sleep'
 | 
			
		||||
    label="Time to sleep"
 | 
			
		||||
    value={dataTime}
 | 
			
		||||
    error={error}
 | 
			
		||||
    helpText='Example helptext...'
 | 
			
		||||
    onChange={value => setDataTime(value)}
 | 
			
		||||
/>
 | 
			
		||||
    helpText="Example helptext..."
 | 
			
		||||
    onChange={(value) => setDataTime(value)}
 | 
			
		||||
/>;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,38 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
DownloadButton.propTypes = {
 | 
			
		||||
    href: PropTypes.string.isRequired,
 | 
			
		||||
    className: PropTypes.string,
 | 
			
		||||
    children: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
        PropTypes.node,
 | 
			
		||||
    ]),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function DownloadButton({ href, children }) {
 | 
			
		||||
    return <a href={href} className="btn btn-primary" download>{children}</a>;
 | 
			
		||||
DownloadButton.defaultProps = {
 | 
			
		||||
    className: "btn-primary",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function DownloadButton({ href, className, children, ...props }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <a
 | 
			
		||||
            href={href}
 | 
			
		||||
            className={`btn ${className}`.trim()}
 | 
			
		||||
            {...props}
 | 
			
		||||
            download
 | 
			
		||||
        >
 | 
			
		||||
            {children}
 | 
			
		||||
        </a>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default DownloadButton;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
Hyperlink with apperance of a button.
 | 
			
		||||
 | 
			
		||||
It has `download` attribute, which prevents closing WebSocket connection on Firefox. See [related issue](https://bugzilla.mozilla.org/show_bug.cgi?id=858538) for more details.
 | 
			
		||||
It has `download` attribute, which prevents closing WebSocket connection on
 | 
			
		||||
Firefox. See
 | 
			
		||||
[related issue](https://bugzilla.mozilla.org/show_bug.cgi?id=858538) for more
 | 
			
		||||
details.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```jsx
 | 
			
		||||
<DownloadButton href="example.zip">Download</DownloadButton>
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,12 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import { Input } from "./Input";
 | 
			
		||||
 | 
			
		||||
export const EmailInput = ({ ...props }) => <Input type="email" {...props} />;
 | 
			
		||||
import Input from "./Input";
 | 
			
		||||
 | 
			
		||||
function EmailInput({ ...props }) {
 | 
			
		||||
    return <Input type="email" {...props} />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
EmailInput.propTypes = {
 | 
			
		||||
    /** Field label. */
 | 
			
		||||
@@ -23,3 +25,5 @@ EmailInput.propTypes = {
 | 
			
		||||
    /** Email value. */
 | 
			
		||||
    value: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default EmailInput;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,22 @@
 | 
			
		||||
Bootstrap component of email input with label with predefined sizes and structure for using in foris forms.
 | 
			
		||||
It use built-in browser email address checking. It's only meaningful using inside `<form>`.
 | 
			
		||||
Bootstrap component of email input with label with predefined sizes and
 | 
			
		||||
structure for using in foris forms. It use built-in browser email address
 | 
			
		||||
checking. It's only meaningful using inside `<form>`.
 | 
			
		||||
 | 
			
		||||
All additional `props` are passed to the `<input type="email">` HTML component.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
const [email, setEmail] = useState('Wrong email');
 | 
			
		||||
<form onSubmit={e=>e.preventDefault()}>
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import Button from "./Button";
 | 
			
		||||
 | 
			
		||||
const [email, setEmail] = useState("Wrong email");
 | 
			
		||||
 | 
			
		||||
<form onSubmit={(e) => e.preventDefault()}>
 | 
			
		||||
    <EmailInput
 | 
			
		||||
        value={email}
 | 
			
		||||
        label="Some label" 
 | 
			
		||||
        label="Some label"
 | 
			
		||||
        helpText="Read the small text!"
 | 
			
		||||
        onChange={event =>setEmail(event.target.value)}
 | 
			
		||||
        onChange={(event) => setEmail(event.target.value)}
 | 
			
		||||
    />
 | 
			
		||||
    <button type="submit">Try to submit</button>
 | 
			
		||||
</form>
 | 
			
		||||
    <Button type="submit">Try to submit</Button>
 | 
			
		||||
</form>;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
@@ -8,7 +8,8 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import { Input } from "./Input";
 | 
			
		||||
 | 
			
		||||
import Input from "./Input";
 | 
			
		||||
 | 
			
		||||
FileInput.propTypes = {
 | 
			
		||||
    /** Field label. */
 | 
			
		||||
@@ -19,9 +20,11 @@ FileInput.propTypes = {
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
    /** Email value. */
 | 
			
		||||
    value: PropTypes.string,
 | 
			
		||||
    /** Allow selecting multiple files. */
 | 
			
		||||
    multiple: PropTypes.bool,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function FileInput({ ...props }) {
 | 
			
		||||
function FileInput({ ...props }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <Input
 | 
			
		||||
            type="file"
 | 
			
		||||
@@ -32,3 +35,5 @@ export function FileInput({ ...props }) {
 | 
			
		||||
        />
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default FileInput;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,48 @@
 | 
			
		||||
Bootstrap component for file input. Includes label and has predefined sizes and structure for using in foris forms. 
 | 
			
		||||
Bootstrap component for file input. Includes label and has predefined sizes and
 | 
			
		||||
structure for using in foris forms.
 | 
			
		||||
 | 
			
		||||
All additional `props` are passed to the `<input type="file">` HTML component.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
 | 
			
		||||
const [files, setFiles] = useState([]);
 | 
			
		||||
 | 
			
		||||
<FileInput
 | 
			
		||||
    files={files}
 | 
			
		||||
    label="Some file"
 | 
			
		||||
    helpText="Will be uploaded"
 | 
			
		||||
    onChange={event =>setFiles(event.target.files)}
 | 
			
		||||
/>
 | 
			
		||||
// Note that files is not an array but FileList.
 | 
			
		||||
const label = files.length === 1 ? files[0].name : "Choose file";
 | 
			
		||||
 | 
			
		||||
<form className="col">
 | 
			
		||||
    <FileInput
 | 
			
		||||
        files={files}
 | 
			
		||||
        label={label}
 | 
			
		||||
        helpText="Will be uploaded"
 | 
			
		||||
        onChange={(event) => setFiles(event.target.files)}
 | 
			
		||||
    />
 | 
			
		||||
</form>;
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### FileInput with multiple files
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
 | 
			
		||||
const [files, setFiles] = useState([]);
 | 
			
		||||
 | 
			
		||||
// Note that files is not an array but FileList.
 | 
			
		||||
const label =
 | 
			
		||||
    files.length > 0
 | 
			
		||||
        ? Array.from(files)
 | 
			
		||||
              .map((file) => file.name)
 | 
			
		||||
              .join(", ")
 | 
			
		||||
        : "Choose files";
 | 
			
		||||
 | 
			
		||||
<form className="col">
 | 
			
		||||
    <FileInput
 | 
			
		||||
        files={files}
 | 
			
		||||
        label={label}
 | 
			
		||||
        helpText="Will be uploaded"
 | 
			
		||||
        onChange={(event) => setFiles(event.target.files)}
 | 
			
		||||
        multiple
 | 
			
		||||
    />
 | 
			
		||||
</form>;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +1,73 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useUID } from "react-uid";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import React, { forwardRef } from "react";
 | 
			
		||||
 | 
			
		||||
import { formFieldsSize } from "./constants";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import { useUID } from "react-uid";
 | 
			
		||||
 | 
			
		||||
/** Base bootstrap input component. */
 | 
			
		||||
const Input = forwardRef(
 | 
			
		||||
    (
 | 
			
		||||
        {
 | 
			
		||||
            type,
 | 
			
		||||
            label,
 | 
			
		||||
            helpText,
 | 
			
		||||
            error,
 | 
			
		||||
            className,
 | 
			
		||||
            children,
 | 
			
		||||
            labelClassName,
 | 
			
		||||
            groupClassName,
 | 
			
		||||
            ...props
 | 
			
		||||
        },
 | 
			
		||||
        ref
 | 
			
		||||
    ) => {
 | 
			
		||||
        const uid = useUID();
 | 
			
		||||
 | 
			
		||||
        const inputClassName = `${className || ""} ${
 | 
			
		||||
            error ? "is-invalid" : ""
 | 
			
		||||
        }`.trim();
 | 
			
		||||
 | 
			
		||||
        return (
 | 
			
		||||
            <div className="mb-3">
 | 
			
		||||
                {label && (
 | 
			
		||||
                    <label
 | 
			
		||||
                        className={`form-label ${labelClassName || ""}`.trim()}
 | 
			
		||||
                        htmlFor={uid}
 | 
			
		||||
                    >
 | 
			
		||||
                        {label}
 | 
			
		||||
                    </label>
 | 
			
		||||
                )}
 | 
			
		||||
                <div className={`input-group ${groupClassName || ""}`.trim()}>
 | 
			
		||||
                    <input
 | 
			
		||||
                        className={`form-control ${inputClassName}`.trim()}
 | 
			
		||||
                        type={type}
 | 
			
		||||
                        id={uid}
 | 
			
		||||
                        ref={ref}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    />
 | 
			
		||||
                    {children}
 | 
			
		||||
                </div>
 | 
			
		||||
                {error && <div className="invalid-feedback">{error}</div>}
 | 
			
		||||
                {helpText && (
 | 
			
		||||
                    <div className="form-text">
 | 
			
		||||
                        <small>{helpText}</small>
 | 
			
		||||
                    </div>
 | 
			
		||||
                )}
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
Input.displayName = "Input";
 | 
			
		||||
 | 
			
		||||
Input.propTypes = {
 | 
			
		||||
    type: PropTypes.string.isRequired,
 | 
			
		||||
    label: PropTypes.string.isRequired,
 | 
			
		||||
    label: PropTypes.string,
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
    error: PropTypes.string,
 | 
			
		||||
    className: PropTypes.string,
 | 
			
		||||
@@ -25,27 +79,4 @@ Input.propTypes = {
 | 
			
		||||
    groupClassName: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/** Base bootstrap input component. */
 | 
			
		||||
export function Input({
 | 
			
		||||
    type, label, helpText, error, className, children, labelClassName, groupClassName, ...props
 | 
			
		||||
}) {
 | 
			
		||||
    const uid = useUID();
 | 
			
		||||
    const inputClassName = `form-control ${className || ""} ${(error ? "is-invalid" : "")}`.trim();
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={`form-group ${formFieldsSize}`}>
 | 
			
		||||
            <label className={labelClassName} htmlFor={uid}>{label}</label>
 | 
			
		||||
            <div className={`input-group ${groupClassName || ""}`.trim()}>
 | 
			
		||||
                <input
 | 
			
		||||
                    className={inputClassName}
 | 
			
		||||
                    type={type}
 | 
			
		||||
                    id={uid}
 | 
			
		||||
 | 
			
		||||
                    {...props}
 | 
			
		||||
                />
 | 
			
		||||
                {children}
 | 
			
		||||
            </div>
 | 
			
		||||
            {error ? <div className="invalid-feedback">{error}</div> : null}
 | 
			
		||||
            {helpText ? <small className="form-text text-muted">{helpText}</small> : null}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
export default Input;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								src/bootstrap/Modal.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/bootstrap/Modal.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
			
		||||
@keyframes modalFade {
 | 
			
		||||
    from {
 | 
			
		||||
        opacity: 0;
 | 
			
		||||
    }
 | 
			
		||||
    to {
 | 
			
		||||
        opacity: 1;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.modal.show {
 | 
			
		||||
    display: block;
 | 
			
		||||
    animation-name: modalFade;
 | 
			
		||||
    animation-duration: 0.3s;
 | 
			
		||||
    background: rgba(0, 0, 0, 0.2);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,20 +1,25 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2020-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useEffect, useRef } from "react";
 | 
			
		||||
import React, { useRef, useEffect } from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { Portal } from "utils/Portal";
 | 
			
		||||
import { useClickOutside, useFocusTrap } from "../utils/hooks";
 | 
			
		||||
import Portal from "../utils/Portal";
 | 
			
		||||
import "./Modal.css";
 | 
			
		||||
 | 
			
		||||
Modal.propTypes = {
 | 
			
		||||
    /** Is modal shown value */
 | 
			
		||||
    shown: PropTypes.bool.isRequired,
 | 
			
		||||
    /** Callback to manage modal visibility */
 | 
			
		||||
    setShown: PropTypes.func.isRequired,
 | 
			
		||||
    scrollable: PropTypes.bool,
 | 
			
		||||
    size: PropTypes.string,
 | 
			
		||||
 | 
			
		||||
    /** Modal content use following: `ModalHeader`, `ModalBody`, `ModalFooter` */
 | 
			
		||||
    children: PropTypes.oneOfType([
 | 
			
		||||
@@ -23,28 +28,57 @@ Modal.propTypes = {
 | 
			
		||||
    ]).isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function Modal({ shown, setShown, children }) {
 | 
			
		||||
    const dialogRef = useRef();
 | 
			
		||||
export function Modal({ shown, setShown, scrollable, size, children }) {
 | 
			
		||||
    const modalRef = useRef();
 | 
			
		||||
    let modalSize = "modal-";
 | 
			
		||||
 | 
			
		||||
    useClickOutside(modalRef, () => setShown(false));
 | 
			
		||||
    useFocusTrap(modalRef, shown);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        function handleClickOutsideDialog(e) {
 | 
			
		||||
            if (!dialogRef.current.contains(e.target)) setShown(false);
 | 
			
		||||
        }
 | 
			
		||||
        const handleEsc = (event) => {
 | 
			
		||||
            if (event.keyCode === 27) {
 | 
			
		||||
                setShown(false);
 | 
			
		||||
            }
 | 
			
		||||
        };
 | 
			
		||||
        window.addEventListener("keydown", handleEsc);
 | 
			
		||||
 | 
			
		||||
        document.addEventListener("mousedown", handleClickOutsideDialog);
 | 
			
		||||
        return () => {
 | 
			
		||||
            document.removeEventListener("mousedown", handleClickOutsideDialog);
 | 
			
		||||
            window.removeEventListener("keydown", handleEsc);
 | 
			
		||||
        };
 | 
			
		||||
    }, [setShown]);
 | 
			
		||||
 | 
			
		||||
    switch (size) {
 | 
			
		||||
        case "sm":
 | 
			
		||||
            modalSize += "sm";
 | 
			
		||||
            break;
 | 
			
		||||
        case "lg":
 | 
			
		||||
            modalSize += "lg";
 | 
			
		||||
            break;
 | 
			
		||||
        case "xl":
 | 
			
		||||
            modalSize += "xl";
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            modalSize = "";
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Portal containerId="modal-container">
 | 
			
		||||
            <div className={`modal fade ${shown ? "show" : ""}`} role="dialog">
 | 
			
		||||
                <div ref={dialogRef} className="modal-dialog" role="document">
 | 
			
		||||
                    <div className="modal-content">
 | 
			
		||||
                        {children}
 | 
			
		||||
                    </div>
 | 
			
		||||
            <div
 | 
			
		||||
                ref={modalRef}
 | 
			
		||||
                className={`modal fade ${shown ? "show" : ""}`.trim()}
 | 
			
		||||
                role="dialog"
 | 
			
		||||
                aria-modal="true"
 | 
			
		||||
                aria-labelledby="modal-title"
 | 
			
		||||
            >
 | 
			
		||||
                <div
 | 
			
		||||
                    className={`${modalSize.trim()} modal-dialog modal-dialog-centered ${
 | 
			
		||||
                        scrollable ? "modal-dialog-scrollable" : ""
 | 
			
		||||
                    }`.trim()}
 | 
			
		||||
                    role="document"
 | 
			
		||||
                >
 | 
			
		||||
                    <div className="modal-content">{children}</div>
 | 
			
		||||
                </div>
 | 
			
		||||
            </div>
 | 
			
		||||
        </Portal>
 | 
			
		||||
@@ -54,15 +88,21 @@ export function Modal({ shown, setShown, children }) {
 | 
			
		||||
ModalHeader.propTypes = {
 | 
			
		||||
    setShown: PropTypes.func.isRequired,
 | 
			
		||||
    title: PropTypes.string.isRequired,
 | 
			
		||||
    showCloseButton: PropTypes.bool,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function ModalHeader({ setShown, title }) {
 | 
			
		||||
export function ModalHeader({ setShown, title, showCloseButton = true }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="modal-header">
 | 
			
		||||
            <h5 className="modal-title">{title}</h5>
 | 
			
		||||
            <button type="button" className="close" onClick={() => setShown(false)}>
 | 
			
		||||
                <span aria-hidden="true">×</span>
 | 
			
		||||
            </button>
 | 
			
		||||
            <h1 className="modal-title fs-5">{title}</h1>
 | 
			
		||||
            {showCloseButton && (
 | 
			
		||||
                <button
 | 
			
		||||
                    type="button"
 | 
			
		||||
                    className="btn-close"
 | 
			
		||||
                    onClick={() => setShown(false)}
 | 
			
		||||
                    aria-label={_("Close")}
 | 
			
		||||
                />
 | 
			
		||||
            )}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
@@ -86,9 +126,5 @@ ModalFooter.propTypes = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function ModalFooter({ children }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="modal-footer">
 | 
			
		||||
            {children}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
    return <div className="modal-footer">{children}</div>;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,33 +1,47 @@
 | 
			
		||||
Bootstrap modal component.
 | 
			
		||||
 | 
			
		||||
it's required to have an element `<div id={"modal-container"}/>` somewhere on the page since modals are rendered in portals.
 | 
			
		||||
It's required to have an element `<div id={"modal-container"}/>` somewhere on
 | 
			
		||||
the page since modals are rendered in portals.
 | 
			
		||||
 | 
			
		||||
Modals also have three optional sizes, which can be defined through the `size`
 | 
			
		||||
prop:
 | 
			
		||||
 | 
			
		||||
- small - `sm`
 | 
			
		||||
- large - `lg`
 | 
			
		||||
- extra-large - `xl`
 | 
			
		||||
 | 
			
		||||
For more details please visit Bootstrap
 | 
			
		||||
<a href="https://getbootstrap.com/docs/4.5/components/modal/#optional-sizes" target="_blank">
 | 
			
		||||
documentation</a>.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
    <div id="modal-container"/>
 | 
			
		||||
<div id="modal-container" />
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
I have no idea why example doesn't work here but you can investigate HTML code and Foris project.
 | 
			
		||||
```jsx
 | 
			
		||||
import { ModalHeader, ModalBody, ModalFooter } from "./Modal";
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import {ModalHeader, ModalBody, ModalFooter} from './Modal';
 | 
			
		||||
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
const [shown, setShown] = useState(false);
 | 
			
		||||
 | 
			
		||||
<>
 | 
			
		||||
    <Modal setShown={setShown} shown={shown}>
 | 
			
		||||
        <ModalHeader setShown={setShown} title='Warning!'/>
 | 
			
		||||
        <ModalBody><p>Bla bla bla...</p></ModalBody>
 | 
			
		||||
    <Modal setShown={setShown} shown={shown} size="sm">
 | 
			
		||||
        <ModalHeader setShown={setShown} title="Warning!" />
 | 
			
		||||
        <ModalBody>
 | 
			
		||||
            <p>Bla bla bla...</p>
 | 
			
		||||
        </ModalBody>
 | 
			
		||||
        <ModalFooter>
 | 
			
		||||
            <button 
 | 
			
		||||
                className='btn btn-secondary' 
 | 
			
		||||
            <button
 | 
			
		||||
                className="btn btn-secondary"
 | 
			
		||||
                onClick={() => setShown(false)}
 | 
			
		||||
            >Skip it</button>
 | 
			
		||||
            >
 | 
			
		||||
                Skip it
 | 
			
		||||
            </button>
 | 
			
		||||
        </ModalFooter>
 | 
			
		||||
    </Modal>
 | 
			
		||||
    
 | 
			
		||||
    <button className='btn btn-secondary' onClick={()=>setShown(true)}>
 | 
			
		||||
 | 
			
		||||
    <button className="btn btn-secondary" onClick={() => setShown(true)}>
 | 
			
		||||
        Show modal
 | 
			
		||||
    </button>
 | 
			
		||||
</>
 | 
			
		||||
</>;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@ input[type="number"] {
 | 
			
		||||
    appearance: textfield;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input[type=number]::-webkit-inner-spin-button,
 | 
			
		||||
input[type=number]::-webkit-outer-spin-button {
 | 
			
		||||
input[type="number"]::-webkit-inner-spin-button,
 | 
			
		||||
input[type="number"]::-webkit-outer-spin-button {
 | 
			
		||||
    -webkit-appearance: none;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,18 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import { faMinus, faPlus } from "@fortawesome/free-solid-svg-icons";
 | 
			
		||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { useConditionalTimeout } from "utils/hooks";
 | 
			
		||||
import { Input } from "./Input";
 | 
			
		||||
import Input from "./Input";
 | 
			
		||||
import { useConditionalTimeout } from "../utils/hooks";
 | 
			
		||||
import "./NumberInput.css";
 | 
			
		||||
 | 
			
		||||
NumberInput.propTypes = {
 | 
			
		||||
@@ -20,13 +23,10 @@ NumberInput.propTypes = {
 | 
			
		||||
    /** Help text message. */
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
    /** Number value. */
 | 
			
		||||
    value: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.string,
 | 
			
		||||
        PropTypes.number,
 | 
			
		||||
    ]),
 | 
			
		||||
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
 | 
			
		||||
    /** Function called when value changes. */
 | 
			
		||||
    onChange: PropTypes.func.isRequired,
 | 
			
		||||
    /** Additional description dispaled to the right of input value. */
 | 
			
		||||
    /** Additional description displayed to the right of input value. */
 | 
			
		||||
    inlineText: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -34,39 +34,77 @@ NumberInput.defaultProps = {
 | 
			
		||||
    value: 0,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function NumberInput({
 | 
			
		||||
    onChange, inlineText, value, ...props
 | 
			
		||||
}) {
 | 
			
		||||
function NumberInput({ onChange, inlineText, value, ...props }) {
 | 
			
		||||
    function updateValue(initialValue, difference) {
 | 
			
		||||
        onChange({ target: { value: initialValue + difference } });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const enableIncrease = useConditionalTimeout({ callback: updateValue }, value, 1);
 | 
			
		||||
    const enableDecrease = useConditionalTimeout({ callback: updateValue }, value, -1);
 | 
			
		||||
    const enableIncrease = useConditionalTimeout(
 | 
			
		||||
        { callback: updateValue },
 | 
			
		||||
        value,
 | 
			
		||||
        1
 | 
			
		||||
    );
 | 
			
		||||
    const enableDecrease = useConditionalTimeout(
 | 
			
		||||
        { callback: updateValue },
 | 
			
		||||
        value,
 | 
			
		||||
        -1
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    function handleKeyDown(event, enableFunction) {
 | 
			
		||||
        if (event.key === "Enter" || event.key === " ") {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            enableFunction(true);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function handleKeyUp(event, enableFunction) {
 | 
			
		||||
        if (event.key === "Enter" || event.key === " ") {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            enableFunction(false);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Input type="number" onChange={onChange} value={value} {...props}>
 | 
			
		||||
            <div className="input-group-append">
 | 
			
		||||
                {inlineText && <p className="input-group-text">{inlineText}</p>}
 | 
			
		||||
                <button
 | 
			
		||||
                    type="button"
 | 
			
		||||
                    className="btn btn-outline-secondary"
 | 
			
		||||
                    onMouseDown={() => enableIncrease(true)}
 | 
			
		||||
                    onMouseUp={() => enableIncrease(false)}
 | 
			
		||||
                    aria-label="Increase"
 | 
			
		||||
                >
 | 
			
		||||
                    <i className="fas fa-plus" />
 | 
			
		||||
                </button>
 | 
			
		||||
                <button
 | 
			
		||||
                    type="button"
 | 
			
		||||
                    className="btn btn-outline-secondary"
 | 
			
		||||
                    onMouseDown={() => enableDecrease(true)}
 | 
			
		||||
                    onMouseUp={() => enableDecrease(false)}
 | 
			
		||||
                    aria-label="Decrease"
 | 
			
		||||
                >
 | 
			
		||||
                    <i className="fas fa-minus" />
 | 
			
		||||
                </button>
 | 
			
		||||
            </div>
 | 
			
		||||
            {inlineText && (
 | 
			
		||||
                <span className="input-group-text">{inlineText}</span>
 | 
			
		||||
            )}
 | 
			
		||||
            <button
 | 
			
		||||
                type="button"
 | 
			
		||||
                className="btn btn-outline-secondary"
 | 
			
		||||
                onMouseDown={() => enableIncrease(true)}
 | 
			
		||||
                onMouseUp={() => enableIncrease(false)}
 | 
			
		||||
                onMouseLeave={() => enableIncrease(false)}
 | 
			
		||||
                onTouchStart={() => enableIncrease(true)}
 | 
			
		||||
                onTouchEnd={() => enableIncrease(false)}
 | 
			
		||||
                onTouchCancel={() => enableIncrease(false)}
 | 
			
		||||
                onKeyDown={(event) => handleKeyDown(event, enableIncrease)}
 | 
			
		||||
                onKeyUp={(event) => handleKeyUp(event, enableIncrease)}
 | 
			
		||||
                onBlur={() => enableIncrease(false)}
 | 
			
		||||
                title={_("Increase value. Hint: Hold to increase faster.")}
 | 
			
		||||
                aria-label={_("Increase value. Hint: Hold to increase faster.")}
 | 
			
		||||
            >
 | 
			
		||||
                <FontAwesomeIcon icon={faPlus} />
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
                type="button"
 | 
			
		||||
                className="btn btn-outline-secondary"
 | 
			
		||||
                onMouseDown={() => enableDecrease(true)}
 | 
			
		||||
                onMouseUp={() => enableDecrease(false)}
 | 
			
		||||
                onMouseLeave={() => enableDecrease(false)}
 | 
			
		||||
                onTouchStart={() => enableDecrease(true)}
 | 
			
		||||
                onTouchEnd={() => enableDecrease(false)}
 | 
			
		||||
                onTouchCancel={() => enableDecrease(false)}
 | 
			
		||||
                onKeyDown={(event) => handleKeyDown(event, enableDecrease)}
 | 
			
		||||
                onKeyUp={(event) => handleKeyUp(event, enableDecrease)}
 | 
			
		||||
                onBlur={() => enableDecrease(false)}
 | 
			
		||||
                title={_("Decrease value. Hint: Hold to decrease faster.")}
 | 
			
		||||
                aria-label={_("Decrease value. Hint: Hold to decrease faster.")}
 | 
			
		||||
            >
 | 
			
		||||
                <FontAwesomeIcon icon={faMinus} />
 | 
			
		||||
            </button>
 | 
			
		||||
        </Input>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default NumberInput;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,19 @@
 | 
			
		||||
Bootstrap component of number input with label with predefined sizes and structure for using in foris forms.
 | 
			
		||||
Bootstrap component of number input with label with predefined sizes and
 | 
			
		||||
structure for using in foris forms.
 | 
			
		||||
 | 
			
		||||
All additional `props` are passed to the `<input type="number">` HTML component.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
 | 
			
		||||
const [value, setValue] = useState(42);
 | 
			
		||||
 | 
			
		||||
<NumberInput
 | 
			
		||||
    value={value}
 | 
			
		||||
    label="Some number" 
 | 
			
		||||
    label="Some number"
 | 
			
		||||
    helpText="Read the small text!"
 | 
			
		||||
    min='33'
 | 
			
		||||
    max='54'
 | 
			
		||||
    onChange={event =>setValue(event.target.value)}
 | 
			
		||||
/>
 | 
			
		||||
    min="33"
 | 
			
		||||
    max="54"
 | 
			
		||||
    onChange={(event) => setValue(event.target.value)}
 | 
			
		||||
/>;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,17 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useState } from "react";
 | 
			
		||||
 | 
			
		||||
import { faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons";
 | 
			
		||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { Input } from "./Input";
 | 
			
		||||
import Input from "./Input";
 | 
			
		||||
 | 
			
		||||
PasswordInput.propTypes = {
 | 
			
		||||
    /** Field label. */
 | 
			
		||||
@@ -21,32 +24,37 @@ PasswordInput.propTypes = {
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
    /** Use show/hide password button. */
 | 
			
		||||
    withEye: PropTypes.bool,
 | 
			
		||||
    /** Use new-password in autocomplete attribute. */
 | 
			
		||||
    newPass: PropTypes.bool,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function PasswordInput({ withEye, ...props }) {
 | 
			
		||||
function PasswordInput({ withEye, newPass, ...props }) {
 | 
			
		||||
    const [isHidden, setHidden] = useState(true);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Input
 | 
			
		||||
            type={withEye && !isHidden ? "text" : "password"}
 | 
			
		||||
            autoComplete={isHidden ? "new-password" : null}
 | 
			
		||||
            autoComplete={newPass ? "new-password" : "current-password"}
 | 
			
		||||
            {...props}
 | 
			
		||||
        >
 | 
			
		||||
            {withEye
 | 
			
		||||
                ? (
 | 
			
		||||
                    <div className="input-group-append">
 | 
			
		||||
                        <button
 | 
			
		||||
                            type="button"
 | 
			
		||||
                            className="input-group-text"
 | 
			
		||||
                            onClick={(e) => {
 | 
			
		||||
                                e.preventDefault();
 | 
			
		||||
                                setHidden((shouldBeHidden) => !shouldBeHidden);
 | 
			
		||||
                            }}
 | 
			
		||||
                        >
 | 
			
		||||
                            <i className={`fa ${isHidden ? "fa-eye" : "fa-eye-slash"}`} />
 | 
			
		||||
                        </button>
 | 
			
		||||
                    </div>
 | 
			
		||||
                )
 | 
			
		||||
                : null}
 | 
			
		||||
            {withEye && (
 | 
			
		||||
                <button
 | 
			
		||||
                    type="button"
 | 
			
		||||
                    className="input-group-text"
 | 
			
		||||
                    onClick={(e) => {
 | 
			
		||||
                        e.preventDefault();
 | 
			
		||||
                        setHidden((shouldBeHidden) => !shouldBeHidden);
 | 
			
		||||
                    }}
 | 
			
		||||
                >
 | 
			
		||||
                    <FontAwesomeIcon
 | 
			
		||||
                        icon={isHidden ? faEye : faEyeSlash}
 | 
			
		||||
                        style={{ width: "1.25rem" }}
 | 
			
		||||
                        className="text-secondary"
 | 
			
		||||
                    />
 | 
			
		||||
                </button>
 | 
			
		||||
            )}
 | 
			
		||||
        </Input>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default PasswordInput;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,17 +1,18 @@
 | 
			
		||||
Password Bootstrap component input with label and predefined sizes and structure for using in foris forms.
 | 
			
		||||
Can be used with "eye" button, see example.
 | 
			
		||||
Password Bootstrap component input with label and predefined sizes and structure
 | 
			
		||||
for using in foris forms. Can be used with "eye" button, see example.
 | 
			
		||||
 | 
			
		||||
All additional `props` are passed to the `<input type="password">` HTML component.
 | 
			
		||||
All additional `props` are passed to the `<input type="password">` HTML
 | 
			
		||||
component.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
const [value, setValue] = useState('secret');
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
const [value, setValue] = useState("secret");
 | 
			
		||||
 | 
			
		||||
<PasswordInput
 | 
			
		||||
    withEye
 | 
			
		||||
    value={value}
 | 
			
		||||
    label="Some password" 
 | 
			
		||||
    label="Some password"
 | 
			
		||||
    helpText="Read the small text!"
 | 
			
		||||
    onChange={event =>setValue(event.target.value)}
 | 
			
		||||
/>
 | 
			
		||||
    onChange={(event) => setValue(event.target.value)}
 | 
			
		||||
/>;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								src/bootstrap/Radio.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/bootstrap/Radio.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
Radio.propTypes = {
 | 
			
		||||
    label: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.string,
 | 
			
		||||
        PropTypes.element,
 | 
			
		||||
        PropTypes.node,
 | 
			
		||||
        PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
    ]).isRequired,
 | 
			
		||||
    id: PropTypes.string.isRequired,
 | 
			
		||||
    inline: PropTypes.bool,
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
    className: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function Radio({ label, id, helpText, inline, className, ...props }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <div
 | 
			
		||||
            className={`${className || "mb-3"} ${inline ? "form-check form-check-inline" : ""}`.trim()}
 | 
			
		||||
        >
 | 
			
		||||
            <input
 | 
			
		||||
                id={id}
 | 
			
		||||
                className="form-check-input me-2"
 | 
			
		||||
                type="radio"
 | 
			
		||||
                {...props}
 | 
			
		||||
            />
 | 
			
		||||
            <label className="form-check-label" htmlFor={id}>
 | 
			
		||||
                {label}
 | 
			
		||||
                {helpText && (
 | 
			
		||||
                    <div className="form-text">
 | 
			
		||||
                        <small>{helpText}</small>
 | 
			
		||||
                    </div>
 | 
			
		||||
                )}
 | 
			
		||||
            </label>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Radio;
 | 
			
		||||
@@ -1,16 +1,16 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2020-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import { useUID } from "react-uid";
 | 
			
		||||
 | 
			
		||||
import { formFieldsSize } from "./constants";
 | 
			
		||||
 | 
			
		||||
import Radio from "./Radio";
 | 
			
		||||
 | 
			
		||||
RadioSet.propTypes = {
 | 
			
		||||
    /** Name attribute of the input HTML tag. */
 | 
			
		||||
@@ -18,21 +18,28 @@ RadioSet.propTypes = {
 | 
			
		||||
    /** RadioSet label . */
 | 
			
		||||
    label: PropTypes.string,
 | 
			
		||||
    /** Choices . */
 | 
			
		||||
    choices: PropTypes.arrayOf(PropTypes.shape({
 | 
			
		||||
        /** Choice lable . */
 | 
			
		||||
        label: PropTypes.string.isRequired,
 | 
			
		||||
        /** Choice value . */
 | 
			
		||||
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
 | 
			
		||||
    })).isRequired,
 | 
			
		||||
    choices: PropTypes.arrayOf(
 | 
			
		||||
        PropTypes.shape({
 | 
			
		||||
            /** Choice label . */
 | 
			
		||||
            label: PropTypes.oneOfType([
 | 
			
		||||
                PropTypes.string,
 | 
			
		||||
                PropTypes.element,
 | 
			
		||||
                PropTypes.node,
 | 
			
		||||
                PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
            ]).isRequired,
 | 
			
		||||
            /** Choice value . */
 | 
			
		||||
            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
 | 
			
		||||
                .isRequired,
 | 
			
		||||
        })
 | 
			
		||||
    ).isRequired,
 | 
			
		||||
    /** Initial value . */
 | 
			
		||||
    value: PropTypes.string,
 | 
			
		||||
    /** Help text message . */
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
    inline: PropTypes.bool,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function RadioSet({
 | 
			
		||||
    name, label, choices, value, helpText, ...props
 | 
			
		||||
}) {
 | 
			
		||||
function RadioSet({ name, label, choices, value, helpText, inline, ...props }) {
 | 
			
		||||
    const uid = useUID();
 | 
			
		||||
    const radios = choices.map((choice, key) => {
 | 
			
		||||
        const id = `${name}-${key}`;
 | 
			
		||||
@@ -45,49 +52,27 @@ export function RadioSet({
 | 
			
		||||
                value={choice.value}
 | 
			
		||||
                helpText={choice.helpText}
 | 
			
		||||
                checked={choice.value === value}
 | 
			
		||||
 | 
			
		||||
                inline={inline}
 | 
			
		||||
                {...props}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={`form-group ${formFieldsSize}`} style={{ marginBottom: "1rem" }}>
 | 
			
		||||
            {label
 | 
			
		||||
                ? (
 | 
			
		||||
                    <label className="col-12" htmlFor={uid} style={{ paddingLeft: "0" }}>
 | 
			
		||||
                        {label}
 | 
			
		||||
                    </label>
 | 
			
		||||
                )
 | 
			
		||||
                : null}
 | 
			
		||||
        <div className="mb-3">
 | 
			
		||||
            {label && (
 | 
			
		||||
                <label htmlFor={uid} className="d-block">
 | 
			
		||||
                    {label}
 | 
			
		||||
                </label>
 | 
			
		||||
            )}
 | 
			
		||||
            {radios}
 | 
			
		||||
            {helpText ? <small className="form-text text-muted">{helpText}</small> : null}
 | 
			
		||||
            {helpText && (
 | 
			
		||||
                <div className="form-text">
 | 
			
		||||
                    <small>{helpText}</small>
 | 
			
		||||
                </div>
 | 
			
		||||
            )}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Radio.propTypes = {
 | 
			
		||||
    label: PropTypes.string.isRequired,
 | 
			
		||||
    id: PropTypes.string.isRequired,
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function Radio({
 | 
			
		||||
    label, id, helpText, ...props
 | 
			
		||||
}) {
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <div className="custom-control custom-radio custom-control-inline">
 | 
			
		||||
                <input
 | 
			
		||||
                    id={id}
 | 
			
		||||
                    className="custom-control-input"
 | 
			
		||||
                    type="radio"
 | 
			
		||||
 | 
			
		||||
                    {...props}
 | 
			
		||||
                />
 | 
			
		||||
                <label className="custom-control-label" htmlFor={id}>{label}</label>
 | 
			
		||||
            </div>
 | 
			
		||||
            {helpText ? <small className="form-text text-muted">{helpText}</small> : null}
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
export default RadioSet;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,17 @@
 | 
			
		||||
Set of radio Bootstrap component input with label and predefined sizes and structure for using in foris forms.
 | 
			
		||||
Set of radio Bootstrap component input with label and predefined sizes and
 | 
			
		||||
structure for using in foris forms.
 | 
			
		||||
 | 
			
		||||
All additional `props` are passed to the `<input type="number">` HTML component.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
const CHOICES=[
 | 
			
		||||
    {value:'one',label:'1'},
 | 
			
		||||
    {value:'two',label:'2'},
 | 
			
		||||
    {value:'three',label:'3'},
 | 
			
		||||
Unless `helpText` is set for one of the options they are displayed inline.
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
 | 
			
		||||
const CHOICES = [
 | 
			
		||||
    { value: "one", label: "1" },
 | 
			
		||||
    { value: "two", label: "2" },
 | 
			
		||||
    { value: "three", label: "3" },
 | 
			
		||||
];
 | 
			
		||||
const [value, setValue] = useState(CHOICES[0].value);
 | 
			
		||||
 | 
			
		||||
@@ -15,10 +19,10 @@ const [value, setValue] = useState(CHOICES[0].value);
 | 
			
		||||
    {/*Yeah, it gets event, not value!*/}
 | 
			
		||||
    <RadioSet
 | 
			
		||||
        value={value}
 | 
			
		||||
        name='some-radio'
 | 
			
		||||
        name="some-radio"
 | 
			
		||||
        choices={CHOICES}
 | 
			
		||||
        onChange={event =>setValue(event.target.value)}
 | 
			
		||||
        onChange={(event) => setValue(event.target.value)}
 | 
			
		||||
    />
 | 
			
		||||
    <p>Selected value: {value}</p>
 | 
			
		||||
</>
 | 
			
		||||
</>;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -1,49 +1,50 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import { useUID } from "react-uid";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Select.propTypes = {
 | 
			
		||||
    /** Select field Label. */
 | 
			
		||||
    label: PropTypes.string.isRequired,
 | 
			
		||||
    /** Choices if form of {value : "Label",...}. */
 | 
			
		||||
    choices: PropTypes.object.isRequired,
 | 
			
		||||
    /** Current value. */
 | 
			
		||||
    value: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.string,
 | 
			
		||||
        PropTypes.number,
 | 
			
		||||
    ]).isRequired,
 | 
			
		||||
    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
 | 
			
		||||
    /** Help text message. */
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function Select({
 | 
			
		||||
    label, choices, helpText, ...props
 | 
			
		||||
}) {
 | 
			
		||||
function Select({ label, choices, helpText, ...props }) {
 | 
			
		||||
    const uid = useUID();
 | 
			
		||||
 | 
			
		||||
    const options = Object.keys(choices).map(
 | 
			
		||||
        (key) => <option key={key} value={key}>{choices[key]}</option>,
 | 
			
		||||
    );
 | 
			
		||||
    const options = Object.keys(choices).map((choice) => (
 | 
			
		||||
        <option key={choice} value={choice}>
 | 
			
		||||
            {choices[choice]}
 | 
			
		||||
        </option>
 | 
			
		||||
    ));
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="form-group col-sm-12 offset-lg-1 col-lg-10">
 | 
			
		||||
            <label htmlFor={uid}>{label}</label>
 | 
			
		||||
            <select
 | 
			
		||||
                className="custom-select"
 | 
			
		||||
                id={uid}
 | 
			
		||||
                {...props}
 | 
			
		||||
            >
 | 
			
		||||
        <div className="mb-3">
 | 
			
		||||
            <label className="form-label" htmlFor={uid}>
 | 
			
		||||
                {label}
 | 
			
		||||
            </label>
 | 
			
		||||
            <select className="form-select" id={uid} {...props}>
 | 
			
		||||
                {options}
 | 
			
		||||
            </select>
 | 
			
		||||
            {helpText ? <small className="form-text text-muted">{helpText}</small> : null}
 | 
			
		||||
            {helpText && (
 | 
			
		||||
                <div className="form-text">
 | 
			
		||||
                    <small>{helpText}</small>
 | 
			
		||||
                </div>
 | 
			
		||||
            )}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Select;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,15 @@
 | 
			
		||||
Select with options Bootstrap component input with label and predefined sizes and structure for using in foris forms.
 | 
			
		||||
Select with options Bootstrap component input with label and predefined sizes
 | 
			
		||||
and structure for using in foris forms.
 | 
			
		||||
 | 
			
		||||
All additional `props` are passed to the `<select>` HTML component.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
const CHOICES={
 | 
			
		||||
    apple:'Apple',
 | 
			
		||||
    banana:'Banana',
 | 
			
		||||
    peach:'Peach',
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
 | 
			
		||||
const CHOICES = {
 | 
			
		||||
    apple: "Apple",
 | 
			
		||||
    banana: "Banana",
 | 
			
		||||
    peach: "Peach",
 | 
			
		||||
};
 | 
			
		||||
const [value, setValue] = useState(Object.keys(CHOICES)[0]);
 | 
			
		||||
 | 
			
		||||
@@ -17,9 +19,9 @@ const [value, setValue] = useState(Object.keys(CHOICES)[0]);
 | 
			
		||||
        label="Fruit"
 | 
			
		||||
        value={value}
 | 
			
		||||
        choices={CHOICES}
 | 
			
		||||
        onChange={event=>setValue(event.target.value)}
 | 
			
		||||
        onChange={(event) => setValue(event.target.value)}
 | 
			
		||||
    />
 | 
			
		||||
    <p>Selected choice label: {CHOICES[value]}</p>
 | 
			
		||||
    <p>Selected choice value: {value}</p>
 | 
			
		||||
</>
 | 
			
		||||
</>;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										43
									
								
								src/bootstrap/Spinner.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/bootstrap/Spinner.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,43 @@
 | 
			
		||||
.spinner-fs-wrapper {
 | 
			
		||||
    position: fixed;
 | 
			
		||||
    top: 50%;
 | 
			
		||||
    left: 50%;
 | 
			
		||||
    transform: translate(-50%, -50%);
 | 
			
		||||
    z-index: 1101; /* increase z-index by 1 to ensure it's on top of spinner-fs-background */
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spinner-wrapper .spinner-border {
 | 
			
		||||
    width: 4rem;
 | 
			
		||||
    height: 4rem;
 | 
			
		||||
    color: var(--bs-primary);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spinner-fs-background {
 | 
			
		||||
    background-color: rgba(2, 2, 2, 0.5);
 | 
			
		||||
    color: rgb(230, 230, 230);
 | 
			
		||||
    width: 100vw;
 | 
			
		||||
    height: 100vh;
 | 
			
		||||
 | 
			
		||||
    display: flex;
 | 
			
		||||
    align-items: center;
 | 
			
		||||
    justify-content: center;
 | 
			
		||||
    flex-direction: column;
 | 
			
		||||
    /*
 | 
			
		||||
     * Set to high value to me sure that it always overlaps all components
 | 
			
		||||
     * https://getbootstrap.com/docs/4.3/layout/overview/#z-index
 | 
			
		||||
     */
 | 
			
		||||
    z-index: 1100;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spinner-fs-wrapper .spinner-border {
 | 
			
		||||
    width: 6rem;
 | 
			
		||||
    height: 6rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spinner-fs-wrapper .spinner-text {
 | 
			
		||||
    margin: 1rem;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
.spinner-border-sm {
 | 
			
		||||
    min-width: 16px;
 | 
			
		||||
}
 | 
			
		||||
@@ -6,15 +6,18 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import "./Spinner.css";
 | 
			
		||||
 | 
			
		||||
Spinner.propTypes = {
 | 
			
		||||
    /** Children components put into `div` with "spinner-text" class. */
 | 
			
		||||
    children: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
        PropTypes.node,
 | 
			
		||||
    ]),
 | 
			
		||||
    /** Render component with full-screen mode (using apropriate `.css` styles) */
 | 
			
		||||
    /** Render component with full-screen mode (using appropriate `.css` styles) */
 | 
			
		||||
    fullScreen: PropTypes.bool.isRequired,
 | 
			
		||||
    className: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
@@ -23,19 +26,19 @@ Spinner.defaultProps = {
 | 
			
		||||
    fullScreen: false,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function Spinner({
 | 
			
		||||
    fullScreen, children, className, ...props
 | 
			
		||||
}) {
 | 
			
		||||
export function Spinner({ fullScreen, children, className }) {
 | 
			
		||||
    if (!fullScreen) {
 | 
			
		||||
        return (
 | 
			
		||||
            <div className={`spinner-wrapper ${className || ""}`} {...props}>
 | 
			
		||||
            <div
 | 
			
		||||
                className={`spinner-wrapper ${className || "my-3 text-center"}`}
 | 
			
		||||
            >
 | 
			
		||||
                <SpinnerElement>{children}</SpinnerElement>
 | 
			
		||||
            </div>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="spinner-fs-wrapper" {...props}>
 | 
			
		||||
        <div className="spinner-fs-wrapper">
 | 
			
		||||
            <div className="spinner-fs-background">
 | 
			
		||||
                <SpinnerElement>{children}</SpinnerElement>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -46,6 +49,8 @@ export function Spinner({
 | 
			
		||||
SpinnerElement.propTypes = {
 | 
			
		||||
    /** Spinner's size */
 | 
			
		||||
    small: PropTypes.bool,
 | 
			
		||||
    /** Additional className */
 | 
			
		||||
    className: PropTypes.string,
 | 
			
		||||
    /** Children components */
 | 
			
		||||
    children: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
@@ -53,13 +58,18 @@ SpinnerElement.propTypes = {
 | 
			
		||||
    ]),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export function SpinnerElement({ small, children }) {
 | 
			
		||||
export function SpinnerElement({ small, className, children }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <div className={`spinner-border ${small ? "spinner-border-sm" : ""}`} role="status">
 | 
			
		||||
            <div
 | 
			
		||||
                className={`spinner-border ${
 | 
			
		||||
                    small ? "spinner-border-sm" : ""
 | 
			
		||||
                } ${className || ""}`.trim()}
 | 
			
		||||
                role="status"
 | 
			
		||||
            >
 | 
			
		||||
                <span className="sr-only" />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className="spinner-text">{children}</div>
 | 
			
		||||
            {children && <div className="spinner-text">{children}</div>}
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
Spiner Bootstrap component.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
```jsx
 | 
			
		||||
<Spinner>You can put text inside or any component you wish.</Spinner>
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								src/bootstrap/Switch.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/bootstrap/Switch.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (c) 2020-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import { useUID } from "react-uid";
 | 
			
		||||
 | 
			
		||||
Switch.propTypes = {
 | 
			
		||||
    label: PropTypes.oneOfType([
 | 
			
		||||
        PropTypes.string,
 | 
			
		||||
        PropTypes.element,
 | 
			
		||||
        PropTypes.node,
 | 
			
		||||
        PropTypes.arrayOf(PropTypes.node),
 | 
			
		||||
    ]).isRequired,
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
    switchHeading: PropTypes.bool,
 | 
			
		||||
    className: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function Switch({ label, helpText, switchHeading, className, ...props }) {
 | 
			
		||||
    const uid = useUID();
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div
 | 
			
		||||
            className={`form-check form-switch ${className || "mb-3"} ${
 | 
			
		||||
                switchHeading ? "d-flex align-items-center" : ""
 | 
			
		||||
            }`.trim()}
 | 
			
		||||
        >
 | 
			
		||||
            <input
 | 
			
		||||
                type="checkbox"
 | 
			
		||||
                className={`form-check-input ${switchHeading ? "me-2" : ""}`.trim()}
 | 
			
		||||
                role="switch"
 | 
			
		||||
                id={uid}
 | 
			
		||||
                {...props}
 | 
			
		||||
            />
 | 
			
		||||
            <label className="form-check-label" htmlFor={uid}>
 | 
			
		||||
                {label}
 | 
			
		||||
            </label>
 | 
			
		||||
            {helpText && (
 | 
			
		||||
                <div className="form-text">
 | 
			
		||||
                    <small>{helpText}</small>
 | 
			
		||||
                </div>
 | 
			
		||||
            )}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Switch;
 | 
			
		||||
							
								
								
									
										5
									
								
								src/bootstrap/Switch.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/bootstrap/Switch.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
Switch example:
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
<Switch label="Enable Switch" helpText="Toggle that switch!" />
 | 
			
		||||
```
 | 
			
		||||
@@ -1,18 +1,19 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { Input } from "./Input";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export const TextInput = ({ ...props }) => <Input type="text" {...props} />;
 | 
			
		||||
import Input from "./Input";
 | 
			
		||||
 | 
			
		||||
function TextInput({ ...props }) {
 | 
			
		||||
    return <Input type="text" {...props} />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
TextInput.propTypes = {
 | 
			
		||||
    /** Field label. */
 | 
			
		||||
@@ -22,3 +23,5 @@ TextInput.propTypes = {
 | 
			
		||||
    /** Help text message. */
 | 
			
		||||
    helpText: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default TextInput;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,15 +1,17 @@
 | 
			
		||||
Text Bootstrap component input with label and predefined sizes and structure for using in foris forms.
 | 
			
		||||
Text Bootstrap component input with label and predefined sizes and structure for
 | 
			
		||||
using in foris forms.
 | 
			
		||||
 | 
			
		||||
All additional `props` are passed to the `<input type="text">` HTML component.
 | 
			
		||||
 | 
			
		||||
```js
 | 
			
		||||
import {useState} from 'react';
 | 
			
		||||
const [value, setValue] = useState('Bla bla');
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
 | 
			
		||||
const [value, setValue] = useState("Bla bla");
 | 
			
		||||
 | 
			
		||||
<TextInput
 | 
			
		||||
    value={value}
 | 
			
		||||
    label="Some text" 
 | 
			
		||||
    label="Some text"
 | 
			
		||||
    helpText="Read the small text!"
 | 
			
		||||
    onChange={event =>setValue(event.target.value)}
 | 
			
		||||
/>
 | 
			
		||||
    onChange={(event) => setValue(event.target.value)}
 | 
			
		||||
/>;
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										42
									
								
								src/bootstrap/ThreeDotsMenu.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/bootstrap/ThreeDotsMenu.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,42 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import { faEllipsisVertical } from "@fortawesome/free-solid-svg-icons";
 | 
			
		||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import Button from "./Button";
 | 
			
		||||
 | 
			
		||||
ThreeDotsMenu.propTypes = {
 | 
			
		||||
    /** Menu items. */
 | 
			
		||||
    children: PropTypes.arrayOf(PropTypes.node).isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function ThreeDotsMenu({ children, ...props }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="dropdown position-static" {...props}>
 | 
			
		||||
            <Button
 | 
			
		||||
                className="btn-sm btn-link text-body"
 | 
			
		||||
                data-bs-toggle="dropdown"
 | 
			
		||||
                aria-expanded="false"
 | 
			
		||||
            >
 | 
			
		||||
                <FontAwesomeIcon icon={faEllipsisVertical} />
 | 
			
		||||
            </Button>
 | 
			
		||||
            <ul className="dropdown-menu">
 | 
			
		||||
                {children.map((child) => (
 | 
			
		||||
                    <li key={child.key || child.props.id || Math.random()}>
 | 
			
		||||
                        {child}
 | 
			
		||||
                    </li>
 | 
			
		||||
                ))}
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ThreeDotsMenu;
 | 
			
		||||
							
								
								
									
										40
									
								
								src/bootstrap/ThreeDotsMenu.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/bootstrap/ThreeDotsMenu.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,40 @@
 | 
			
		||||
ThreeDotsMenu Bootstrap component is a dropdown menu that appears when the user
 | 
			
		||||
clicks on three dots. It is used to display a list of actions that can be
 | 
			
		||||
performed on a particular item.
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
import { useState } from "react";
 | 
			
		||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 | 
			
		||||
import { faEdit, faTrash } from "@fortawesome/free-solid-svg-icons";
 | 
			
		||||
 | 
			
		||||
const threeDotsMenuItems = [
 | 
			
		||||
    {
 | 
			
		||||
        text: "Edit",
 | 
			
		||||
        icon: faEdit,
 | 
			
		||||
        onClick: () => {
 | 
			
		||||
            alert("Edit clicked");
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        text: "Delete",
 | 
			
		||||
        icon: faTrash,
 | 
			
		||||
        onClick: () => {
 | 
			
		||||
            alert("Delete clicked");
 | 
			
		||||
        },
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
<ThreeDotsMenu>
 | 
			
		||||
    {threeDotsMenuItems.map((item, index) => (
 | 
			
		||||
        <button key={index} onClick={item.onClick} className="dropdown-item">
 | 
			
		||||
            <FontAwesomeIcon
 | 
			
		||||
                icon={item.icon}
 | 
			
		||||
                className="me-1"
 | 
			
		||||
                width="1rem"
 | 
			
		||||
                size="sm"
 | 
			
		||||
            />
 | 
			
		||||
            {item.text}
 | 
			
		||||
        </button>
 | 
			
		||||
    ))}
 | 
			
		||||
</ThreeDotsMenu>;
 | 
			
		||||
```
 | 
			
		||||
@@ -9,24 +9,23 @@ import React from "react";
 | 
			
		||||
 | 
			
		||||
import { render } from "customTestRender";
 | 
			
		||||
 | 
			
		||||
import { Button } from "../Button";
 | 
			
		||||
import Button from "../Button";
 | 
			
		||||
 | 
			
		||||
describe("<Button />", () => {
 | 
			
		||||
    it("Render button correctly", () => {
 | 
			
		||||
        const { container } = render(<Button>Test Button</Button>);
 | 
			
		||||
        expect(container.firstChild)
 | 
			
		||||
            .toMatchSnapshot();
 | 
			
		||||
        expect(container.firstChild).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Render button with custom classes", () => {
 | 
			
		||||
        const { container } = render(<Button className="one two three">Test Button</Button>);
 | 
			
		||||
        expect(container.firstChild)
 | 
			
		||||
            .toMatchSnapshot();
 | 
			
		||||
        const { container } = render(
 | 
			
		||||
            <Button className="one two three">Test Button</Button>
 | 
			
		||||
        );
 | 
			
		||||
        expect(container.firstChild).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Render button with spinner", () => {
 | 
			
		||||
        const { container } = render(<Button loading={true}>Test Button</Button>);
 | 
			
		||||
        expect(container.firstChild)
 | 
			
		||||
            .toMatchSnapshot();
 | 
			
		||||
        const { container } = render(<Button loading>Test Button</Button>);
 | 
			
		||||
        expect(container.firstChild).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import React from "react";
 | 
			
		||||
 | 
			
		||||
import { render } from "customTestRender";
 | 
			
		||||
 | 
			
		||||
import { CheckBox } from "../CheckBox";
 | 
			
		||||
import CheckBox from "../CheckBox";
 | 
			
		||||
 | 
			
		||||
describe("<Checkbox/>", () => {
 | 
			
		||||
    it("Render checkbox", () => {
 | 
			
		||||
@@ -18,22 +18,16 @@ describe("<Checkbox/>", () => {
 | 
			
		||||
                label="Test label"
 | 
			
		||||
                checked
 | 
			
		||||
                helpText="Some help text"
 | 
			
		||||
                onChange={() => {
 | 
			
		||||
                }}
 | 
			
		||||
                onChange={() => {}}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
        expect(container.firstChild)
 | 
			
		||||
            .toMatchSnapshot();
 | 
			
		||||
        expect(container.firstChild).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Render uncheked checkbox", () => {
 | 
			
		||||
        const { container } = render(
 | 
			
		||||
            <CheckBox
 | 
			
		||||
                label="Test label"
 | 
			
		||||
                helpText="Some help text"
 | 
			
		||||
            />
 | 
			
		||||
            <CheckBox label="Test label" helpText="Some help text" />
 | 
			
		||||
        );
 | 
			
		||||
        expect(container.firstChild)
 | 
			
		||||
            .toMatchSnapshot();
 | 
			
		||||
        expect(container.firstChild).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,15 @@ import React from "react";
 | 
			
		||||
 | 
			
		||||
import { render } from "customTestRender";
 | 
			
		||||
 | 
			
		||||
import { DownloadButton } from "../DownloadButton";
 | 
			
		||||
import DownloadButton from "../DownloadButton";
 | 
			
		||||
 | 
			
		||||
describe("<DownloadButton />", () => {
 | 
			
		||||
    it("should have download attribute", () => {
 | 
			
		||||
        const { container } = render(<DownloadButton href="http://example.com">Test Button</DownloadButton>);
 | 
			
		||||
        const { container } = render(
 | 
			
		||||
            <DownloadButton href="http://example.com">
 | 
			
		||||
                Test Button
 | 
			
		||||
            </DownloadButton>
 | 
			
		||||
        );
 | 
			
		||||
        expect(container.firstChild.getAttribute("download")).not.toBeNull();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2025 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
@@ -7,9 +7,9 @@
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import { render, fireEvent, getByLabelText, wait } from "customTestRender";
 | 
			
		||||
import { render, fireEvent, getByLabelText, waitFor } from "customTestRender";
 | 
			
		||||
 | 
			
		||||
import { NumberInput } from "../NumberInput";
 | 
			
		||||
import NumberInput from "../NumberInput";
 | 
			
		||||
 | 
			
		||||
describe("<NumberInput/>", () => {
 | 
			
		||||
    const onChangeMock = jest.fn();
 | 
			
		||||
@@ -32,14 +32,18 @@ describe("<NumberInput/>", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Increase number with button", async () => {
 | 
			
		||||
        const increaseButton = getByLabelText(componentContainer, "Increase");
 | 
			
		||||
        const increaseButton = getByLabelText(componentContainer, /Increase/);
 | 
			
		||||
        fireEvent.mouseDown(increaseButton);
 | 
			
		||||
        await wait(() => expect(onChangeMock).toHaveBeenCalledWith({"target": {"value": 2}}));
 | 
			
		||||
        await waitFor(() =>
 | 
			
		||||
            expect(onChangeMock).toHaveBeenCalledWith({ target: { value: 2 } })
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Decrease number with button", async () => {
 | 
			
		||||
        const decreaseButton = getByLabelText(componentContainer, "Decrease");
 | 
			
		||||
        const decreaseButton = getByLabelText(componentContainer, /Decrease/);
 | 
			
		||||
        fireEvent.mouseDown(decreaseButton);
 | 
			
		||||
        await wait(() => expect(onChangeMock).toHaveBeenCalledWith({"target": {"value": 0}}));
 | 
			
		||||
        await waitFor(() =>
 | 
			
		||||
            expect(onChangeMock).toHaveBeenCalledWith({ target: { value: 0 } })
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,7 @@ import React from "react";
 | 
			
		||||
 | 
			
		||||
import { render } from "customTestRender";
 | 
			
		||||
 | 
			
		||||
import { PasswordInput } from "../PasswordInput";
 | 
			
		||||
import PasswordInput from "../PasswordInput";
 | 
			
		||||
 | 
			
		||||
describe("<PasswordInput/>", () => {
 | 
			
		||||
    it("Render password input", () => {
 | 
			
		||||
@@ -18,11 +18,9 @@ describe("<PasswordInput/>", () => {
 | 
			
		||||
                label="Test label"
 | 
			
		||||
                helpText="Some help text"
 | 
			
		||||
                value="Some password"
 | 
			
		||||
                onChange={() => {
 | 
			
		||||
                }}
 | 
			
		||||
                onChange={() => {}}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
        expect(container.firstChild)
 | 
			
		||||
            .toMatchSnapshot();
 | 
			
		||||
        expect(container.firstChild).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -9,37 +9,35 @@ import React from "react";
 | 
			
		||||
 | 
			
		||||
import { render } from "customTestRender";
 | 
			
		||||
 | 
			
		||||
import { RadioSet } from "../RadioSet";
 | 
			
		||||
import RadioSet from "../RadioSet";
 | 
			
		||||
 | 
			
		||||
const TEST_CHOICES = [
 | 
			
		||||
    {
 | 
			
		||||
        label: "label",
 | 
			
		||||
        value: "value"
 | 
			
		||||
        value: "value",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        label: "another label",
 | 
			
		||||
        value: "another value"
 | 
			
		||||
        value: "another value",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        label: "another one  label",
 | 
			
		||||
        value: "another on value"
 | 
			
		||||
    }
 | 
			
		||||
        value: "another on value",
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
describe("<RadioSet/>", () => {
 | 
			
		||||
    it("Render radio set", () => {
 | 
			
		||||
        const { container } = render(
 | 
			
		||||
            <RadioSet
 | 
			
		||||
                name={"test_name"}
 | 
			
		||||
                label='Radios set label'
 | 
			
		||||
                value='value'
 | 
			
		||||
                name="test_name"
 | 
			
		||||
                label="Radios set label"
 | 
			
		||||
                value="value"
 | 
			
		||||
                choices={TEST_CHOICES}
 | 
			
		||||
                helpText={"Some help text"}
 | 
			
		||||
                onChange={() => {
 | 
			
		||||
                }}
 | 
			
		||||
                helpText="Some help text"
 | 
			
		||||
                onChange={() => {}}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
        expect(container.firstChild)
 | 
			
		||||
            .toMatchSnapshot();
 | 
			
		||||
        expect(container.firstChild).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -7,27 +7,31 @@
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import { fireEvent, getByDisplayValue, getByText, render } from "customTestRender";
 | 
			
		||||
import {
 | 
			
		||||
    fireEvent,
 | 
			
		||||
    getByDisplayValue,
 | 
			
		||||
    getByText,
 | 
			
		||||
    render,
 | 
			
		||||
} from "customTestRender";
 | 
			
		||||
 | 
			
		||||
import { Select } from "../Select";
 | 
			
		||||
import Select from "../Select";
 | 
			
		||||
 | 
			
		||||
const TEST_CHOICES = {
 | 
			
		||||
    "1": "one",
 | 
			
		||||
    "2": "two",
 | 
			
		||||
    "3": "three",
 | 
			
		||||
    1: "one",
 | 
			
		||||
    2: "two",
 | 
			
		||||
    3: "three",
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe("<Select/>", () => {
 | 
			
		||||
    var selectContainer;
 | 
			
		||||
    let selectContainer;
 | 
			
		||||
    const onChangeHandler = jest.fn();
 | 
			
		||||
    beforeEach(() => {
 | 
			
		||||
        const { container } = render(
 | 
			
		||||
            <Select
 | 
			
		||||
                label='Test label'
 | 
			
		||||
                value='1'
 | 
			
		||||
                label="Test label"
 | 
			
		||||
                value="1"
 | 
			
		||||
                choices={TEST_CHOICES}
 | 
			
		||||
                helpText='Help text'
 | 
			
		||||
 | 
			
		||||
                helpText="Help text"
 | 
			
		||||
                onChange={onChangeHandler}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
@@ -35,21 +39,17 @@ describe("<Select/>", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Test with snapshot.", () => {
 | 
			
		||||
        expect(selectContainer)
 | 
			
		||||
            .toMatchSnapshot();
 | 
			
		||||
        expect(selectContainer).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Test onChange handling.", () => {
 | 
			
		||||
        const select = getByDisplayValue(selectContainer, "one");
 | 
			
		||||
        expect(select.value)
 | 
			
		||||
            .toBe("1");
 | 
			
		||||
        expect(select.value).toBe("1");
 | 
			
		||||
        fireEvent.change(select, { target: { value: "2" } });
 | 
			
		||||
 | 
			
		||||
        const option = getByText(selectContainer, "two");
 | 
			
		||||
        expect(onChangeHandler)
 | 
			
		||||
            .toBeCalled();
 | 
			
		||||
        expect(onChangeHandler).toBeCalled();
 | 
			
		||||
 | 
			
		||||
        expect(option.value)
 | 
			
		||||
            .toBe("2");
 | 
			
		||||
        expect(option.value).toBe("2");
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										33
									
								
								src/bootstrap/__tests__/Switch.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/bootstrap/__tests__/Switch.test.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2020 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import { render } from "customTestRender";
 | 
			
		||||
 | 
			
		||||
import Switch from "../Switch";
 | 
			
		||||
 | 
			
		||||
describe("<Switch/>", () => {
 | 
			
		||||
    it("Render switch", () => {
 | 
			
		||||
        const { container } = render(
 | 
			
		||||
            <Switch
 | 
			
		||||
                label="Test label"
 | 
			
		||||
                checked
 | 
			
		||||
                helpText="Some help text"
 | 
			
		||||
                onChange={() => {}}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
        expect(container.firstChild).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it("Render uncheked switch", () => {
 | 
			
		||||
        const { container } = render(
 | 
			
		||||
            <Switch label="Test label" helpText="Some help text" />
 | 
			
		||||
        );
 | 
			
		||||
        expect(container.firstChild).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
@@ -9,7 +9,7 @@ import React from "react";
 | 
			
		||||
 | 
			
		||||
import { render } from "customTestRender";
 | 
			
		||||
 | 
			
		||||
import { TextInput } from "../TextInput";
 | 
			
		||||
import TextInput from "../TextInput";
 | 
			
		||||
 | 
			
		||||
describe("<TextInput/>", () => {
 | 
			
		||||
    it("Render text input", () => {
 | 
			
		||||
@@ -18,11 +18,9 @@ describe("<TextInput/>", () => {
 | 
			
		||||
                label="Test label"
 | 
			
		||||
                helpText="Some help text"
 | 
			
		||||
                value="Some text"
 | 
			
		||||
                onChange={() => {
 | 
			
		||||
                }}
 | 
			
		||||
                onChange={() => {}}
 | 
			
		||||
            />
 | 
			
		||||
        );
 | 
			
		||||
        expect(container.firstChild)
 | 
			
		||||
            .toMatchSnapshot();
 | 
			
		||||
        expect(container.firstChild).toMatchSnapshot();
 | 
			
		||||
    });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -2,39 +2,38 @@
 | 
			
		||||
 | 
			
		||||
exports[`<Button /> Render button correctly 1`] = `
 | 
			
		||||
<button
 | 
			
		||||
  class="btn btn-primary "
 | 
			
		||||
  class="btn btn-primary d-inline-flex justify-content-center align-items-center"
 | 
			
		||||
  type="button"
 | 
			
		||||
>
 | 
			
		||||
   
 | 
			
		||||
   
 | 
			
		||||
  Test Button
 | 
			
		||||
  <span>
 | 
			
		||||
    Test Button
 | 
			
		||||
  </span>
 | 
			
		||||
</button>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`<Button /> Render button with custom classes 1`] = `
 | 
			
		||||
<button
 | 
			
		||||
  class="btn one two three"
 | 
			
		||||
  class="btn one two three d-inline-flex justify-content-center align-items-center"
 | 
			
		||||
  type="button"
 | 
			
		||||
>
 | 
			
		||||
   
 | 
			
		||||
   
 | 
			
		||||
  Test Button
 | 
			
		||||
  <span>
 | 
			
		||||
    Test Button
 | 
			
		||||
  </span>
 | 
			
		||||
</button>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`<Button /> Render button with spinner 1`] = `
 | 
			
		||||
<button
 | 
			
		||||
  class="btn btn-primary "
 | 
			
		||||
  class="btn btn-primary d-inline-flex justify-content-center align-items-center"
 | 
			
		||||
  type="button"
 | 
			
		||||
>
 | 
			
		||||
  <span
 | 
			
		||||
    aria-hidden="true"
 | 
			
		||||
    class="spinner-border spinner-border-sm"
 | 
			
		||||
    class="spinner-border spinner-border-sm me-1"
 | 
			
		||||
    role="status"
 | 
			
		||||
  />
 | 
			
		||||
   
 | 
			
		||||
   
 | 
			
		||||
   
 | 
			
		||||
  Test Button
 | 
			
		||||
  <span>
 | 
			
		||||
    Test Button
 | 
			
		||||
  </span>
 | 
			
		||||
</button>
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,61 +2,51 @@
 | 
			
		||||
 | 
			
		||||
exports[`<Checkbox/> Render checkbox 1`] = `
 | 
			
		||||
<div
 | 
			
		||||
  class="col-sm-12 offset-lg-1 col-lg-10"
 | 
			
		||||
  style="margin-bottom: 1rem;"
 | 
			
		||||
  class="mb-3 form-check"
 | 
			
		||||
>
 | 
			
		||||
  <input
 | 
			
		||||
    checked=""
 | 
			
		||||
    class="form-check-input"
 | 
			
		||||
    id="1"
 | 
			
		||||
    type="checkbox"
 | 
			
		||||
  />
 | 
			
		||||
  <label
 | 
			
		||||
    class="form-check-label"
 | 
			
		||||
    for="1"
 | 
			
		||||
  >
 | 
			
		||||
    Test label
 | 
			
		||||
  </label>
 | 
			
		||||
  <div
 | 
			
		||||
    class="custom-control custom-checkbox"
 | 
			
		||||
    style="margin-bottom: 0px;"
 | 
			
		||||
    class="form-text"
 | 
			
		||||
  >
 | 
			
		||||
    <input
 | 
			
		||||
      checked=""
 | 
			
		||||
      class="custom-control-input"
 | 
			
		||||
      id="1"
 | 
			
		||||
      type="checkbox"
 | 
			
		||||
    />
 | 
			
		||||
    <label
 | 
			
		||||
      class="custom-control-label"
 | 
			
		||||
      for="1"
 | 
			
		||||
      style="margin-bottom: 0px;"
 | 
			
		||||
    >
 | 
			
		||||
      Test label
 | 
			
		||||
    </label>
 | 
			
		||||
    <small>
 | 
			
		||||
      Some help text
 | 
			
		||||
    </small>
 | 
			
		||||
  </div>
 | 
			
		||||
  <small
 | 
			
		||||
    class="form-text text-muted"
 | 
			
		||||
  >
 | 
			
		||||
    Some help text
 | 
			
		||||
  </small>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`<Checkbox/> Render uncheked checkbox 1`] = `
 | 
			
		||||
<div
 | 
			
		||||
  class="col-sm-12 offset-lg-1 col-lg-10"
 | 
			
		||||
  style="margin-bottom: 1rem;"
 | 
			
		||||
  class="mb-3 form-check"
 | 
			
		||||
>
 | 
			
		||||
  <input
 | 
			
		||||
    class="form-check-input"
 | 
			
		||||
    id="1"
 | 
			
		||||
    type="checkbox"
 | 
			
		||||
  />
 | 
			
		||||
  <label
 | 
			
		||||
    class="form-check-label"
 | 
			
		||||
    for="1"
 | 
			
		||||
  >
 | 
			
		||||
    Test label
 | 
			
		||||
  </label>
 | 
			
		||||
  <div
 | 
			
		||||
    class="custom-control custom-checkbox"
 | 
			
		||||
    style="margin-bottom: 0px;"
 | 
			
		||||
    class="form-text"
 | 
			
		||||
  >
 | 
			
		||||
    <input
 | 
			
		||||
      class="custom-control-input"
 | 
			
		||||
      id="1"
 | 
			
		||||
      type="checkbox"
 | 
			
		||||
    />
 | 
			
		||||
    <label
 | 
			
		||||
      class="custom-control-label"
 | 
			
		||||
      for="1"
 | 
			
		||||
      style="margin-bottom: 0px;"
 | 
			
		||||
    >
 | 
			
		||||
      Test label
 | 
			
		||||
    </label>
 | 
			
		||||
    <small>
 | 
			
		||||
      Some help text
 | 
			
		||||
    </small>
 | 
			
		||||
  </div>
 | 
			
		||||
  <small
 | 
			
		||||
    class="form-text text-muted"
 | 
			
		||||
  >
 | 
			
		||||
    Some help text
 | 
			
		||||
  </small>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,10 @@
 | 
			
		||||
 | 
			
		||||
exports[`<NumberInput/> Render number input 1`] = `
 | 
			
		||||
<div
 | 
			
		||||
  class="form-group col-sm-12 offset-lg-1 col-lg-10"
 | 
			
		||||
  class="mb-3"
 | 
			
		||||
>
 | 
			
		||||
  <label
 | 
			
		||||
    class="form-label"
 | 
			
		||||
    for="1"
 | 
			
		||||
  >
 | 
			
		||||
    Test label
 | 
			
		||||
@@ -18,33 +19,33 @@ exports[`<NumberInput/> Render number input 1`] = `
 | 
			
		||||
      type="number"
 | 
			
		||||
      value="1"
 | 
			
		||||
    />
 | 
			
		||||
    <div
 | 
			
		||||
      class="input-group-append"
 | 
			
		||||
    <button
 | 
			
		||||
      aria-label="Increase value. Hint: Hold to increase faster."
 | 
			
		||||
      class="btn btn-outline-secondary"
 | 
			
		||||
      title="Increase value. Hint: Hold to increase faster."
 | 
			
		||||
      type="button"
 | 
			
		||||
    >
 | 
			
		||||
      <button
 | 
			
		||||
        aria-label="Increase"
 | 
			
		||||
        class="btn btn-outline-secondary"
 | 
			
		||||
        type="button"
 | 
			
		||||
      >
 | 
			
		||||
        <i
 | 
			
		||||
          class="fas fa-plus"
 | 
			
		||||
        />
 | 
			
		||||
      </button>
 | 
			
		||||
      <button
 | 
			
		||||
        aria-label="Decrease"
 | 
			
		||||
        class="btn btn-outline-secondary"
 | 
			
		||||
        type="button"
 | 
			
		||||
      >
 | 
			
		||||
        <i
 | 
			
		||||
          class="fas fa-minus"
 | 
			
		||||
        />
 | 
			
		||||
      </button>
 | 
			
		||||
    </div>
 | 
			
		||||
      <i
 | 
			
		||||
        class="fa"
 | 
			
		||||
      />
 | 
			
		||||
    </button>
 | 
			
		||||
    <button
 | 
			
		||||
      aria-label="Decrease value. Hint: Hold to decrease faster."
 | 
			
		||||
      class="btn btn-outline-secondary"
 | 
			
		||||
      title="Decrease value. Hint: Hold to decrease faster."
 | 
			
		||||
      type="button"
 | 
			
		||||
    >
 | 
			
		||||
      <i
 | 
			
		||||
        class="fa"
 | 
			
		||||
      />
 | 
			
		||||
    </button>
 | 
			
		||||
  </div>
 | 
			
		||||
  <small
 | 
			
		||||
    class="form-text text-muted"
 | 
			
		||||
  <div
 | 
			
		||||
    class="form-text"
 | 
			
		||||
  >
 | 
			
		||||
    Some help text
 | 
			
		||||
  </small>
 | 
			
		||||
    <small>
 | 
			
		||||
      Some help text
 | 
			
		||||
    </small>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,9 +2,10 @@
 | 
			
		||||
 | 
			
		||||
exports[`<PasswordInput/> Render password input 1`] = `
 | 
			
		||||
<div
 | 
			
		||||
  class="form-group col-sm-12 offset-lg-1 col-lg-10"
 | 
			
		||||
  class="mb-3"
 | 
			
		||||
>
 | 
			
		||||
  <label
 | 
			
		||||
    class="form-label"
 | 
			
		||||
    for="1"
 | 
			
		||||
  >
 | 
			
		||||
    Test label
 | 
			
		||||
@@ -13,17 +14,19 @@ exports[`<PasswordInput/> Render password input 1`] = `
 | 
			
		||||
    class="input-group"
 | 
			
		||||
  >
 | 
			
		||||
    <input
 | 
			
		||||
      autocomplete="new-password"
 | 
			
		||||
      autocomplete="current-password"
 | 
			
		||||
      class="form-control"
 | 
			
		||||
      id="1"
 | 
			
		||||
      type="password"
 | 
			
		||||
      value="Some password"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
  <small
 | 
			
		||||
    class="form-text text-muted"
 | 
			
		||||
  <div
 | 
			
		||||
    class="form-text"
 | 
			
		||||
  >
 | 
			
		||||
    Some help text
 | 
			
		||||
  </small>
 | 
			
		||||
    <small>
 | 
			
		||||
      Some help text
 | 
			
		||||
    </small>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,72 +2,72 @@
 | 
			
		||||
 | 
			
		||||
exports[`<RadioSet/> Render radio set 1`] = `
 | 
			
		||||
<div
 | 
			
		||||
  class="form-group col-sm-12 offset-lg-1 col-lg-10"
 | 
			
		||||
  style="margin-bottom: 1rem;"
 | 
			
		||||
  class="mb-3"
 | 
			
		||||
>
 | 
			
		||||
  <label
 | 
			
		||||
    class="col-12"
 | 
			
		||||
    class="d-block"
 | 
			
		||||
    for="1"
 | 
			
		||||
    style="padding-left: 0px;"
 | 
			
		||||
  >
 | 
			
		||||
    Radios set label
 | 
			
		||||
  </label>
 | 
			
		||||
  <div
 | 
			
		||||
    class="custom-control custom-radio custom-control-inline"
 | 
			
		||||
    class="mb-3"
 | 
			
		||||
  >
 | 
			
		||||
    <input
 | 
			
		||||
      checked=""
 | 
			
		||||
      class="custom-control-input"
 | 
			
		||||
      class="form-check-input me-2"
 | 
			
		||||
      id="test_name-0"
 | 
			
		||||
      name="test_name"
 | 
			
		||||
      type="radio"
 | 
			
		||||
      value="value"
 | 
			
		||||
    />
 | 
			
		||||
    <label
 | 
			
		||||
      class="custom-control-label"
 | 
			
		||||
      class="form-check-label"
 | 
			
		||||
      for="test_name-0"
 | 
			
		||||
    >
 | 
			
		||||
      label
 | 
			
		||||
    </label>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div
 | 
			
		||||
    class="custom-control custom-radio custom-control-inline"
 | 
			
		||||
    class="mb-3"
 | 
			
		||||
  >
 | 
			
		||||
    <input
 | 
			
		||||
      class="custom-control-input"
 | 
			
		||||
      class="form-check-input me-2"
 | 
			
		||||
      id="test_name-1"
 | 
			
		||||
      name="test_name"
 | 
			
		||||
      type="radio"
 | 
			
		||||
      value="another value"
 | 
			
		||||
    />
 | 
			
		||||
    <label
 | 
			
		||||
      class="custom-control-label"
 | 
			
		||||
      class="form-check-label"
 | 
			
		||||
      for="test_name-1"
 | 
			
		||||
    >
 | 
			
		||||
      another label
 | 
			
		||||
    </label>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div
 | 
			
		||||
    class="custom-control custom-radio custom-control-inline"
 | 
			
		||||
    class="mb-3"
 | 
			
		||||
  >
 | 
			
		||||
    <input
 | 
			
		||||
      class="custom-control-input"
 | 
			
		||||
      class="form-check-input me-2"
 | 
			
		||||
      id="test_name-2"
 | 
			
		||||
      name="test_name"
 | 
			
		||||
      type="radio"
 | 
			
		||||
      value="another on value"
 | 
			
		||||
    />
 | 
			
		||||
    <label
 | 
			
		||||
      class="custom-control-label"
 | 
			
		||||
      class="form-check-label"
 | 
			
		||||
      for="test_name-2"
 | 
			
		||||
    >
 | 
			
		||||
      another one  label
 | 
			
		||||
    </label>
 | 
			
		||||
  </div>
 | 
			
		||||
  <small
 | 
			
		||||
    class="form-text text-muted"
 | 
			
		||||
  <div
 | 
			
		||||
    class="form-text"
 | 
			
		||||
  >
 | 
			
		||||
    Some help text
 | 
			
		||||
  </small>
 | 
			
		||||
    <small>
 | 
			
		||||
      Some help text
 | 
			
		||||
    </small>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,15 +3,16 @@
 | 
			
		||||
exports[`<Select/> Test with snapshot. 1`] = `
 | 
			
		||||
<div>
 | 
			
		||||
  <div
 | 
			
		||||
    class="form-group col-sm-12 offset-lg-1 col-lg-10"
 | 
			
		||||
    class="mb-3"
 | 
			
		||||
  >
 | 
			
		||||
    <label
 | 
			
		||||
      class="form-label"
 | 
			
		||||
      for="1"
 | 
			
		||||
    >
 | 
			
		||||
      Test label
 | 
			
		||||
    </label>
 | 
			
		||||
    <select
 | 
			
		||||
      class="custom-select"
 | 
			
		||||
      class="form-select"
 | 
			
		||||
      id="1"
 | 
			
		||||
    >
 | 
			
		||||
      <option
 | 
			
		||||
@@ -30,11 +31,13 @@ exports[`<Select/> Test with snapshot. 1`] = `
 | 
			
		||||
        three
 | 
			
		||||
      </option>
 | 
			
		||||
    </select>
 | 
			
		||||
    <small
 | 
			
		||||
      class="form-text text-muted"
 | 
			
		||||
    <div
 | 
			
		||||
      class="form-text"
 | 
			
		||||
    >
 | 
			
		||||
      Help text
 | 
			
		||||
    </small>
 | 
			
		||||
      <small>
 | 
			
		||||
        Help text
 | 
			
		||||
      </small>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										54
									
								
								src/bootstrap/__tests__/__snapshots__/Switch.test.js.snap
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/bootstrap/__tests__/__snapshots__/Switch.test.js.snap
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,54 @@
 | 
			
		||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
 | 
			
		||||
 | 
			
		||||
exports[`<Switch/> Render switch 1`] = `
 | 
			
		||||
<div
 | 
			
		||||
  class="form-check form-switch mb-3"
 | 
			
		||||
>
 | 
			
		||||
  <input
 | 
			
		||||
    checked=""
 | 
			
		||||
    class="form-check-input"
 | 
			
		||||
    id="1"
 | 
			
		||||
    role="switch"
 | 
			
		||||
    type="checkbox"
 | 
			
		||||
  />
 | 
			
		||||
  <label
 | 
			
		||||
    class="form-check-label"
 | 
			
		||||
    for="1"
 | 
			
		||||
  >
 | 
			
		||||
    Test label
 | 
			
		||||
  </label>
 | 
			
		||||
  <div
 | 
			
		||||
    class="form-text"
 | 
			
		||||
  >
 | 
			
		||||
    <small>
 | 
			
		||||
      Some help text
 | 
			
		||||
    </small>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
exports[`<Switch/> Render uncheked switch 1`] = `
 | 
			
		||||
<div
 | 
			
		||||
  class="form-check form-switch mb-3"
 | 
			
		||||
>
 | 
			
		||||
  <input
 | 
			
		||||
    class="form-check-input"
 | 
			
		||||
    id="1"
 | 
			
		||||
    role="switch"
 | 
			
		||||
    type="checkbox"
 | 
			
		||||
  />
 | 
			
		||||
  <label
 | 
			
		||||
    class="form-check-label"
 | 
			
		||||
    for="1"
 | 
			
		||||
  >
 | 
			
		||||
    Test label
 | 
			
		||||
  </label>
 | 
			
		||||
  <div
 | 
			
		||||
    class="form-text"
 | 
			
		||||
  >
 | 
			
		||||
    <small>
 | 
			
		||||
      Some help text
 | 
			
		||||
    </small>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
@@ -2,9 +2,10 @@
 | 
			
		||||
 | 
			
		||||
exports[`<TextInput/> Render text input 1`] = `
 | 
			
		||||
<div
 | 
			
		||||
  class="form-group col-sm-12 offset-lg-1 col-lg-10"
 | 
			
		||||
  class="mb-3"
 | 
			
		||||
>
 | 
			
		||||
  <label
 | 
			
		||||
    class="form-label"
 | 
			
		||||
    for="1"
 | 
			
		||||
  >
 | 
			
		||||
    Test label
 | 
			
		||||
@@ -19,10 +20,12 @@ exports[`<TextInput/> Render text input 1`] = `
 | 
			
		||||
      value="Some text"
 | 
			
		||||
    />
 | 
			
		||||
  </div>
 | 
			
		||||
  <small
 | 
			
		||||
    class="form-text text-muted"
 | 
			
		||||
  <div
 | 
			
		||||
    class="form-text"
 | 
			
		||||
  >
 | 
			
		||||
    Some help text
 | 
			
		||||
  </small>
 | 
			
		||||
    <small>
 | 
			
		||||
      Some help text
 | 
			
		||||
    </small>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
`;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,12 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019 CZ.NIC z.s.p.o. (http://www.nic.cz/)
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/** Bootstrap column size for form fields */
 | 
			
		||||
// eslint-disable-next-line import/prefer-default-export
 | 
			
		||||
export const formFieldsSize = "col-sm-12 offset-lg-1 col-lg-10";
 | 
			
		||||
const formFieldsSize = "card p-4 col-sm-12 col-lg-12 p-0 mb-4";
 | 
			
		||||
const buttonFormFieldsSize = "col-sm-12 col-lg-12 p-0 mb-3";
 | 
			
		||||
 | 
			
		||||
export { formFieldsSize, buttonFormFieldsSize };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										157
									
								
								src/common/ActionButtonWithModal/ActionButtonWithModal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								src/common/ActionButtonWithModal/ActionButtonWithModal.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2025 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useState, useEffect } from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { useAPIPost, useAPIPut } from "../../api/hooks";
 | 
			
		||||
import { API_STATE } from "../../api/utils";
 | 
			
		||||
import Button from "../../bootstrap/Button";
 | 
			
		||||
import {
 | 
			
		||||
    Modal,
 | 
			
		||||
    ModalHeader,
 | 
			
		||||
    ModalBody,
 | 
			
		||||
    ModalFooter,
 | 
			
		||||
} from "../../bootstrap/Modal";
 | 
			
		||||
import { useAlert } from "../../context/alertContext/AlertContext";
 | 
			
		||||
 | 
			
		||||
ActionButtonWithModal.propTypes = {
 | 
			
		||||
    /** Component that triggers the action. */
 | 
			
		||||
    actionTrigger: PropTypes.elementType.isRequired,
 | 
			
		||||
    /** Method to use for the action. */
 | 
			
		||||
    actionMethod: PropTypes.string,
 | 
			
		||||
    /** URL to send the action to. */
 | 
			
		||||
    actionUrl: PropTypes.string.isRequired,
 | 
			
		||||
    /** Title of the modal. */
 | 
			
		||||
    modalTitle: PropTypes.string.isRequired,
 | 
			
		||||
    /** Message of the modal. */
 | 
			
		||||
    modalMessage: PropTypes.string.isRequired,
 | 
			
		||||
    /** Text of the action button in the modal. */
 | 
			
		||||
    modalActionText: PropTypes.string,
 | 
			
		||||
    /** Props for the action button in the modal. */
 | 
			
		||||
    modalActionProps: PropTypes.object,
 | 
			
		||||
    /** Message to display on successful action. */
 | 
			
		||||
    successMessage: PropTypes.string,
 | 
			
		||||
    /** Message to display on failed action. */
 | 
			
		||||
    errorMessage: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function ActionButtonWithModal({
 | 
			
		||||
    actionTrigger: ActionTriggerComponent,
 | 
			
		||||
    actionMethod = "POST",
 | 
			
		||||
    actionUrl,
 | 
			
		||||
    modalTitle,
 | 
			
		||||
    modalMessage,
 | 
			
		||||
    modalActionText,
 | 
			
		||||
    modalActionProps,
 | 
			
		||||
    successMessage,
 | 
			
		||||
    errorMessage,
 | 
			
		||||
}) {
 | 
			
		||||
    const [triggered, setTriggered] = useState(false);
 | 
			
		||||
    const [modalShown, setModalShown] = useState(false);
 | 
			
		||||
    const [triggerPostActionStatus, triggerPostAction] = useAPIPost(actionUrl);
 | 
			
		||||
    const [triggerPutActionStatus, triggerPutAction] = useAPIPut(actionUrl);
 | 
			
		||||
 | 
			
		||||
    const [setAlert] = useAlert();
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (
 | 
			
		||||
            triggerPostActionStatus.state === API_STATE.SUCCESS ||
 | 
			
		||||
            triggerPutActionStatus.state === API_STATE.SUCCESS
 | 
			
		||||
        ) {
 | 
			
		||||
            setAlert(
 | 
			
		||||
                successMessage || _("Action successful."),
 | 
			
		||||
                API_STATE.SUCCESS
 | 
			
		||||
            );
 | 
			
		||||
            setTriggered(false);
 | 
			
		||||
        }
 | 
			
		||||
        if (
 | 
			
		||||
            triggerPostActionStatus.state === API_STATE.ERROR ||
 | 
			
		||||
            triggerPutActionStatus.state === API_STATE.ERROR
 | 
			
		||||
        ) {
 | 
			
		||||
            setAlert(errorMessage || _("Action failed."));
 | 
			
		||||
            setTriggered(false);
 | 
			
		||||
        }
 | 
			
		||||
    }, [
 | 
			
		||||
        triggerPostActionStatus,
 | 
			
		||||
        triggerPutActionStatus,
 | 
			
		||||
        setAlert,
 | 
			
		||||
        successMessage,
 | 
			
		||||
        errorMessage,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    const actionHandler = () => {
 | 
			
		||||
        setTriggered(true);
 | 
			
		||||
        if (actionMethod === "POST") {
 | 
			
		||||
            triggerPostAction();
 | 
			
		||||
        } else {
 | 
			
		||||
            triggerPutAction();
 | 
			
		||||
        }
 | 
			
		||||
        setModalShown(false);
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <ActionModal
 | 
			
		||||
                shown={modalShown}
 | 
			
		||||
                setShown={setModalShown}
 | 
			
		||||
                onAction={actionHandler}
 | 
			
		||||
                title={modalTitle}
 | 
			
		||||
                message={modalMessage}
 | 
			
		||||
                actionText={modalActionText}
 | 
			
		||||
                actionProps={modalActionProps}
 | 
			
		||||
            />
 | 
			
		||||
            <ActionTriggerComponent
 | 
			
		||||
                loading={triggered}
 | 
			
		||||
                disabled={triggered}
 | 
			
		||||
                onClick={() => setModalShown(true)}
 | 
			
		||||
            />
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ActionModal.propTypes = {
 | 
			
		||||
    shown: PropTypes.bool.isRequired,
 | 
			
		||||
    setShown: PropTypes.func.isRequired,
 | 
			
		||||
    onAction: PropTypes.func.isRequired,
 | 
			
		||||
    title: PropTypes.string.isRequired,
 | 
			
		||||
    message: PropTypes.string.isRequired,
 | 
			
		||||
    actionText: PropTypes.string,
 | 
			
		||||
    actionProps: PropTypes.object,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function ActionModal({
 | 
			
		||||
    shown,
 | 
			
		||||
    setShown,
 | 
			
		||||
    onAction,
 | 
			
		||||
    title,
 | 
			
		||||
    message,
 | 
			
		||||
    actionText,
 | 
			
		||||
    actionProps,
 | 
			
		||||
}) {
 | 
			
		||||
    return (
 | 
			
		||||
        <Modal shown={shown} setShown={setShown}>
 | 
			
		||||
            <ModalHeader setShown={setShown} title={title} />
 | 
			
		||||
            <ModalBody>
 | 
			
		||||
                <p className="mb-0">{message}</p>
 | 
			
		||||
            </ModalBody>
 | 
			
		||||
            <ModalFooter>
 | 
			
		||||
                <Button
 | 
			
		||||
                    className="btn-secondary"
 | 
			
		||||
                    onClick={() => setShown(false)}
 | 
			
		||||
                >
 | 
			
		||||
                    {_("Cancel")}
 | 
			
		||||
                </Button>
 | 
			
		||||
                <Button onClick={onAction} {...actionProps}>
 | 
			
		||||
                    {actionText || _("Confirm")}
 | 
			
		||||
                </Button>
 | 
			
		||||
            </ModalFooter>
 | 
			
		||||
        </Modal>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ActionButtonWithModal;
 | 
			
		||||
							
								
								
									
										39
									
								
								src/common/ActionButtonWithModal/ActionButtonWithModal.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/common/ActionButtonWithModal/ActionButtonWithModal.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
RebootButton component is a button that opens a modal dialog to confirm the
 | 
			
		||||
reboot of the device.
 | 
			
		||||
 | 
			
		||||
## Usage
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
import React, { useEffect, createContext } from "react";
 | 
			
		||||
 | 
			
		||||
import Button from "../../bootstrap/Button";
 | 
			
		||||
import { AlertContextProvider } from "../../context/alertContext/AlertContext";
 | 
			
		||||
import ActionButtonWithModal from "./ActionButtonWithModal";
 | 
			
		||||
 | 
			
		||||
window.AlertContext = React.createContext();
 | 
			
		||||
 | 
			
		||||
const RebootButtonExample = () => {
 | 
			
		||||
    const ActionButton = (props) => {
 | 
			
		||||
        return <Button {...props}>Action</Button>;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <AlertContextProvider>
 | 
			
		||||
            <div id="modal-container" />
 | 
			
		||||
            <div id="alert-container" />
 | 
			
		||||
            <ActionButtonWithModal
 | 
			
		||||
                actionTrigger={ActionButton}
 | 
			
		||||
                actionUrl="/reforis/api/action"
 | 
			
		||||
                modalTitle="Warning!"
 | 
			
		||||
                modalMessage="Are you sure you want to perform this action?"
 | 
			
		||||
                modalActionText="Confirm action"
 | 
			
		||||
                modalActionProps={{ className: "btn-danger" }}
 | 
			
		||||
                successMessage="Action request succeeded."
 | 
			
		||||
                errorMessage="Action request failed."
 | 
			
		||||
            />
 | 
			
		||||
        </AlertContextProvider>
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
<RebootButtonExample />;
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										118
									
								
								src/common/RichTable/RichTable.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/common/RichTable/RichTable.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,118 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2025 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useMemo, useState } from "react";
 | 
			
		||||
 | 
			
		||||
import { rankItem } from "@tanstack/match-sorter-utils";
 | 
			
		||||
import {
 | 
			
		||||
    flexRender,
 | 
			
		||||
    getCoreRowModel,
 | 
			
		||||
    getSortedRowModel,
 | 
			
		||||
    getFilteredRowModel,
 | 
			
		||||
    getPaginationRowModel,
 | 
			
		||||
    useReactTable,
 | 
			
		||||
} from "@tanstack/react-table";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import RichTableBody from "./RichTableBody";
 | 
			
		||||
import RichTableColumnsDropdown from "./RichTableColumnsDropdown";
 | 
			
		||||
import RichTableHeader from "./RichTableHeader";
 | 
			
		||||
import RichTablePagination from "./RichTablePagination";
 | 
			
		||||
import Input from "../../bootstrap/Input";
 | 
			
		||||
 | 
			
		||||
RichTable.propTypes = {
 | 
			
		||||
    /** Columns to be displayed in the table */
 | 
			
		||||
    columns: PropTypes.array.isRequired,
 | 
			
		||||
    /** Data to be displayed in the table, must be passed as a stable reference, for example, useState */
 | 
			
		||||
    data: PropTypes.array.isRequired,
 | 
			
		||||
    /** Whether to display pagination */
 | 
			
		||||
    withPagination: PropTypes.bool,
 | 
			
		||||
    /** Number of rows per page, the default is 5 */
 | 
			
		||||
    pageSize: PropTypes.number,
 | 
			
		||||
    /** Index of the current page */
 | 
			
		||||
    pageIndex: PropTypes.number,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function RichTable({
 | 
			
		||||
    columns,
 | 
			
		||||
    data,
 | 
			
		||||
    withPagination,
 | 
			
		||||
    pageSize = 5,
 | 
			
		||||
    pageIndex = 0,
 | 
			
		||||
}) {
 | 
			
		||||
    const tableColumns = useMemo(() => columns, [columns]);
 | 
			
		||||
    const [sorting, setSorting] = useState([]);
 | 
			
		||||
    const [pagination, setPagination] = useState({
 | 
			
		||||
        pageIndex,
 | 
			
		||||
        pageSize,
 | 
			
		||||
    });
 | 
			
		||||
    const [globalFilter, setGlobalFilter] = useState("");
 | 
			
		||||
    const [columnVisibility, setColumnVisibility] = useState({});
 | 
			
		||||
 | 
			
		||||
    const table = useReactTable({
 | 
			
		||||
        data,
 | 
			
		||||
        columns: tableColumns,
 | 
			
		||||
        filterFns: {
 | 
			
		||||
            fuzzy: fuzzyFilter,
 | 
			
		||||
        },
 | 
			
		||||
        globalFilterFn: "fuzzy",
 | 
			
		||||
        getCoreRowModel: getCoreRowModel(),
 | 
			
		||||
        getSortedRowModel: getSortedRowModel(),
 | 
			
		||||
        getPaginationRowModel: getPaginationRowModel(),
 | 
			
		||||
        getFilteredRowModel: getFilteredRowModel(),
 | 
			
		||||
        onSortingChange: setSorting,
 | 
			
		||||
        onPaginationChange: setPagination,
 | 
			
		||||
        onGlobalFilterChange: setGlobalFilter,
 | 
			
		||||
        onColumnVisibilityChange: setColumnVisibility,
 | 
			
		||||
        state: {
 | 
			
		||||
            sorting,
 | 
			
		||||
            pagination,
 | 
			
		||||
            globalFilter,
 | 
			
		||||
            columnVisibility,
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    const paginationIsNeeded = data.length > pageSize && withPagination;
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div>
 | 
			
		||||
            <div className="d-flex justify-content-between align-items-center">
 | 
			
		||||
                <Input
 | 
			
		||||
                    className="me-3"
 | 
			
		||||
                    type="text"
 | 
			
		||||
                    placeholder={_("Search…")}
 | 
			
		||||
                    value={globalFilter ?? ""}
 | 
			
		||||
                    onChange={(e) => setGlobalFilter(String(e.target.value))}
 | 
			
		||||
                />
 | 
			
		||||
                <RichTableColumnsDropdown columns={table.getAllLeafColumns()} />
 | 
			
		||||
            </div>
 | 
			
		||||
            <div className="table-responsive">
 | 
			
		||||
                <table className="table table-hover text-nowrap">
 | 
			
		||||
                    <RichTableHeader table={table} flexRender={flexRender} />
 | 
			
		||||
                    <RichTableBody
 | 
			
		||||
                        table={table}
 | 
			
		||||
                        columns={tableColumns}
 | 
			
		||||
                        flexRender={flexRender}
 | 
			
		||||
                    />
 | 
			
		||||
                </table>
 | 
			
		||||
                {paginationIsNeeded && (
 | 
			
		||||
                    <RichTablePagination
 | 
			
		||||
                        table={table}
 | 
			
		||||
                        tablePageSize={pageSize}
 | 
			
		||||
                        allRows={data.length}
 | 
			
		||||
                    />
 | 
			
		||||
                )}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function fuzzyFilter(row, columnId, value, addMeta) {
 | 
			
		||||
    const itemRank = rankItem(row.getValue(columnId), value);
 | 
			
		||||
    addMeta({ itemRank });
 | 
			
		||||
    return itemRank.passed;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								src/common/RichTable/RichTable.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/common/RichTable/RichTable.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
### Description
 | 
			
		||||
 | 
			
		||||
Rich Table is a table component based on
 | 
			
		||||
[Tanstack React Table](https://tanstack.com/table/). It adds some features to
 | 
			
		||||
the table component, such as:
 | 
			
		||||
 | 
			
		||||
- **Pagination**: The table can be paginated.
 | 
			
		||||
- **Sorting**: The table can be sorted by columns.
 | 
			
		||||
- **Row Expansion**: The table rows can be expanded. (To be implemented)
 | 
			
		||||
 | 
			
		||||
### Example
 | 
			
		||||
 | 
			
		||||
```jsx
 | 
			
		||||
import { columns, data } from "./mockData";
 | 
			
		||||
 | 
			
		||||
<RichTable columns={columns} data={data} withPagination />;
 | 
			
		||||
```
 | 
			
		||||
							
								
								
									
										58
									
								
								src/common/RichTable/RichTableBody.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/common/RichTable/RichTableBody.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import propTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
RichTableBody.propTypes = {
 | 
			
		||||
    table: propTypes.shape({
 | 
			
		||||
        getRowModel: propTypes.func.isRequired,
 | 
			
		||||
    }).isRequired,
 | 
			
		||||
    columns: propTypes.array.isRequired,
 | 
			
		||||
    flexRender: propTypes.func.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function RichTableBody({ table, columns, flexRender }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <tbody>
 | 
			
		||||
            {table.getRowModel().rows?.length ? (
 | 
			
		||||
                table.getRowModel().rows.map((row) => {
 | 
			
		||||
                    return (
 | 
			
		||||
                        <tr key={row.id} className="align-middle">
 | 
			
		||||
                            {row.getVisibleCells().map((cell) => {
 | 
			
		||||
                                return (
 | 
			
		||||
                                    <td
 | 
			
		||||
                                        key={cell.id}
 | 
			
		||||
                                        {...(cell.column.columnDef
 | 
			
		||||
                                            .className && {
 | 
			
		||||
                                            className:
 | 
			
		||||
                                                cell.column.columnDef.className,
 | 
			
		||||
                                        })}
 | 
			
		||||
                                    >
 | 
			
		||||
                                        {flexRender(
 | 
			
		||||
                                            cell.column.columnDef.cell,
 | 
			
		||||
                                            cell.getContext()
 | 
			
		||||
                                        )}
 | 
			
		||||
                                    </td>
 | 
			
		||||
                                );
 | 
			
		||||
                            })}
 | 
			
		||||
                        </tr>
 | 
			
		||||
                    );
 | 
			
		||||
                })
 | 
			
		||||
            ) : (
 | 
			
		||||
                <tr>
 | 
			
		||||
                    <td colSpan={columns.length} className="text-center py-4">
 | 
			
		||||
                        <span>{_("No results.")}</span>
 | 
			
		||||
                    </td>
 | 
			
		||||
                </tr>
 | 
			
		||||
            )}
 | 
			
		||||
        </tbody>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default RichTableBody;
 | 
			
		||||
							
								
								
									
										90
									
								
								src/common/RichTable/RichTableColumnsDropdown.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/common/RichTable/RichTableColumnsDropdown.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2025 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import { faCheck, faRotateLeft } from "@fortawesome/free-solid-svg-icons";
 | 
			
		||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import Button from "../../bootstrap/Button";
 | 
			
		||||
 | 
			
		||||
RichTableColumnsDropdown.propTypes = {
 | 
			
		||||
    columns: PropTypes.array.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function RichTableColumnsDropdown({ columns }) {
 | 
			
		||||
    return (
 | 
			
		||||
        <div className="dropdown mb-3">
 | 
			
		||||
            <Button
 | 
			
		||||
                className="btn btn-outline-secondary dropdown-toggle"
 | 
			
		||||
                data-bs-toggle="dropdown"
 | 
			
		||||
            >
 | 
			
		||||
                {_("Columns")}
 | 
			
		||||
            </Button>
 | 
			
		||||
            <ul className="dropdown-menu dropdown-menu-end">
 | 
			
		||||
                {columns.map((column) => {
 | 
			
		||||
                    return (
 | 
			
		||||
                        <li key={column.id}>
 | 
			
		||||
                            <button
 | 
			
		||||
                                type="button"
 | 
			
		||||
                                className="dropdown-item d-flex align-items-center"
 | 
			
		||||
                                onClick={column.getToggleVisibilityHandler()}
 | 
			
		||||
                                style={{ paddingLeft: "2rem" }}
 | 
			
		||||
                                disabled={
 | 
			
		||||
                                    column.columnDef?.enableHiding === false
 | 
			
		||||
                                }
 | 
			
		||||
                            >
 | 
			
		||||
                                {column.getIsVisible() && (
 | 
			
		||||
                                    <FontAwesomeIcon
 | 
			
		||||
                                        icon={faCheck}
 | 
			
		||||
                                        className="position-absolute text-secondary me-2"
 | 
			
		||||
                                        style={{ left: "0.6rem" }}
 | 
			
		||||
                                        width="1rem"
 | 
			
		||||
                                    />
 | 
			
		||||
                                )}
 | 
			
		||||
                                <span>{column.columnDef.header}</span>
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </li>
 | 
			
		||||
                    );
 | 
			
		||||
                })}
 | 
			
		||||
                {columns.some((column) => !column.getIsVisible()) && (
 | 
			
		||||
                    <>
 | 
			
		||||
                        <li>
 | 
			
		||||
                            <hr className="dropdown-divider" />
 | 
			
		||||
                        </li>
 | 
			
		||||
                        <li>
 | 
			
		||||
                            <button
 | 
			
		||||
                                type="button"
 | 
			
		||||
                                className="dropdown-item d-flex align-items-center"
 | 
			
		||||
                                style={{ paddingLeft: "2rem" }}
 | 
			
		||||
                                onClick={() => {
 | 
			
		||||
                                    // toggleVisibility for columns that are hidden
 | 
			
		||||
                                    columns.forEach((column) => {
 | 
			
		||||
                                        if (!column.getIsVisible()) {
 | 
			
		||||
                                            column.toggleVisibility();
 | 
			
		||||
                                        }
 | 
			
		||||
                                    });
 | 
			
		||||
                                }}
 | 
			
		||||
                            >
 | 
			
		||||
                                <FontAwesomeIcon
 | 
			
		||||
                                    icon={faRotateLeft}
 | 
			
		||||
                                    className="position-absolute text-secondary me-2"
 | 
			
		||||
                                    width="1rem"
 | 
			
		||||
                                    style={{ left: "0.6rem" }}
 | 
			
		||||
                                />
 | 
			
		||||
                                {_("Reset")}
 | 
			
		||||
                            </button>
 | 
			
		||||
                        </li>
 | 
			
		||||
                    </>
 | 
			
		||||
                )}
 | 
			
		||||
            </ul>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default RichTableColumnsDropdown;
 | 
			
		||||
							
								
								
									
										102
									
								
								src/common/RichTable/RichTableHeader.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								src/common/RichTable/RichTableHeader.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,102 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2025 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    faSquareCaretUp,
 | 
			
		||||
    faSquareCaretDown,
 | 
			
		||||
} from "@fortawesome/free-solid-svg-icons";
 | 
			
		||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 | 
			
		||||
import propTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
RichTableHeader.propTypes = {
 | 
			
		||||
    table: propTypes.shape({
 | 
			
		||||
        getHeaderGroups: propTypes.func.isRequired,
 | 
			
		||||
    }).isRequired,
 | 
			
		||||
    flexRender: propTypes.func.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function RichTableHeader({ table, flexRender }) {
 | 
			
		||||
    const getThTitle = (header) => {
 | 
			
		||||
        if (!header.column.getCanSort()) return undefined;
 | 
			
		||||
 | 
			
		||||
        const nextSortingOrder = header.column.getNextSortingOrder();
 | 
			
		||||
        if (nextSortingOrder === "asc") return _("Sort ascending");
 | 
			
		||||
        if (nextSortingOrder === "desc") return _("Sort descending");
 | 
			
		||||
        return _("Clear sort");
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <thead className="table-light">
 | 
			
		||||
            {table.getHeaderGroups().map((headerGroup) => (
 | 
			
		||||
                <tr key={headerGroup.id} role="row">
 | 
			
		||||
                    {headerGroup.headers.map((header) => (
 | 
			
		||||
                        <th
 | 
			
		||||
                            key={header.id}
 | 
			
		||||
                            colSpan={header.colSpan}
 | 
			
		||||
                            {...(header.column.columnDef.headerClassName && {
 | 
			
		||||
                                className:
 | 
			
		||||
                                    header.column.columnDef.headerClassName,
 | 
			
		||||
                            })}
 | 
			
		||||
                        >
 | 
			
		||||
                            {header.isPlaceholder ||
 | 
			
		||||
                            header.column.columnDef.headerIsHidden ? (
 | 
			
		||||
                                <div className="d-none" aria-hidden="true">
 | 
			
		||||
                                    {flexRender(
 | 
			
		||||
                                        header.column.columnDef.header,
 | 
			
		||||
                                        header.getContext()
 | 
			
		||||
                                    )}
 | 
			
		||||
                                </div>
 | 
			
		||||
                            ) : (
 | 
			
		||||
                                <button
 | 
			
		||||
                                    type="button"
 | 
			
		||||
                                    style={
 | 
			
		||||
                                        header.column.columnDef
 | 
			
		||||
                                            .headerClassName === "text-center"
 | 
			
		||||
                                            ? { justifySelf: "center" }
 | 
			
		||||
                                            : {}
 | 
			
		||||
                                    }
 | 
			
		||||
                                    className={`btn btn-link text-decoration-none text-reset fw-bold p-0 d-flex align-items-center
 | 
			
		||||
                                                    ${
 | 
			
		||||
                                                        header.column.getCanSort()
 | 
			
		||||
                                                            ? "d-flex align-items-center"
 | 
			
		||||
                                                            : ""
 | 
			
		||||
                                                    }
 | 
			
		||||
                                                `}
 | 
			
		||||
                                    onClick={header.column.getToggleSortingHandler()}
 | 
			
		||||
                                    title={getThTitle(header)}
 | 
			
		||||
                                >
 | 
			
		||||
                                    {flexRender(
 | 
			
		||||
                                        header.column.columnDef.header,
 | 
			
		||||
                                        header.getContext()
 | 
			
		||||
                                    )}
 | 
			
		||||
                                    {{
 | 
			
		||||
                                        asc: (
 | 
			
		||||
                                            <FontAwesomeIcon
 | 
			
		||||
                                                icon={faSquareCaretUp}
 | 
			
		||||
                                                className="ms-1 text-primary"
 | 
			
		||||
                                            />
 | 
			
		||||
                                        ),
 | 
			
		||||
                                        desc: (
 | 
			
		||||
                                            <FontAwesomeIcon
 | 
			
		||||
                                                icon={faSquareCaretDown}
 | 
			
		||||
                                                className="ms-1 text-primary"
 | 
			
		||||
                                            />
 | 
			
		||||
                                        ),
 | 
			
		||||
                                    }[header.column.getIsSorted()] ?? null}
 | 
			
		||||
                                </button>
 | 
			
		||||
                            )}
 | 
			
		||||
                        </th>
 | 
			
		||||
                    ))}
 | 
			
		||||
                </tr>
 | 
			
		||||
            ))}
 | 
			
		||||
        </thead>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default RichTableHeader;
 | 
			
		||||
							
								
								
									
										128
									
								
								src/common/RichTable/RichTablePagination.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/common/RichTable/RichTablePagination.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2025 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useMemo } from "react";
 | 
			
		||||
 | 
			
		||||
import {
 | 
			
		||||
    faAngleLeft,
 | 
			
		||||
    faAnglesLeft,
 | 
			
		||||
    faAngleRight,
 | 
			
		||||
    faAnglesRight,
 | 
			
		||||
} from "@fortawesome/free-solid-svg-icons";
 | 
			
		||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 | 
			
		||||
import propTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
RichTablePagination.propTypes = {
 | 
			
		||||
    table: propTypes.shape({
 | 
			
		||||
        getState: propTypes.func.isRequired,
 | 
			
		||||
        getCanPreviousPage: propTypes.func.isRequired,
 | 
			
		||||
        getCanNextPage: propTypes.func.isRequired,
 | 
			
		||||
        firstPage: propTypes.func.isRequired,
 | 
			
		||||
        previousPage: propTypes.func.isRequired,
 | 
			
		||||
        nextPage: propTypes.func.isRequired,
 | 
			
		||||
        lastPage: propTypes.func.isRequired,
 | 
			
		||||
        setPageSize: propTypes.func.isRequired,
 | 
			
		||||
        getPageCount: propTypes.func.isRequired,
 | 
			
		||||
    }).isRequired,
 | 
			
		||||
    tablePageSize: propTypes.number,
 | 
			
		||||
    allRows: propTypes.number,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function RichTablePagination({ table, tablePageSize, allRows }) {
 | 
			
		||||
    const { pagination } = table.getState();
 | 
			
		||||
    const prevPagBtnDisabled = !table.getCanPreviousPage();
 | 
			
		||||
    const nextPagBtnDisabled = !table.getCanNextPage();
 | 
			
		||||
 | 
			
		||||
    const pageSizes = useMemo(() => {
 | 
			
		||||
        return [tablePageSize ?? 5, 10, 25].filter(
 | 
			
		||||
            (value, index, self) => self.indexOf(value) === index
 | 
			
		||||
        );
 | 
			
		||||
    }, [tablePageSize]);
 | 
			
		||||
 | 
			
		||||
    const renderPaginationButton = (icon, ariaLabel, onClick, disabled) => (
 | 
			
		||||
        <li
 | 
			
		||||
            className={`page-item ${disabled ? "disabled" : ""}`}
 | 
			
		||||
            style={{ cursor: disabled ? "not-allowed" : "pointer" }}
 | 
			
		||||
        >
 | 
			
		||||
            <button
 | 
			
		||||
                type="button"
 | 
			
		||||
                className="page-link"
 | 
			
		||||
                aria-label={ariaLabel}
 | 
			
		||||
                onClick={onClick}
 | 
			
		||||
                disabled={disabled}
 | 
			
		||||
            >
 | 
			
		||||
                <FontAwesomeIcon icon={icon} />
 | 
			
		||||
            </button>
 | 
			
		||||
        </li>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <nav
 | 
			
		||||
            aria-label={_("Pagination navigation bar")}
 | 
			
		||||
            className="d-flex gap-2 justify-content-start align-items-center mx-2 mb-1 text-nowrap"
 | 
			
		||||
        >
 | 
			
		||||
            <ul className="pagination pagination-sm mb-0">
 | 
			
		||||
                {renderPaginationButton(
 | 
			
		||||
                    faAnglesLeft,
 | 
			
		||||
                    _("First page"),
 | 
			
		||||
                    () => table.firstPage(),
 | 
			
		||||
                    prevPagBtnDisabled
 | 
			
		||||
                )}
 | 
			
		||||
                {renderPaginationButton(
 | 
			
		||||
                    faAngleLeft,
 | 
			
		||||
                    _("Previous page"),
 | 
			
		||||
                    () => table.previousPage(),
 | 
			
		||||
                    prevPagBtnDisabled
 | 
			
		||||
                )}
 | 
			
		||||
                {renderPaginationButton(
 | 
			
		||||
                    faAngleRight,
 | 
			
		||||
                    _("Next page"),
 | 
			
		||||
                    () => table.nextPage(),
 | 
			
		||||
                    nextPagBtnDisabled
 | 
			
		||||
                )}
 | 
			
		||||
                {renderPaginationButton(
 | 
			
		||||
                    faAnglesRight,
 | 
			
		||||
                    _("Last page"),
 | 
			
		||||
                    () => table.lastPage(),
 | 
			
		||||
                    nextPagBtnDisabled
 | 
			
		||||
                )}
 | 
			
		||||
            </ul>
 | 
			
		||||
            <span>
 | 
			
		||||
                {_("Page")} 
 | 
			
		||||
                <span className="fw-bold">
 | 
			
		||||
                    {pagination.pageIndex + 1}
 | 
			
		||||
                     {_("of")} 
 | 
			
		||||
                    {table.getPageCount().toLocaleString()}
 | 
			
		||||
                </span>
 | 
			
		||||
            </span>
 | 
			
		||||
            <div
 | 
			
		||||
                className="vr mx-1 align-self-center"
 | 
			
		||||
                style={{ height: "1.5rem" }}
 | 
			
		||||
            />
 | 
			
		||||
            <span>{_("Rows per page:")}</span>
 | 
			
		||||
            <select
 | 
			
		||||
                className="form-select form-select-sm w-auto"
 | 
			
		||||
                aria-label={_("Select rows per page")}
 | 
			
		||||
                value={pagination.pageSize}
 | 
			
		||||
                onChange={(e) => {
 | 
			
		||||
                    table.setPageSize(Number(e.target.value));
 | 
			
		||||
                }}
 | 
			
		||||
            >
 | 
			
		||||
                {pageSizes.map((pageSize) => (
 | 
			
		||||
                    <option key={pageSize} value={pageSize}>
 | 
			
		||||
                        {pageSize}
 | 
			
		||||
                    </option>
 | 
			
		||||
                ))}
 | 
			
		||||
                <option key={allRows} value={allRows}>
 | 
			
		||||
                    {_("All")}
 | 
			
		||||
                </option>
 | 
			
		||||
            </select>
 | 
			
		||||
        </nav>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default RichTablePagination;
 | 
			
		||||
							
								
								
									
										119
									
								
								src/common/RichTable/mockData.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/common/RichTable/mockData.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
const columns = [
 | 
			
		||||
    {
 | 
			
		||||
        header: "Name",
 | 
			
		||||
        accessorKey: "name",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        header: "Surname",
 | 
			
		||||
        accessorKey: "surname",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        header: "Age",
 | 
			
		||||
        accessorKey: "age",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        header: "Phone",
 | 
			
		||||
        accessorKey: "phone",
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
const data = [
 | 
			
		||||
    {
 | 
			
		||||
        name: "John",
 | 
			
		||||
        surname: "Coltrane",
 | 
			
		||||
        age: 30,
 | 
			
		||||
        phone: "123456789",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Jane",
 | 
			
		||||
        surname: "Doe",
 | 
			
		||||
        age: 25,
 | 
			
		||||
        phone: "987654321",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Alice",
 | 
			
		||||
        surname: "Smith",
 | 
			
		||||
        age: 35,
 | 
			
		||||
        phone: "123456789",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Bob",
 | 
			
		||||
        surname: "Smith",
 | 
			
		||||
        age: 40,
 | 
			
		||||
        phone: "987654321",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Charlie",
 | 
			
		||||
        surname: "Brown",
 | 
			
		||||
        age: 45,
 | 
			
		||||
        phone: "123456789",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Daisy",
 | 
			
		||||
        surname: "Brown",
 | 
			
		||||
        age: 50,
 | 
			
		||||
        phone: "987654321",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Eve",
 | 
			
		||||
        surname: "Johnson",
 | 
			
		||||
        age: 55,
 | 
			
		||||
        phone: "123456789",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Frank",
 | 
			
		||||
        surname: "Johnson",
 | 
			
		||||
        age: 60,
 | 
			
		||||
        phone: "987654321",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Grace",
 | 
			
		||||
        surname: "Williams",
 | 
			
		||||
        age: 65,
 | 
			
		||||
        phone: "123456789",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Henry",
 | 
			
		||||
        surname: "Williams",
 | 
			
		||||
        age: 70,
 | 
			
		||||
        phone: "987654321",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Ivy",
 | 
			
		||||
        surname: "Brown",
 | 
			
		||||
        age: 75,
 | 
			
		||||
        phone: "123456789",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Jack",
 | 
			
		||||
        surname: "Brown",
 | 
			
		||||
        age: 80,
 | 
			
		||||
        phone: "987654321",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Kelly",
 | 
			
		||||
        surname: "Johnson",
 | 
			
		||||
        age: 85,
 | 
			
		||||
        phone: "123456789",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Liam",
 | 
			
		||||
        surname: "Johnson",
 | 
			
		||||
        age: 90,
 | 
			
		||||
        phone: "987654321",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Mia",
 | 
			
		||||
        surname: "Williams",
 | 
			
		||||
        age: 95,
 | 
			
		||||
        phone: "123456789",
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        name: "Nathan",
 | 
			
		||||
        surname: "Williams",
 | 
			
		||||
        age: 100,
 | 
			
		||||
        phone: "987654321",
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
export { columns, data };
 | 
			
		||||
							
								
								
									
										77
									
								
								src/common/WiFiSettings/ResetWiFiSettings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/common/WiFiSettings/ResetWiFiSettings.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,77 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React, { useEffect, useState } from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { useAPIPost } from "../../api/hooks";
 | 
			
		||||
import { API_STATE } from "../../api/utils";
 | 
			
		||||
import { ALERT_TYPES } from "../../bootstrap/Alert";
 | 
			
		||||
import Button from "../../bootstrap/Button";
 | 
			
		||||
import { formFieldsSize } from "../../bootstrap/constants";
 | 
			
		||||
import { useAlert } from "../../context/alertContext/AlertContext";
 | 
			
		||||
 | 
			
		||||
ResetWiFiSettings.propTypes = {
 | 
			
		||||
    ws: PropTypes.object.isRequired,
 | 
			
		||||
    endpoint: PropTypes.string.isRequired,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function ResetWiFiSettings({ ws, endpoint }) {
 | 
			
		||||
    const [isLoading, setIsLoading] = useState(false);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        const module = "wifi";
 | 
			
		||||
        ws.subscribe(module).bind(module, "reset", () => {
 | 
			
		||||
            // eslint-disable-next-line no-restricted-globals
 | 
			
		||||
            setTimeout(() => location.reload(), 1000);
 | 
			
		||||
        });
 | 
			
		||||
    }, [ws]);
 | 
			
		||||
 | 
			
		||||
    const [postResetResponse, postReset] = useAPIPost(endpoint);
 | 
			
		||||
    const [setAlert, dismissAlert] = useAlert();
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
        if (postResetResponse.state === API_STATE.ERROR) {
 | 
			
		||||
            setAlert(_("An error occurred during resetting Wi-Fi settings."));
 | 
			
		||||
        } else if (postResetResponse.state === API_STATE.SUCCESS) {
 | 
			
		||||
            setAlert(
 | 
			
		||||
                _("Wi-Fi settings are set to defaults."),
 | 
			
		||||
                ALERT_TYPES.SUCCESS
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    }, [postResetResponse, setAlert]);
 | 
			
		||||
 | 
			
		||||
    const onReset = () => {
 | 
			
		||||
        dismissAlert();
 | 
			
		||||
        setIsLoading(true);
 | 
			
		||||
        postReset();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <div className={formFieldsSize}>
 | 
			
		||||
            <h2>{_("Reset Wi-Fi Settings")}</h2>
 | 
			
		||||
            <p>
 | 
			
		||||
                {_(
 | 
			
		||||
                    "If a number of wireless cards doesn't match, you may try to reset the Wi-Fi settings. Note that this will remove the current Wi-Fi configuration and restore the default values."
 | 
			
		||||
                )}
 | 
			
		||||
            </p>
 | 
			
		||||
            <div className="text-end">
 | 
			
		||||
                <Button
 | 
			
		||||
                    className="btn-primary"
 | 
			
		||||
                    forisFormSize
 | 
			
		||||
                    loading={isLoading}
 | 
			
		||||
                    disabled={isLoading}
 | 
			
		||||
                    onClick={onReset}
 | 
			
		||||
                >
 | 
			
		||||
                    {_("Reset Wi-Fi Settings")}
 | 
			
		||||
                </Button>
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ResetWiFiSettings;
 | 
			
		||||
							
								
								
									
										306
									
								
								src/common/WiFiSettings/WiFiForm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										306
									
								
								src/common/WiFiSettings/WiFiForm.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,306 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2022 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { HELP_TEXTS, HTMODES, BANDS, ENCRYPTIONMODES } from "./constants";
 | 
			
		||||
import WifiGuestForm from "./WiFiGuestForm";
 | 
			
		||||
import WiFiQRCode from "./WiFiQRCode";
 | 
			
		||||
import PasswordInput from "../../bootstrap/PasswordInput";
 | 
			
		||||
import RadioSet from "../../bootstrap/RadioSet";
 | 
			
		||||
import Select from "../../bootstrap/Select";
 | 
			
		||||
import Switch from "../../bootstrap/Switch";
 | 
			
		||||
import TextInput from "../../bootstrap/TextInput";
 | 
			
		||||
 | 
			
		||||
WiFiForm.propTypes = {
 | 
			
		||||
    formData: PropTypes.shape({ devices: PropTypes.arrayOf(PropTypes.object) })
 | 
			
		||||
        .isRequired,
 | 
			
		||||
    formErrors: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
 | 
			
		||||
    setFormValue: PropTypes.func.isRequired,
 | 
			
		||||
    hasGuestNetwork: PropTypes.bool,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
WiFiForm.defaultProps = {
 | 
			
		||||
    formData: { devices: [] },
 | 
			
		||||
    setFormValue: () => {},
 | 
			
		||||
    hasGuestNetwork: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function WiFiForm({
 | 
			
		||||
    formData,
 | 
			
		||||
    formErrors,
 | 
			
		||||
    setFormValue,
 | 
			
		||||
    hasGuestNetwork,
 | 
			
		||||
    disabled,
 | 
			
		||||
}) {
 | 
			
		||||
    return formData.devices.map((device, index) => (
 | 
			
		||||
        <DeviceForm
 | 
			
		||||
            key={device.id}
 | 
			
		||||
            formData={device}
 | 
			
		||||
            deviceIndex={index}
 | 
			
		||||
            formErrors={(formErrors || [])[index]}
 | 
			
		||||
            setFormValue={setFormValue}
 | 
			
		||||
            hasGuestNetwork={hasGuestNetwork}
 | 
			
		||||
            disabled={disabled}
 | 
			
		||||
            divider={index + 1 !== formData.devices.length}
 | 
			
		||||
        />
 | 
			
		||||
    ));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DeviceForm.propTypes = {
 | 
			
		||||
    formData: PropTypes.shape({
 | 
			
		||||
        id: PropTypes.number.isRequired,
 | 
			
		||||
        enabled: PropTypes.bool.isRequired,
 | 
			
		||||
        SSID: PropTypes.string.isRequired,
 | 
			
		||||
        password: PropTypes.string.isRequired,
 | 
			
		||||
        hidden: PropTypes.bool.isRequired,
 | 
			
		||||
        band: PropTypes.string.isRequired,
 | 
			
		||||
        htmode: PropTypes.string.isRequired,
 | 
			
		||||
        channel: PropTypes.string.isRequired,
 | 
			
		||||
        guest_wifi: PropTypes.object.isRequired,
 | 
			
		||||
        encryption: PropTypes.string.isRequired,
 | 
			
		||||
        available_bands: PropTypes.array.isRequired,
 | 
			
		||||
        ieee80211w_disabled: PropTypes.bool,
 | 
			
		||||
    }),
 | 
			
		||||
    formErrors: PropTypes.object.isRequired,
 | 
			
		||||
    setFormValue: PropTypes.func.isRequired,
 | 
			
		||||
    hasGuestNetwork: PropTypes.bool,
 | 
			
		||||
    deviceIndex: PropTypes.number,
 | 
			
		||||
    divider: PropTypes.bool,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
DeviceForm.defaultProps = {
 | 
			
		||||
    formErrors: {},
 | 
			
		||||
    hasGuestNetwork: true,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function DeviceForm({
 | 
			
		||||
    formData,
 | 
			
		||||
    formErrors,
 | 
			
		||||
    setFormValue,
 | 
			
		||||
    hasGuestNetwork,
 | 
			
		||||
    deviceIndex,
 | 
			
		||||
    divider,
 | 
			
		||||
    ...props
 | 
			
		||||
}) {
 | 
			
		||||
    const deviceID = formData.id;
 | 
			
		||||
    const bnds = formData.available_bands;
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <Switch
 | 
			
		||||
                label={<h2 className="mb-0">{_(`Wi-Fi ${deviceID + 1}`)}</h2>}
 | 
			
		||||
                checked={formData.enabled}
 | 
			
		||||
                onChange={setFormValue((value) => ({
 | 
			
		||||
                    devices: {
 | 
			
		||||
                        [deviceIndex]: { enabled: { $set: value } },
 | 
			
		||||
                    },
 | 
			
		||||
                }))}
 | 
			
		||||
                switchHeading
 | 
			
		||||
                {...props}
 | 
			
		||||
            />
 | 
			
		||||
            {formData.enabled && (
 | 
			
		||||
                <>
 | 
			
		||||
                    <TextInput
 | 
			
		||||
                        label="SSID"
 | 
			
		||||
                        value={formData.SSID}
 | 
			
		||||
                        error={formErrors.SSID || null}
 | 
			
		||||
                        helpText={HELP_TEXTS.ssid}
 | 
			
		||||
                        required
 | 
			
		||||
                        onChange={setFormValue((value) => ({
 | 
			
		||||
                            devices: {
 | 
			
		||||
                                [deviceIndex]: {
 | 
			
		||||
                                    SSID: { $set: value },
 | 
			
		||||
                                },
 | 
			
		||||
                            },
 | 
			
		||||
                        }))}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    >
 | 
			
		||||
                        <WiFiQRCode
 | 
			
		||||
                            SSID={formData.SSID}
 | 
			
		||||
                            password={formData.password}
 | 
			
		||||
                        />
 | 
			
		||||
                    </TextInput>
 | 
			
		||||
 | 
			
		||||
                    <PasswordInput
 | 
			
		||||
                        withEye
 | 
			
		||||
                        label={_("Password")}
 | 
			
		||||
                        value={formData.password}
 | 
			
		||||
                        error={formErrors.password}
 | 
			
		||||
                        helpText={HELP_TEXTS.password}
 | 
			
		||||
                        required
 | 
			
		||||
                        onChange={setFormValue((value) => ({
 | 
			
		||||
                            devices: {
 | 
			
		||||
                                [deviceIndex]: { password: { $set: value } },
 | 
			
		||||
                            },
 | 
			
		||||
                        }))}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    <Switch
 | 
			
		||||
                        label={_("Hide SSID")}
 | 
			
		||||
                        helpText={HELP_TEXTS.hidden}
 | 
			
		||||
                        checked={formData.hidden}
 | 
			
		||||
                        onChange={setFormValue((value) => ({
 | 
			
		||||
                            devices: {
 | 
			
		||||
                                [deviceIndex]: { hidden: { $set: value } },
 | 
			
		||||
                            },
 | 
			
		||||
                        }))}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    <RadioSet
 | 
			
		||||
                        name={`band-${deviceID}`}
 | 
			
		||||
                        label={_("Band")}
 | 
			
		||||
                        choices={getBandChoices(formData)}
 | 
			
		||||
                        value={formData.band}
 | 
			
		||||
                        helpText={HELP_TEXTS.band}
 | 
			
		||||
                        inline
 | 
			
		||||
                        onChange={setFormValue((value) => {
 | 
			
		||||
                            // Find the selected band
 | 
			
		||||
                            const selectedBand = bnds.find(
 | 
			
		||||
                                (band) => band.band === value
 | 
			
		||||
                            );
 | 
			
		||||
                            // Get the last item in the available HT modes for the selected band
 | 
			
		||||
                            const bestHtmode =
 | 
			
		||||
                                selectedBand.available_htmodes.slice(-1)[0];
 | 
			
		||||
                            return {
 | 
			
		||||
                                devices: {
 | 
			
		||||
                                    [deviceIndex]: {
 | 
			
		||||
                                        band: { $set: value },
 | 
			
		||||
                                        channel: { $set: "0" },
 | 
			
		||||
                                        htmode: { $set: bestHtmode },
 | 
			
		||||
                                    },
 | 
			
		||||
                                },
 | 
			
		||||
                            };
 | 
			
		||||
                        })}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    <Select
 | 
			
		||||
                        label={_("802.11n/ac/ax mode")}
 | 
			
		||||
                        choices={getHtmodeChoices(formData)}
 | 
			
		||||
                        value={formData.htmode}
 | 
			
		||||
                        helpText={HELP_TEXTS.htmode}
 | 
			
		||||
                        onChange={setFormValue((value) => ({
 | 
			
		||||
                            devices: {
 | 
			
		||||
                                [deviceIndex]: { htmode: { $set: value } },
 | 
			
		||||
                            },
 | 
			
		||||
                        }))}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    <Select
 | 
			
		||||
                        label={_("Channel")}
 | 
			
		||||
                        choices={getChannelChoices(formData)}
 | 
			
		||||
                        value={formData.channel}
 | 
			
		||||
                        onChange={setFormValue((value) => ({
 | 
			
		||||
                            devices: {
 | 
			
		||||
                                [deviceIndex]: { channel: { $set: value } },
 | 
			
		||||
                            },
 | 
			
		||||
                        }))}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    <Select
 | 
			
		||||
                        label={_("Encryption")}
 | 
			
		||||
                        choices={getEncryptionChoices(formData)}
 | 
			
		||||
                        helpText={HELP_TEXTS.wpa3}
 | 
			
		||||
                        value={formData.encryption}
 | 
			
		||||
                        onChange={setFormValue((value) => ({
 | 
			
		||||
                            devices: {
 | 
			
		||||
                                [deviceIndex]: { encryption: { $set: value } },
 | 
			
		||||
                            },
 | 
			
		||||
                        }))}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                    {(formData.encryption === "WPA3" ||
 | 
			
		||||
                        formData.encryption === "WPA2/3") && (
 | 
			
		||||
                        <Switch
 | 
			
		||||
                            label={_("Disable Management Frame Protection")}
 | 
			
		||||
                            helpText={_(
 | 
			
		||||
                                "In case you have trouble connecting to WiFi Access Point, try disabling Management Frame Protection."
 | 
			
		||||
                            )}
 | 
			
		||||
                            checked={formData.ieee80211w_disabled}
 | 
			
		||||
                            onChange={setFormValue((value) => ({
 | 
			
		||||
                                devices: {
 | 
			
		||||
                                    [deviceIndex]: {
 | 
			
		||||
                                        ieee80211w_disabled: { $set: value },
 | 
			
		||||
                                    },
 | 
			
		||||
                                },
 | 
			
		||||
                            }))}
 | 
			
		||||
                            {...props}
 | 
			
		||||
                        />
 | 
			
		||||
                    )}
 | 
			
		||||
 | 
			
		||||
                    {hasGuestNetwork && (
 | 
			
		||||
                        <WifiGuestForm
 | 
			
		||||
                            formData={{
 | 
			
		||||
                                id: deviceIndex,
 | 
			
		||||
                                ...formData.guest_wifi,
 | 
			
		||||
                            }}
 | 
			
		||||
                            formErrors={formErrors.guest_wifi || {}}
 | 
			
		||||
                            setFormValue={setFormValue}
 | 
			
		||||
                            {...props}
 | 
			
		||||
                        />
 | 
			
		||||
                    )}
 | 
			
		||||
                </>
 | 
			
		||||
            )}
 | 
			
		||||
            {divider && <hr />}
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getChannelChoices(device) {
 | 
			
		||||
    const channelChoices = {
 | 
			
		||||
        0: _("auto"),
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    device.available_bands.forEach((availableBand) => {
 | 
			
		||||
        if (availableBand.band !== device.band) return;
 | 
			
		||||
 | 
			
		||||
        availableBand.available_channels.forEach((availableChannel) => {
 | 
			
		||||
            channelChoices[availableChannel.number.toString()] = `
 | 
			
		||||
                        ${availableChannel.number}
 | 
			
		||||
                        (${availableChannel.frequency} MHz ${
 | 
			
		||||
                            availableChannel.radar ? " ,DFS" : ""
 | 
			
		||||
                        })
 | 
			
		||||
                    `;
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    return channelChoices;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getHtmodeChoices(device) {
 | 
			
		||||
    const htmodeChoices = {};
 | 
			
		||||
 | 
			
		||||
    device.available_bands.forEach((availableBand) => {
 | 
			
		||||
        if (availableBand.band !== device.band) return;
 | 
			
		||||
 | 
			
		||||
        availableBand.available_htmodes.forEach((availableHtmod) => {
 | 
			
		||||
            htmodeChoices[availableHtmod] = HTMODES[availableHtmod];
 | 
			
		||||
        });
 | 
			
		||||
    });
 | 
			
		||||
    return htmodeChoices;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getBandChoices(device) {
 | 
			
		||||
    return device.available_bands.map((availableBand) => ({
 | 
			
		||||
        label: `${BANDS[availableBand.band]} GHz`,
 | 
			
		||||
        value: availableBand.band,
 | 
			
		||||
    }));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getEncryptionChoices(device) {
 | 
			
		||||
    if (device.encryption === "custom") {
 | 
			
		||||
        ENCRYPTIONMODES.custom = _("Custom");
 | 
			
		||||
    }
 | 
			
		||||
    return ENCRYPTIONMODES;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										112
									
								
								src/common/WiFiSettings/WiFiGuestForm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/common/WiFiSettings/WiFiGuestForm.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,112 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright (C) 2019-2024 CZ.NIC z.s.p.o. (https://www.nic.cz/)
 | 
			
		||||
 *
 | 
			
		||||
 * This is free software, licensed under the GNU General Public License v3.
 | 
			
		||||
 * See /LICENSE for more information.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
 | 
			
		||||
import { HELP_TEXTS, ENCRYPTIONMODES } from "./constants";
 | 
			
		||||
import WiFiQRCode from "./WiFiQRCode";
 | 
			
		||||
import PasswordInput from "../../bootstrap/PasswordInput";
 | 
			
		||||
import Select from "../../bootstrap/Select";
 | 
			
		||||
import Switch from "../../bootstrap/Switch";
 | 
			
		||||
import TextInput from "../../bootstrap/TextInput";
 | 
			
		||||
 | 
			
		||||
WifiGuestForm.propTypes = {
 | 
			
		||||
    formData: PropTypes.shape({
 | 
			
		||||
        id: PropTypes.number.isRequired,
 | 
			
		||||
        SSID: PropTypes.string.isRequired,
 | 
			
		||||
        password: PropTypes.string.isRequired,
 | 
			
		||||
        enabled: PropTypes.bool.isRequired,
 | 
			
		||||
        encryption: PropTypes.string.isRequired,
 | 
			
		||||
    }),
 | 
			
		||||
    formErrors: PropTypes.shape({
 | 
			
		||||
        SSID: PropTypes.string,
 | 
			
		||||
        password: PropTypes.string,
 | 
			
		||||
    }),
 | 
			
		||||
    setFormValue: PropTypes.func.isRequired,
 | 
			
		||||
    deviceIndex: PropTypes.string,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default function WifiGuestForm({
 | 
			
		||||
    formData,
 | 
			
		||||
    formErrors,
 | 
			
		||||
    setFormValue,
 | 
			
		||||
    deviceIndex,
 | 
			
		||||
    ...props
 | 
			
		||||
}) {
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <Switch
 | 
			
		||||
                label={_("Enable Guest Wi-Fi")}
 | 
			
		||||
                checked={formData.enabled}
 | 
			
		||||
                helpText={HELP_TEXTS.guest_wifi_enabled}
 | 
			
		||||
                onChange={setFormValue((value) => ({
 | 
			
		||||
                    devices: {
 | 
			
		||||
                        [formData.id]: {
 | 
			
		||||
                            guest_wifi: { enabled: { $set: value } },
 | 
			
		||||
                        },
 | 
			
		||||
                    },
 | 
			
		||||
                }))}
 | 
			
		||||
                {...props}
 | 
			
		||||
            />
 | 
			
		||||
            {formData.enabled ? (
 | 
			
		||||
                <>
 | 
			
		||||
                    <TextInput
 | 
			
		||||
                        label="SSID"
 | 
			
		||||
                        value={formData.SSID}
 | 
			
		||||
                        error={formErrors.SSID}
 | 
			
		||||
                        helpText={HELP_TEXTS.ssid}
 | 
			
		||||
                        onChange={setFormValue((value) => ({
 | 
			
		||||
                            devices: {
 | 
			
		||||
                                [formData.id]: {
 | 
			
		||||
                                    guest_wifi: { SSID: { $set: value } },
 | 
			
		||||
                                },
 | 
			
		||||
                            },
 | 
			
		||||
                        }))}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    >
 | 
			
		||||
                        <WiFiQRCode
 | 
			
		||||
                            SSID={formData.SSID}
 | 
			
		||||
                            password={formData.password}
 | 
			
		||||
                        />
 | 
			
		||||
                    </TextInput>
 | 
			
		||||
                    <PasswordInput
 | 
			
		||||
                        withEye
 | 
			
		||||
                        label={_("Password")}
 | 
			
		||||
                        value={formData.password}
 | 
			
		||||
                        helpText={HELP_TEXTS.password}
 | 
			
		||||
                        error={formErrors.password}
 | 
			
		||||
                        required
 | 
			
		||||
                        onChange={setFormValue((value) => ({
 | 
			
		||||
                            devices: {
 | 
			
		||||
                                [formData.id]: {
 | 
			
		||||
                                    guest_wifi: { password: { $set: value } },
 | 
			
		||||
                                },
 | 
			
		||||
                            },
 | 
			
		||||
                        }))}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    />
 | 
			
		||||
                    <Select
 | 
			
		||||
                        label={_("Encryption")}
 | 
			
		||||
                        choices={ENCRYPTIONMODES}
 | 
			
		||||
                        helpText={HELP_TEXTS.wpa3}
 | 
			
		||||
                        value={formData.encryption}
 | 
			
		||||
                        onChange={setFormValue((value) => ({
 | 
			
		||||
                            devices: {
 | 
			
		||||
                                [formData.id]: {
 | 
			
		||||
                                    guest_wifi: { encryption: { $set: value } },
 | 
			
		||||
                                },
 | 
			
		||||
                            },
 | 
			
		||||
                        }))}
 | 
			
		||||
                        {...props}
 | 
			
		||||
                    />
 | 
			
		||||
                </>
 | 
			
		||||
            ) : null}
 | 
			
		||||
        </>
 | 
			
		||||
    );
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user