mirror of
				https://gitlab.nic.cz/turris/reforis/foris-js.git
				synced 2025-11-03 23:00:31 +01:00 
			
		
		
		
	Compare commits
	
		
			649 Commits
		
	
	
		
			v1.0.0
			...
			5a53eca138
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					5a53eca138 | ||
| 
						 | 
					8d2a4dc108 | ||
| 
						 | 
					2481a0c025 | ||
| 
						 | 
					71697424c5 | ||
| 
						 | 
					07f8e3b9de | ||
| 
						 | 
					c9f2b24095 | ||
| 
						 | 
					087ecfa670 | ||
| 
						 | 
					e6365ecac4 | ||
| 
						 | 
					e57722caa0 | ||
| 
						 | 
					babdf92ddd | ||
| 
						 | 
					42294316d9 | ||
| 
						 | 
					b65e034b04 | ||
| 
						 | 
					85b207b1dd | ||
| 
						 | 
					79e61d9507 | ||
| 
						 | 
					6795c3941b | ||
| 
						 | 
					969e8e6411 | ||
| 
						 | 
					0099759279 | ||
| 
						 | 
					87c81a2a2d | ||
| 
						 | 
					81b71f8153 | ||
| 
						 | 
					1ec0a26199 | ||
| 
						 | 
					76e37b738a | ||
| 
						 | 
					8a69d14429 | ||
| 
						 | 
					4d5395c826 | ||
| 
						 | 
					1fb83e08ea | ||
| 
						 | 
					e6cfc6dbb0 | ||
| 
						 | 
					a93a64bf96 | ||
| 
						 | 
					1ab77decfd | ||
| 
						 | 
					b6e1e0adae | ||
| 
						 | 
					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 | ||
| 
						 | 
					8f88b09e66 | ||
| 
						 | 
					f2aa28f172 | ||
| 
						 | 
					a88fbf63e9 | ||
| 
						 | 
					ff9e8fdeb1 | ||
| 
						 | 
					8cd4ac8b44 | ||
| 
						 | 
					760e6d9243 | ||
| 
						 | 
					2429f4662c | ||
| 
						 | 
					b320e6a860 | ||
| 
						 | 
					1e3e9433ec | ||
| 
						 | 
					e3a795e40d | 
							
								
								
									
										65
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								.eslintrc.js
									
									
									
									
									
								
							@@ -1,66 +1,3 @@
 | 
				
			|||||||
const path = require("path");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    "env": {
 | 
					    extends: "eslint-config-reforis",
 | 
				
			||||||
        "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"]
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -4,6 +4,9 @@
 | 
				
			|||||||
logs
 | 
					logs
 | 
				
			||||||
*.log
 | 
					*.log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Python
 | 
				
			||||||
 | 
					venv/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# NodeJS
 | 
					# NodeJS
 | 
				
			||||||
## Logs
 | 
					## Logs
 | 
				
			||||||
npm-debug.log*
 | 
					npm-debug.log*
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,43 +1,44 @@
 | 
				
			|||||||
image: node:8-alpine
 | 
					image: registry.nic.cz/turris/reforis/reforis/reforis-image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
stages:
 | 
					stages:
 | 
				
			||||||
  - test
 | 
					    - test
 | 
				
			||||||
  - build
 | 
					    - build
 | 
				
			||||||
  - publish
 | 
					    - publish
 | 
				
			||||||
 | 
					
 | 
				
			||||||
before_script:
 | 
					before_script:
 | 
				
			||||||
  - npm install
 | 
					    - apt-get update && apt-get install -y make
 | 
				
			||||||
 | 
					    - npm install
 | 
				
			||||||
 | 
					
 | 
				
			||||||
test:
 | 
					test:
 | 
				
			||||||
  stage: test
 | 
					    stage: test
 | 
				
			||||||
  script:
 | 
					    script:
 | 
				
			||||||
    - npm test
 | 
					        - make test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lint:
 | 
					lint:
 | 
				
			||||||
  stage: test
 | 
					    stage: test
 | 
				
			||||||
  script:
 | 
					    script:
 | 
				
			||||||
    - npm run lint
 | 
					        - make lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
build:
 | 
					build:
 | 
				
			||||||
  stage: build
 | 
					    stage: build
 | 
				
			||||||
  script:
 | 
					    script:
 | 
				
			||||||
    - npm pack
 | 
					        - make pack
 | 
				
			||||||
  artifacts:
 | 
					    artifacts:
 | 
				
			||||||
    paths:
 | 
					        paths:
 | 
				
			||||||
      - foris-*.tgz
 | 
					            - dist/foris-*.tgz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
publish_beta:
 | 
					publish_beta:
 | 
				
			||||||
  stage: publish
 | 
					    stage: publish
 | 
				
			||||||
  only:
 | 
					    only:
 | 
				
			||||||
    refs:
 | 
					        refs:
 | 
				
			||||||
      - dev
 | 
					            - dev
 | 
				
			||||||
  script:
 | 
					    script:
 | 
				
			||||||
    - sh scripts/publish.sh beta
 | 
					        - make publish-beta
 | 
				
			||||||
 | 
					
 | 
				
			||||||
publish_latest:
 | 
					publish_latest:
 | 
				
			||||||
  stage: publish
 | 
					    stage: publish
 | 
				
			||||||
  only:
 | 
					    only:
 | 
				
			||||||
    refs:
 | 
					        refs:
 | 
				
			||||||
      - master
 | 
					            - master
 | 
				
			||||||
  script:
 | 
					    script:
 | 
				
			||||||
    - sh scripts/publish.sh latest
 | 
					        - 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
 | 
				
			||||||
							
								
								
									
										493
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										493
									
								
								CHANGELOG.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,493 @@
 | 
				
			|||||||
 | 
					# 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.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.5.0...dev
 | 
				
			||||||
 | 
					[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:
 | 
					all:
 | 
				
			||||||
	@echo "make install-js"
 | 
						@echo "make install-js"
 | 
				
			||||||
	@echo "    Install dependencies"
 | 
						@echo "    Install npm dependencies."
 | 
				
			||||||
	@echo "make watch-js"
 | 
						@echo "make lint"
 | 
				
			||||||
	@echo "    Compile JS in watch mode."
 | 
						@echo "    Run linter on the project."
 | 
				
			||||||
	@echo "make build-js"
 | 
						@echo "make test"
 | 
				
			||||||
	@echo "    Compile JS."
 | 
						@echo "    Run tests on the project."
 | 
				
			||||||
	@echo "make lint-js"
 | 
						@echo "make test-js-watch"
 | 
				
			||||||
	@echo "    Run linter"
 | 
						@echo "    Run tests on the project in watch mode."
 | 
				
			||||||
	@echo "make test-js"
 | 
						@echo "make test-js-update-snapshots"
 | 
				
			||||||
	@echo "    Run tests"
 | 
						@echo "    Update snapshots."
 | 
				
			||||||
	@echo "make create-messages"
 | 
						@echo "make create-messages"
 | 
				
			||||||
	@echo "    Create locale messages (.pot)."
 | 
						@echo "    Create locale messages (.pot)."
 | 
				
			||||||
	@echo "make update-messages"
 | 
						@echo "make update-messages"
 | 
				
			||||||
@@ -22,29 +37,93 @@ all:
 | 
				
			|||||||
	@echo "make clean"
 | 
						@echo "make clean"
 | 
				
			||||||
	@echo "    Remove python artifacts and virtualenv."
 | 
						@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
 | 
					install-js: package.json
 | 
				
			||||||
	npm install --save-dev
 | 
						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:
 | 
					lint:
 | 
				
			||||||
	npm run lint
 | 
						npm run lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: lint-js-fix
 | 
				
			||||||
 | 
					lint-js-fix:
 | 
				
			||||||
 | 
						npm run lint:fix
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Testing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: test
 | 
				
			||||||
test:
 | 
					test:
 | 
				
			||||||
	npm test
 | 
						npm test
 | 
				
			||||||
 | 
					
 | 
				
			||||||
create-messages:
 | 
					.PHONY: test-js-watch
 | 
				
			||||||
	pybabel extract -F babel.cfg -o ./translations/forisjs.pot .
 | 
					test-js-watch:
 | 
				
			||||||
update-messages:
 | 
						cd $(JS_DIR); npm test -- --watch
 | 
				
			||||||
	pybabel update -i translations/forisjs.pot -d translations
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.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:
 | 
					docs:
 | 
				
			||||||
	npm run-script docs
 | 
						npm run-script docs
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: docs-watch
 | 
				
			||||||
docs-watch:
 | 
					docs-watch:
 | 
				
			||||||
	npm run-script docs:watch
 | 
						npm run-script docs:watch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Other
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: clean
 | 
				
			||||||
clean:
 | 
					clean:
 | 
				
			||||||
	rm -rf node_modules dist
 | 
						rm -rf node_modules dist
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										41
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								README.md
									
									
									
									
									
								
							@@ -1,17 +1,52 @@
 | 
				
			|||||||
# foris-js
 | 
					# foris-js
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Set of utils and common React elements for reForis.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Publishing package
 | 
					## Publishing package
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Beta versions
 | 
					### Beta versions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Each commit to `dev` branch will result in publishing a new version of library
 | 
					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-d9073aa4.0`.
 | 
					`foris@0.1.0-beta.d9073aa4`.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Preparing a release
 | 
					### Preparing a release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
1. Crete a merge request to `dev` branch with version bumped
 | 
					1. Crete a merge request to `dev` branch with version bumped
 | 
				
			||||||
2. When merging add `[skip ci]` to commit message to prevent publishing
 | 
					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
 | 
					3. Create a merge request from `dev` to `master` branch
 | 
				
			||||||
4. New version should be published automatically
 | 
					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 = {
 | 
					module.exports = {
 | 
				
			||||||
    presets: [
 | 
					    presets: ["@babel/preset-env", "@babel/preset-react"],
 | 
				
			||||||
        "@babel/preset-env",
 | 
					    plugins: ["@babel/plugin-transform-runtime"],
 | 
				
			||||||
        "@babel/preset-react",
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
    plugins: [
 | 
					 | 
				
			||||||
        "@babel/plugin-transform-runtime",
 | 
					 | 
				
			||||||
        "@babel/plugin-syntax-export-default-from",
 | 
					 | 
				
			||||||
        ["module-resolver", {
 | 
					 | 
				
			||||||
            root: ["./src"],
 | 
					 | 
				
			||||||
            alias: {
 | 
					 | 
				
			||||||
                test: "./test",
 | 
					 | 
				
			||||||
                underscore: "lodash",
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        }],
 | 
					 | 
				
			||||||
    ],
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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  | 
							
								
								
									
										25
									
								
								docs/development.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								docs/development.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					Sooner or later, you will face with situation when you want/need to make some
 | 
				
			||||||
 | 
					changes in the library. Then the most important tool for you it's the
 | 
				
			||||||
 | 
					[`npm link`](https://docs.npmjs.com/cli/link).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please, notice that it will not work if you link the library just from the root
 | 
				
			||||||
 | 
					of the repo. It happens due to the location of sources `./src`. You need to pack
 | 
				
			||||||
 | 
					the library first, `make pack` and then link it from the `./dist` directory.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Yeah, it's not such a comfortable solution for development. But it can be fixed
 | 
				
			||||||
 | 
					by writing a small script similar to making a pack but by linking every file and
 | 
				
			||||||
 | 
					directory from `./src` to the same directory and linking then from it. Notice
 | 
				
			||||||
 | 
					that you need to link a `package.json` and a `package-lock.json` as well.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					So step by step:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					make pack;
 | 
				
			||||||
 | 
					cd dist;
 | 
				
			||||||
 | 
					npm link;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cd $project_dir/js # Navigate to JS directory of the project where you want to link the library
 | 
				
			||||||
 | 
					npm link foris
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					And that's it ;)
 | 
				
			||||||
							
								
								
									
										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.
 | 
					 | 
				
			||||||
							
								
								
									
										36
									
								
								docs/introduction.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								docs/introduction.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					Welcome! This is the official documentation for Foris JS.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## What Foris JS is
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Foris JS library is a set of components and utils for reForis application and
 | 
				
			||||||
 | 
					plugins.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please notice that all of these components or utils are used in reForis and
 | 
				
			||||||
 | 
					plugins. If you want to study them by example, I recommend you to full-text
 | 
				
			||||||
 | 
					search those repositories.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Prerequisites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Please make sure that [Node.js](https://nodejs.org/en/) is installed on your
 | 
				
			||||||
 | 
					system.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					The current Long Term Support (LTS) release is an ideal starting point, see
 | 
				
			||||||
 | 
					[here](https://github.com/nodejs/Release#release-schedule).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Installation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To install the latest release:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```plain
 | 
				
			||||||
 | 
					npm install foris
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					To install a specific version:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```plain
 | 
				
			||||||
 | 
					npm install foris@version
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://badge.fury.io/js/foris)
 | 
				
			||||||
@@ -12,10 +12,14 @@ module.exports = {
 | 
				
			|||||||
        "<rootDir>/src/testUtils",
 | 
					        "<rootDir>/src/testUtils",
 | 
				
			||||||
        "<rootDir>/src/",
 | 
					        "<rootDir>/src/",
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
 | 
					    moduleNameMapper: {
 | 
				
			||||||
 | 
					        "\\.(css|less)$": "<rootDir>/src/__mocks__/styleMock.js",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    clearMocks: true,
 | 
					    clearMocks: true,
 | 
				
			||||||
    collectCoverageFrom: ["src/**/*.{js,jsx}"],
 | 
					    collectCoverageFrom: ["src/**/*.{js,jsx}"],
 | 
				
			||||||
    coverageDirectory: "coverage",
 | 
					    coverageDirectory: "coverage",
 | 
				
			||||||
    testPathIgnorePatterns: ["/node_modules/", "/__fixtures__/"],
 | 
					    testPathIgnorePatterns: ["/node_modules/", "/__fixtures__/", "/dist/"],
 | 
				
			||||||
 | 
					    testEnvironment: "jsdom",
 | 
				
			||||||
    verbose: false,
 | 
					    verbose: false,
 | 
				
			||||||
    setupFilesAfterEnv: [
 | 
					    setupFilesAfterEnv: [
 | 
				
			||||||
        "@testing-library/react/cleanup-after-each",
 | 
					        "@testing-library/react/cleanup-after-each",
 | 
				
			||||||
@@ -24,8 +28,5 @@ module.exports = {
 | 
				
			|||||||
    globals: {
 | 
					    globals: {
 | 
				
			||||||
        TZ: "utc",
 | 
					        TZ: "utc",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    transform: {
 | 
					    transformIgnorePatterns: ["node_modules/(?!(react-datetime)/)"],
 | 
				
			||||||
        "^.+\\.js$": "babel-jest",
 | 
					 | 
				
			||||||
        "^.+\\.css$": "jest-transform-css",
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										44891
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										44891
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										153
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										153
									
								
								package.json
									
									
									
									
									
								
							@@ -1,84 +1,73 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "foris",
 | 
					    "name": "foris",
 | 
				
			||||||
  "version": "1.0.0",
 | 
					    "version": "6.5.0",
 | 
				
			||||||
  "description": "Set of components and utils for Foris and its plugins.",
 | 
					    "description": "Foris JS library is a set of components and utils for reForis application and plugins.",
 | 
				
			||||||
  "author": "CZ.NIC, z.s.p.o.",
 | 
					    "author": "CZ.NIC, z.s.p.o.",
 | 
				
			||||||
  "repository": {
 | 
					    "repository": {
 | 
				
			||||||
    "type": "git",
 | 
					        "type": "git",
 | 
				
			||||||
    "url": "https://gitlab.labs.nic.cz/turris/reforis/forisjs.git"
 | 
					        "url": "https://gitlab.nic.cz/turris/reforis/foris-js.git"
 | 
				
			||||||
  },
 | 
					    },
 | 
				
			||||||
  "keywords": [
 | 
					    "keywords": [
 | 
				
			||||||
    "foris",
 | 
					        "foris",
 | 
				
			||||||
    "reforis"
 | 
					        "reforis"
 | 
				
			||||||
  ],
 | 
					    ],
 | 
				
			||||||
  "license": "GPL-3.0",
 | 
					    "license": "GPL-3.0",
 | 
				
			||||||
  "main": "./dist/index.js",
 | 
					    "main": "./src/index.js",
 | 
				
			||||||
  "dependencies": {
 | 
					    "dependencies": {
 | 
				
			||||||
    "axios": "^0.19.0",
 | 
					        "@fortawesome/fontawesome-svg-core": "^6.6.0",
 | 
				
			||||||
    "immutability-helper": "^3.0.0",
 | 
					        "@fortawesome/free-regular-svg-icons": "^6.6.0",
 | 
				
			||||||
    "jest-transform-css": "^2.0.0",
 | 
					        "@fortawesome/free-solid-svg-icons": "^6.6.0",
 | 
				
			||||||
    "moment": "^2.24.0",
 | 
					        "@fortawesome/react-fontawesome": "^0.2.2",
 | 
				
			||||||
    "moment-timezone": "^0.5.25",
 | 
					        "@tanstack/react-table": "^8.20.5",
 | 
				
			||||||
    "prop-types": "^15.7.2",
 | 
					        "axios": "^1.7.2",
 | 
				
			||||||
    "react-datetime": "^2.16.3",
 | 
					        "immutability-helper": "^3.1.1",
 | 
				
			||||||
    "react-router": "^5.0.1",
 | 
					        "moment": "^2.30.1",
 | 
				
			||||||
    "react-uid": "^2.2.0"
 | 
					        "qrcode.react": "^3.1.0",
 | 
				
			||||||
  },
 | 
					        "react-datetime": "^3.2.0",
 | 
				
			||||||
  "peerDependencies": {
 | 
					        "react-uid": "^2.3.3"
 | 
				
			||||||
    "react": "^16.9.0",
 | 
					    },
 | 
				
			||||||
    "react-dom": "^16.9.0"
 | 
					    "peerDependencies": {
 | 
				
			||||||
  },
 | 
					        "bootstrap": "^5.3.3",
 | 
				
			||||||
  "devDependencies": {
 | 
					        "prop-types": "15.8.1",
 | 
				
			||||||
    "@babel/cli": "^7.4.4",
 | 
					        "react": "16.9.0",
 | 
				
			||||||
    "@babel/core": "^7.4.5",
 | 
					        "react-dom": "16.9.0",
 | 
				
			||||||
    "@babel/plugin-proposal-class-properties": "^7.4.4",
 | 
					        "react-router-dom": "^5.1.2"
 | 
				
			||||||
    "@babel/plugin-syntax-export-default-from": "^7.2.0",
 | 
					    },
 | 
				
			||||||
    "@babel/plugin-transform-runtime": "^7.4.4",
 | 
					    "devDependencies": {
 | 
				
			||||||
    "@babel/preset-env": "^7.4.5",
 | 
					        "@babel/cli": "^7.24.7",
 | 
				
			||||||
    "@babel/preset-react": "^7.0.0",
 | 
					        "@babel/core": "^7.24.7",
 | 
				
			||||||
    "@fortawesome/fontawesome-free": "^5.11.2",
 | 
					        "@babel/plugin-transform-runtime": "^7.24.7",
 | 
				
			||||||
    "@testing-library/react": "^8.0.9",
 | 
					        "@babel/preset-env": "^7.24.7",
 | 
				
			||||||
    "babel-eslint": "^9.0.0",
 | 
					        "@babel/preset-react": "^7.24.7",
 | 
				
			||||||
    "babel-jest": "^24.8.0",
 | 
					        "@testing-library/react": "^8.0.9",
 | 
				
			||||||
    "babel-loader": "^8.0.6",
 | 
					        "babel-loader": "^8.1.0",
 | 
				
			||||||
    "babel-plugin-module-resolver": "^3.2.0",
 | 
					        "babel-polyfill": "^6.26.0",
 | 
				
			||||||
    "babel-plugin-react-transform": "^3.0.0",
 | 
					        "bootstrap": "^5.3.3",
 | 
				
			||||||
    "babel-polyfill": "^6.26.0",
 | 
					        "css-loader": "^5.2.4",
 | 
				
			||||||
    "bootstrap": "^4.3.1",
 | 
					        "eslint": "^8.57.0",
 | 
				
			||||||
    "copy-webpack-plugin": "^5.0.4",
 | 
					        "eslint-config-reforis": "^2.1.1",
 | 
				
			||||||
    "css-loader": "^3.2.0",
 | 
					        "file-loader": "^6.0.0",
 | 
				
			||||||
    "eslint": "^6.1.0",
 | 
					        "jest": "^29.7.0",
 | 
				
			||||||
    "eslint-config-airbnb": "^18.0.1",
 | 
					        "jest-environment-jsdom": "^29.7.0",
 | 
				
			||||||
    "eslint-plugin-import": "^2.18.2",
 | 
					        "jest-mock-axios": "^4.7.3",
 | 
				
			||||||
    "eslint-plugin-jsx-a11y": "^6.2.3",
 | 
					        "moment-timezone": "^0.5.45",
 | 
				
			||||||
    "eslint-plugin-react": "^7.14.3",
 | 
					        "prettier": "^3.3.2",
 | 
				
			||||||
    "eslint-plugin-react-hooks": "^1.7.0",
 | 
					        "prop-types": "15.8.1",
 | 
				
			||||||
    "file-loader": "^4.2.0",
 | 
					        "react": "16.9.0",
 | 
				
			||||||
    "jest": "^24.8.0",
 | 
					        "react-dom": "16.9.0",
 | 
				
			||||||
    "jest-mock-axios": "^3.0.0",
 | 
					        "react-router-dom": "^5.1.2",
 | 
				
			||||||
    "moment": "^2.24.0",
 | 
					        "react-styleguidist": "^12.0.1",
 | 
				
			||||||
    "moment-timezone": "^0.5.25",
 | 
					        "snapshot-diff": "^0.10.0",
 | 
				
			||||||
    "react": "^16.9.0",
 | 
					        "style-loader": "^1.2.1",
 | 
				
			||||||
    "react-dom": "^16.9.0",
 | 
					        "webpack": "^5.92.1"
 | 
				
			||||||
    "react-styleguidist": "^9.1.16",
 | 
					    },
 | 
				
			||||||
    "snapshot-diff": "^0.5.1",
 | 
					    "scripts": {
 | 
				
			||||||
    "style-loader": "^1.0.0",
 | 
					        "lint": "eslint src",
 | 
				
			||||||
    "webpack": "^4.41.0"
 | 
					        "lint:fix": "eslint --fix src",
 | 
				
			||||||
  },
 | 
					        "test": "jest",
 | 
				
			||||||
  "scripts": {
 | 
					        "test:watch": "jest --watch",
 | 
				
			||||||
    "build": "rm -rf dist; babel src --out-dir dist --ignore '**/__tests__' --source-maps inline --copy-files",
 | 
					        "test:coverage": "jest --coverage --colors",
 | 
				
			||||||
    "build:watch": "babel src --verbose --watch --out-dir dist --ignore '**/__tests__' --source-maps inline --copy-files",
 | 
					        "docs": "npx styleguidist build ",
 | 
				
			||||||
    "prepare": "rm -rf ./dist && npm run build",
 | 
					        "docs:watch": "styleguidist server"
 | 
				
			||||||
    "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"
 | 
					 | 
				
			||||||
  ]
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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,12 +5,13 @@ then
 | 
				
			|||||||
    echo "\$NPM_TOKEN is not set"
 | 
					    echo "\$NPM_TOKEN is not set"
 | 
				
			||||||
    exit 1
 | 
					    exit 1
 | 
				
			||||||
else
 | 
					else
 | 
				
			||||||
    # Need to replace "_" with "_" as GitLab CI won't accept secret vars with "-"
 | 
					    cd dist
 | 
				
			||||||
    echo "//registry.npmjs.org/:_authToken=$(echo $NPM_TOKEN | tr _ -)" > .npmrc
 | 
					    echo "//registry.npmjs.org/:_authToken=$(echo "$NPM_TOKEN")" > .npmrc
 | 
				
			||||||
    echo "unsafe-perm = true" >> ~/.npmrc
 | 
					    echo "unsafe-perm = true" >> ~/.npmrc
 | 
				
			||||||
    if test "$1" = "beta"
 | 
					    if test "$1" = "beta"
 | 
				
			||||||
    then
 | 
					    then
 | 
				
			||||||
        npm version prerelease --preid=$CI_COMMIT_SHORT_SHA --git-tag-version false
 | 
					        BETA_VERSION=$(npx -c 'echo "$npm_package_version"')-beta.$CI_COMMIT_SHORT_SHA
 | 
				
			||||||
 | 
					        npm version "$BETA_VERSION" --git-tag-version false
 | 
				
			||||||
        npm publish --tag beta
 | 
					        npm publish --tag beta
 | 
				
			||||||
    elif test "$1" = "latest"
 | 
					    elif test "$1" = "latest"
 | 
				
			||||||
    then
 | 
					    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,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 useAPIPost(url) {
 | 
					 | 
				
			||||||
    const [state, dispatch] = useReducer(APIReducer, {
 | 
					 | 
				
			||||||
        isSending: false,
 | 
					 | 
				
			||||||
        isError: false,
 | 
					 | 
				
			||||||
        isSuccess: false,
 | 
					 | 
				
			||||||
        data: null,
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const post = async (data) => {
 | 
					 | 
				
			||||||
        dispatch({ type: API_ACTIONS.INIT });
 | 
					 | 
				
			||||||
        try {
 | 
					 | 
				
			||||||
            const result = await axios.post(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, 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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * 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) {
 | 
					function getCookie(name) {
 | 
				
			||||||
    let cookieValue = null;
 | 
					    let cookieValue = null;
 | 
				
			||||||
@@ -14,8 +44,10 @@ function getCookie(name) {
 | 
				
			|||||||
        for (let i = 0; i < cookies.length; i++) {
 | 
					        for (let i = 0; i < cookies.length; i++) {
 | 
				
			||||||
            const cookie = cookies[i].trim();
 | 
					            const cookie = cookies[i].trim();
 | 
				
			||||||
            // Does this cookie string begin with the name we want?
 | 
					            // Does this cookie string begin with the name we want?
 | 
				
			||||||
            if (cookie.substring(0, name.length + 1) === (`${name}=`)) {
 | 
					            if (cookie.substring(0, name.length + 1) === `${name}=`) {
 | 
				
			||||||
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
 | 
					                cookieValue = decodeURIComponent(
 | 
				
			||||||
 | 
					                    cookie.substring(name.length + 1)
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -23,55 +55,26 @@ function getCookie(name) {
 | 
				
			|||||||
    return cookieValue;
 | 
					    return cookieValue;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const HEADERS = {
 | 
					export function getErrorPayload(error) {
 | 
				
			||||||
    Accept: "application/json",
 | 
					    if (error.response) {
 | 
				
			||||||
    "Content-Type": "application/json",
 | 
					        if (error.response.status === 401) {
 | 
				
			||||||
    "X-CSRFToken": getCookie("_csrf_token"),
 | 
					            return _("The session is expired. Please log in again.");
 | 
				
			||||||
};
 | 
					        }
 | 
				
			||||||
 | 
					        return getJSONErrorMessage(error);
 | 
				
			||||||
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();
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    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) {
 | 
					export function getJSONErrorMessage(error) {
 | 
				
			||||||
    let payload = "An unknown error occurred";
 | 
					 | 
				
			||||||
    if (error.response.headers["content-type"] === "application/json") {
 | 
					    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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React, { useRef, useEffect, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					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 = {
 | 
					Alert.propTypes = {
 | 
				
			||||||
    /** Type of the alert it adds as `alert-${type}` class. */
 | 
					    /** Type of the alert it adds as `alert-${type}` class. */
 | 
				
			||||||
    type: PropTypes.string.isRequired,
 | 
					    type: PropTypes.oneOf(Object.values(ALERT_TYPES)),
 | 
				
			||||||
    /** Alert message. */
 | 
					 | 
				
			||||||
    message: PropTypes.string,
 | 
					 | 
				
			||||||
    /** Alert content. */
 | 
					    /** Alert content. */
 | 
				
			||||||
    children: PropTypes.oneOfType([
 | 
					    children: PropTypes.oneOfType([
 | 
				
			||||||
        PropTypes.arrayOf(PropTypes.node),
 | 
					        PropTypes.arrayOf(PropTypes.node),
 | 
				
			||||||
@@ -22,14 +34,48 @@ Alert.propTypes = {
 | 
				
			|||||||
    onDismiss: PropTypes.func,
 | 
					    onDismiss: PropTypes.func,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Alert({
 | 
					Alert.defaultProps = {
 | 
				
			||||||
    type, message, onDismiss, children,
 | 
					    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 (
 | 
					    return (
 | 
				
			||||||
        <div className={`alert alert-dismissible alert-${type}`}>
 | 
					        <div
 | 
				
			||||||
            {onDismiss ? <button type="button" className="close" onClick={onDismiss}>×</button> : false}
 | 
					            ref={alertRef}
 | 
				
			||||||
            {message}
 | 
					            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}
 | 
					            {children}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Alert;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,21 @@
 | 
				
			|||||||
Bootstrap alert component.
 | 
					Bootstrap alert component.
 | 
				
			||||||
```jsx
 | 
					 | 
				
			||||||
import {useState} from 'react';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function AlertExample(){
 | 
					```jsx
 | 
				
			||||||
 | 
					import { useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function AlertExample() {
 | 
				
			||||||
    const [alert, setAlert] = useState(true);
 | 
					    const [alert, setAlert] = useState(true);
 | 
				
			||||||
    if (alert)
 | 
					    if (alert)
 | 
				
			||||||
        return <Alert 
 | 
					        return (
 | 
				
			||||||
            type='warning' 
 | 
					            <Alert type="warning" onDismiss={() => setAlert(false)}>
 | 
				
			||||||
            message='Some warning out there!' 
 | 
					                Some warning out there!
 | 
				
			||||||
            onDismiss={()=>setAlert(false)}
 | 
					            </Alert>
 | 
				
			||||||
        />;
 | 
					        );
 | 
				
			||||||
    return <button 
 | 
					    return (
 | 
				
			||||||
        className='btn btn-secondary' 
 | 
					        <button className="btn btn-secondary" onClick={() => setAlert(true)}>
 | 
				
			||||||
        onClick={()=>setAlert(true)}
 | 
					            Show alert again
 | 
				
			||||||
    >Show alert again</button>;
 | 
					        </button>
 | 
				
			||||||
};
 | 
					    );
 | 
				
			||||||
<AlertExample/>
 | 
					}
 | 
				
			||||||
 | 
					<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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const OFFSET = 8;
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
const SIZE = 3;
 | 
					 | 
				
			||||||
const SIZE_CLASS = ` offset-lg-${OFFSET} col-lg-${SIZE}`;
 | 
					 | 
				
			||||||
const SIZE_CLASS_SM = " col-sm-12";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
Button.propTypes = {
 | 
					Button.propTypes = {
 | 
				
			||||||
    /** Additional class name. */
 | 
					    /** Additional class name. */
 | 
				
			||||||
@@ -29,22 +25,28 @@ Button.propTypes = {
 | 
				
			|||||||
    ]).isRequired,
 | 
					    ]).isRequired,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Button({
 | 
					function Button({ className, loading, forisFormSize, children, ...props }) {
 | 
				
			||||||
    className, loading, forisFormSize, children, ...props
 | 
					    let buttonClass = className ? `btn ${className}` : "btn btn-primary";
 | 
				
			||||||
}) {
 | 
					    if (forisFormSize) {
 | 
				
			||||||
    className = className ? `btn ${className}` : "btn btn-primary ";
 | 
					        buttonClass = `${buttonClass} col-12 col-md-3 col-lg-2`;
 | 
				
			||||||
    if (forisFormSize) className += SIZE_CLASS + SIZE_CLASS_SM;
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const span = loading
 | 
					 | 
				
			||||||
        ? <span className="spinner-border spinner-border-sm" role="status" aria-hidden="true" /> : null;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <button type="button" className={className} {...props}>
 | 
					        <button
 | 
				
			||||||
            {span}
 | 
					            type="button"
 | 
				
			||||||
            {" "}
 | 
					            className={`${buttonClass} d-inline-flex justify-content-center align-items-center`}
 | 
				
			||||||
            {span ? " " : null}
 | 
					            {...props}
 | 
				
			||||||
            {" "}
 | 
					        >
 | 
				
			||||||
            {children}
 | 
					            {loading && (
 | 
				
			||||||
 | 
					                <span
 | 
				
			||||||
 | 
					                    className="spinner-border spinner-border-sm me-1"
 | 
				
			||||||
 | 
					                    role="status"
 | 
				
			||||||
 | 
					                    aria-hidden="true"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					            <span>{children}</span>
 | 
				
			||||||
        </button>
 | 
					        </button>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Button;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,5 +11,7 @@ Can be used without parameters:
 | 
				
			|||||||
Using loading spinner:
 | 
					Using loading spinner:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```jsx
 | 
					```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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					 | 
				
			||||||
import { useUID } from "react-uid/dist/es5/index";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { formFieldsSize } from "./constants";
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					import { useUID } from "react-uid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CheckBox.propTypes = {
 | 
					CheckBox.propTypes = {
 | 
				
			||||||
    /** Label message */
 | 
					    /** Label message */
 | 
				
			||||||
    label: PropTypes.string.isRequired,
 | 
					    label: PropTypes.string.isRequired,
 | 
				
			||||||
    /** Help text message */
 | 
					    /** Help text message */
 | 
				
			||||||
    helpText: PropTypes.string,
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
    /** Apply default size (full-width) */
 | 
					 | 
				
			||||||
    useDefaultSize: PropTypes.bool,
 | 
					 | 
				
			||||||
    /** Control if checkbox is clickable */
 | 
					    /** Control if checkbox is clickable */
 | 
				
			||||||
    disabled: PropTypes.bool,
 | 
					    disabled: PropTypes.bool,
 | 
				
			||||||
 | 
					    /** Additional class name */
 | 
				
			||||||
 | 
					    className: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CheckBox.defaultProps = {
 | 
					CheckBox.defaultProps = {
 | 
				
			||||||
    useDefaultSize: true,
 | 
					 | 
				
			||||||
    disabled: false,
 | 
					    disabled: false,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function CheckBox({
 | 
					function CheckBox({ label, helpText, disabled, className, ...props }) {
 | 
				
			||||||
    label, helpText, useDefaultSize, disabled, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    const uid = useUID();
 | 
					    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}
 | 
					    return (
 | 
				
			||||||
                />
 | 
					        <div className={`${className || "mb-3"} form-check`.trim()}>
 | 
				
			||||||
                <label className="custom-control-label" htmlFor={uid} style={helpText ? { marginBottom: "0" } : null}>{label}</label>
 | 
					            <input
 | 
				
			||||||
            </div>
 | 
					                className="form-check-input"
 | 
				
			||||||
            {helpText ? <small className="form-text text-muted">{helpText}</small> : null}
 | 
					                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>
 | 
					        </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
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [value, setValue] = useState(false);
 | 
					const [value, setValue] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<CheckBox
 | 
					<CheckBox
 | 
				
			||||||
    value={value}
 | 
					    value={value}
 | 
				
			||||||
    label="Some label" 
 | 
					    label="Some label"
 | 
				
			||||||
    helpText="Read the small text!"
 | 
					    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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					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 = {
 | 
					DataTimeInput.propTypes = {
 | 
				
			||||||
    /** Field label. */
 | 
					    /** Field label. */
 | 
				
			||||||
@@ -36,25 +38,32 @@ DataTimeInput.propTypes = {
 | 
				
			|||||||
const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
 | 
					const DEFAULT_DATE_FORMAT = "YYYY-MM-DD";
 | 
				
			||||||
const DEFAULT_TIME_FORMAT = "HH:mm:ss";
 | 
					const DEFAULT_TIME_FORMAT = "HH:mm:ss";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function DataTimeInput({
 | 
					function DataTimeInput({
 | 
				
			||||||
    value, onChange, isValidDate, dateFormat, timeFormat, children, ...props
 | 
					    value,
 | 
				
			||||||
 | 
					    onChange,
 | 
				
			||||||
 | 
					    isValidDate,
 | 
				
			||||||
 | 
					    dateFormat,
 | 
				
			||||||
 | 
					    timeFormat,
 | 
				
			||||||
 | 
					    children,
 | 
				
			||||||
 | 
					    ...props
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
    function renderInput(datetimeProps) {
 | 
					    const renderInput = (datetimeProps) => {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <Input
 | 
					            <Input {...props} {...datetimeProps}>
 | 
				
			||||||
                {...props}
 | 
					 | 
				
			||||||
                {...datetimeProps}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
                {children}
 | 
					                {children}
 | 
				
			||||||
            </Input>
 | 
					            </Input>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Datetime
 | 
					        <Datetime
 | 
				
			||||||
            locale={ForisTranslations.locale}
 | 
					            locale={ForisTranslations.locale}
 | 
				
			||||||
            dateFormat={dateFormat !== undefined ? dateFormat : DEFAULT_DATE_FORMAT}
 | 
					            dateFormat={
 | 
				
			||||||
            timeFormat={timeFormat !== undefined ? timeFormat : DEFAULT_TIME_FORMAT}
 | 
					                dateFormat !== undefined ? dateFormat : DEFAULT_DATE_FORMAT
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            timeFormat={
 | 
				
			||||||
 | 
					                timeFormat !== undefined ? timeFormat : DEFAULT_TIME_FORMAT
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            value={value}
 | 
					            value={value}
 | 
				
			||||||
            onChange={onChange}
 | 
					            onChange={onChange}
 | 
				
			||||||
            isValidDate={isValidDate}
 | 
					            isValidDate={isValidDate}
 | 
				
			||||||
@@ -62,3 +71,5 @@ export function DataTimeInput({
 | 
				
			|||||||
        />
 | 
					        />
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default DataTimeInput;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,26 @@
 | 
				
			|||||||
Adopted from `react-datetime/DateTime` datatime picker component.
 | 
					Adopted from `react-datetime/DateTime` datatime picker component. It uses
 | 
				
			||||||
It uses `momentjs` see example.
 | 
					`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
 | 
					```js
 | 
				
			||||||
ForisTranslations={locale:'en'};
 | 
					ForisTranslations = { locale: "en" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {useState, useEffect} from 'react';
 | 
					import { useState, useEffect } from "react";
 | 
				
			||||||
import moment from 'moment/moment';
 | 
					import moment from "moment/moment";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const [dataTime, setDataTime] = useState(moment());
 | 
					const [dataTime, setDataTime] = useState(moment());
 | 
				
			||||||
const [error, setError] = useState();
 | 
					const [error, setError] = useState();
 | 
				
			||||||
useEffect(()=>{
 | 
					useEffect(() => {
 | 
				
			||||||
   dataTime.isValid() ? setError(null) : setError('Invalid value!');
 | 
					    dataTime.isValid() ? setError(null) : setError("Invalid value!");
 | 
				
			||||||
},[dataTime]);
 | 
					}, [dataTime]);
 | 
				
			||||||
 
 | 
					
 | 
				
			||||||
<DataTimeInput
 | 
					<DataTimeInput
 | 
				
			||||||
    label='Time to sleep'
 | 
					    label="Time to sleep"
 | 
				
			||||||
    value={dataTime}
 | 
					    value={dataTime}
 | 
				
			||||||
    error={error}
 | 
					    error={error}
 | 
				
			||||||
    helpText='Example helptext...'
 | 
					    helpText="Example helptext..."
 | 
				
			||||||
    onChange={value => setDataTime(value)}
 | 
					    onChange={(value) => setDataTime(value)}
 | 
				
			||||||
/>
 | 
					/>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										38
									
								
								src/bootstrap/DownloadButton.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/bootstrap/DownloadButton.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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,
 | 
				
			||||||
 | 
					    ]),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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;
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/bootstrap/DownloadButton.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/bootstrap/DownloadButton.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					<DownloadButton href="example.zip">Download</DownloadButton>
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
@@ -8,10 +8,12 @@
 | 
				
			|||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					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 = {
 | 
					EmailInput.propTypes = {
 | 
				
			||||||
    /** Field label. */
 | 
					    /** Field label. */
 | 
				
			||||||
@@ -23,3 +25,5 @@ EmailInput.propTypes = {
 | 
				
			|||||||
    /** Email value. */
 | 
					    /** Email value. */
 | 
				
			||||||
    value: PropTypes.string,
 | 
					    value: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default EmailInput;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,18 +1,20 @@
 | 
				
			|||||||
Bootstrap component of email input with label with predefined sizes and structure for using in foris forms.
 | 
					Bootstrap component of email input with label with predefined sizes and
 | 
				
			||||||
It use built-in browser email address checking. It's only meaningful using inside `<form>`.
 | 
					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.
 | 
					All additional `props` are passed to the `<input type="email">` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [email, setEmail] = useState('Wrong email');
 | 
					import Button from "./Button";
 | 
				
			||||||
<form onSubmit={e=>e.preventDefault()}>
 | 
					const [email, setEmail] = useState("Wrong email");
 | 
				
			||||||
 | 
					<form onSubmit={(e) => e.preventDefault()}>
 | 
				
			||||||
    <EmailInput
 | 
					    <EmailInput
 | 
				
			||||||
        value={email}
 | 
					        value={email}
 | 
				
			||||||
        label="Some label" 
 | 
					        label="Some label"
 | 
				
			||||||
        helpText="Read the small text!"
 | 
					        helpText="Read the small text!"
 | 
				
			||||||
        onChange={event =>setEmail(event.target.value)}
 | 
					        onChange={(event) => setEmail(event.target.value)}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <button type="submit">Try to submit</button>
 | 
					    <Button type="submit">Try to submit</Button>
 | 
				
			||||||
</form>
 | 
					</form>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										39
									
								
								src/bootstrap/FileInput.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/bootstrap/FileInput.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FileInput.propTypes = {
 | 
				
			||||||
 | 
					    /** Field label. */
 | 
				
			||||||
 | 
					    label: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    /** Error message. */
 | 
				
			||||||
 | 
					    error: PropTypes.string,
 | 
				
			||||||
 | 
					    /** Help text message. */
 | 
				
			||||||
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
 | 
					    /** Email value. */
 | 
				
			||||||
 | 
					    value: PropTypes.string,
 | 
				
			||||||
 | 
					    /** Allow selecting multiple files. */
 | 
				
			||||||
 | 
					    multiple: PropTypes.bool,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function FileInput({ ...props }) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Input
 | 
				
			||||||
 | 
					            type="file"
 | 
				
			||||||
 | 
					            className="custom-file-input"
 | 
				
			||||||
 | 
					            labelClassName="custom-file-label"
 | 
				
			||||||
 | 
					            groupClassName="custom-file"
 | 
				
			||||||
 | 
					            {...props}
 | 
				
			||||||
 | 
					        />
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default FileInput;
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/bootstrap/FileInput.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/bootstrap/FileInput.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					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";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const [files, setFiles] = useState([]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					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,15 +1,67 @@
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
 * 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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React, { forwardRef } from "react";
 | 
				
			||||||
import { useUID } from "react-uid/dist/es5/index";
 | 
					 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
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
 | 
				
			||||||
 | 
					                    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 = {
 | 
					Input.propTypes = {
 | 
				
			||||||
    type: PropTypes.string.isRequired,
 | 
					    type: PropTypes.string.isRequired,
 | 
				
			||||||
@@ -21,31 +73,8 @@ Input.propTypes = {
 | 
				
			|||||||
        PropTypes.arrayOf(PropTypes.node),
 | 
					        PropTypes.arrayOf(PropTypes.node),
 | 
				
			||||||
        PropTypes.node,
 | 
					        PropTypes.node,
 | 
				
			||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
 | 
					    labelClassName: PropTypes.string,
 | 
				
			||||||
 | 
					    groupClassName: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Base bootstrap input component. */
 | 
					export default Input;
 | 
				
			||||||
export function Input({
 | 
					 | 
				
			||||||
    type, label, helpText, error, className, children, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    const uid = useUID();
 | 
					 | 
				
			||||||
    const inputClassName = `form-control ${className || ""} ${(error ? "is-invalid" : "")}`.trim();
 | 
					 | 
				
			||||||
    return (
 | 
					 | 
				
			||||||
        <div className={formFieldsSize}>
 | 
					 | 
				
			||||||
            <div className="form-group">
 | 
					 | 
				
			||||||
                <label htmlFor={uid}>{label}</label>
 | 
					 | 
				
			||||||
                <div className="input-group">
 | 
					 | 
				
			||||||
                    <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>
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React, { useEffect, useRef } from "react";
 | 
					import React, { useRef, useEffect } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					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 = {
 | 
					Modal.propTypes = {
 | 
				
			||||||
    /** Is modal shown value */
 | 
					    /** Is modal shown value */
 | 
				
			||||||
    shown: PropTypes.bool.isRequired,
 | 
					    shown: PropTypes.bool.isRequired,
 | 
				
			||||||
    /** Callback to manage modal visibility */
 | 
					    /** Callback to manage modal visibility */
 | 
				
			||||||
    setShown: PropTypes.func.isRequired,
 | 
					    setShown: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					    scrollable: PropTypes.bool,
 | 
				
			||||||
 | 
					    size: PropTypes.string,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** Modal content use following: `ModalHeader`, `ModalBody`, `ModalFooter` */
 | 
					    /** Modal content use following: `ModalHeader`, `ModalBody`, `ModalFooter` */
 | 
				
			||||||
    children: PropTypes.oneOfType([
 | 
					    children: PropTypes.oneOfType([
 | 
				
			||||||
@@ -23,28 +28,57 @@ Modal.propTypes = {
 | 
				
			|||||||
    ]).isRequired,
 | 
					    ]).isRequired,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Modal({ shown, setShown, children }) {
 | 
					export function Modal({ shown, setShown, scrollable, size, children }) {
 | 
				
			||||||
    const dialogRef = useRef();
 | 
					    const modalRef = useRef();
 | 
				
			||||||
 | 
					    let modalSize = "modal-";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    useClickOutside(modalRef, () => setShown(false));
 | 
				
			||||||
 | 
					    useFocusTrap(modalRef, shown);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    useEffect(() => {
 | 
					    useEffect(() => {
 | 
				
			||||||
        function handleClickOutsideDialog(e) {
 | 
					        const handleEsc = (event) => {
 | 
				
			||||||
            if (!dialogRef.current.contains(e.target)) setShown(false);
 | 
					            if (event.keyCode === 27) {
 | 
				
			||||||
        }
 | 
					                setShown(false);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        window.addEventListener("keydown", handleEsc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        document.addEventListener("mousedown", handleClickOutsideDialog);
 | 
					 | 
				
			||||||
        return () => {
 | 
					        return () => {
 | 
				
			||||||
            document.removeEventListener("mousedown", handleClickOutsideDialog);
 | 
					            window.removeEventListener("keydown", handleEsc);
 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
    }, [setShown]);
 | 
					    }, [setShown]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch (size) {
 | 
				
			||||||
 | 
					        case "sm":
 | 
				
			||||||
 | 
					            modalSize += "sm";
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case "lg":
 | 
				
			||||||
 | 
					            modalSize += "lg";
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        case "xl":
 | 
				
			||||||
 | 
					            modalSize += "xl";
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					            modalSize = "";
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Portal containerId="modal-container">
 | 
					        <Portal containerId="modal-container">
 | 
				
			||||||
            <div className={`modal fade ${shown ? "show" : ""}`} role="dialog">
 | 
					            <div
 | 
				
			||||||
                <div ref={dialogRef} className="modal-dialog" role="document">
 | 
					                ref={modalRef}
 | 
				
			||||||
                    <div className="modal-content">
 | 
					                className={`modal fade ${shown ? "show" : ""}`.trim()}
 | 
				
			||||||
                        {children}
 | 
					                role="dialog"
 | 
				
			||||||
                    </div>
 | 
					                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>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        </Portal>
 | 
					        </Portal>
 | 
				
			||||||
@@ -59,10 +93,13 @@ ModalHeader.propTypes = {
 | 
				
			|||||||
export function ModalHeader({ setShown, title }) {
 | 
					export function ModalHeader({ setShown, title }) {
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="modal-header">
 | 
					        <div className="modal-header">
 | 
				
			||||||
            <h5 className="modal-title">{title}</h5>
 | 
					            <h1 className="modal-title fs-5">{title}</h1>
 | 
				
			||||||
            <button type="button" className="close" onClick={() => setShown(false)}>
 | 
					            <button
 | 
				
			||||||
                <span aria-hidden="true">×</span>
 | 
					                type="button"
 | 
				
			||||||
            </button>
 | 
					                className="btn-close"
 | 
				
			||||||
 | 
					                onClick={() => setShown(false)}
 | 
				
			||||||
 | 
					                aria-label={_("Close")}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -86,9 +123,5 @@ ModalFooter.propTypes = {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function ModalFooter({ children }) {
 | 
					export function ModalFooter({ children }) {
 | 
				
			||||||
    return (
 | 
					    return <div className="modal-footer">{children}</div>;
 | 
				
			||||||
        <div className="modal-footer">
 | 
					 | 
				
			||||||
            {children}
 | 
					 | 
				
			||||||
        </div>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,33 +1,47 @@
 | 
				
			|||||||
Bootstrap modal component.
 | 
					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
 | 
					```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.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {ModalHeader, ModalBody, ModalFooter} from './Modal';
 | 
					import { ModalHeader, ModalBody, ModalFooter } from "./Modal";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [shown, setShown] = useState(false);
 | 
					const [shown, setShown] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<>
 | 
					<>
 | 
				
			||||||
    <Modal setShown={setShown} shown={shown}>
 | 
					    <Modal setShown={setShown} shown={shown} size="sm">
 | 
				
			||||||
        <ModalHeader setShown={setShown} title='Warning!'/>
 | 
					        <ModalHeader setShown={setShown} title="Warning!" />
 | 
				
			||||||
        <ModalBody><p>Bla bla bla...</p></ModalBody>
 | 
					        <ModalBody>
 | 
				
			||||||
 | 
					            <p>Bla bla bla...</p>
 | 
				
			||||||
 | 
					        </ModalBody>
 | 
				
			||||||
        <ModalFooter>
 | 
					        <ModalFooter>
 | 
				
			||||||
            <button 
 | 
					            <button
 | 
				
			||||||
                className='btn btn-secondary' 
 | 
					                className="btn btn-secondary"
 | 
				
			||||||
                onClick={() => setShown(false)}
 | 
					                onClick={() => setShown(false)}
 | 
				
			||||||
            >Skip it</button>
 | 
					            >
 | 
				
			||||||
 | 
					                Skip it
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
        </ModalFooter>
 | 
					        </ModalFooter>
 | 
				
			||||||
    </Modal>
 | 
					    </Modal>
 | 
				
			||||||
    
 | 
					
 | 
				
			||||||
    <button className='btn btn-secondary' onClick={()=>setShown(true)}>
 | 
					    <button className="btn btn-secondary" onClick={() => setShown(true)}>
 | 
				
			||||||
        Show modal
 | 
					        Show modal
 | 
				
			||||||
    </button>
 | 
					    </button>
 | 
				
			||||||
</>
 | 
					</>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@ input[type="number"] {
 | 
				
			|||||||
    appearance: textfield;
 | 
					    appearance: textfield;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
input[type=number]::-webkit-inner-spin-button,
 | 
					input[type="number"]::-webkit-inner-spin-button,
 | 
				
			||||||
input[type=number]::-webkit-outer-spin-button {
 | 
					input[type="number"]::-webkit-outer-spin-button {
 | 
				
			||||||
    -webkit-appearance: none;
 | 
					    -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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					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 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";
 | 
					import "./NumberInput.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
NumberInput.propTypes = {
 | 
					NumberInput.propTypes = {
 | 
				
			||||||
@@ -20,13 +23,10 @@ NumberInput.propTypes = {
 | 
				
			|||||||
    /** Help text message. */
 | 
					    /** Help text message. */
 | 
				
			||||||
    helpText: PropTypes.string,
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
    /** Number value. */
 | 
					    /** Number value. */
 | 
				
			||||||
    value: PropTypes.oneOfType([
 | 
					    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
 | 
				
			||||||
        PropTypes.string,
 | 
					 | 
				
			||||||
        PropTypes.number,
 | 
					 | 
				
			||||||
    ]),
 | 
					 | 
				
			||||||
    /** Function called when value changes. */
 | 
					    /** Function called when value changes. */
 | 
				
			||||||
    onChange: PropTypes.func.isRequired,
 | 
					    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,
 | 
					    inlineText: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,39 +34,47 @@ NumberInput.defaultProps = {
 | 
				
			|||||||
    value: 0,
 | 
					    value: 0,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function NumberInput({
 | 
					function NumberInput({ onChange, inlineText, value, ...props }) {
 | 
				
			||||||
    onChange, inlineText, value, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    function updateValue(initialValue, difference) {
 | 
					    function updateValue(initialValue, difference) {
 | 
				
			||||||
        onChange({ target: { value: initialValue + difference } });
 | 
					        onChange({ target: { value: initialValue + difference } });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const enableIncrease = useConditionalTimeout({ callback: updateValue }, value, 1);
 | 
					    const enableIncrease = useConditionalTimeout(
 | 
				
			||||||
    const enableDecrease = useConditionalTimeout({ callback: updateValue }, value, -1);
 | 
					        { callback: updateValue },
 | 
				
			||||||
 | 
					        value,
 | 
				
			||||||
 | 
					        1
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    const enableDecrease = useConditionalTimeout(
 | 
				
			||||||
 | 
					        { callback: updateValue },
 | 
				
			||||||
 | 
					        value,
 | 
				
			||||||
 | 
					        -1
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Input type="number" onChange={onChange} value={value} {...props}>
 | 
					        <Input type="number" onChange={onChange} value={value} {...props}>
 | 
				
			||||||
            <div className="input-group-append">
 | 
					            {inlineText && (
 | 
				
			||||||
                {inlineText && <p className="input-group-text">{inlineText}</p>}
 | 
					                <span className="input-group-text">{inlineText}</span>
 | 
				
			||||||
                <button
 | 
					            )}
 | 
				
			||||||
                    type="button"
 | 
					            <button
 | 
				
			||||||
                    className="btn btn-outline-secondary"
 | 
					                type="button"
 | 
				
			||||||
                    onMouseDown={() => enableIncrease(true)}
 | 
					                className="btn btn-outline-secondary"
 | 
				
			||||||
                    onMouseUp={() => enableIncrease(false)}
 | 
					                onMouseDown={() => enableIncrease(true)}
 | 
				
			||||||
                    aria-label="Increase"
 | 
					                onMouseUp={() => enableIncrease(false)}
 | 
				
			||||||
                >
 | 
					                aria-label="Increase"
 | 
				
			||||||
                    <i className="fas fa-plus" />
 | 
					            >
 | 
				
			||||||
                </button>
 | 
					                <FontAwesomeIcon icon={faPlus} />
 | 
				
			||||||
                <button
 | 
					            </button>
 | 
				
			||||||
                    type="button"
 | 
					            <button
 | 
				
			||||||
                    className="btn btn-outline-secondary"
 | 
					                type="button"
 | 
				
			||||||
                    onMouseDown={() => enableDecrease(true)}
 | 
					                className="btn btn-outline-secondary"
 | 
				
			||||||
                    onMouseUp={() => enableDecrease(false)}
 | 
					                onMouseDown={() => enableDecrease(true)}
 | 
				
			||||||
                    aria-label="Decrease"
 | 
					                onMouseUp={() => enableDecrease(false)}
 | 
				
			||||||
                >
 | 
					                aria-label="Decrease"
 | 
				
			||||||
                    <i className="fas fa-minus" />
 | 
					            >
 | 
				
			||||||
                </button>
 | 
					                <FontAwesomeIcon icon={faMinus} />
 | 
				
			||||||
            </div>
 | 
					            </button>
 | 
				
			||||||
        </Input>
 | 
					        </Input>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default NumberInput;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,18 @@
 | 
				
			|||||||
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.
 | 
					All additional `props` are passed to the `<input type="number">` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [value, setValue] = useState(42);
 | 
					const [value, setValue] = useState(42);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<NumberInput
 | 
					<NumberInput
 | 
				
			||||||
    value={value}
 | 
					    value={value}
 | 
				
			||||||
    label="Some number" 
 | 
					    label="Some number"
 | 
				
			||||||
    helpText="Read the small text!"
 | 
					    helpText="Read the small text!"
 | 
				
			||||||
    min='33'
 | 
					    min="33"
 | 
				
			||||||
    max='54'
 | 
					    max="54"
 | 
				
			||||||
    onChange={event =>setValue(event.target.value)}
 | 
					    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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React, { useState } from "react";
 | 
					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 PropTypes from "prop-types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Input } from "./Input";
 | 
					import Input from "./Input";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
PasswordInput.propTypes = {
 | 
					PasswordInput.propTypes = {
 | 
				
			||||||
    /** Field label. */
 | 
					    /** Field label. */
 | 
				
			||||||
@@ -21,32 +24,37 @@ PasswordInput.propTypes = {
 | 
				
			|||||||
    helpText: PropTypes.string,
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
    /** Use show/hide password button. */
 | 
					    /** Use show/hide password button. */
 | 
				
			||||||
    withEye: PropTypes.bool,
 | 
					    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);
 | 
					    const [isHidden, setHidden] = useState(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <Input
 | 
					        <Input
 | 
				
			||||||
            type={withEye && !isHidden ? "text" : "password"}
 | 
					            type={withEye && !isHidden ? "text" : "password"}
 | 
				
			||||||
            autoComplete={isHidden ? "new-password" : null}
 | 
					            autoComplete={newPass ? "new-password" : "current-password"}
 | 
				
			||||||
            {...props}
 | 
					            {...props}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
            {withEye
 | 
					            {withEye && (
 | 
				
			||||||
                ? (
 | 
					                <button
 | 
				
			||||||
                    <div className="input-group-append">
 | 
					                    type="button"
 | 
				
			||||||
                        <button
 | 
					                    className="input-group-text"
 | 
				
			||||||
                            type="button"
 | 
					                    onClick={(e) => {
 | 
				
			||||||
                            className="input-group-text"
 | 
					                        e.preventDefault();
 | 
				
			||||||
                            onClick={(e) => {
 | 
					                        setHidden((shouldBeHidden) => !shouldBeHidden);
 | 
				
			||||||
                                e.preventDefault();
 | 
					                    }}
 | 
				
			||||||
                                setHidden((shouldBeHidden) => !shouldBeHidden);
 | 
					                >
 | 
				
			||||||
                            }}
 | 
					                    <FontAwesomeIcon
 | 
				
			||||||
                        >
 | 
					                        icon={isHidden ? faEye : faEyeSlash}
 | 
				
			||||||
                            <i className={`fa ${isHidden ? "fa-eye" : "fa-eye-slash"}`} />
 | 
					                        style={{ width: "1.25rem" }}
 | 
				
			||||||
                        </button>
 | 
					                        className="text-secondary"
 | 
				
			||||||
                    </div>
 | 
					                    />
 | 
				
			||||||
                )
 | 
					                </button>
 | 
				
			||||||
                : null}
 | 
					            )}
 | 
				
			||||||
        </Input>
 | 
					        </Input>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default PasswordInput;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,18 @@
 | 
				
			|||||||
Password Bootstrap component input with label and predefined sizes and structure for using in foris forms.
 | 
					Password Bootstrap component input with label and predefined sizes and structure
 | 
				
			||||||
Can be used with "eye" button, see example.
 | 
					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
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [value, setValue] = useState('secret');
 | 
					const [value, setValue] = useState("secret");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<PasswordInput
 | 
					<PasswordInput
 | 
				
			||||||
    withEye
 | 
					    withEye
 | 
				
			||||||
    value={value}
 | 
					    value={value}
 | 
				
			||||||
    label="Some password" 
 | 
					    label="Some password"
 | 
				
			||||||
    helpText="Read the small text!"
 | 
					    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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
import { useUID } from "react-uid/dist/es5/index";
 | 
					import { useUID } from "react-uid";
 | 
				
			||||||
 | 
					 | 
				
			||||||
import { formFieldsSize } from "./constants";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Radio from "./Radio";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RadioSet.propTypes = {
 | 
					RadioSet.propTypes = {
 | 
				
			||||||
    /** Name attribute of the input HTML tag. */
 | 
					    /** Name attribute of the input HTML tag. */
 | 
				
			||||||
@@ -18,21 +18,28 @@ RadioSet.propTypes = {
 | 
				
			|||||||
    /** RadioSet label . */
 | 
					    /** RadioSet label . */
 | 
				
			||||||
    label: PropTypes.string,
 | 
					    label: PropTypes.string,
 | 
				
			||||||
    /** Choices . */
 | 
					    /** Choices . */
 | 
				
			||||||
    choices: PropTypes.arrayOf(PropTypes.shape({
 | 
					    choices: PropTypes.arrayOf(
 | 
				
			||||||
        /** Choice lable . */
 | 
					        PropTypes.shape({
 | 
				
			||||||
        label: PropTypes.string.isRequired,
 | 
					            /** Choice label . */
 | 
				
			||||||
        /** Choice value . */
 | 
					            label: PropTypes.oneOfType([
 | 
				
			||||||
        value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
 | 
					                PropTypes.string,
 | 
				
			||||||
    })).isRequired,
 | 
					                PropTypes.element,
 | 
				
			||||||
 | 
					                PropTypes.node,
 | 
				
			||||||
 | 
					                PropTypes.arrayOf(PropTypes.node),
 | 
				
			||||||
 | 
					            ]).isRequired,
 | 
				
			||||||
 | 
					            /** Choice value . */
 | 
				
			||||||
 | 
					            value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
 | 
				
			||||||
 | 
					                .isRequired,
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    ).isRequired,
 | 
				
			||||||
    /** Initial value . */
 | 
					    /** Initial value . */
 | 
				
			||||||
    value: PropTypes.string,
 | 
					    value: PropTypes.string,
 | 
				
			||||||
    /** Help text message . */
 | 
					    /** Help text message . */
 | 
				
			||||||
    helpText: PropTypes.string,
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
 | 
					    inline: PropTypes.bool,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function RadioSet({
 | 
					function RadioSet({ name, label, choices, value, helpText, inline, ...props }) {
 | 
				
			||||||
    name, label, choices, value, helpText, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    const uid = useUID();
 | 
					    const uid = useUID();
 | 
				
			||||||
    const radios = choices.map((choice, key) => {
 | 
					    const radios = choices.map((choice, key) => {
 | 
				
			||||||
        const id = `${name}-${key}`;
 | 
					        const id = `${name}-${key}`;
 | 
				
			||||||
@@ -45,49 +52,27 @@ export function RadioSet({
 | 
				
			|||||||
                value={choice.value}
 | 
					                value={choice.value}
 | 
				
			||||||
                helpText={choice.helpText}
 | 
					                helpText={choice.helpText}
 | 
				
			||||||
                checked={choice.value === value}
 | 
					                checked={choice.value === value}
 | 
				
			||||||
 | 
					                inline={inline}
 | 
				
			||||||
                {...props}
 | 
					                {...props}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className={`form-group ${formFieldsSize}`} style={{ marginBottom: "1rem" }}>
 | 
					        <div className="mb-3">
 | 
				
			||||||
            {label
 | 
					            {label && (
 | 
				
			||||||
                ? (
 | 
					                <label htmlFor={uid} className="d-block">
 | 
				
			||||||
                    <label className="col-12" htmlFor={uid} style={{ paddingLeft: "0" }}>
 | 
					                    {label}
 | 
				
			||||||
                        {label}
 | 
					                </label>
 | 
				
			||||||
                    </label>
 | 
					            )}
 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                : null}
 | 
					 | 
				
			||||||
            {radios}
 | 
					            {radios}
 | 
				
			||||||
            {helpText ? <small className="form-text text-muted">{helpText}</small> : null}
 | 
					            {helpText && (
 | 
				
			||||||
 | 
					                <div className="form-text">
 | 
				
			||||||
 | 
					                    <small>{helpText}</small>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Radio.propTypes = {
 | 
					export default RadioSet;
 | 
				
			||||||
    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}
 | 
					 | 
				
			||||||
        </>
 | 
					 | 
				
			||||||
    );
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,16 @@
 | 
				
			|||||||
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.
 | 
					All additional `props` are passed to the `<input type="number">` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Unless `helpText` is set for one of the options they are displayed inline.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const CHOICES=[
 | 
					const CHOICES = [
 | 
				
			||||||
    {value:'one',label:'1'},
 | 
					    { value: "one", label: "1" },
 | 
				
			||||||
    {value:'two',label:'2'},
 | 
					    { value: "two", label: "2" },
 | 
				
			||||||
    {value:'three',label:'3'},
 | 
					    { value: "three", label: "3" },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
const [value, setValue] = useState(CHOICES[0].value);
 | 
					const [value, setValue] = useState(CHOICES[0].value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,10 +18,10 @@ const [value, setValue] = useState(CHOICES[0].value);
 | 
				
			|||||||
    {/*Yeah, it gets event, not value!*/}
 | 
					    {/*Yeah, it gets event, not value!*/}
 | 
				
			||||||
    <RadioSet
 | 
					    <RadioSet
 | 
				
			||||||
        value={value}
 | 
					        value={value}
 | 
				
			||||||
        name='some-radio'
 | 
					        name="some-radio"
 | 
				
			||||||
        choices={CHOICES}
 | 
					        choices={CHOICES}
 | 
				
			||||||
        onChange={event =>setValue(event.target.value)}
 | 
					        onChange={(event) => setValue(event.target.value)}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <p>Selected value: {value}</p>
 | 
					    <p>Selected value: {value}</p>
 | 
				
			||||||
</>
 | 
					</>;
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,14 @@
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
 * 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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					 | 
				
			||||||
import { useUID } from "react-uid/dist/es5/index";
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					import { useUID } from "react-uid";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Select.propTypes = {
 | 
					Select.propTypes = {
 | 
				
			||||||
    /** Select field Label. */
 | 
					    /** Select field Label. */
 | 
				
			||||||
@@ -16,34 +16,35 @@ Select.propTypes = {
 | 
				
			|||||||
    /** Choices if form of {value : "Label",...}. */
 | 
					    /** Choices if form of {value : "Label",...}. */
 | 
				
			||||||
    choices: PropTypes.object.isRequired,
 | 
					    choices: PropTypes.object.isRequired,
 | 
				
			||||||
    /** Current value. */
 | 
					    /** Current value. */
 | 
				
			||||||
    value: PropTypes.oneOfType([
 | 
					    value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
 | 
				
			||||||
        PropTypes.string,
 | 
					 | 
				
			||||||
        PropTypes.number,
 | 
					 | 
				
			||||||
    ]).isRequired,
 | 
					 | 
				
			||||||
    /** Help text message. */
 | 
					    /** Help text message. */
 | 
				
			||||||
    helpText: PropTypes.string,
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Select({
 | 
					function Select({ label, choices, helpText, ...props }) {
 | 
				
			||||||
    label, choices, helpText, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    const uid = useUID();
 | 
					    const uid = useUID();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const options = Object.keys(choices).map(
 | 
					    const options = Object.keys(choices).map((choice) => (
 | 
				
			||||||
        (key) => <option key={key} value={key}>{choices[key]}</option>,
 | 
					        <option key={choice} value={choice}>
 | 
				
			||||||
    );
 | 
					            {choices[choice]}
 | 
				
			||||||
 | 
					        </option>
 | 
				
			||||||
 | 
					    ));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="form-group col-sm-12 offset-lg-1 col-lg-10">
 | 
					        <div className="mb-3">
 | 
				
			||||||
            <label htmlFor={uid}>{label}</label>
 | 
					            <label className="form-label" htmlFor={uid}>
 | 
				
			||||||
            <select
 | 
					                {label}
 | 
				
			||||||
                className="custom-select"
 | 
					            </label>
 | 
				
			||||||
                id={uid}
 | 
					            <select className="form-select" id={uid} {...props}>
 | 
				
			||||||
                {...props}
 | 
					 | 
				
			||||||
            >
 | 
					 | 
				
			||||||
                {options}
 | 
					                {options}
 | 
				
			||||||
            </select>
 | 
					            </select>
 | 
				
			||||||
            {helpText ? <small className="form-text text-muted">{helpText}</small> : null}
 | 
					            {helpText && (
 | 
				
			||||||
 | 
					                <div className="form-text">
 | 
				
			||||||
 | 
					                    <small>{helpText}</small>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default Select;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,13 +1,14 @@
 | 
				
			|||||||
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.
 | 
					All additional `props` are passed to the `<select>` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const CHOICES={
 | 
					const CHOICES = {
 | 
				
			||||||
    apple:'Apple',
 | 
					    apple: "Apple",
 | 
				
			||||||
    banana:'Banana',
 | 
					    banana: "Banana",
 | 
				
			||||||
    peach:'Peach',
 | 
					    peach: "Peach",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
const [value, setValue] = useState(Object.keys(CHOICES)[0]);
 | 
					const [value, setValue] = useState(Object.keys(CHOICES)[0]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,9 +18,9 @@ const [value, setValue] = useState(Object.keys(CHOICES)[0]);
 | 
				
			|||||||
        label="Fruit"
 | 
					        label="Fruit"
 | 
				
			||||||
        value={value}
 | 
					        value={value}
 | 
				
			||||||
        choices={CHOICES}
 | 
					        choices={CHOICES}
 | 
				
			||||||
        onChange={event=>setValue(event.target.value)}
 | 
					        onChange={(event) => setValue(event.target.value)}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <p>Selected choice label: {CHOICES[value]}</p>
 | 
					    <p>Selected choice label: {CHOICES[value]}</p>
 | 
				
			||||||
    <p>Selected choice value: {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 React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import "./Spinner.css";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Spinner.propTypes = {
 | 
					Spinner.propTypes = {
 | 
				
			||||||
    /** Children components put into `div` with "spinner-text" class. */
 | 
					    /** Children components put into `div` with "spinner-text" class. */
 | 
				
			||||||
    children: PropTypes.oneOfType([
 | 
					    children: PropTypes.oneOfType([
 | 
				
			||||||
        PropTypes.arrayOf(PropTypes.node),
 | 
					        PropTypes.arrayOf(PropTypes.node),
 | 
				
			||||||
        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,
 | 
					    fullScreen: PropTypes.bool.isRequired,
 | 
				
			||||||
    className: PropTypes.string,
 | 
					    className: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -23,19 +26,19 @@ Spinner.defaultProps = {
 | 
				
			|||||||
    fullScreen: false,
 | 
					    fullScreen: false,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Spinner({
 | 
					export function Spinner({ fullScreen, children, className }) {
 | 
				
			||||||
    fullScreen, children, className, ...props
 | 
					 | 
				
			||||||
}) {
 | 
					 | 
				
			||||||
    if (!fullScreen) {
 | 
					    if (!fullScreen) {
 | 
				
			||||||
        return (
 | 
					        return (
 | 
				
			||||||
            <div className={`spinner-wrapper ${className || ""}`} {...props}>
 | 
					            <div
 | 
				
			||||||
 | 
					                className={`spinner-wrapper ${className || "my-3 text-center"}`}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
                <SpinnerElement>{children}</SpinnerElement>
 | 
					                <SpinnerElement>{children}</SpinnerElement>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
        <div className="spinner-fs-wrapper" {...props}>
 | 
					        <div className="spinner-fs-wrapper">
 | 
				
			||||||
            <div className="spinner-fs-background">
 | 
					            <div className="spinner-fs-background">
 | 
				
			||||||
                <SpinnerElement>{children}</SpinnerElement>
 | 
					                <SpinnerElement>{children}</SpinnerElement>
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
@@ -46,6 +49,8 @@ export function Spinner({
 | 
				
			|||||||
SpinnerElement.propTypes = {
 | 
					SpinnerElement.propTypes = {
 | 
				
			||||||
    /** Spinner's size */
 | 
					    /** Spinner's size */
 | 
				
			||||||
    small: PropTypes.bool,
 | 
					    small: PropTypes.bool,
 | 
				
			||||||
 | 
					    /** Additional className */
 | 
				
			||||||
 | 
					    className: PropTypes.string,
 | 
				
			||||||
    /** Children components */
 | 
					    /** Children components */
 | 
				
			||||||
    children: PropTypes.oneOfType([
 | 
					    children: PropTypes.oneOfType([
 | 
				
			||||||
        PropTypes.arrayOf(PropTypes.node),
 | 
					        PropTypes.arrayOf(PropTypes.node),
 | 
				
			||||||
@@ -53,13 +58,18 @@ SpinnerElement.propTypes = {
 | 
				
			|||||||
    ]),
 | 
					    ]),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function SpinnerElement({ small, children }) {
 | 
					export function SpinnerElement({ small, className, children }) {
 | 
				
			||||||
    return (
 | 
					    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" />
 | 
					                <span className="sr-only" />
 | 
				
			||||||
            </div>
 | 
					            </div>
 | 
				
			||||||
            <div className="spinner-text">{children}</div>
 | 
					            {children && <div className="spinner-text">{children}</div>}
 | 
				
			||||||
        </>
 | 
					        </>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					<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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					import React from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import PropTypes from "prop-types";
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Input } from "./Input";
 | 
					import Input from "./Input";
 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const TextInput = ({ ...props }) => <Input type="text" {...props} />;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function TextInput({ ...props }) {
 | 
				
			||||||
 | 
					    return <Input type="text" {...props} />;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TextInput.propTypes = {
 | 
					TextInput.propTypes = {
 | 
				
			||||||
    /** Field label. */
 | 
					    /** Field label. */
 | 
				
			||||||
@@ -22,3 +23,5 @@ TextInput.propTypes = {
 | 
				
			|||||||
    /** Help text message. */
 | 
					    /** Help text message. */
 | 
				
			||||||
    helpText: PropTypes.string,
 | 
					    helpText: PropTypes.string,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default TextInput;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,16 @@
 | 
				
			|||||||
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.
 | 
					All additional `props` are passed to the `<input type="text">` HTML component.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```js
 | 
					```js
 | 
				
			||||||
import {useState} from 'react';
 | 
					import { useState } from "react";
 | 
				
			||||||
const [value, setValue] = useState('Bla bla');
 | 
					const [value, setValue] = useState("Bla bla");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<TextInput
 | 
					<TextInput
 | 
				
			||||||
    value={value}
 | 
					    value={value}
 | 
				
			||||||
    label="Some text" 
 | 
					    label="Some text"
 | 
				
			||||||
    helpText="Read the small 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					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 { render } from "customTestRender";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Button } from "../Button";
 | 
					import Button from "../Button";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("<Button />", () => {
 | 
					describe("<Button />", () => {
 | 
				
			||||||
    it("Render button correctly", () => {
 | 
					    it("Render button correctly", () => {
 | 
				
			||||||
        const { container } = render(<Button>Test Button</Button>);
 | 
					        const { container } = render(<Button>Test Button</Button>);
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Render button with custom classes", () => {
 | 
					    it("Render button with custom classes", () => {
 | 
				
			||||||
        const { container } = render(<Button className="one two three">Test Button</Button>);
 | 
					        const { container } = render(
 | 
				
			||||||
        expect(container.firstChild)
 | 
					            <Button className="one two three">Test Button</Button>
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					        );
 | 
				
			||||||
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Render button with spinner", () => {
 | 
					    it("Render button with spinner", () => {
 | 
				
			||||||
        const { container } = render(<Button loading={true}>Test Button</Button>);
 | 
					        const { container } = render(<Button loading>Test Button</Button>);
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ import React from "react";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { render } from "customTestRender";
 | 
					import { render } from "customTestRender";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { CheckBox } from "../CheckBox";
 | 
					import CheckBox from "../CheckBox";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("<Checkbox/>", () => {
 | 
					describe("<Checkbox/>", () => {
 | 
				
			||||||
    it("Render checkbox", () => {
 | 
					    it("Render checkbox", () => {
 | 
				
			||||||
@@ -18,22 +18,16 @@ describe("<Checkbox/>", () => {
 | 
				
			|||||||
                label="Test label"
 | 
					                label="Test label"
 | 
				
			||||||
                checked
 | 
					                checked
 | 
				
			||||||
                helpText="Some help text"
 | 
					                helpText="Some help text"
 | 
				
			||||||
                onChange={() => {
 | 
					                onChange={() => {}}
 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Render uncheked checkbox", () => {
 | 
					    it("Render uncheked checkbox", () => {
 | 
				
			||||||
        const { container } = render(
 | 
					        const { container } = render(
 | 
				
			||||||
            <CheckBox
 | 
					            <CheckBox label="Test label" helpText="Some help text" />
 | 
				
			||||||
                label="Test label"
 | 
					 | 
				
			||||||
                helpText="Some help text"
 | 
					 | 
				
			||||||
            />
 | 
					 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										23
									
								
								src/bootstrap/__tests__/DownloadButton.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/bootstrap/__tests__/DownloadButton.test.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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 from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { render } from "customTestRender";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import DownloadButton from "../DownloadButton";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("<DownloadButton />", () => {
 | 
				
			||||||
 | 
					    it("should have download attribute", () => {
 | 
				
			||||||
 | 
					        const { container } = render(
 | 
				
			||||||
 | 
					            <DownloadButton href="http://example.com">
 | 
				
			||||||
 | 
					                Test Button
 | 
				
			||||||
 | 
					            </DownloadButton>
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        expect(container.firstChild.getAttribute("download")).not.toBeNull();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@@ -9,7 +9,7 @@ import React from "react";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { render, fireEvent, getByLabelText, wait } from "customTestRender";
 | 
					import { render, fireEvent, getByLabelText, wait } from "customTestRender";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { NumberInput } from "../NumberInput";
 | 
					import NumberInput from "../NumberInput";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("<NumberInput/>", () => {
 | 
					describe("<NumberInput/>", () => {
 | 
				
			||||||
    const onChangeMock = jest.fn();
 | 
					    const onChangeMock = jest.fn();
 | 
				
			||||||
@@ -34,12 +34,16 @@ describe("<NumberInput/>", () => {
 | 
				
			|||||||
    it("Increase number with button", async () => {
 | 
					    it("Increase number with button", async () => {
 | 
				
			||||||
        const increaseButton = getByLabelText(componentContainer, "Increase");
 | 
					        const increaseButton = getByLabelText(componentContainer, "Increase");
 | 
				
			||||||
        fireEvent.mouseDown(increaseButton);
 | 
					        fireEvent.mouseDown(increaseButton);
 | 
				
			||||||
        await wait(() => expect(onChangeMock).toHaveBeenCalledWith({"target": {"value": 2}}));
 | 
					        await wait(() =>
 | 
				
			||||||
 | 
					            expect(onChangeMock).toHaveBeenCalledWith({ target: { value: 2 } })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Decrease number with button", async () => {
 | 
					    it("Decrease number with button", async () => {
 | 
				
			||||||
        const decreaseButton = getByLabelText(componentContainer, "Decrease");
 | 
					        const decreaseButton = getByLabelText(componentContainer, "Decrease");
 | 
				
			||||||
        fireEvent.mouseDown(decreaseButton);
 | 
					        fireEvent.mouseDown(decreaseButton);
 | 
				
			||||||
        await wait(() => expect(onChangeMock).toHaveBeenCalledWith({"target": {"value": 0}}));
 | 
					        await wait(() =>
 | 
				
			||||||
 | 
					            expect(onChangeMock).toHaveBeenCalledWith({ target: { value: 0 } })
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,7 @@ import React from "react";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { render } from "customTestRender";
 | 
					import { render } from "customTestRender";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { PasswordInput } from "../PasswordInput";
 | 
					import PasswordInput from "../PasswordInput";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("<PasswordInput/>", () => {
 | 
					describe("<PasswordInput/>", () => {
 | 
				
			||||||
    it("Render password input", () => {
 | 
					    it("Render password input", () => {
 | 
				
			||||||
@@ -18,11 +18,9 @@ describe("<PasswordInput/>", () => {
 | 
				
			|||||||
                label="Test label"
 | 
					                label="Test label"
 | 
				
			||||||
                helpText="Some help text"
 | 
					                helpText="Some help text"
 | 
				
			||||||
                value="Some password"
 | 
					                value="Some password"
 | 
				
			||||||
                onChange={() => {
 | 
					                onChange={() => {}}
 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,37 +9,35 @@ import React from "react";
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import { render } from "customTestRender";
 | 
					import { render } from "customTestRender";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { RadioSet } from "../RadioSet";
 | 
					import RadioSet from "../RadioSet";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TEST_CHOICES = [
 | 
					const TEST_CHOICES = [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        label: "label",
 | 
					        label: "label",
 | 
				
			||||||
        value: "value"
 | 
					        value: "value",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        label: "another label",
 | 
					        label: "another label",
 | 
				
			||||||
        value: "another value"
 | 
					        value: "another value",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
        label: "another one  label",
 | 
					        label: "another one  label",
 | 
				
			||||||
        value: "another on value"
 | 
					        value: "another on value",
 | 
				
			||||||
    }
 | 
					    },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("<RadioSet/>", () => {
 | 
					describe("<RadioSet/>", () => {
 | 
				
			||||||
    it("Render radio set", () => {
 | 
					    it("Render radio set", () => {
 | 
				
			||||||
        const { container } = render(
 | 
					        const { container } = render(
 | 
				
			||||||
            <RadioSet
 | 
					            <RadioSet
 | 
				
			||||||
                name={"test_name"}
 | 
					                name="test_name"
 | 
				
			||||||
                label='Radios set label'
 | 
					                label="Radios set label"
 | 
				
			||||||
                value='value'
 | 
					                value="value"
 | 
				
			||||||
                choices={TEST_CHOICES}
 | 
					                choices={TEST_CHOICES}
 | 
				
			||||||
                helpText={"Some help text"}
 | 
					                helpText="Some help text"
 | 
				
			||||||
                onChange={() => {
 | 
					                onChange={() => {}}
 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,27 +7,31 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import React from "react";
 | 
					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 = {
 | 
					const TEST_CHOICES = {
 | 
				
			||||||
    "1": "one",
 | 
					    1: "one",
 | 
				
			||||||
    "2": "two",
 | 
					    2: "two",
 | 
				
			||||||
    "3": "three",
 | 
					    3: "three",
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("<Select/>", () => {
 | 
					describe("<Select/>", () => {
 | 
				
			||||||
    var selectContainer;
 | 
					    let selectContainer;
 | 
				
			||||||
    const onChangeHandler = jest.fn();
 | 
					    const onChangeHandler = jest.fn();
 | 
				
			||||||
    beforeEach(() => {
 | 
					    beforeEach(() => {
 | 
				
			||||||
        const { container } = render(
 | 
					        const { container } = render(
 | 
				
			||||||
            <Select
 | 
					            <Select
 | 
				
			||||||
                label='Test label'
 | 
					                label="Test label"
 | 
				
			||||||
                value='1'
 | 
					                value="1"
 | 
				
			||||||
                choices={TEST_CHOICES}
 | 
					                choices={TEST_CHOICES}
 | 
				
			||||||
                helpText='Help text'
 | 
					                helpText="Help text"
 | 
				
			||||||
 | 
					 | 
				
			||||||
                onChange={onChangeHandler}
 | 
					                onChange={onChangeHandler}
 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
@@ -35,21 +39,17 @@ describe("<Select/>", () => {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Test with snapshot.", () => {
 | 
					    it("Test with snapshot.", () => {
 | 
				
			||||||
        expect(selectContainer)
 | 
					        expect(selectContainer).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("Test onChange handling.", () => {
 | 
					    it("Test onChange handling.", () => {
 | 
				
			||||||
        const select = getByDisplayValue(selectContainer, "one");
 | 
					        const select = getByDisplayValue(selectContainer, "one");
 | 
				
			||||||
        expect(select.value)
 | 
					        expect(select.value).toBe("1");
 | 
				
			||||||
            .toBe("1");
 | 
					 | 
				
			||||||
        fireEvent.change(select, { target: { value: "2" } });
 | 
					        fireEvent.change(select, { target: { value: "2" } });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const option = getByText(selectContainer, "two");
 | 
					        const option = getByText(selectContainer, "two");
 | 
				
			||||||
        expect(onChangeHandler)
 | 
					        expect(onChangeHandler).toBeCalled();
 | 
				
			||||||
            .toBeCalled();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(option.value)
 | 
					        expect(option.value).toBe("2");
 | 
				
			||||||
            .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 { render } from "customTestRender";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { TextInput } from "../TextInput";
 | 
					import TextInput from "../TextInput";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe("<TextInput/>", () => {
 | 
					describe("<TextInput/>", () => {
 | 
				
			||||||
    it("Render text input", () => {
 | 
					    it("Render text input", () => {
 | 
				
			||||||
@@ -18,11 +18,9 @@ describe("<TextInput/>", () => {
 | 
				
			|||||||
                label="Test label"
 | 
					                label="Test label"
 | 
				
			||||||
                helpText="Some help text"
 | 
					                helpText="Some help text"
 | 
				
			||||||
                value="Some text"
 | 
					                value="Some text"
 | 
				
			||||||
                onChange={() => {
 | 
					                onChange={() => {}}
 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
            />
 | 
					            />
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
        expect(container.firstChild)
 | 
					        expect(container.firstChild).toMatchSnapshot();
 | 
				
			||||||
            .toMatchSnapshot();
 | 
					 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,39 +2,38 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
exports[`<Button /> Render button correctly 1`] = `
 | 
					exports[`<Button /> Render button correctly 1`] = `
 | 
				
			||||||
<button
 | 
					<button
 | 
				
			||||||
  class="btn btn-primary "
 | 
					  class="btn btn-primary d-inline-flex justify-content-center align-items-center"
 | 
				
			||||||
  type="button"
 | 
					  type="button"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
   
 | 
					  <span>
 | 
				
			||||||
   
 | 
					    Test Button
 | 
				
			||||||
  Test Button
 | 
					  </span>
 | 
				
			||||||
</button>
 | 
					</button>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exports[`<Button /> Render button with custom classes 1`] = `
 | 
					exports[`<Button /> Render button with custom classes 1`] = `
 | 
				
			||||||
<button
 | 
					<button
 | 
				
			||||||
  class="btn one two three"
 | 
					  class="btn one two three d-inline-flex justify-content-center align-items-center"
 | 
				
			||||||
  type="button"
 | 
					  type="button"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
   
 | 
					  <span>
 | 
				
			||||||
   
 | 
					    Test Button
 | 
				
			||||||
  Test Button
 | 
					  </span>
 | 
				
			||||||
</button>
 | 
					</button>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exports[`<Button /> Render button with spinner 1`] = `
 | 
					exports[`<Button /> Render button with spinner 1`] = `
 | 
				
			||||||
<button
 | 
					<button
 | 
				
			||||||
  class="btn btn-primary "
 | 
					  class="btn btn-primary d-inline-flex justify-content-center align-items-center"
 | 
				
			||||||
  type="button"
 | 
					  type="button"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
  <span
 | 
					  <span
 | 
				
			||||||
    aria-hidden="true"
 | 
					    aria-hidden="true"
 | 
				
			||||||
    class="spinner-border spinner-border-sm"
 | 
					    class="spinner-border spinner-border-sm me-1"
 | 
				
			||||||
    role="status"
 | 
					    role="status"
 | 
				
			||||||
  />
 | 
					  />
 | 
				
			||||||
   
 | 
					  <span>
 | 
				
			||||||
   
 | 
					    Test Button
 | 
				
			||||||
   
 | 
					  </span>
 | 
				
			||||||
  Test Button
 | 
					 | 
				
			||||||
</button>
 | 
					</button>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,61 +2,51 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
exports[`<Checkbox/> Render checkbox 1`] = `
 | 
					exports[`<Checkbox/> Render checkbox 1`] = `
 | 
				
			||||||
<div
 | 
					<div
 | 
				
			||||||
  class="col-sm-12 offset-lg-1 col-lg-10"
 | 
					  class="mb-3 form-check"
 | 
				
			||||||
  style="margin-bottom: 1rem;"
 | 
					 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
 | 
					  <input
 | 
				
			||||||
 | 
					    checked=""
 | 
				
			||||||
 | 
					    class="form-check-input"
 | 
				
			||||||
 | 
					    id="1"
 | 
				
			||||||
 | 
					    type="checkbox"
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					  <label
 | 
				
			||||||
 | 
					    class="form-check-label"
 | 
				
			||||||
 | 
					    for="1"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    Test label
 | 
				
			||||||
 | 
					  </label>
 | 
				
			||||||
  <div
 | 
					  <div
 | 
				
			||||||
    class="custom-control custom-checkbox"
 | 
					    class="form-text"
 | 
				
			||||||
    style="margin-bottom: 0px;"
 | 
					 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <input
 | 
					    <small>
 | 
				
			||||||
      checked=""
 | 
					      Some help text
 | 
				
			||||||
      class="custom-control-input"
 | 
					    </small>
 | 
				
			||||||
      id="1"
 | 
					 | 
				
			||||||
      type="checkbox"
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
    <label
 | 
					 | 
				
			||||||
      class="custom-control-label"
 | 
					 | 
				
			||||||
      for="1"
 | 
					 | 
				
			||||||
      style="margin-bottom: 0px;"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      Test label
 | 
					 | 
				
			||||||
    </label>
 | 
					 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <small
 | 
					 | 
				
			||||||
    class="form-text text-muted"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    Some help text
 | 
					 | 
				
			||||||
  </small>
 | 
					 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exports[`<Checkbox/> Render uncheked checkbox 1`] = `
 | 
					exports[`<Checkbox/> Render uncheked checkbox 1`] = `
 | 
				
			||||||
<div
 | 
					<div
 | 
				
			||||||
  class="col-sm-12 offset-lg-1 col-lg-10"
 | 
					  class="mb-3 form-check"
 | 
				
			||||||
  style="margin-bottom: 1rem;"
 | 
					 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
 | 
					  <input
 | 
				
			||||||
 | 
					    class="form-check-input"
 | 
				
			||||||
 | 
					    id="1"
 | 
				
			||||||
 | 
					    type="checkbox"
 | 
				
			||||||
 | 
					  />
 | 
				
			||||||
 | 
					  <label
 | 
				
			||||||
 | 
					    class="form-check-label"
 | 
				
			||||||
 | 
					    for="1"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    Test label
 | 
				
			||||||
 | 
					  </label>
 | 
				
			||||||
  <div
 | 
					  <div
 | 
				
			||||||
    class="custom-control custom-checkbox"
 | 
					    class="form-text"
 | 
				
			||||||
    style="margin-bottom: 0px;"
 | 
					 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <input
 | 
					    <small>
 | 
				
			||||||
      class="custom-control-input"
 | 
					      Some help text
 | 
				
			||||||
      id="1"
 | 
					    </small>
 | 
				
			||||||
      type="checkbox"
 | 
					 | 
				
			||||||
    />
 | 
					 | 
				
			||||||
    <label
 | 
					 | 
				
			||||||
      class="custom-control-label"
 | 
					 | 
				
			||||||
      for="1"
 | 
					 | 
				
			||||||
      style="margin-bottom: 0px;"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      Test label
 | 
					 | 
				
			||||||
    </label>
 | 
					 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <small
 | 
					 | 
				
			||||||
    class="form-text text-muted"
 | 
					 | 
				
			||||||
  >
 | 
					 | 
				
			||||||
    Some help text
 | 
					 | 
				
			||||||
  </small>
 | 
					 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,51 +2,46 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
exports[`<NumberInput/> Render number input 1`] = `
 | 
					exports[`<NumberInput/> Render number input 1`] = `
 | 
				
			||||||
<div
 | 
					<div
 | 
				
			||||||
  class="col-sm-12 offset-lg-1 col-lg-10"
 | 
					  class="mb-3"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
  <div
 | 
					  <label
 | 
				
			||||||
    class="form-group"
 | 
					    class="form-label"
 | 
				
			||||||
 | 
					    for="1"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <label
 | 
					    Test label
 | 
				
			||||||
      for="1"
 | 
					  </label>
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    class="input-group"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <input
 | 
				
			||||||
 | 
					      class="form-control"
 | 
				
			||||||
 | 
					      id="1"
 | 
				
			||||||
 | 
					      type="number"
 | 
				
			||||||
 | 
					      value="1"
 | 
				
			||||||
 | 
					    />
 | 
				
			||||||
 | 
					    <button
 | 
				
			||||||
 | 
					      aria-label="Increase"
 | 
				
			||||||
 | 
					      class="btn btn-outline-secondary"
 | 
				
			||||||
 | 
					      type="button"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      Test label
 | 
					      <i
 | 
				
			||||||
    </label>
 | 
					        class="fa"
 | 
				
			||||||
    <div
 | 
					 | 
				
			||||||
      class="input-group"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      <input
 | 
					 | 
				
			||||||
        class="form-control"
 | 
					 | 
				
			||||||
        id="1"
 | 
					 | 
				
			||||||
        type="number"
 | 
					 | 
				
			||||||
        value="1"
 | 
					 | 
				
			||||||
      />
 | 
					      />
 | 
				
			||||||
      <div
 | 
					    </button>
 | 
				
			||||||
        class="input-group-append"
 | 
					    <button
 | 
				
			||||||
      >
 | 
					      aria-label="Decrease"
 | 
				
			||||||
        <button
 | 
					      class="btn btn-outline-secondary"
 | 
				
			||||||
          aria-label="Increase"
 | 
					      type="button"
 | 
				
			||||||
          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>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
    <small
 | 
					 | 
				
			||||||
      class="form-text text-muted"
 | 
					 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
 | 
					      <i
 | 
				
			||||||
 | 
					        class="fa"
 | 
				
			||||||
 | 
					      />
 | 
				
			||||||
 | 
					    </button>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
 | 
					  <div
 | 
				
			||||||
 | 
					    class="form-text"
 | 
				
			||||||
 | 
					  >
 | 
				
			||||||
 | 
					    <small>
 | 
				
			||||||
      Some help text
 | 
					      Some help text
 | 
				
			||||||
    </small>
 | 
					    </small>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,30 +2,29 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
exports[`<PasswordInput/> Render password input 1`] = `
 | 
					exports[`<PasswordInput/> Render password input 1`] = `
 | 
				
			||||||
<div
 | 
					<div
 | 
				
			||||||
  class="col-sm-12 offset-lg-1 col-lg-10"
 | 
					  class="mb-3"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
  <div
 | 
					  <label
 | 
				
			||||||
    class="form-group"
 | 
					    class="form-label"
 | 
				
			||||||
 | 
					    for="1"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <label
 | 
					    Test label
 | 
				
			||||||
      for="1"
 | 
					  </label>
 | 
				
			||||||
    >
 | 
					  <div
 | 
				
			||||||
      Test label
 | 
					    class="input-group"
 | 
				
			||||||
    </label>
 | 
					  >
 | 
				
			||||||
    <div
 | 
					    <input
 | 
				
			||||||
      class="input-group"
 | 
					      autocomplete="current-password"
 | 
				
			||||||
    >
 | 
					      class="form-control"
 | 
				
			||||||
      <input
 | 
					      id="1"
 | 
				
			||||||
        autocomplete="new-password"
 | 
					      type="password"
 | 
				
			||||||
        class="form-control"
 | 
					      value="Some password"
 | 
				
			||||||
        id="1"
 | 
					    />
 | 
				
			||||||
        type="password"
 | 
					  </div>
 | 
				
			||||||
        value="Some password"
 | 
					  <div
 | 
				
			||||||
      />
 | 
					    class="form-text"
 | 
				
			||||||
    </div>
 | 
					  >
 | 
				
			||||||
    <small
 | 
					    <small>
 | 
				
			||||||
      class="form-text text-muted"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      Some help text
 | 
					      Some help text
 | 
				
			||||||
    </small>
 | 
					    </small>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,72 +2,72 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
exports[`<RadioSet/> Render radio set 1`] = `
 | 
					exports[`<RadioSet/> Render radio set 1`] = `
 | 
				
			||||||
<div
 | 
					<div
 | 
				
			||||||
  class="form-group col-sm-12 offset-lg-1 col-lg-10"
 | 
					  class="mb-3"
 | 
				
			||||||
  style="margin-bottom: 1rem;"
 | 
					 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
  <label
 | 
					  <label
 | 
				
			||||||
    class="col-12"
 | 
					    class="d-block"
 | 
				
			||||||
    for="1"
 | 
					    for="1"
 | 
				
			||||||
    style="padding-left: 0px;"
 | 
					 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    Radios set label
 | 
					    Radios set label
 | 
				
			||||||
  </label>
 | 
					  </label>
 | 
				
			||||||
  <div
 | 
					  <div
 | 
				
			||||||
    class="custom-control custom-radio custom-control-inline"
 | 
					    class="mb-3"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <input
 | 
					    <input
 | 
				
			||||||
      checked=""
 | 
					      checked=""
 | 
				
			||||||
      class="custom-control-input"
 | 
					      class="form-check-input me-2"
 | 
				
			||||||
      id="test_name-0"
 | 
					      id="test_name-0"
 | 
				
			||||||
      name="test_name"
 | 
					      name="test_name"
 | 
				
			||||||
      type="radio"
 | 
					      type="radio"
 | 
				
			||||||
      value="value"
 | 
					      value="value"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <label
 | 
					    <label
 | 
				
			||||||
      class="custom-control-label"
 | 
					      class="form-check-label"
 | 
				
			||||||
      for="test_name-0"
 | 
					      for="test_name-0"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      label
 | 
					      label
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <div
 | 
					  <div
 | 
				
			||||||
    class="custom-control custom-radio custom-control-inline"
 | 
					    class="mb-3"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <input
 | 
					    <input
 | 
				
			||||||
      class="custom-control-input"
 | 
					      class="form-check-input me-2"
 | 
				
			||||||
      id="test_name-1"
 | 
					      id="test_name-1"
 | 
				
			||||||
      name="test_name"
 | 
					      name="test_name"
 | 
				
			||||||
      type="radio"
 | 
					      type="radio"
 | 
				
			||||||
      value="another value"
 | 
					      value="another value"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <label
 | 
					    <label
 | 
				
			||||||
      class="custom-control-label"
 | 
					      class="form-check-label"
 | 
				
			||||||
      for="test_name-1"
 | 
					      for="test_name-1"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      another label
 | 
					      another label
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <div
 | 
					  <div
 | 
				
			||||||
    class="custom-control custom-radio custom-control-inline"
 | 
					    class="mb-3"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <input
 | 
					    <input
 | 
				
			||||||
      class="custom-control-input"
 | 
					      class="form-check-input me-2"
 | 
				
			||||||
      id="test_name-2"
 | 
					      id="test_name-2"
 | 
				
			||||||
      name="test_name"
 | 
					      name="test_name"
 | 
				
			||||||
      type="radio"
 | 
					      type="radio"
 | 
				
			||||||
      value="another on value"
 | 
					      value="another on value"
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
    <label
 | 
					    <label
 | 
				
			||||||
      class="custom-control-label"
 | 
					      class="form-check-label"
 | 
				
			||||||
      for="test_name-2"
 | 
					      for="test_name-2"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      another one  label
 | 
					      another one  label
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
  </div>
 | 
					  </div>
 | 
				
			||||||
  <small
 | 
					  <div
 | 
				
			||||||
    class="form-text text-muted"
 | 
					    class="form-text"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    Some help text
 | 
					    <small>
 | 
				
			||||||
  </small>
 | 
					      Some help text
 | 
				
			||||||
 | 
					    </small>
 | 
				
			||||||
 | 
					  </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
`;
 | 
					`;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,15 +3,16 @@
 | 
				
			|||||||
exports[`<Select/> Test with snapshot. 1`] = `
 | 
					exports[`<Select/> Test with snapshot. 1`] = `
 | 
				
			||||||
<div>
 | 
					<div>
 | 
				
			||||||
  <div
 | 
					  <div
 | 
				
			||||||
    class="form-group col-sm-12 offset-lg-1 col-lg-10"
 | 
					    class="mb-3"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <label
 | 
					    <label
 | 
				
			||||||
 | 
					      class="form-label"
 | 
				
			||||||
      for="1"
 | 
					      for="1"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      Test label
 | 
					      Test label
 | 
				
			||||||
    </label>
 | 
					    </label>
 | 
				
			||||||
    <select
 | 
					    <select
 | 
				
			||||||
      class="custom-select"
 | 
					      class="form-select"
 | 
				
			||||||
      id="1"
 | 
					      id="1"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <option
 | 
					      <option
 | 
				
			||||||
@@ -30,11 +31,13 @@ exports[`<Select/> Test with snapshot. 1`] = `
 | 
				
			|||||||
        three
 | 
					        three
 | 
				
			||||||
      </option>
 | 
					      </option>
 | 
				
			||||||
    </select>
 | 
					    </select>
 | 
				
			||||||
    <small
 | 
					    <div
 | 
				
			||||||
      class="form-text text-muted"
 | 
					      class="form-text"
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      Help text
 | 
					      <small>
 | 
				
			||||||
    </small>
 | 
					        Help text
 | 
				
			||||||
 | 
					      </small>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
  </div>
 | 
					  </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,29 +2,28 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
exports[`<TextInput/> Render text input 1`] = `
 | 
					exports[`<TextInput/> Render text input 1`] = `
 | 
				
			||||||
<div
 | 
					<div
 | 
				
			||||||
  class="col-sm-12 offset-lg-1 col-lg-10"
 | 
					  class="mb-3"
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
  <div
 | 
					  <label
 | 
				
			||||||
    class="form-group"
 | 
					    class="form-label"
 | 
				
			||||||
 | 
					    for="1"
 | 
				
			||||||
  >
 | 
					  >
 | 
				
			||||||
    <label
 | 
					    Test label
 | 
				
			||||||
      for="1"
 | 
					  </label>
 | 
				
			||||||
    >
 | 
					  <div
 | 
				
			||||||
      Test label
 | 
					    class="input-group"
 | 
				
			||||||
    </label>
 | 
					  >
 | 
				
			||||||
    <div
 | 
					    <input
 | 
				
			||||||
      class="input-group"
 | 
					      class="form-control"
 | 
				
			||||||
    >
 | 
					      id="1"
 | 
				
			||||||
      <input
 | 
					      type="text"
 | 
				
			||||||
        class="form-control"
 | 
					      value="Some text"
 | 
				
			||||||
        id="1"
 | 
					    />
 | 
				
			||||||
        type="text"
 | 
					  </div>
 | 
				
			||||||
        value="Some text"
 | 
					  <div
 | 
				
			||||||
      />
 | 
					    class="form-text"
 | 
				
			||||||
    </div>
 | 
					  >
 | 
				
			||||||
    <small
 | 
					    <small>
 | 
				
			||||||
      class="form-text text-muted"
 | 
					 | 
				
			||||||
    >
 | 
					 | 
				
			||||||
      Some help text
 | 
					      Some help text
 | 
				
			||||||
    </small>
 | 
					    </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.
 | 
					 * This is free software, licensed under the GNU General Public License v3.
 | 
				
			||||||
 * See /LICENSE for more information.
 | 
					 * See /LICENSE for more information.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/** Bootstrap column size for form fields */
 | 
					/** Bootstrap column size for form fields */
 | 
				
			||||||
// eslint-disable-next-line import/prefer-default-export
 | 
					const formFieldsSize = "card p-4 col-sm-12 col-lg-12 p-0 mb-4";
 | 
				
			||||||
export const formFieldsSize = "col-sm-12 offset-lg-1 col-lg-10";
 | 
					const buttonFormFieldsSize = "col-sm-12 col-lg-12 p-0 mb-3";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export { formFieldsSize, buttonFormFieldsSize };
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										135
									
								
								src/common/ActionButtonWithModal/ActionButtonWithModal.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/common/ActionButtonWithModal/ActionButtonWithModal.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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, useEffect } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { useAPIPost } 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,
 | 
				
			||||||
 | 
					    /** 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,
 | 
				
			||||||
 | 
					    actionUrl,
 | 
				
			||||||
 | 
					    modalTitle,
 | 
				
			||||||
 | 
					    modalMessage,
 | 
				
			||||||
 | 
					    modalActionText,
 | 
				
			||||||
 | 
					    modalActionProps,
 | 
				
			||||||
 | 
					    successMessage,
 | 
				
			||||||
 | 
					    errorMessage,
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					    const [triggered, setTriggered] = useState(false);
 | 
				
			||||||
 | 
					    const [modalShown, setModalShown] = useState(false);
 | 
				
			||||||
 | 
					    const [triggerActionStatus, triggerAction] = useAPIPost(actionUrl);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const [setAlert] = useAlert();
 | 
				
			||||||
 | 
					    useEffect(() => {
 | 
				
			||||||
 | 
					        if (triggerActionStatus.state === API_STATE.SUCCESS) {
 | 
				
			||||||
 | 
					            setAlert(
 | 
				
			||||||
 | 
					                successMessage || _("Action successful."),
 | 
				
			||||||
 | 
					                API_STATE.SUCCESS
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (triggerActionStatus.state === API_STATE.ERROR) {
 | 
				
			||||||
 | 
					            setAlert(errorMessage || _("Action failed."));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }, [triggerActionStatus, setAlert, successMessage, errorMessage]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const actionHandler = () => {
 | 
				
			||||||
 | 
					        setTriggered(true);
 | 
				
			||||||
 | 
					        triggerAction();
 | 
				
			||||||
 | 
					        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 />;
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										84
									
								
								src/common/RichTable/RichTable.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/common/RichTable/RichTable.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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, { useMemo, useState } from "react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    flexRender,
 | 
				
			||||||
 | 
					    getCoreRowModel,
 | 
				
			||||||
 | 
					    getSortedRowModel,
 | 
				
			||||||
 | 
					    getPaginationRowModel,
 | 
				
			||||||
 | 
					    useReactTable,
 | 
				
			||||||
 | 
					} from "@tanstack/react-table";
 | 
				
			||||||
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import RichTableBody from "./RichTableBody";
 | 
				
			||||||
 | 
					import RichTableHeader from "./RichTableHeader";
 | 
				
			||||||
 | 
					import RichTablePagination from "./RichTablePagination";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fallbackData = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RichTable.propTypes = {
 | 
				
			||||||
 | 
					    /** Columns to be displayed in the table */
 | 
				
			||||||
 | 
					    columns: PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					    /** Data to be displayed in the table */
 | 
				
			||||||
 | 
					    data: PropTypes.array.isRequired,
 | 
				
			||||||
 | 
					    /** Whether to display pagination */
 | 
				
			||||||
 | 
					    withPagination: PropTypes.bool,
 | 
				
			||||||
 | 
					    /** Number of rows per page */
 | 
				
			||||||
 | 
					    pageSize: PropTypes.number,
 | 
				
			||||||
 | 
					    /** Index of the current page */
 | 
				
			||||||
 | 
					    pageIndex: PropTypes.number,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function RichTable({
 | 
				
			||||||
 | 
					    columns,
 | 
				
			||||||
 | 
					    data,
 | 
				
			||||||
 | 
					    withPagination,
 | 
				
			||||||
 | 
					    pageSize = 5,
 | 
				
			||||||
 | 
					    pageIndex = 0,
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					    const tableColumns = useMemo(() => columns, [columns]);
 | 
				
			||||||
 | 
					    const [tableData] = useState(data ?? fallbackData);
 | 
				
			||||||
 | 
					    const [sorting, setSorting] = useState([]);
 | 
				
			||||||
 | 
					    const [pagination, setPagination] = useState({
 | 
				
			||||||
 | 
					        pageIndex,
 | 
				
			||||||
 | 
					        pageSize,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const table = useReactTable({
 | 
				
			||||||
 | 
					        data: tableData,
 | 
				
			||||||
 | 
					        columns: tableColumns,
 | 
				
			||||||
 | 
					        getCoreRowModel: getCoreRowModel(),
 | 
				
			||||||
 | 
					        getSortedRowModel: getSortedRowModel(),
 | 
				
			||||||
 | 
					        getPaginationRowModel: getPaginationRowModel(),
 | 
				
			||||||
 | 
					        onPaginationChange: setPagination,
 | 
				
			||||||
 | 
					        onSortingChange: setSorting,
 | 
				
			||||||
 | 
					        state: {
 | 
				
			||||||
 | 
					            sorting,
 | 
				
			||||||
 | 
					            pagination,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <div className="table-responsive">
 | 
				
			||||||
 | 
					            <table className="table table-hover text-nowrap">
 | 
				
			||||||
 | 
					                <RichTableHeader table={table} flexRender={flexRender} />
 | 
				
			||||||
 | 
					                <RichTableBody table={table} flexRender={flexRender} />
 | 
				
			||||||
 | 
					            </table>
 | 
				
			||||||
 | 
					            {withPagination && (
 | 
				
			||||||
 | 
					                <RichTablePagination
 | 
				
			||||||
 | 
					                    table={table}
 | 
				
			||||||
 | 
					                    tablePageSize={pageSize}
 | 
				
			||||||
 | 
					                    allRows={tableData.length}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default RichTable;
 | 
				
			||||||
							
								
								
									
										135
									
								
								src/common/RichTable/RichTable.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/common/RichTable/RichTable.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,135 @@
 | 
				
			|||||||
 | 
					### 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
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```js
 | 
				
			||||||
 | 
					import RichTable from "./RichTable";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<RichTable columns={columns} data={data} withPagination />;
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
							
								
								
									
										48
									
								
								src/common/RichTable/RichTableBody.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/common/RichTable/RichTableBody.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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,
 | 
				
			||||||
 | 
					    flexRender: propTypes.func.isRequired,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function RichTableBody({ table, flexRender }) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <tbody>
 | 
				
			||||||
 | 
					            {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>
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					            })}
 | 
				
			||||||
 | 
					        </tbody>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default RichTableBody;
 | 
				
			||||||
							
								
								
									
										96
									
								
								src/common/RichTable/RichTableHeader.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/common/RichTable/RichTableHeader.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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 {
 | 
				
			||||||
 | 
					    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="thead-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"
 | 
				
			||||||
 | 
					                                    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-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, { 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;
 | 
				
			||||||
							
								
								
									
										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, HWMODES, 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,
 | 
				
			||||||
 | 
					        hwmode: 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={`hwmode-${deviceID}`}
 | 
				
			||||||
 | 
					                        label="GHz"
 | 
				
			||||||
 | 
					                        choices={getHwmodeChoices(formData)}
 | 
				
			||||||
 | 
					                        value={formData.hwmode}
 | 
				
			||||||
 | 
					                        helpText={HELP_TEXTS.hwmode}
 | 
				
			||||||
 | 
					                        inline
 | 
				
			||||||
 | 
					                        onChange={setFormValue((value) => {
 | 
				
			||||||
 | 
					                            // Get the last item in an array of available HT modes
 | 
				
			||||||
 | 
					                            const [best2] = bnds[0].available_htmodes.slice(-1);
 | 
				
			||||||
 | 
					                            const [best5] = bnds[1].available_htmodes.slice(-1);
 | 
				
			||||||
 | 
					                            return {
 | 
				
			||||||
 | 
					                                devices: {
 | 
				
			||||||
 | 
					                                    [deviceIndex]: {
 | 
				
			||||||
 | 
					                                        hwmode: { $set: value },
 | 
				
			||||||
 | 
					                                        channel: { $set: "0" },
 | 
				
			||||||
 | 
					                                        htmode: {
 | 
				
			||||||
 | 
					                                            $set:
 | 
				
			||||||
 | 
					                                                // Set HT mode depending on checked frequency
 | 
				
			||||||
 | 
					                                                value === "11a" ? best5 : best2,
 | 
				
			||||||
 | 
					                                        },
 | 
				
			||||||
 | 
					                                    },
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                            };
 | 
				
			||||||
 | 
					                        })}
 | 
				
			||||||
 | 
					                        {...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.hwmode !== device.hwmode) 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.hwmode !== device.hwmode) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        availableBand.available_htmodes.forEach((availableHtmod) => {
 | 
				
			||||||
 | 
					            htmodeChoices[availableHtmod] = HTMODES[availableHtmod];
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return htmodeChoices;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getHwmodeChoices(device) {
 | 
				
			||||||
 | 
					    return device.available_bands.map((availableBand) => ({
 | 
				
			||||||
 | 
					        label: HWMODES[availableBand.hwmode],
 | 
				
			||||||
 | 
					        value: availableBand.hwmode,
 | 
				
			||||||
 | 
					    }));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getEncryptionChoices(device) {
 | 
				
			||||||
 | 
					    if (device.encryption === "custom") {
 | 
				
			||||||
 | 
					        ENCRYPTIONMODES.custom = _("Custom");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return ENCRYPTIONMODES;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										96
									
								
								src/common/WiFiSettings/WiFiGuestForm.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/common/WiFiSettings/WiFiGuestForm.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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 } from "./constants";
 | 
				
			||||||
 | 
					import WiFiQRCode from "./WiFiQRCode";
 | 
				
			||||||
 | 
					import PasswordInput from "../../bootstrap/PasswordInput";
 | 
				
			||||||
 | 
					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,
 | 
				
			||||||
 | 
					    }),
 | 
				
			||||||
 | 
					    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}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                </>
 | 
				
			||||||
 | 
					            ) : null}
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										107
									
								
								src/common/WiFiSettings/WiFiQRCode.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								src/common/WiFiSettings/WiFiQRCode.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 | 
				
			||||||
 | 
					import PropTypes from "prop-types";
 | 
				
			||||||
 | 
					import QRCode from "qrcode.react";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { createAndDownloadPdf, toQRCodeContent } from "./qrCodeHelpers";
 | 
				
			||||||
 | 
					import Button from "../../bootstrap/Button";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    Modal,
 | 
				
			||||||
 | 
					    ModalBody,
 | 
				
			||||||
 | 
					    ModalFooter,
 | 
				
			||||||
 | 
					    ModalHeader,
 | 
				
			||||||
 | 
					} from "../../bootstrap/Modal";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WiFiQRCode.propTypes = {
 | 
				
			||||||
 | 
					    SSID: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    password: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default function WiFiQRCode({ SSID, password }) {
 | 
				
			||||||
 | 
					    const [modal, setModal] = useState(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					            <button
 | 
				
			||||||
 | 
					                type="button"
 | 
				
			||||||
 | 
					                className="input-group-text"
 | 
				
			||||||
 | 
					                onClick={(e) => {
 | 
				
			||||||
 | 
					                    e.preventDefault();
 | 
				
			||||||
 | 
					                    setModal(true);
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <FontAwesomeIcon
 | 
				
			||||||
 | 
					                    icon="fa-solid fa-qrcode"
 | 
				
			||||||
 | 
					                    title={_("Show QR code")}
 | 
				
			||||||
 | 
					                    aria-label={_("Show QR code")}
 | 
				
			||||||
 | 
					                    className="text-secondary"
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </button>
 | 
				
			||||||
 | 
					            {modal ? (
 | 
				
			||||||
 | 
					                <QRCodeModal
 | 
				
			||||||
 | 
					                    setShown={setModal}
 | 
				
			||||||
 | 
					                    shown={modal}
 | 
				
			||||||
 | 
					                    SSID={SSID}
 | 
				
			||||||
 | 
					                    password={password}
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            ) : null}
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QRCodeModal.propTypes = {
 | 
				
			||||||
 | 
					    SSID: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    password: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    shown: PropTypes.bool.isRequired,
 | 
				
			||||||
 | 
					    setShown: PropTypes.func.isRequired,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function QRCodeModal({ shown, setShown, SSID, password }) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <Modal setShown={setShown} shown={shown}>
 | 
				
			||||||
 | 
					            <ModalHeader setShown={setShown} title={_("Wi-Fi QR Code")} />
 | 
				
			||||||
 | 
					            <ModalBody>
 | 
				
			||||||
 | 
					                <QRCode
 | 
				
			||||||
 | 
					                    className="d-block mx-auto img-logo-black"
 | 
				
			||||||
 | 
					                    renderAs="svg"
 | 
				
			||||||
 | 
					                    value={toQRCodeContent(SSID, password)}
 | 
				
			||||||
 | 
					                    level="M"
 | 
				
			||||||
 | 
					                    size={350}
 | 
				
			||||||
 | 
					                    includeMargin
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					            </ModalBody>
 | 
				
			||||||
 | 
					            <ModalFooter>
 | 
				
			||||||
 | 
					                <Button
 | 
				
			||||||
 | 
					                    className="btn-secondary"
 | 
				
			||||||
 | 
					                    onClick={(e) => {
 | 
				
			||||||
 | 
					                        e.preventDefault();
 | 
				
			||||||
 | 
					                        setShown(false);
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    {_("Close")}
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					                <Button
 | 
				
			||||||
 | 
					                    className="btn-primary"
 | 
				
			||||||
 | 
					                    onClick={(e) => {
 | 
				
			||||||
 | 
					                        e.preventDefault();
 | 
				
			||||||
 | 
					                        createAndDownloadPdf(SSID, password);
 | 
				
			||||||
 | 
					                    }}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                    <FontAwesomeIcon
 | 
				
			||||||
 | 
					                        icon="fa-solid fa-file-download"
 | 
				
			||||||
 | 
					                        className="me-2"
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                    {_("Download PDF")}
 | 
				
			||||||
 | 
					                </Button>
 | 
				
			||||||
 | 
					            </ModalFooter>
 | 
				
			||||||
 | 
					        </Modal>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										122
									
								
								src/common/WiFiSettings/WiFiSettings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/common/WiFiSettings/WiFiSettings.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,122 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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 ResetWiFiSettings from "./ResetWiFiSettings";
 | 
				
			||||||
 | 
					import WiFiForm from "./WiFiForm";
 | 
				
			||||||
 | 
					import ForisForm from "../../form/components/ForisForm";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WiFiSettings.propTypes = {
 | 
				
			||||||
 | 
					    ws: PropTypes.object.isRequired,
 | 
				
			||||||
 | 
					    endpoint: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    resetEndpoint: PropTypes.string.isRequired,
 | 
				
			||||||
 | 
					    hasGuestNetwork: PropTypes.bool,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function WiFiSettings({ ws, endpoint, resetEndpoint, hasGuestNetwork }) {
 | 
				
			||||||
 | 
					    return (
 | 
				
			||||||
 | 
					        <>
 | 
				
			||||||
 | 
					            <ForisForm
 | 
				
			||||||
 | 
					                ws={ws}
 | 
				
			||||||
 | 
					                forisConfig={{
 | 
				
			||||||
 | 
					                    endpoint,
 | 
				
			||||||
 | 
					                    wsModule: "wifi",
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					                prepData={prepData}
 | 
				
			||||||
 | 
					                prepDataToSubmit={prepDataToSubmit}
 | 
				
			||||||
 | 
					                validator={validator}
 | 
				
			||||||
 | 
					            >
 | 
				
			||||||
 | 
					                <WiFiForm hasGuestNetwork={hasGuestNetwork} />
 | 
				
			||||||
 | 
					            </ForisForm>
 | 
				
			||||||
 | 
					            <ResetWiFiSettings ws={ws} endpoint={resetEndpoint} />
 | 
				
			||||||
 | 
					        </>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function prepData(formData) {
 | 
				
			||||||
 | 
					    formData.devices.forEach((device, idx) => {
 | 
				
			||||||
 | 
					        formData.devices[idx].channel = device.channel.toString();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return formData;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function prepDataToSubmit(formData) {
 | 
				
			||||||
 | 
					    formData.devices.forEach((device, idx) => {
 | 
				
			||||||
 | 
					        delete device.available_bands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        formData.devices[idx].channel = parseInt(device.channel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!device.enabled) {
 | 
				
			||||||
 | 
					            formData.devices[idx] = { id: device.id, enabled: false };
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!device.guest_wifi.enabled)
 | 
				
			||||||
 | 
					            formData.devices[idx].guest_wifi = { enabled: false };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (device.encryption === "WPA2") {
 | 
				
			||||||
 | 
					            delete formData.devices[idx].ieee80211w_disabled;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return formData;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function byteCount(string) {
 | 
				
			||||||
 | 
					    const buffer = Buffer.from(string, "utf-8");
 | 
				
			||||||
 | 
					    const count = buffer.byteLength;
 | 
				
			||||||
 | 
					    return count;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function validator(formData) {
 | 
				
			||||||
 | 
					    const formErrors = formData.devices.map((device) => {
 | 
				
			||||||
 | 
					        if (!device.enabled) return {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const errors = {};
 | 
				
			||||||
 | 
					        if (device.SSID.length > 32)
 | 
				
			||||||
 | 
					            errors.SSID = _("SSID can't be longer than 32 symbols");
 | 
				
			||||||
 | 
					        if (device.SSID.length === 0) errors.SSID = _("SSID can't be empty");
 | 
				
			||||||
 | 
					        if (byteCount(device.SSID) > 32)
 | 
				
			||||||
 | 
					            errors.SSID = _("SSID can't be longer than 32 bytes");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (device.password.length < 8)
 | 
				
			||||||
 | 
					            errors.password = _("Password must contain at least 8 symbols");
 | 
				
			||||||
 | 
					        if (device.password.length >= 64)
 | 
				
			||||||
 | 
					            errors.password = _(
 | 
				
			||||||
 | 
					                "Password must not contain more than 63 symbols"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!device.guest_wifi.enabled) return errors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const guest_wifi_errors = {};
 | 
				
			||||||
 | 
					        if (device.guest_wifi.SSID.length > 32)
 | 
				
			||||||
 | 
					            guest_wifi_errors.SSID = _("SSID can't be longer than 32 symbols");
 | 
				
			||||||
 | 
					        if (device.guest_wifi.SSID.length === 0)
 | 
				
			||||||
 | 
					            guest_wifi_errors.SSID = _("SSID can't be empty");
 | 
				
			||||||
 | 
					        if (byteCount(device.guest_wifi.SSID) > 32)
 | 
				
			||||||
 | 
					            guest_wifi_errors.SSID = _("SSID can't be longer than 32 bytes");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (device.guest_wifi.password.length < 8)
 | 
				
			||||||
 | 
					            guest_wifi_errors.password = _(
 | 
				
			||||||
 | 
					                "Password must contain at least 8 symbols"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        if (device.guest_wifi.password.length >= 64)
 | 
				
			||||||
 | 
					            guest_wifi_errors.password = _(
 | 
				
			||||||
 | 
					                "Password must not contain more than 63 symbols"
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (guest_wifi_errors.SSID || guest_wifi_errors.password) {
 | 
				
			||||||
 | 
					            errors.guest_wifi = guest_wifi_errors;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return errors;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    return JSON.stringify(formErrors).match(/\[[{},?]+\]/) ? null : formErrors;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default WiFiSettings;
 | 
				
			||||||
							
								
								
									
										55
									
								
								src/common/WiFiSettings/__tests__/ResetWiFiSettings.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/common/WiFiSettings/__tests__/ResetWiFiSettings.test.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * 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 React from "react";
 | 
				
			||||||
 | 
					import { render, fireEvent, wait } from "customTestRender";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import mockAxios from "jest-mock-axios";
 | 
				
			||||||
 | 
					import WebSockets from "webSockets/WebSockets";
 | 
				
			||||||
 | 
					import { mockJSONError } from "testUtils/network";
 | 
				
			||||||
 | 
					import { mockSetAlert } from "testUtils/alertContextMock";
 | 
				
			||||||
 | 
					import { ALERT_TYPES } from "../../../bootstrap/Alert";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import ResetWiFiSettings from "../ResetWiFiSettings";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("<ResetWiFiSettings/>", () => {
 | 
				
			||||||
 | 
					    const webSockets = new WebSockets();
 | 
				
			||||||
 | 
					    const endpoint = "/reforis/api/wifi-reset";
 | 
				
			||||||
 | 
					    let getAllByText;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    beforeEach(() => {
 | 
				
			||||||
 | 
					        ({ getAllByText } = render(
 | 
				
			||||||
 | 
					            <ResetWiFiSettings ws={webSockets} endpoint={endpoint} />
 | 
				
			||||||
 | 
					        ));
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("should display alert on open ports - success", async () => {
 | 
				
			||||||
 | 
					        fireEvent.click(getAllByText("Reset Wi-Fi Settings")[1]);
 | 
				
			||||||
 | 
					        expect(mockAxios.post).toBeCalledWith(
 | 
				
			||||||
 | 
					            endpoint,
 | 
				
			||||||
 | 
					            undefined,
 | 
				
			||||||
 | 
					            expect.anything()
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        mockAxios.mockResponse({ data: { foo: "bar" } });
 | 
				
			||||||
 | 
					        await wait(() =>
 | 
				
			||||||
 | 
					            expect(mockSetAlert).toBeCalledWith(
 | 
				
			||||||
 | 
					                "Wi-Fi settings are set to defaults.",
 | 
				
			||||||
 | 
					                ALERT_TYPES.SUCCESS
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("should display alert on open ports - failure", async () => {
 | 
				
			||||||
 | 
					        fireEvent.click(getAllByText("Reset Wi-Fi Settings")[1]);
 | 
				
			||||||
 | 
					        mockJSONError();
 | 
				
			||||||
 | 
					        await wait(() =>
 | 
				
			||||||
 | 
					            expect(mockSetAlert).toBeCalledWith(
 | 
				
			||||||
 | 
					                "An error occurred during resetting Wi-Fi settings."
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user