Merge branch 'develop' into feature/markdown
1075
package-lock.json
generated
@ -23,6 +23,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-hotkeys": "^2.0.0",
|
||||
"react-i18next": "^11.18.3",
|
||||
"react-loading-skeleton": "^3.1.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-redux": "^8.0.2",
|
||||
"react-router-dom": "^6.3.0",
|
||||
@ -32,6 +33,7 @@
|
||||
"remark-code-blocks": "^2.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"storybook-addon-pseudo-states": "^1.15.1",
|
||||
"swiper": "^8.3.2",
|
||||
"tailwindcss": "^3.1.7",
|
||||
"tsconfig-paths-webpack-plugin": "^4.0.0",
|
||||
"typescript": "^4.7.4",
|
||||
@ -96,6 +98,7 @@
|
||||
"@storybook/react": "^6.5.9",
|
||||
"@storybook/testing-library": "^0.0.13",
|
||||
"@svgr/webpack": "^6.3.1",
|
||||
"@tailwindcss/line-clamp": "^0.4.0",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^13.3.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
|
3
src/assets/svg/agricultural.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5495 18.1523C20.0358 21.1083 21.1165 25.66 19.7396 29.9152H30.4102C31.4258 26.9366 34.4857 25.2281 37.9199 26.6722L39.1536 19.4836C39.362 18.2715 38.138 17.2497 36.8978 17.2497H30.0098V11.8372C30.0098 11.415 29.668 11.0636 29.2448 11.0442V8.24616H28.1901V11.0442C27.7767 11.0733 27.4479 11.4182 27.4479 11.834V17.2497H22.0703C23.099 12.4304 21.8978 10.7831 21.2044 5.88004C20.931 5.1773 20.3939 5.01934 19.7884 5H7.25586C6.6276 5.00645 6.19141 5.31591 5.96354 5.96063L5.11393 14.068L3.42122 16.0473C2.68229 16.8951 2.89714 16.6984 3.57747 17.7106C3.69466 17.6268 3.81185 17.543 3.93229 17.4656C7.49349 15.1576 13.1966 15.5734 16.5495 18.1523ZM29.0951 6.74397C29.1439 6.69561 29.222 6.69239 29.2741 6.74074C29.3229 6.7891 29.3262 6.86646 29.2773 6.91804C29.1829 7.01475 29.2188 7.15336 29.2513 7.28231C29.3099 7.50151 29.362 7.70137 29.0951 7.85288C29.0332 7.88834 28.9583 7.86578 28.9225 7.80775C28.8867 7.74973 28.9128 7.67236 28.9714 7.6369C29.069 7.5821 29.0397 7.46928 29.0072 7.34678C28.9583 7.15336 28.903 6.94383 29.0951 6.74397ZM28.3333 6.74397C28.3822 6.69561 28.4603 6.69239 28.5124 6.74074C28.5612 6.7891 28.5645 6.86646 28.5156 6.91804C28.4212 7.01475 28.457 7.15336 28.4896 7.28231C28.5482 7.50151 28.6003 7.70137 28.3333 7.85288C28.2715 7.88834 28.1966 7.86578 28.1608 7.80775C28.125 7.7465 28.1478 7.67236 28.2064 7.6369C28.304 7.5821 28.2747 7.46928 28.2422 7.34678C28.1934 7.15336 28.138 6.94383 28.3333 6.74397ZM14.4694 7.68203V13.6618C15.8659 13.9745 17.2461 14.4032 18.6035 14.9512C19.3132 15.2381 20.1497 14.313 20 13.5683L19.0885 9.06173C18.9388 8.31708 18.4603 7.67881 17.6921 7.67881H14.4694V7.68203ZM13.0827 13.3878V7.68203H9.63216C8.86393 7.68203 8.41146 8.32353 8.23568 9.06496L7.63021 11.6503C7.45768 12.3917 8.25846 13.0203 9.02669 13.0332C10.3939 13.0461 11.7448 13.1654 13.0827 13.3878ZM35.7585 27.2428C38.099 27.2428 40 29.1222 40 31.4432C40 33.7609 38.1022 35.6403 35.7585 35.6403C33.4147 35.6403 31.5202 33.7609 31.5202 31.4432C31.5202 29.1222 33.418 27.2428 35.7585 27.2428ZM9.05925 17.8267C14.0625 17.8267 18.1185 21.8433 18.1185 26.798C18.1185 31.7526 14.0625 35.7692 9.05925 35.7692C4.05599 35.7692 0 31.7526 0 26.798C0 21.8433 4.05599 17.8267 9.05925 17.8267ZM9.05925 21.9755C11.748 21.9755 13.929 24.1353 13.929 26.798C13.929 29.4607 11.748 31.6205 9.05925 31.6205C6.37044 31.6205 4.18945 29.4607 4.18945 26.798C4.18945 24.1353 6.37044 21.9755 9.05925 21.9755ZM35.7585 29.3124C36.9466 29.3124 37.9069 30.2666 37.9069 31.4399C37.9069 32.6166 36.9434 33.5675 35.7585 33.5675C34.5703 33.5675 33.61 32.6133 33.61 31.4399C33.61 30.2666 34.5736 29.3124 35.7585 29.3124ZM29.4564 19.8737H34.8112L34.6908 20.4862H29.3522L29.4564 19.8737ZM27.5781 23.1746H34.8112L34.6908 23.7871H27.4707L27.5781 23.1746ZM28.2454 21.5242H34.8145L34.694 22.1366H28.138L28.2454 21.5242Z" fill="#8C8C8C"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
7
src/assets/svg/background.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg width="1187" height="1234" viewBox="0 0 1187 1234" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M408.998 745.256C206.829 467.159 -0.745132 678.488 53.4493 872.858C189.728 1182.79 541.828 1319.16 839.898 1177.41C942.643 1128.57 1029.84 1050.24 1091.25 951.643C934.96 1103.83 651.196 1078.4 408.998 745.208V745.256Z" fill="#F0F0F0"/>
|
||||
<path d="M589.732 553.768C311.677 115.242 0.000483082 348.859 0.000483082 616.83C-0.108684 705.549 18.2856 793.237 53.9153 873.85C16.9159 692.296 217.547 552.363 404.339 812.896C675.684 1191.27 941.763 1124.69 1090.72 952.538C1138.53 876.188 1169.42 789.736 1181.14 699.443V700.63C1117.18 906.92 834.959 940.474 589.732 553.768Z" fill="#F5F5F5"/>
|
||||
<path d="M780.298 357.556C573.819 3.33809 311.98 35.3415 134.904 225.06C47.4877 335.363 -0.21692 473.901 0.000741558 616.829C18.6403 357.604 320.112 177.116 593.041 616.829C835.635 1008.16 1137.87 906.216 1180.86 701.744V699.781C1184.64 671.939 1186.24 643.826 1185.66 615.715V587.685C1080.6 665.864 942.439 635.678 780.252 357.532L780.298 357.556Z" fill="#FAFAFA"/>
|
||||
<path d="M781.72 420.618C970.818 736.727 1133.84 655.859 1186.06 588.242C1183.96 540.989 1176.61 494.146 1164.16 448.648C1076.01 450.611 1055.46 435.275 968.721 297.983C836.17 86.7264 669.183 -55.8716 377.451 42.2463C282.888 80.7758 199.359 143.787 134.788 225.303C335.256 33.4762 581.228 85.5393 781.72 420.618Z" fill="white"/>
|
||||
<path d="M955.787 325.116C1042.25 462.675 1094.88 469.676 1164.15 448.672C1074.33 120.595 745.743 -69.6327 430.245 23.761C412.352 29.0424 394.753 35.204 377.449 42.2459C638.053 -43.2499 822.887 113.86 955.787 325.116Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.6 KiB |
6
src/assets/svg/duplicate.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M8 20.5V8.5C8 7.39543 8.89543 6.5 10 6.5H15.5H18C19.1046 6.5 20 7.39543 20 8.5L20 11.3V20.5C20 21.6046 19.1046 22.5 18 22.5H10C8.89543 22.5 8 21.6046 8 20.5Z" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M11 14.5H17" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M11 18.5H17" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M16 6.3L16 4.50001C16 3.39543 15.1046 2.5 14 2.5H11.5H6C4.89543 2.5 4 3.39543 4 4.5V16.5C4 17.6046 4.89543 18.5 6 18.5H8" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 639 B |
3
src/assets/svg/fundamental.svg
Normal file
After Width: | Height: | Size: 6.0 KiB |
7
src/assets/svg/grid.svg
Normal file
@ -0,0 +1,7 @@
|
||||
<svg viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="24" height="24" transform="translate(0 0.5)" fill="white"/>
|
||||
<rect x="2" y="7.5" width="20" height="2" rx="1" stroke="none"/>
|
||||
<rect x="2" y="15.5" width="20" height="2" rx="1" stroke="none"/>
|
||||
<rect x="7" y="22.5" width="20" height="2" rx="1" transform="rotate(-90 7 22.5)" stroke="none"/>
|
||||
<rect x="15" y="22.5" width="20" height="2" rx="1" transform="rotate(-90 15 22.5)" stroke="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 480 B |
10
src/assets/svg/humanitarian.svg
Normal file
After Width: | Height: | Size: 7.8 KiB |
10
src/assets/svg/medicine.svg
Normal file
@ -0,0 +1,10 @@
|
||||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_22_4699)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M26.3027 20.5378C27.0006 19.6363 28.0276 19.0479 29.1589 18.9015C30.2901 18.7552 31.4332 19.0628 32.3379 19.7571C33.2409 20.4539 33.8303 21.4802 33.9768 22.6107C34.1233 23.7413 33.8149 24.8838 33.1193 25.7874C30.1398 29.6467 27.1603 33.4712 24.1777 37.3369C23.4852 38.2341 22.4647 38.8199 21.3402 38.9657C20.2158 39.1115 19.0794 38.8054 18.1807 38.1145C17.2849 37.4211 16.7003 36.4016 16.5544 35.2788C16.4086 34.156 16.7135 33.0212 17.4024 32.1223C20.3851 28.2502 23.3169 24.4099 26.2964 20.5378H26.3027ZM7.40947 27.0093H18.7556L19.9499 25.4573C21.3698 23.6038 22.7864 21.7598 24.6891 19.2905C25.1615 18.6793 25.7431 18.1606 26.4044 17.7607V13.511C26.4361 10.9307 25.1084 8.07422 23.7108 6.21118L23.2661 5.61451H24.0252C24.4465 5.61451 24.8504 5.44731 25.1483 5.14971C25.4461 4.8521 25.6134 4.44847 25.6134 4.02759V1.58692C25.6093 1.16732 25.4406 0.766082 25.1437 0.46937C24.8467 0.172657 24.4452 0.00413754 24.0252 3.30446e-06H8.55932C8.13608 -0.00085484 7.72965 0.165459 7.42859 0.462705C7.12754 0.759951 6.95625 1.16404 6.95206 1.58692V4.01172C6.95206 4.4326 7.11939 4.83624 7.41724 5.13384C7.71508 5.43144 8.11905 5.59864 8.54027 5.59864H9.12472C8.96273 5.8208 8.80708 6.03662 8.63873 6.25244C7.0283 8.42969 5.83715 11.2163 6.0182 14.0537V31.5098C6.00916 32.1551 6.24918 32.7791 6.68842 33.2522C7.12041 33.6711 7.74934 33.887 8.61332 33.8616H14.531C14.5668 33.596 14.6198 33.3331 14.6898 33.0745C14.7427 32.8744 14.8063 32.6774 14.8804 32.4841H8.96908C8.55495 32.5135 8.14605 32.3777 7.83193 32.1064C7.60567 31.8633 7.46706 31.5517 7.43805 31.221C7.43805 31.1511 7.40947 27.0125 7.40947 27.0125V27.0093ZM15.3028 15.9707H17.1166C17.28 15.9707 17.4367 16.0356 17.5523 16.151C17.6679 16.2665 17.7328 16.4231 17.7328 16.5864V18.6558H19.8038C19.9667 18.6558 20.1229 18.7202 20.2384 18.835C20.3539 18.9498 20.4192 19.1056 20.42 19.2683V21.0774C20.42 21.2407 20.3551 21.3973 20.2395 21.5128C20.124 21.6282 19.9672 21.6931 19.8038 21.6931H17.7264V23.7625C17.7256 23.9241 17.6612 24.079 17.5471 24.1936C17.433 24.3082 17.2783 24.3733 17.1166 24.375H15.3028C15.1399 24.375 14.9837 24.3106 14.8682 24.1958C14.7527 24.081 14.6874 23.9252 14.6866 23.7625V21.6931H12.6156C12.4527 21.6923 12.2968 21.627 12.1819 21.5117C12.067 21.3963 12.0025 21.2401 12.0025 21.0774V19.2683C12.0034 19.1061 12.0682 18.9508 12.183 18.8361C12.2978 18.7214 12.4533 18.6566 12.6156 18.6558H14.6866V16.5864C14.6866 16.4231 14.7515 16.2665 14.8671 16.151C14.9827 16.0356 15.1394 15.9707 15.3028 15.9707ZM24.9019 13.3301H7.52064C7.52064 12.6731 7.47935 13.3586 7.54923 12.7461C7.87798 10.5869 8.71277 8.53587 9.98553 6.76026C10.2428 6.41748 10.5065 6.05884 10.7796 5.67481H21.6143C21.8558 6.01123 22.1162 6.35718 22.3735 6.70313C23.5456 8.2583 24.6605 10.8291 24.867 12.7683C24.8892 12.9587 24.9019 13.3364 24.9019 13.3364V13.3301ZM27.2175 31.8081L22.0559 27.8408L18.3204 32.6904C17.7956 33.3752 17.5635 34.2396 17.6748 35.0949C17.7861 35.9501 18.2318 36.7266 18.9144 37.2544C19.5995 37.7736 20.4619 38.0022 21.3145 37.8905C22.1671 37.7788 22.9413 37.3359 23.4694 36.6577L27.2175 31.8145V31.8081Z" fill="#8C8C8C"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_22_4699">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 3.3 KiB |
14
src/assets/svg/pages.svg
Normal file
@ -0,0 +1,14 @@
|
||||
<svg viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_889_22377)">
|
||||
<path d="M20 23.5V21.3284C20 20.798 19.7893 20.2893 19.4142 19.9142L14.5858 15.0858C14.2107 14.7107 13.702 14.5 13.1716 14.5H6C4.89543 14.5 4 15.3954 4 16.5V23.5" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M4 1.5V4.5C4 5.60457 4.89543 6.5 6 6.5H18C19.1046 6.5 20 5.60457 20 4.5V1.5" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M14 20.5L19 20.5" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M14 15.5V20.5" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||
<path d="M4 10.5H20" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_889_22377">
|
||||
<rect width="24" height="24" fill="white" transform="translate(0 0.5)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 846 B |
4
src/assets/svg/rotate.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23 4.5V10.5H17" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M20.49 15.5C19.84 17.3399 18.6096 18.9187 16.9842 19.9985C15.3588 21.0783 13.4265 21.6006 11.4784 21.4866C9.53038 21.3726 7.67216 20.6286 6.18376 19.3667C4.69536 18.1047 3.65743 16.3932 3.22637 14.4901C2.79531 12.5869 2.99448 10.5952 3.79386 8.81508C4.59325 7.03496 5.94954 5.56288 7.65836 4.62065C9.36717 3.67843 11.3359 3.31711 13.268 3.59116C15.2 3.8652 16.9906 4.75975 18.37 6.14001L23 10.5" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 667 B |
@ -1,3 +0,0 @@
|
||||
<svg width="12" height="8" viewBox="0 0 12 8" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.1315 0.6875H0.868974C0.561161 0.6875 0.389286 1.0125 0.579911 1.23438L5.71116 7.18437C5.85804 7.35469 6.14085 7.35469 6.28929 7.18437L11.4205 1.23438C11.6112 1.0125 11.4393 0.6875 11.1315 0.6875Z" fill="#262626"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 327 B |
3
src/assets/svg/socials.svg
Normal file
After Width: | Height: | Size: 5.3 KiB |
3
src/assets/svg/technics-and-techology.svg
Normal file
After Width: | Height: | Size: 7.8 KiB |
4
src/assets/svg/text-transition.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg viewBox="0 0 24 25" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 15.5L8.00001 19.5L4 15.5" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M4 5.5H17C18.6569 5.5 20 6.84315 20 8.5V8.5C20 10.1569 18.6569 11.5 17 11.5H12C9.79086 11.5 8 13.2909 8 15.5V19.5" fill="none" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
After Width: | Height: | Size: 375 B |
75
src/components/Article/Article.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import React, { useState } from "react";
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* imports Article parts */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import { ArticleTitle } from "./ArticleParts/ArticleTitle";
|
||||
import { ArticleBreadcumbs } from "./ArticleParts/ArticleBreadcumbs";
|
||||
import { ArticleAuthors } from "./ArticleParts/ArticleAuthors";
|
||||
import { ArticleKeywords } from "./ArticleParts/ArticleKeywords";
|
||||
import { ArticleInteractionButtons } from "./ArticleParts/ArticleInteractionButtons";
|
||||
import { ArticleDescription } from "./ArticleParts/ArticleDescription";
|
||||
import { ArticleSubscriptionsButtons } from "./ArticleParts/ArticleSubscriptionsButton";
|
||||
|
||||
/**
|
||||
* Reduces a sequence of names to initials.
|
||||
* @param {String} name Space Delimited sequence of names.
|
||||
* @param {String} sep A period separating the initials.
|
||||
* @param {String} trail A period ending the initials.
|
||||
* @param {String} hyph A hypen separating double names.
|
||||
* @return {String} Properly formatted initials.
|
||||
*/
|
||||
type ArticleTileExtentions = {
|
||||
Title?: {
|
||||
children?: string;
|
||||
className?: string;
|
||||
};
|
||||
Breadcumbs?: {
|
||||
children?: string[];
|
||||
highlightLAstChild?: boolean;
|
||||
};
|
||||
Authors?: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
emphasis?: "low" | "high";
|
||||
};
|
||||
Keywords?: {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
emphasis?: "low" | "high";
|
||||
};
|
||||
Description?: {
|
||||
children?: React.ReactNode;
|
||||
emphasis?: "low" | "high";
|
||||
isShowing?: boolean;
|
||||
};
|
||||
InteractionButtons?: {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
emphasis?: "high" | "low";
|
||||
};
|
||||
SubscriptionButtons?: {
|
||||
className?: string;
|
||||
};
|
||||
};
|
||||
|
||||
type ArticleTileProps = {
|
||||
/** Description of prop "foo". */
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export function Article({
|
||||
/** Description of prop "foo". */
|
||||
children,
|
||||
}: ArticleTileProps & ArticleTileExtentions) {
|
||||
const [isShowing, setIsShowing] = useState(false);
|
||||
|
||||
return <div className="flex flex-col w-full">{children}</div>;
|
||||
}
|
||||
|
||||
Article.Title = ArticleTitle;
|
||||
Article.Breadcumbs = ArticleBreadcumbs;
|
||||
Article.Authors = ArticleAuthors;
|
||||
Article.Keywords = ArticleKeywords;
|
||||
Article.InteractionButtons = ArticleInteractionButtons;
|
||||
Article.Description = ArticleDescription;
|
||||
Article.SubscriptionsButtons = ArticleSubscriptionsButtons;
|
53
src/components/Article/ArticleParts/ArticleAuthors.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { useMemo } from "react";
|
||||
import { RouterLink } from "components/typography/RouterLink";
|
||||
import Typography from "components/typography/Typography";
|
||||
import classNames from "classnames";
|
||||
import { SVGUser } from "components/icons";
|
||||
|
||||
type AuthorsProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
emphasis?: "low" | "high";
|
||||
linkTo?: string;
|
||||
};
|
||||
|
||||
export function ArticleAuthors({
|
||||
children,
|
||||
|
||||
className,
|
||||
emphasis = "high",
|
||||
linkTo = "#",
|
||||
}: AuthorsProps) {
|
||||
const authors = React.Children.map(children, (author, i) => {
|
||||
return (
|
||||
<RouterLink to={linkTo}>
|
||||
<Typography
|
||||
fontWeightVariant={emphasis === "high" ? "medium" : "normal"}
|
||||
className={classNames(
|
||||
"hover:text-blue-600",
|
||||
{
|
||||
"text-xs leading-5 text-gray-500": emphasis === "low",
|
||||
"text-lg leading-6 text-gray-900": emphasis === "high",
|
||||
},
|
||||
{
|
||||
"mr-1": i != React.Children.count(children) - 1,
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
{author}
|
||||
{i != React.Children.count(children) - 1 ? "," : null}
|
||||
</Typography>
|
||||
</RouterLink>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-row items-center">
|
||||
<SVGUser className="w-6 fill-gray-500 stroke-gray-500"></SVGUser>
|
||||
<div className="ml-2 flex flex-row">{authors}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ArticleAuthors.displayName = "ArticleAuthors";
|
31
src/components/Article/ArticleParts/ArticleBreadcumbs.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
import Breadcrumbs from "components/breadcrumbs";
|
||||
import Logo from "components/Logo";
|
||||
import classNames from "classnames";
|
||||
|
||||
type ArticleBreadcumbsProps = {
|
||||
emphasis?: "high" | "low";
|
||||
children?: string[];
|
||||
};
|
||||
|
||||
export function ArticleBreadcumbs({
|
||||
children,
|
||||
emphasis = "high", //Emphasis high uses when we display article page
|
||||
}: ArticleBreadcumbsProps) {
|
||||
return (
|
||||
<Breadcrumbs
|
||||
divider="slash"
|
||||
className={classNames(
|
||||
"text-xs leading-4 text-gray-700 flex flex-row items-center",
|
||||
{
|
||||
"last:text-gray-900": emphasis === "high",
|
||||
}
|
||||
)}
|
||||
>
|
||||
{emphasis === "high" ? <Logo className="w-4" fillColors="gray" /> : null}
|
||||
{children}
|
||||
</Breadcrumbs>
|
||||
);
|
||||
}
|
||||
|
||||
ArticleBreadcumbs.displayName = "ArticleBreadcumbs";
|
38
src/components/Article/ArticleParts/ArticleDescription.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import React from "react";
|
||||
import Typography from "components/typography/Typography";
|
||||
import { Transition } from "@headlessui/react";
|
||||
|
||||
type ArticleDescriptionProps = {
|
||||
children?: React.ReactNode;
|
||||
emphasis?: "low" | "high";
|
||||
isShowing?: boolean;
|
||||
};
|
||||
|
||||
export function ArticleDescription({
|
||||
children,
|
||||
emphasis = "high",
|
||||
isShowing = false,
|
||||
}: ArticleDescriptionProps) {
|
||||
return emphasis === "low" ? (
|
||||
<div className="overflow-hidden">
|
||||
<Transition
|
||||
appear
|
||||
show={isShowing}
|
||||
enter="ease-in-out duration-200"
|
||||
enterFrom="-translate-y-full"
|
||||
enterTo="translate-y-0"
|
||||
leave="ease-in-out duration-200"
|
||||
leaveFrom="translate-y-0"
|
||||
leaveTo="-translate-y-full"
|
||||
>
|
||||
<Typography className="text-base text-gray-900">{children}</Typography>
|
||||
</Transition>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<Typography className="text-base text-gray-900">{children}</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ArticleDescription.displayName = "ArticleDescription";
|
@ -0,0 +1,87 @@
|
||||
import React from "react";
|
||||
import { Button } from "components/Button/Button";
|
||||
import Typography from "components/typography/Typography";
|
||||
import {
|
||||
SVGArrowDown,
|
||||
SVGArrowUp,
|
||||
SVGCite,
|
||||
SVGFiletext,
|
||||
SVGDownload,
|
||||
SVGShare,
|
||||
SVGFolder,
|
||||
} from "components/icons";
|
||||
import classNames from "classnames";
|
||||
|
||||
const interactionButtonsStore = [
|
||||
{
|
||||
icon: <SVGFiletext />,
|
||||
title: "Read file",
|
||||
buttonEmphasis: "high",
|
||||
iconClassName: "h-6 fill-white stroke-white",
|
||||
},
|
||||
{
|
||||
icon: <SVGDownload />,
|
||||
title: "Download",
|
||||
buttonEmphasis: "low",
|
||||
iconClassName: "w-6 fill-gray-900 stroke-gray-900",
|
||||
},
|
||||
{
|
||||
icon: <SVGCite />,
|
||||
title: "Cite",
|
||||
buttonEmphasis: "low",
|
||||
iconClassName: "w-6 fill-gray-900 stroke-gray-900",
|
||||
},
|
||||
{
|
||||
icon: <SVGShare />,
|
||||
title: "Share",
|
||||
buttonEmphasis: "low",
|
||||
iconClassName: "w-6 fill-gray-900 stroke-gray-900",
|
||||
},
|
||||
];
|
||||
|
||||
type ArticleButtonProps = {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
emphasis?: "high" | "low";
|
||||
} & Omit<React.ComponentPropsWithoutRef<"button">, "">;
|
||||
|
||||
export function ArticleInteractionButtons({
|
||||
children,
|
||||
className,
|
||||
emphasis, //to change displaying of component
|
||||
...props
|
||||
}: ArticleButtonProps) {
|
||||
const abstractButton = (
|
||||
<Button emphasis="medium" className="text-sm leading-4 items-center px-3">
|
||||
<Typography fontWeightVariant="bold" className="pr-2">
|
||||
Abstract
|
||||
</Typography>
|
||||
<Button.Icon>
|
||||
<SVGArrowDown className="w-4 fill-blue-700 stroke-blue-700" />
|
||||
</Button.Icon>
|
||||
</Button>
|
||||
);
|
||||
|
||||
const fileInteractionButtons = interactionButtonsStore.map((button) => {
|
||||
return (
|
||||
<Button
|
||||
emphasis={button.buttonEmphasis === "high" ? "high" : "low"}
|
||||
className="h-max px-2"
|
||||
>
|
||||
<Button.Icon>
|
||||
{React.cloneElement(button.icon, { className: button.iconClassName })}
|
||||
</Button.Icon>
|
||||
{emphasis === "high" ? <Typography>{button.title}</Typography> : null}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="flex flex-row">
|
||||
{emphasis === "low" && !children ? abstractButton : null}
|
||||
{children ? children : fileInteractionButtons}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ArticleInteractionButtons.displayName = "ArticleInteractionButtons";
|
55
src/components/Article/ArticleParts/ArticleKeywords.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
import Typography from "components/typography/Typography";
|
||||
import { SVGKey } from "components/icons";
|
||||
import { RouterLink } from "components/typography/RouterLink";
|
||||
import classNames from "classnames";
|
||||
|
||||
type KeywordsProps = {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
emphasis?: "low" | "high";
|
||||
linkTo?: string;
|
||||
};
|
||||
|
||||
export function ArticleKeywords({
|
||||
children,
|
||||
className,
|
||||
emphasis = "high",
|
||||
linkTo = "#",
|
||||
}: KeywordsProps) {
|
||||
const keywords = React.Children.map(children, (keyword, i) => {
|
||||
return (
|
||||
<RouterLink to={linkTo}>
|
||||
<div
|
||||
className={classNames(
|
||||
"mr-1",
|
||||
"hover:text-blue-600",
|
||||
{
|
||||
"text-xs text-gray-500 leading-5": emphasis === "low",
|
||||
"text-base text-gray-900 px-2 border rounded hover:border-blue-600 border-gray-900":
|
||||
emphasis === "high",
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
<Typography>
|
||||
{keyword}
|
||||
{i != React.Children.count(children) - 1 && emphasis === "low"
|
||||
? ","
|
||||
: null}
|
||||
</Typography>
|
||||
</div>
|
||||
</RouterLink>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<div className="flex flex-row items-center">
|
||||
{emphasis === "low" ? (
|
||||
<SVGKey className="w-6 fill-gray-500 stroke-gray-500" />
|
||||
) : null}
|
||||
<div className="flex flex-row ml-2">{keywords}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ArticleKeywords.displayName = "ArticleKeywords";
|
@ -0,0 +1,39 @@
|
||||
import React, { useState } from "react";
|
||||
import { Button } from "components/Button/Button";
|
||||
import {
|
||||
SVGFavoriteFilled,
|
||||
SVGFavoriteOutlined,
|
||||
SVGFolder,
|
||||
} from "components/icons";
|
||||
|
||||
const subscriptionStyles = "fill-gray-500 stroke-gray-500 w-6";
|
||||
|
||||
type ArticleSubscriptionsButtonsProps = {
|
||||
className?: string;
|
||||
favorite?: boolean;
|
||||
//Todo create tracking subscriptions and onClick props
|
||||
} & Omit<React.ComponentPropsWithoutRef<"button">, "">;
|
||||
|
||||
export function ArticleSubscriptionsButtons({
|
||||
className,
|
||||
favorite = false,
|
||||
}: ArticleSubscriptionsButtonsProps) {
|
||||
return (
|
||||
<div className="flex flex-row">
|
||||
<Button emphasis="low">
|
||||
<Button.Icon>
|
||||
<SVGFolder className={subscriptionStyles} />
|
||||
</Button.Icon>
|
||||
</Button>
|
||||
<Button emphasis="low" onClick={() => {}}>
|
||||
<Button.Icon>
|
||||
{!favorite ? (
|
||||
<SVGFavoriteOutlined className={subscriptionStyles} />
|
||||
) : (
|
||||
<SVGFavoriteFilled className="fill-blue-600 stroke-blue-600 w-6" />
|
||||
)}
|
||||
</Button.Icon>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
24
src/components/Article/ArticleParts/ArticleTitle.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import React from "react";
|
||||
import Typography from "components/typography/Typography";
|
||||
import { RouterLink } from "components/typography/RouterLink";
|
||||
type ArticleTitleProps = {
|
||||
className?: string;
|
||||
children?: string;
|
||||
linkTo?: string;
|
||||
};
|
||||
|
||||
export function ArticleTitle({
|
||||
className,
|
||||
children,
|
||||
linkTo = "#",
|
||||
}: ArticleTitleProps) {
|
||||
return (
|
||||
<RouterLink to={linkTo}>
|
||||
<Typography fontWeightVariant="semibold" className={className}>
|
||||
{children}
|
||||
</Typography>
|
||||
</RouterLink>
|
||||
);
|
||||
}
|
||||
|
||||
ArticleTitle.displayName = "ArticleTitle";
|
45
src/components/Article/ArticleSearch.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
import { Article } from "./Article";
|
||||
|
||||
export function ArticleSearch() {
|
||||
return (
|
||||
<Article>
|
||||
<div className="flex flex-row justify-between">
|
||||
<Article.Breadcumbs emphasis="high">
|
||||
{["Yoda", "Lallalla", "Maxim"]}
|
||||
</Article.Breadcumbs>
|
||||
<Article.SubscriptionsButtons />
|
||||
</div>
|
||||
<div className="flex flex-row">
|
||||
<Article.Title className="text-3xl">Yeanda lacreav</Article.Title>
|
||||
</div>
|
||||
|
||||
<Article.Authors emphasis="low">
|
||||
{["Reavap", "aldjfoa", "dkfjaoif"]}
|
||||
</Article.Authors>
|
||||
<Article.Keywords emphasis="low">
|
||||
{["porn", "development", "frontend"]}
|
||||
</Article.Keywords>
|
||||
|
||||
<Article.InteractionButtons emphasis="low">
|
||||
low
|
||||
</Article.InteractionButtons>
|
||||
<Article.Description>
|
||||
{" "}
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere quidem
|
||||
provident temporibus! Fuga dolores placeat at voluptatem quia, vero
|
||||
molestiae animi et itaque a, officia ullam expedita temporibus cum
|
||||
deserunt.Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere
|
||||
quidem provident temporibus! Fuga dolores placeat at voluptatem quia,
|
||||
vero molestiae animi et itaque a, officia ullam expedita temporibus cum
|
||||
deserunt.Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere
|
||||
quidem provident temporibus! Fuga dolores placeat at voluptatem quia,
|
||||
vero molestiae animi et itaque a, officia ullam expedita temporibus cum
|
||||
deserunt.Lorem ipsum dolor sit amet consectetur adipisicing elit. Facere
|
||||
quidem provident temporibus! Fuga dolores placeat at voluptatem quia,
|
||||
vero molestiae animi et itaque a, officia ullam expedita temporibus cum
|
||||
deserunt.
|
||||
</Article.Description>
|
||||
</Article>
|
||||
);
|
||||
}
|
38
src/components/AspectRatio.tsx
Normal file
@ -0,0 +1,38 @@
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
export type Props = {
|
||||
/**
|
||||
* The style of component
|
||||
*/
|
||||
className?: string;
|
||||
/**
|
||||
* The optional child
|
||||
*/
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
const AspectRatio = ({ className, children }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
"relative overflow-hidden pt-[55%] rounded w-full",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
AspectRatio.Content = function AspectRatioContent({
|
||||
className,
|
||||
children,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className={classNames([className, "absolute top-0 w-full h-full"])}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AspectRatio;
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
export type Props = {
|
||||
/**
|
||||
@ -20,13 +20,47 @@ export type Props = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
// Choosing random color for the avatar background
|
||||
function getRandomInt(max: number) {
|
||||
return Math.floor(Math.random() * max);
|
||||
}
|
||||
let colors: string[] = [
|
||||
"bg-red-400",
|
||||
"bg-orange-400",
|
||||
"bg-green-400",
|
||||
"bg-amber-400",
|
||||
"bg-yellow-400",
|
||||
"bg-lime-400",
|
||||
"bg-emerald-400",
|
||||
"bg-teal-400",
|
||||
"bg-cyan-400",
|
||||
"bg-sky-400",
|
||||
"bg-blue-400",
|
||||
"bg-indigo-400",
|
||||
"bg-violet-400",
|
||||
"bg-purple-400",
|
||||
"bg-fuchsia-400",
|
||||
];
|
||||
|
||||
const Avatar = ({ className, src, alt, children }: Props) => {
|
||||
const wrapperClasses = src ? "" : colors[getRandomInt(colors.length)];
|
||||
const position = src ? "relative pt-[100%]" : "";
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className={classNames(
|
||||
wrapperClasses,
|
||||
position,
|
||||
"rounded-full ",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{/* In case the src is valid, it will show the image */}
|
||||
{src && (
|
||||
<img
|
||||
className="h-9 w-9 bg-contain bg-no-repeat rounded-full"
|
||||
className={classNames(
|
||||
"h-9 w-9 rounded-full absolute top-1/2 left-1/2 object-cover -translate-x-1/2 -translate-y-1/2",
|
||||
className
|
||||
)}
|
||||
src={src}
|
||||
alt={alt}
|
||||
/>
|
||||
@ -35,7 +69,7 @@ const Avatar = ({ className, src, alt, children }: Props) => {
|
||||
{children && (
|
||||
<div
|
||||
className={classNames(
|
||||
"h-9 w-9 flex justify-center items-center text-base rounded-full bg-red-400",
|
||||
"h-9 w-9 flex justify-center items-center text-base text-white",
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
@ -1,28 +1,23 @@
|
||||
import React from "react";
|
||||
import { Footer } from "./parts/Footer";
|
||||
import Header from "./parts/Header";
|
||||
type Props = {
|
||||
header?: React.ReactElement,
|
||||
children: React.ReactNode,
|
||||
footer?: React.ReactElement,
|
||||
className : string,
|
||||
header?: React.ReactElement;
|
||||
children: React.ReactNode;
|
||||
footer?: React.ReactElement;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function BaseLayout({ header, footer, children, className }: Props) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<Header />
|
||||
|
||||
<main>{children}</main>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BaseLayout( { header, footer, children, className }: Props ) {
|
||||
return (
|
||||
<div className={className}>
|
||||
<header>
|
||||
{header}
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{children}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
{footer}
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default BaseLayout;
|
||||
|
||||
export default BaseLayout;
|
||||
|
@ -38,9 +38,6 @@ export const Button: React.FC<ButtonProps> & ButtonExtentions = ({
|
||||
children,
|
||||
...props
|
||||
}) => {
|
||||
console.log(
|
||||
React.isValidElement(children) && React.Children.only(children).type
|
||||
);
|
||||
const isOnlyIcon =
|
||||
children &&
|
||||
React.isValidElement(children) &&
|
||||
@ -51,7 +48,7 @@ export const Button: React.FC<ButtonProps> & ButtonExtentions = ({
|
||||
return (
|
||||
<button
|
||||
//TODO change on click event
|
||||
onClick={!disabled ? () => alert("Click") : undefined}
|
||||
onClick={!disabled ? () => {} : undefined}
|
||||
className={classNames([
|
||||
"flex content-center justify-between",
|
||||
"text-center",
|
||||
|
@ -2,8 +2,8 @@
|
||||
/* styles */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
export const EnableHigh = `bg-blue-500
|
||||
hover:bg-blue-400
|
||||
export const EnableHigh = `bg-blue-600
|
||||
hover:bg-blue-500
|
||||
active:bg-blue-700
|
||||
focus:shadow-lg shadow-blue-500
|
||||
focus:outline outline-blue-400/10 outline-8
|
||||
@ -18,22 +18,22 @@ export const GeneralHigh = `
|
||||
stroke-white
|
||||
`;
|
||||
|
||||
export const EnabledMedium = `text-blue-500
|
||||
export const EnabledMedium = `text-blue-600
|
||||
border-gray-500
|
||||
active:border-blue-600
|
||||
active:text-blue-600
|
||||
hover:border-blue-400
|
||||
hover:text-blue-400
|
||||
active:border-blue-700
|
||||
active:text-blue-700
|
||||
hover:border-blue-500
|
||||
hover:text-blue-500
|
||||
focus:outline outline-blue-400/10 outline-8
|
||||
focus:border-blue-600/70
|
||||
fill-blue-500
|
||||
hover:fill-blue-400
|
||||
active:fill-blue-600
|
||||
focus:fill-blue-600
|
||||
stroke-blue-500
|
||||
hover:stroke-blue-400
|
||||
active:stroke-blue-600
|
||||
focus:stroke-blue-600
|
||||
focus:border-blue-700/70
|
||||
fill-blue-600
|
||||
hover:fill-blue-500
|
||||
active:fill-blue-700
|
||||
focus:fill-blue-700
|
||||
stroke-blue-600
|
||||
hover:stroke-blue-500
|
||||
active:stroke-blue-700
|
||||
focus:stroke-blue-700
|
||||
`;
|
||||
|
||||
export const DisabledMedium = `text-gray-200
|
||||
@ -47,13 +47,13 @@ export const GeneralMedium = `bg-white
|
||||
|
||||
export const EnabledLow = ` text-gray-900
|
||||
hover:bg-gray-100
|
||||
active:text-blue-600
|
||||
active:text-blue-700
|
||||
active:bg-blue-100
|
||||
focus:bg-blue-100
|
||||
fill-gray-900
|
||||
stroke-gray-900
|
||||
active:fill-blue-500
|
||||
active:stroke-blue-500
|
||||
active:fill-blue-600
|
||||
active:stroke-blue-600
|
||||
`;
|
||||
|
||||
export const DisabledLow = `text-gray-200
|
||||
|
127
src/components/Card.tsx
Normal file
@ -0,0 +1,127 @@
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Components */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import Typography from "./typography/Typography";
|
||||
import Heading from "./typography/Heading";
|
||||
import Link from "./typography/Link";
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Props */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
type Props = {
|
||||
/**
|
||||
* Card component accept children
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Styling the card component
|
||||
*/
|
||||
className?: string | undefined;
|
||||
};
|
||||
|
||||
const Card = ({ children, className }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames([
|
||||
className,
|
||||
"inline-flex flex-col justify-between p-4 items-start rounded border border-gray-75 gap-y-8 overflow-hidden",
|
||||
])}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Avatar function
|
||||
type AvatarProps = {
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
Card.Avatar = function CardAvatar({ children }: AvatarProps) {
|
||||
return <div>{children}</div>;
|
||||
};
|
||||
|
||||
// Title function
|
||||
Card.Title = function CardTitle({ children, className }: Props) {
|
||||
return (
|
||||
<Heading className={classNames([className, "select-none "])}>
|
||||
{children}
|
||||
</Heading>
|
||||
);
|
||||
};
|
||||
|
||||
// SubTitle function
|
||||
Card.SubTitle = function CardSubTitle({ children, className }: Props) {
|
||||
return (
|
||||
<Typography className={classNames([className, ""])}>{children}</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
// Body function
|
||||
Card.Body = function CardTitle({ children, className }: Props) {
|
||||
return (
|
||||
<Typography //
|
||||
fontWeightVariant="normal"
|
||||
className={classNames([className, "text-sm h-14 overflow-hidden "])}
|
||||
>
|
||||
{children}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
// Cardheader function
|
||||
Card.CardHeader = function CardCardHeader({ children, className }: Props) {
|
||||
return (
|
||||
<div className={classNames([className, "flex items-start gap-4"])}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Cardcontent function
|
||||
Card.CardContent = function CardCardContent({ children, className }: Props) {
|
||||
return (
|
||||
<div className={classNames([className, "flex flex-col gap-y-4 "])}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Cardaction function
|
||||
type CardActionProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string | undefined;
|
||||
href?: string;
|
||||
};
|
||||
Card.CardAction = function CardCardAction({
|
||||
children,
|
||||
className,
|
||||
href = "#",
|
||||
}: CardActionProps) {
|
||||
return (
|
||||
<Link
|
||||
to={href}
|
||||
className={classNames([className, "flex items-center gap-2"])}
|
||||
>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
// CardMedia function
|
||||
type CardMediaProps = {
|
||||
children?: React.ReactNode;
|
||||
className?: string | undefined;
|
||||
src?: string;
|
||||
};
|
||||
Card.CardMedia = function CardCardMedia({
|
||||
className,
|
||||
src = "#",
|
||||
}: CardMediaProps) {
|
||||
return (
|
||||
<img src={src} className={classNames([className, "w-full h-32 rounded"])} />
|
||||
);
|
||||
};
|
||||
|
||||
export default Card;
|
55
src/components/Cards/CategoryCard.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import React from "react";
|
||||
import { SVGMedicine } from "../icons";
|
||||
import Typography from "components/typography/Typography";
|
||||
import { Button } from "components/Button/Button";
|
||||
import classNames from "classnames";
|
||||
import { JsxElement } from "typescript";
|
||||
|
||||
type Props = {
|
||||
count?: number;
|
||||
title: string;
|
||||
|
||||
iconChild: Required<JSX.Element>;
|
||||
} & Omit<React.ComponentPropsWithoutRef<"div">, "">;
|
||||
|
||||
function CategoryCard({ count, title, iconChild, className, ...props }: Props) {
|
||||
const iconChildStyle =
|
||||
"h-7 fill-gray-500 stroke-gray-500 group-focus:fill-blue-600 group-active:fill-blue-600 group-focus:stroke-blue-600 group-active:stroke-blue-600";
|
||||
|
||||
return (
|
||||
<div className="snap-start">
|
||||
<Button
|
||||
defaultStyle={false}
|
||||
className="focus:outline-none group hover:bg-gray-75 active:bg-white active:outline active:outline-1 active:outline-blue-600 focus:outline-1 focus:outline-blue-600"
|
||||
>
|
||||
<div className=" rounded py-1 px-4 flex flex-row items-center ">
|
||||
<div className="justify-center max-w-max">
|
||||
{React.cloneElement(iconChild, {
|
||||
className: classNames(iconChildStyle, className),
|
||||
})}
|
||||
</div>
|
||||
<div className="flex flex-col ml-3 min-w-max">
|
||||
<div className="">
|
||||
<Typography
|
||||
fontWeightVariant="bold"
|
||||
className="text-sm leading-6 min-w-max group-active:text-blue-600 group-focus:text-blue-600"
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="max-w-max ">
|
||||
<Typography
|
||||
fontWeightVariant="normal"
|
||||
className="text-xs text-gray-500 group-active:text-blue-600 group-focus:text-blue-600"
|
||||
>
|
||||
{count} Items
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CategoryCard;
|
@ -5,19 +5,24 @@ type Props = {
|
||||
* Content of component
|
||||
*/
|
||||
children: React.ReactNode;
|
||||
/**
|
||||
* Display variants of container
|
||||
*/
|
||||
variant?: "straight" | "wide";
|
||||
/**
|
||||
* Component styling
|
||||
*/
|
||||
className?: string;
|
||||
} & Omit<React.ComponentPropsWithoutRef<"div">, "">
|
||||
};
|
||||
/**
|
||||
* Main container to handle page content max-width on
|
||||
* different screen sizes
|
||||
*/
|
||||
export default function Container({children, className}: Props) {
|
||||
export default function Container({ children, variant, className }: Props) {
|
||||
const wideClass = variant == "straight" ? "container" : "container-wide";
|
||||
return (
|
||||
<div className={ classNames('container mx-auto', className) }>
|
||||
<div className={classNames("mx-auto", wideClass, className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -1,115 +0,0 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Imports */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import React, { Fragment } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { PropsPartion } from "./ContextMenuItem";
|
||||
import classNames from "classnames";
|
||||
import { ReactComponent as SelectIcon } from "assets/svg/select-arrow.svg";
|
||||
type ChildType = React.ReactElement<any & PropsPartion[]>;
|
||||
type ChildrenType = ChildType[] | ChildType;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component props */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
type MenuProps = {
|
||||
emphasis?: "high" | "low";
|
||||
disabled?: boolean;
|
||||
className?: string | undefined;
|
||||
button: React.ReactNode;
|
||||
children: ChildrenType;
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Styles */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
const MenuButtonStyle = `
|
||||
inline-flex
|
||||
justify-center w-full
|
||||
cursor-default
|
||||
rounded
|
||||
border border-gray-100
|
||||
outline-8
|
||||
py-2
|
||||
px-2
|
||||
text-base`;
|
||||
|
||||
const MenuItemStyle = `
|
||||
absolute
|
||||
left-0
|
||||
mt-2 w-60
|
||||
origin-top-left
|
||||
rounded
|
||||
shadow-lg
|
||||
focus:outline-none
|
||||
py-2 px-4 sm:text-sm`;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component implementation */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/**
|
||||
* Use width ContextMenuAction.tsx , for example:
|
||||
* <ContextMenu button="MyButton" emphasis="low/high">
|
||||
* <ContextMenuAction
|
||||
* caption="First Menu"
|
||||
* icon={icon}
|
||||
* action={() => alert('click')}
|
||||
* ></ContextMenuAction>
|
||||
* ...
|
||||
* </ContextMenu>
|
||||
*/
|
||||
export default function ContextMenu({
|
||||
button,
|
||||
children,
|
||||
className,
|
||||
emphasis = "low",
|
||||
}: MenuProps) {
|
||||
return (
|
||||
<Menu as="div" className="relative inline-block text-right">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Menu.Button
|
||||
className={classNames([
|
||||
`${MenuButtonStyle}`,
|
||||
{
|
||||
"hover:bg-gray-100 font-bold uppercase": emphasis === "high",
|
||||
},
|
||||
className,
|
||||
])}
|
||||
>
|
||||
{button}
|
||||
<SelectIcon
|
||||
className={`${
|
||||
open ? "rotate-180 transform" : "font-normal"
|
||||
} my-2 mx-3 h-2 w-3 flex-center`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
show={open}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
static
|
||||
className={classNames([
|
||||
className,
|
||||
`${MenuItemStyle}`,
|
||||
{ "ml-2": emphasis === "high" },
|
||||
])}
|
||||
>
|
||||
{children}
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
type Props = {
|
||||
action: Function;
|
||||
caption: string;
|
||||
disabled?: boolean;
|
||||
icon?: React.ReactNode;
|
||||
className?: string | undefined;
|
||||
};
|
||||
|
||||
export default function ContextMenuAction({
|
||||
action,
|
||||
caption,
|
||||
disabled,
|
||||
icon,
|
||||
className,
|
||||
}: Props) {
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => action(e)}
|
||||
className={classNames([
|
||||
"group flex px-2 rounded items-center text-base hover:bg-gray-100",
|
||||
className,
|
||||
])}
|
||||
>
|
||||
{icon && <div className="mr-2 h-5 w-5">{icon}</div>}
|
||||
<span className="px-2 py-2">{caption}</span>
|
||||
</a>
|
||||
);
|
||||
}
|
230
src/components/FeaturedArticlesCards.tsx
Normal file
@ -0,0 +1,230 @@
|
||||
import { useRef } from "react";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Skeleton */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import "react-loading-skeleton/dist/skeleton.css";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Components */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import Typography from "./typography/Typography";
|
||||
import SkeletonCard from "./SkeletonCard";
|
||||
import AspectRatio from "./AspectRatio";
|
||||
import Card from "./Card";
|
||||
import Link from "./typography/Link";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Icons */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import { ReactComponent as SVGArrowRight } from "assets/svg/arrow-right.svg";
|
||||
import { ReactComponent as SVGCaretRight } from "assets/svg/caret-right.svg";
|
||||
import { ReactComponent as SVGArrowLeft } from "assets/svg/arrow-left.svg";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Swiper */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import SwiperCore, { Navigation } from "swiper";
|
||||
import { Pagination } from "swiper";
|
||||
import "swiper/css/pagination";
|
||||
import "swiper/css/navigation";
|
||||
// import "./styles.css";
|
||||
import "swiper/css";
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Article mock data */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
const articles = [
|
||||
{
|
||||
CoverImg:
|
||||
"https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png",
|
||||
Body: "1 ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.sit amet consectetur adipisicing elit.Consequuntur ma",
|
||||
Link: "http://pinterest.com",
|
||||
},
|
||||
{
|
||||
CoverImg:
|
||||
"https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png",
|
||||
Body: "2 ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.",
|
||||
Link: "http://pinterest.com",
|
||||
},
|
||||
{
|
||||
CoverImg:
|
||||
"https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png",
|
||||
Body: "2 ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.",
|
||||
Link: "http://pinterest.com",
|
||||
},
|
||||
{
|
||||
CoverImg:
|
||||
"https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png",
|
||||
Body: "2 ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.",
|
||||
Link: "http://pinterest.com",
|
||||
},
|
||||
{
|
||||
CoverImg:
|
||||
"https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png",
|
||||
Body: "2 ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.",
|
||||
Link: "http://pinterest.com",
|
||||
},
|
||||
{
|
||||
CoverImg:
|
||||
"https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png",
|
||||
Body: "2 ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.",
|
||||
Link: "http://pinterest.com",
|
||||
},
|
||||
{
|
||||
CoverImg:
|
||||
"https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png",
|
||||
Body: "2 ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.",
|
||||
Link: "http://pinterest.com",
|
||||
},
|
||||
];
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* How many Skeleton cards should be added to the design */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
let twoCards: boolean = false;
|
||||
let threeCards: boolean = false;
|
||||
console.log(`Number of cards ${articles.length}`);
|
||||
if (articles.length == 2) {
|
||||
twoCards = true;
|
||||
} else if (articles.length == 3) {
|
||||
threeCards = true;
|
||||
}
|
||||
|
||||
SwiperCore.use([Navigation]);
|
||||
|
||||
const FeaturedArticlesCards = () => {
|
||||
const navigationPrevRef = useRef(null);
|
||||
const navigationNextRef = useRef(null);
|
||||
const paginationRef = useRef(null);
|
||||
|
||||
return (
|
||||
<div className="slider-wrapper articles">
|
||||
<div className="flex justify-end gap-2 my-2">
|
||||
<div
|
||||
className="prev inline-flex justify-center items-center
|
||||
w-9 h-9 bg-blue-600 rounded cursor-pointer
|
||||
opacity-0 md:opacity-100"
|
||||
ref={navigationPrevRef}
|
||||
>
|
||||
<SVGArrowLeft className="w-6 h-6 fill-white "></SVGArrowLeft>
|
||||
</div>
|
||||
<div
|
||||
className="next inline-flex justify-center items-center
|
||||
w-9 h-9 bg-blue-600 rounded cursor-pointer
|
||||
opacity-0 md:opacity-100"
|
||||
ref={navigationNextRef}
|
||||
>
|
||||
<SVGArrowRight className="w-6 h-6 fill-white "></SVGArrowRight>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Swiper
|
||||
slidesPerView={1.25}
|
||||
slidesPerGroup={1}
|
||||
loop={articles.length > 4 ? true : false}
|
||||
pagination={{ el: ".pagination", clickable: true }}
|
||||
navigation={{
|
||||
prevEl: ".prev",
|
||||
nextEl: ".next",
|
||||
}}
|
||||
breakpoints={{
|
||||
768: {
|
||||
slidesPerView: 2,
|
||||
slidesPerGroup: 2,
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 2,
|
||||
slidesPerGroup: 2,
|
||||
},
|
||||
1280: {
|
||||
slidesPerView: 4,
|
||||
slidesPerGroup: 4,
|
||||
},
|
||||
1580: {
|
||||
slidesPerView: 4,
|
||||
slidesPerGroup: 4,
|
||||
},
|
||||
}}
|
||||
spaceBetween={25}
|
||||
loopFillGroupWithBlank={true}
|
||||
modules={[Pagination, Navigation]}
|
||||
>
|
||||
{articles.map((Articale) => (
|
||||
<SwiperSlide>
|
||||
<Card className="flex-1">
|
||||
<Card.CardContent>
|
||||
<AspectRatio>
|
||||
<AspectRatio.Content>
|
||||
<img src={Articale.CoverImg} />
|
||||
</AspectRatio.Content>
|
||||
</AspectRatio>
|
||||
|
||||
<Card.Body className="h-14 overflow-hidden">
|
||||
{Articale.Body}
|
||||
</Card.Body>
|
||||
</Card.CardContent>
|
||||
|
||||
<Card.CardAction href={Articale.Link}>
|
||||
<Link to="*">
|
||||
<Typography className="text-blue-500 font-bold">
|
||||
Read More
|
||||
</Typography>
|
||||
</Link>
|
||||
<SVGCaretRight className="fill-blue-500 w-4 h-4" />
|
||||
</Card.CardAction>
|
||||
</Card>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
|
||||
{twoCards && [
|
||||
<SwiperSlide className="hidden xl:block">
|
||||
<SwiperSlide>
|
||||
<SkeletonCard className="flex-1">
|
||||
<SkeletonCard.Content>
|
||||
<SkeletonCard.Media />
|
||||
<SkeletonCard.Body />
|
||||
</SkeletonCard.Content>
|
||||
<SkeletonCard.Action />
|
||||
</SkeletonCard>
|
||||
</SwiperSlide>
|
||||
</SwiperSlide>,
|
||||
|
||||
<SwiperSlide className="hidden xl:block">
|
||||
<SwiperSlide>
|
||||
<SkeletonCard className="flex-1">
|
||||
<SkeletonCard.Content>
|
||||
<SkeletonCard.Media />
|
||||
<SkeletonCard.Body />
|
||||
</SkeletonCard.Content>
|
||||
<SkeletonCard.Action />
|
||||
</SkeletonCard>
|
||||
</SwiperSlide>
|
||||
</SwiperSlide>,
|
||||
]}
|
||||
|
||||
{threeCards && [
|
||||
<SwiperSlide className="hidden xl:block">
|
||||
<SwiperSlide>
|
||||
<SkeletonCard className="flex-1">
|
||||
<SkeletonCard.Content>
|
||||
<SkeletonCard.Media />
|
||||
<SkeletonCard.Body />
|
||||
</SkeletonCard.Content>
|
||||
<SkeletonCard.Action />
|
||||
</SkeletonCard>
|
||||
</SwiperSlide>
|
||||
</SwiperSlide>,
|
||||
]}
|
||||
</Swiper>
|
||||
|
||||
<div
|
||||
className="pagination my-6 w-full h-2 flex justify-center items-center
|
||||
opacity-0 md:opacity-100"
|
||||
ref={paginationRef}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default FeaturedArticlesCards;
|
195
src/components/FeaturedAuthorsCards.tsx
Normal file
@ -0,0 +1,195 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Components */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import Heading from "./typography/Heading";
|
||||
import SkeletonCard from "./SkeletonCard";
|
||||
import { Button } from "./Button/Button";
|
||||
import Avatar from "./Avatar";
|
||||
import Card from "./Card";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Swiper */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import { Swiper, SwiperSlide } from "swiper/react";
|
||||
import SwiperCore, { Navigation } from "swiper";
|
||||
import "swiper/css/pagination";
|
||||
import "swiper/css/navigation";
|
||||
// import "./styles.css";
|
||||
import "swiper/css";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Icons */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import { ReactComponent as SVGCaretRight } from "assets/svg/caret-right.svg";
|
||||
import Link from "./typography/Link";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Variables */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
SwiperCore.use([Navigation]);
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Authors data mock */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
const authors = [
|
||||
{
|
||||
Title: "JSON Three",
|
||||
Body: "Lorem ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.am elit.Consequuntur maxime, adipisicing elit.",
|
||||
Link: "http://pinterest.com",
|
||||
},
|
||||
{
|
||||
Title: "JSON Two",
|
||||
Body: "Lorem ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.am elit.Consequuntur maxime, libero est unde sapiente repellendus quam",
|
||||
Link: "http://pinterest.com",
|
||||
img: "https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png",
|
||||
},
|
||||
{
|
||||
Title: "JSON Two",
|
||||
Body: "Lorem ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.am elit.Consequuntur maxime, libero est unde sapiente repellendus quam",
|
||||
Link: "http://pinterest.com",
|
||||
img: "https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png",
|
||||
},
|
||||
{
|
||||
Title: "JSON Two",
|
||||
Body: "Lorem ipsum dolor sit amet consectetur adipisicing elit.Consequuntur maxime, adipisicing elit.am elit.Consequuntur maxime, libero est unde sapiente repellendus quam",
|
||||
Link: "http://pinterest.com",
|
||||
img: "https://i.kinja-img.com/gawker-media/image/upload/t_original/ijsi5fzb1nbkbhxa2gc1.png",
|
||||
},
|
||||
];
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Number of Cards */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
let twoCards: boolean = false;
|
||||
let threeCards: boolean = false;
|
||||
console.log(`Number of cards ${authors.length}`);
|
||||
if (authors.length == 2) {
|
||||
twoCards = true;
|
||||
} else if (authors.length == 3) {
|
||||
threeCards = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Featured authors component to display ...
|
||||
*/
|
||||
export default function FeaturedAuthorsCards(): JSX.Element {
|
||||
return (
|
||||
<div>
|
||||
{/* The Title of Featured Authors section */}
|
||||
<Heading className="text-center my-8">Featured Authors</Heading>
|
||||
|
||||
{/* Featured Authors section */}
|
||||
<div className="slider-wrapper Authors">
|
||||
<Swiper
|
||||
slidesPerView={1.25}
|
||||
slidesPerGroup={1}
|
||||
spaceBetween={25}
|
||||
loop={authors.length > 4 ? true : false}
|
||||
loopFillGroupWithBlank={false}
|
||||
breakpoints={{
|
||||
768: {
|
||||
slidesPerView: 2,
|
||||
slidesPerGroup: 2,
|
||||
},
|
||||
1024: {
|
||||
slidesPerView: 2,
|
||||
slidesPerGroup: 2,
|
||||
},
|
||||
1280: {
|
||||
slidesPerView: 4,
|
||||
slidesPerGroup: 4,
|
||||
},
|
||||
1580: {
|
||||
slidesPerView: 4,
|
||||
slidesPerGroup: 4,
|
||||
},
|
||||
}}
|
||||
>
|
||||
{authors.map((card) => (
|
||||
<SwiperSlide>
|
||||
<Card className="flex-1">
|
||||
<Card.CardContent>
|
||||
<Card.CardHeader>
|
||||
<Card.Avatar>
|
||||
{card.img ? (
|
||||
<Avatar
|
||||
className="w-12 h-12"
|
||||
src={card.img}
|
||||
alt={card.Title}
|
||||
/>
|
||||
) : (
|
||||
<Avatar
|
||||
className="w-12 h-12"
|
||||
src={card.img}
|
||||
alt={card.Title}
|
||||
>
|
||||
{card.Title[0]}
|
||||
</Avatar>
|
||||
)}
|
||||
</Card.Avatar>
|
||||
<Card.Title>{card.Title}</Card.Title>
|
||||
</Card.CardHeader>
|
||||
<Card.Body>{card.Body}</Card.Body>
|
||||
</Card.CardContent>
|
||||
|
||||
<Card.CardAction href={card.Link}>
|
||||
<Link className="text-blue-500 font-bold" to="*">
|
||||
See More
|
||||
</Link>
|
||||
<SVGCaretRight className="fill-blue-500 w-4 h-4" />
|
||||
</Card.CardAction>
|
||||
</Card>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
|
||||
{twoCards && [
|
||||
<SwiperSlide className="hidden xl:block">
|
||||
<SkeletonCard className="flex-1">
|
||||
<SkeletonCard.Content>
|
||||
<SkeletonCard.Header>
|
||||
<SkeletonCard.Avatar />
|
||||
<SkeletonCard.Title />
|
||||
</SkeletonCard.Header>
|
||||
<SkeletonCard.Body />
|
||||
</SkeletonCard.Content>
|
||||
<SkeletonCard.Action />
|
||||
</SkeletonCard>
|
||||
</SwiperSlide>,
|
||||
|
||||
<SwiperSlide className="hidden xl:block">
|
||||
<SkeletonCard className="flex-1">
|
||||
<SkeletonCard.Content>
|
||||
<SkeletonCard.Header>
|
||||
<SkeletonCard.Avatar />
|
||||
<SkeletonCard.Title />
|
||||
</SkeletonCard.Header>
|
||||
<SkeletonCard.Body />
|
||||
</SkeletonCard.Content>
|
||||
<SkeletonCard.Action />
|
||||
</SkeletonCard>
|
||||
</SwiperSlide>,
|
||||
]}
|
||||
|
||||
{threeCards && [
|
||||
<SwiperSlide className="hidden xl:block">
|
||||
<SkeletonCard className="flex-1">
|
||||
<SkeletonCard.Content>
|
||||
<SkeletonCard.Header>
|
||||
<SkeletonCard.Avatar />
|
||||
<SkeletonCard.Title />
|
||||
</SkeletonCard.Header>
|
||||
<SkeletonCard.Body />
|
||||
</SkeletonCard.Content>
|
||||
<SkeletonCard.Action />
|
||||
</SkeletonCard>
|
||||
</SwiperSlide>,
|
||||
]}
|
||||
</Swiper>
|
||||
</div>
|
||||
|
||||
<Button emphasis="high" className="font-bold m-auto my-8">
|
||||
Show All
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
37
src/components/Logo.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
export type Props = {
|
||||
className?: string;
|
||||
fillColors?: "blue" | "gray";
|
||||
};
|
||||
|
||||
const Logo = ({ className, fillColors = "blue" }: Props) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<svg viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M14.4144 23.7511C8.27781 15.6345 1.97719 21.8024 3.62218 27.4753C7.75871 36.521 18.4462 40.5011 27.4937 36.364C30.6123 34.9384 33.2592 32.6525 35.1232 29.7747C30.3791 34.2166 21.7659 33.4741 14.4144 23.7497V23.7511Z"
|
||||
className={fillColors === "blue" ? "fill-blue-800" : "fill-gray-800"}
|
||||
/>
|
||||
<path
|
||||
d="M19.9003 18.1623C11.4604 5.36346 1.99988 12.1818 1.99988 20.0029C1.99656 22.5922 2.55489 25.1515 3.63638 27.5043C2.51332 22.2054 8.60318 18.1213 14.273 25.7253C22.5092 36.7684 30.5857 34.8254 35.1069 29.8009C36.5583 27.5725 37.4959 25.0493 37.8517 22.414V22.4486C35.9103 28.4694 27.3438 29.4487 19.9003 18.1623Z"
|
||||
className={fillColors === "blue" ? "fill-blue-700" : "fill-gray-700"}
|
||||
/>
|
||||
<path
|
||||
d="M25.6847 12.4357C19.4173 2.09746 11.4696 3.03152 6.09469 8.56867C3.44128 11.788 1.99328 15.8313 1.99989 20.0029C2.56566 12.4371 11.7164 7.16935 20.0007 20.0029C27.3643 31.4243 36.5384 28.4489 37.8432 22.4812V22.4239C37.9578 21.6113 38.0065 20.7908 37.9889 19.9703V19.1522C34.8 21.434 30.6062 20.553 25.6832 12.435L25.6847 12.4357Z"
|
||||
className={fillColors === "blue" ? "fill-blue-600" : "fill-gray-500"}
|
||||
/>
|
||||
<path
|
||||
d="M25.7278 14.2762C31.4676 23.5022 36.416 21.1419 38.0009 19.1685C37.9371 17.7893 37.714 16.4222 37.3361 15.0943C34.6607 15.1515 34.0369 14.704 31.404 10.6969C27.3806 4.53119 22.3119 0.369312 13.4568 3.23299C10.5865 4.35751 8.05111 6.19656 6.09116 8.57569C12.1761 2.97702 19.6422 4.49654 25.7278 14.2762Z"
|
||||
className={fillColors === "blue" ? "fill-blue-400" : "fill-gray-200"}
|
||||
/>
|
||||
<path
|
||||
d="M31.0114 11.4889C33.6359 15.5037 35.2335 15.708 37.336 15.095C34.6097 5.51969 24.6358 -0.0323048 15.0593 2.69349C14.5162 2.84763 13.982 3.02747 13.4568 3.23299C21.367 0.737706 26.9774 5.32312 31.0114 11.4889Z"
|
||||
className={fillColors === "blue" ? "fill-blue-200" : "fill-gray-75"}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Logo;
|
39
src/components/Logofreeland.tsx
Normal file
@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import {ReactComponent as SVGLogotype} from 'assets/svg/logotype.svg';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
type Props = {
|
||||
name?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Horizontal variant of logotype component
|
||||
* @param {string} name Name of service to attach to logotype
|
||||
* @return {React.ReactNode}
|
||||
*/
|
||||
export default function Logotype({name}: Props): JSX.Element {
|
||||
return (
|
||||
<div className="inline-flex flex-row flex-nowrap items-center">
|
||||
<div className="flex-none">
|
||||
<Link to="/">
|
||||
<SVGLogotype className="w-8 h-8 mr-2" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex-initial text-2xl font-bold">
|
||||
{name ?? ''} {process.env.REACT_APP_CMS_APP_NAME?.toLowerCase()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
104
src/components/MainSection.tsx
Normal file
@ -0,0 +1,104 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Imports */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import React from "react";
|
||||
import Inputgroup from "./Inputgroup";
|
||||
import Select from "./Select";
|
||||
import { useState } from "react";
|
||||
import { Button } from "./Button/Button";
|
||||
import { SVGSearch } from "../components/icons";
|
||||
import SearchBar from "components/SearchBar";
|
||||
|
||||
type Props<T, H> = {
|
||||
className?: string;
|
||||
options: T[];
|
||||
hintsValues: H[];
|
||||
displayValueResolver?: (element: T) => any;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component implementation */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
export default function MainSection<T, H>({
|
||||
className,
|
||||
options,
|
||||
hintsValues,
|
||||
displayValueResolver,
|
||||
...props
|
||||
}: Props<T, H>) {
|
||||
const [value, setValue] = useState(options[0]); // Category
|
||||
|
||||
const [hints, setHints] = useState<any[]>([]); //Response list
|
||||
const [onSelected, setOnSelected] = useState(""); // Selected item from response list
|
||||
const [query, setQuery] = useState(""); // Query
|
||||
|
||||
const onChange = (query: string) => {
|
||||
console.log(query)
|
||||
setQuery(query);
|
||||
setHints(hintsValues);
|
||||
};
|
||||
const onClick = () => {
|
||||
console.log(displayValueResolver ? displayValueResolver(value) : value);
|
||||
console.log(onSelected);
|
||||
console.log(query);
|
||||
};
|
||||
const searchResolver = (item: any) => {
|
||||
setOnSelected(item.caption);
|
||||
console.log(onSelected);
|
||||
};
|
||||
//empty items message
|
||||
const IsEmptyItems = () => {
|
||||
return <p className="text-blue-500">Nothing Found</p>;
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="bg-main bg-center bg-cover bg-origin-border bg-no-repeat min-h-[100vh] py-32 px-2 sm:px-6 md:px-6 lg:px-0">
|
||||
<div className="m-auto text-center font-bold text-4xl ">
|
||||
Scientific Library with Free Access
|
||||
</div>
|
||||
<div className="flex flex-row items-center justify-center space-x-3 pt-2">
|
||||
<div className=" text-2xl text-gray-400">Search</div>
|
||||
<div className=" text-3xl text-blue-500">320 455</div>
|
||||
<div className=" text-2xl text-gray-400">Items</div>
|
||||
</div>
|
||||
<div className="max-w-xl m-auto pt-16">
|
||||
<Inputgroup className="m-0 p-0">
|
||||
<div className="flex items-center w-full divide-x-2 divide-solid divide-gray-200">
|
||||
<div className="flex w-1/3">
|
||||
<Select
|
||||
inGroup={true}
|
||||
className="w-full top-0"
|
||||
options={options}
|
||||
value={value}
|
||||
onChange={setValue}
|
||||
displayValueResolver={(value: any) => value?.name ?? ""}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<SearchBar
|
||||
className="w-full"
|
||||
hints={hints}
|
||||
onChange={onChange}
|
||||
onSelected={searchResolver}
|
||||
inGroup={true}
|
||||
IsEmptyItems={IsEmptyItems}
|
||||
displayValueResolver={(value: any) => value.caption}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="pr-0.5">
|
||||
<Button onClick={onClick}>
|
||||
<Button.Icon>
|
||||
<SVGSearch className="fill-white stroke-white w-4 "></SVGSearch>
|
||||
</Button.Icon>
|
||||
</Button>
|
||||
</div>
|
||||
</Inputgroup>
|
||||
<div className="mt-7 pr-1 text-right font-semibold text-sm">
|
||||
Advanced Search
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
147
src/components/Navbar.tsx
Normal file
@ -0,0 +1,147 @@
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import React, { Fragment } from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Components */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import ContextMenuAction from "./drop-down-menu/ContextMenuAction";
|
||||
import ContextMenu from "./drop-down-menu/ContextMenu";
|
||||
import { Button } from "./Button/Button";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Icons */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import { ReactComponent as SVGFavoriteOutlined } from "assets/svg/favorite-outlined.svg";
|
||||
import { ReactComponent as SVGHamburger } from "assets/svg/hamburger.svg";
|
||||
import { ReactComponent as SVGFolder } from "assets/svg/folder.svg";
|
||||
import { ReactComponent as SVGFile } from "assets/svg/file.svg";
|
||||
import { ReactComponent as SVGEye } from "assets/svg/eye.svg";
|
||||
|
||||
type Props = React.ComponentPropsWithoutRef<"div">;
|
||||
|
||||
const Navbar = (props: Props) => {
|
||||
return (
|
||||
<div {...props}>
|
||||
<Menu as="div" className="relative inline-block text-left">
|
||||
<div>
|
||||
<Menu.Button as={Button} emphasis="low">
|
||||
<Button.Icon>
|
||||
<SVGHamburger className="h-6 w-6" />
|
||||
</Button.Icon>
|
||||
</Menu.Button>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
className="origin-top-right absolute right-0 mt-5 w-56 rounded-md
|
||||
shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none"
|
||||
>
|
||||
<div className="py-1">
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
href="#"
|
||||
className={classNames(
|
||||
active ? "bg-gray-100 text-gray-900" : "text-gray-700",
|
||||
"block px-4 py-2 text-sm"
|
||||
)}
|
||||
>
|
||||
CREATE NEW
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
href="#"
|
||||
className={classNames(
|
||||
active ? "bg-gray-100 text-gray-900" : "text-gray-700",
|
||||
"block px-4 py-2 text-sm"
|
||||
)}
|
||||
>
|
||||
{/* Dropdown Menu start My library */}
|
||||
<ContextMenu
|
||||
emphasis="low"
|
||||
button="MY LIBRARY"
|
||||
className="border-none"
|
||||
>
|
||||
<ContextMenuAction
|
||||
caption="My Publications"
|
||||
action={() => console.log("My publications")}
|
||||
icon={<SVGFile className="stroke-black" />}
|
||||
></ContextMenuAction>
|
||||
|
||||
<ContextMenuAction
|
||||
caption="My Favorites"
|
||||
action={() => console.log("My Favorites")}
|
||||
icon={<SVGFavoriteOutlined className="stroke-black" />}
|
||||
></ContextMenuAction>
|
||||
|
||||
<ContextMenuAction
|
||||
caption="My Collections"
|
||||
action={() => console.log("My Collections")}
|
||||
icon={<SVGFolder className="stroke-black fill-black" />}
|
||||
></ContextMenuAction>
|
||||
|
||||
<ContextMenuAction
|
||||
caption="Recent Viewed"
|
||||
action={() => console.log("Recent Viewed")}
|
||||
icon={<SVGEye className="stroke-black " />}
|
||||
></ContextMenuAction>
|
||||
</ContextMenu>
|
||||
{/* Dropdown Menu End My library */}
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
<Menu.Item>
|
||||
{({ active }) => (
|
||||
<a
|
||||
href="#"
|
||||
className={classNames(
|
||||
active ? "bg-gray-100 text-gray-900" : "text-gray-700",
|
||||
"block px-4 py-2 text-sm"
|
||||
)}
|
||||
>
|
||||
{/* Dropdown Menu About - start - */}
|
||||
<ContextMenu
|
||||
emphasis="low"
|
||||
button="ABOUT"
|
||||
className="border-none"
|
||||
>
|
||||
<ContextMenuAction
|
||||
caption="About Freeland"
|
||||
action={() => console.log("About Freeland")}
|
||||
></ContextMenuAction>
|
||||
|
||||
<ContextMenuAction
|
||||
caption="Contact Us"
|
||||
action={() => console.log("Contact Us")}
|
||||
></ContextMenuAction>
|
||||
|
||||
<ContextMenuAction
|
||||
caption="Help"
|
||||
action={() => console.log("Help")}
|
||||
></ContextMenuAction>
|
||||
</ContextMenu>
|
||||
{/* Dropdown Menu About - End - */}
|
||||
</a>
|
||||
)}
|
||||
</Menu.Item>
|
||||
</div>
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
@ -26,7 +26,7 @@ const Page = ({ title, withOutlet, children, activePath }: Props) => {
|
||||
return (
|
||||
<div className="grid grid-flow-col grid-rows-page grid-cols-layout max-h-screen overflow-hidden transition-all">
|
||||
<header className="col-span-2">
|
||||
<Header title={title} />
|
||||
<Header />
|
||||
</header>
|
||||
<aside
|
||||
className={classNames(
|
||||
|
162
src/components/SearchBar.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Imports */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import "../index.css";
|
||||
import { Combobox, Transition } from "@headlessui/react";
|
||||
import { Fragment, useEffect, useState } from "react";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component props */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
type Hint = {
|
||||
id: string;
|
||||
caption: string;
|
||||
};
|
||||
|
||||
type Props<T> = {
|
||||
onChange: (query: string) => void;
|
||||
onSelected: (value: Hint) => void;
|
||||
IsEmptyItems: () => React.ReactNode;
|
||||
hints: Hint[];
|
||||
displayValueResolver?: (element: T) => any;
|
||||
disabled?: boolean;
|
||||
inGroup?: boolean;
|
||||
className?: string;
|
||||
maxScrollSize?: number;
|
||||
elementScrollSize?: number;
|
||||
placeHolder?: string;
|
||||
};
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* styles */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
const inputStyle = `
|
||||
w-full
|
||||
cursor-default
|
||||
rounded
|
||||
overflow-hidden
|
||||
border
|
||||
border-solid
|
||||
shadow-none
|
||||
border-gray-100
|
||||
focus:outline-none
|
||||
focus:border-gray-200
|
||||
hover:border-gray-200
|
||||
py-2 pl-3
|
||||
text-sm
|
||||
text-gray-900
|
||||
`;
|
||||
|
||||
const inputList = `
|
||||
absolute z-10 mt-1 w-full max-h-56
|
||||
bg-white shadow-lg
|
||||
rounded py-1
|
||||
overflow-hidden
|
||||
focus:outline-none
|
||||
text-base
|
||||
sm:text-sm`;
|
||||
|
||||
const inputInGroup = [
|
||||
`border-none
|
||||
hover:none
|
||||
active:none
|
||||
focus:none
|
||||
`,
|
||||
];
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component implementation */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
export default function SearchBar<T>({
|
||||
onChange,
|
||||
onSelected,
|
||||
hints,
|
||||
displayValueResolver,
|
||||
disabled,
|
||||
inGroup = false,
|
||||
className,
|
||||
maxScrollSize = 140,
|
||||
elementScrollSize = 36,
|
||||
placeHolder = "Search...",
|
||||
IsEmptyItems,
|
||||
...props
|
||||
}: Props<T>) {
|
||||
const [selected, setSelected] = useState<any>(hints);
|
||||
const [query, setQuery] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
onChange(query);
|
||||
}, [query, onChange]);
|
||||
|
||||
const handleSelected = (value: Hint) => {
|
||||
setSelected(value);
|
||||
onSelected && onSelected(value);
|
||||
};
|
||||
return (
|
||||
<div>
|
||||
<Combobox value={selected} {...props} onChange={handleSelected}>
|
||||
<div className="relative">
|
||||
<div className="relative w-full bg-white text-left focus:outline-none sm:text-sm">
|
||||
<Combobox.Input
|
||||
className={classNames([
|
||||
[`${inputStyle}`],
|
||||
{ [`${inputInGroup}`]: inGroup },
|
||||
className,
|
||||
])}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
placeholder={placeHolder}
|
||||
displayValue={(value: T) =>
|
||||
displayValueResolver ? displayValueResolver(value) : value
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
{query.length > 0 && (
|
||||
<div className={`${inputList}`}>
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
afterLeave={() => setQuery("")}
|
||||
>
|
||||
<Combobox.Options>
|
||||
{hints.length === 0 && query !== "" ? IsEmptyItems() : null}
|
||||
{/* <Scrollbar
|
||||
style={{
|
||||
height: hints.length * elementScrollSize,
|
||||
maxHeight: maxScrollSize,
|
||||
}}
|
||||
> */}
|
||||
{hints.map((item: any, id: number) => (
|
||||
<Combobox.Option
|
||||
key={id}
|
||||
className={({ active, selected }) =>
|
||||
classNames(
|
||||
active
|
||||
? "text-gray-900 bg-blue-50"
|
||||
: "font-normal ",
|
||||
"cursor-default select-none relative py-2 pl-3 pr-9",
|
||||
selected
|
||||
? "text-gray-900 bg-blue-100"
|
||||
: "font-normal "
|
||||
)
|
||||
}
|
||||
value={item}
|
||||
>
|
||||
<div>{item.caption}</div>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
{/* </Scrollbar> */}
|
||||
</Combobox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Combobox>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -6,19 +6,23 @@ import { Fragment } from "react";
|
||||
import { Listbox, Transition } from "@headlessui/react";
|
||||
import classNames from "classnames";
|
||||
import "../index.css";
|
||||
import { ReactComponent as SelectIcon } from "../assets/svg/select-arrow.svg";
|
||||
import { ReactComponent as SelectIcon } from "../assets/svg/caret-down.svg";
|
||||
import { Scrollbar } from "react-scrollbars-custom";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component props */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
type Props<T> = {
|
||||
inGroup?: boolean;
|
||||
options?: T[];
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
value: T;
|
||||
displayValueResolver?: (element: T) => any;
|
||||
onChange: (element: T) => void;
|
||||
maxScrollSize?: number;
|
||||
elementScrollSize?: number;
|
||||
} & Omit<React.ComponentPropsWithRef<"select">, "value" | "onChange">;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@ -43,7 +47,7 @@ const SelectOptionsStyle = `
|
||||
absolute z-10 mt-1 w-full max-h-56
|
||||
bg-white shadow-lg
|
||||
rounded py-1
|
||||
overflow-auto
|
||||
overflow-hidden
|
||||
focus:outline-none
|
||||
text-base
|
||||
sm:text-sm
|
||||
@ -54,39 +58,55 @@ const SelectIconStyle = `
|
||||
absolute inset-y-0 right-0
|
||||
flex items-center pr-2
|
||||
`;
|
||||
|
||||
const inputInGroup = [
|
||||
` border-none
|
||||
hover:none
|
||||
active:none
|
||||
focus:none
|
||||
`,
|
||||
];
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component implementation */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
function Select<T>({
|
||||
inGroup = false, // We should use this flag to choose how we will style our component
|
||||
className,
|
||||
options = [],
|
||||
value,
|
||||
onChange,
|
||||
displayValueResolver,
|
||||
disabled,
|
||||
maxScrollSize = 140,
|
||||
elementScrollSize = 36,
|
||||
...props
|
||||
}: Props<T>): JSX.Element {
|
||||
return (
|
||||
<div className={classNames("fixed top-16 w-60", className)}>
|
||||
<div className={classNames("top-16 w-60", className)}>
|
||||
<Listbox value={value} {...props} onChange={onChange}>
|
||||
<div className="relative mt-1">
|
||||
<Listbox.Button className={`${SelectButtonStyle}`}>
|
||||
<div className="relative">
|
||||
<Listbox.Button
|
||||
className={classNames([
|
||||
[`${SelectButtonStyle}`],
|
||||
{ [`${inputInGroup}`]: inGroup },
|
||||
className,
|
||||
])}
|
||||
>
|
||||
{({ open }) => (
|
||||
<>
|
||||
<span className="block truncate">{`${
|
||||
displayValueResolver ? displayValueResolver(value) : value
|
||||
}`}</span>
|
||||
<span className={`${SelectIconStyle}`}>
|
||||
<SelectIcon
|
||||
<SelectIcon
|
||||
className={`${
|
||||
open ? "rotate-180 transform" : "font-normal"
|
||||
} h-2 w-3`}
|
||||
} h-3 w-4 fill-black hover:fill-black stroke-black`}
|
||||
/>
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</Listbox.Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
leave="transition ease-in duration-100"
|
||||
@ -94,28 +114,38 @@ function Select<T>({
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<Listbox.Options className={`${SelectOptionsStyle}`}>
|
||||
{options.map((option, id) => (
|
||||
<Listbox.Option
|
||||
key={id}
|
||||
className={({ active, selected }) =>
|
||||
classNames(
|
||||
active ? "text-gray-900 bg-blue-50" : "font-normal ",
|
||||
"cursor-default select-none relative py-2 pl-3 pr-9",
|
||||
selected ? "text-gray-900 bg-blue-100" : "font-normal "
|
||||
)
|
||||
}
|
||||
value={option}
|
||||
>
|
||||
{`${
|
||||
displayValueResolver ? displayValueResolver(option) : option
|
||||
}`}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
{/* <Scrollbar
|
||||
style={{
|
||||
height: options.length * elementScrollSize,
|
||||
maxHeight: maxScrollSize,
|
||||
}}
|
||||
> */}
|
||||
{options.map((option, id) => (
|
||||
<Listbox.Option
|
||||
key={id}
|
||||
className={({ active, selected }) =>
|
||||
classNames(
|
||||
active ? "text-gray-900 bg-blue-50" : "font-normal ",
|
||||
"cursor-default select-none relative py-2 pl-3 pr-9",
|
||||
selected ? "text-gray-900 bg-blue-100" : "font-normal "
|
||||
)
|
||||
}
|
||||
value={option}
|
||||
>
|
||||
{`${
|
||||
displayValueResolver
|
||||
? displayValueResolver(option)
|
||||
: option
|
||||
}`}
|
||||
</Listbox.Option>
|
||||
))}
|
||||
{/* </Scrollbar> */}
|
||||
</Listbox.Options>
|
||||
</Transition>
|
||||
</div>
|
||||
</Listbox>
|
||||
</div>
|
||||
//
|
||||
);
|
||||
}
|
||||
export default Select;
|
||||
|
94
src/components/SkeletonCard.tsx
Normal file
@ -0,0 +1,94 @@
|
||||
import Skeleton from "react-loading-skeleton";
|
||||
import AspectRatio from "./AspectRatio";
|
||||
import classNames from "classnames";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Props */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
type Props = {
|
||||
/**
|
||||
* Card component accept children
|
||||
*/
|
||||
children?: React.ReactNode;
|
||||
/**
|
||||
* Styling the card component
|
||||
*/
|
||||
className?: string | undefined;
|
||||
};
|
||||
|
||||
const SkeletonCard = ({ className, children }: Props) => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
" justify-between p-4 items-start rounded border border-[#F000F0] overflow-hidden",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Media
|
||||
SkeletonCard.Media = function SkeletonCardCardMedia() {
|
||||
return (
|
||||
<AspectRatio>
|
||||
<AspectRatio.Content>
|
||||
<Skeleton count={1} className="h-full" />
|
||||
</AspectRatio.Content>
|
||||
</AspectRatio>
|
||||
);
|
||||
};
|
||||
|
||||
// Content
|
||||
SkeletonCard.Content = function SkeletonCardCardContent({
|
||||
children,
|
||||
className,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className={classNames([className, "flex flex-col gap-y-4 mb-3"])}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Header
|
||||
SkeletonCard.Header = function SkeletonCardCardHeader({
|
||||
children,
|
||||
className,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className={classNames([className, "flex items-start gap-4 w-full"])}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Avatar
|
||||
SkeletonCard.Avatar = function SkeletonCardCardAvatar() {
|
||||
return <Skeleton circle width={48} height={48} />;
|
||||
};
|
||||
|
||||
// Title
|
||||
SkeletonCard.Title = function SkeletonCardCardTitle() {
|
||||
return <Skeleton count={1} width={150} height={28} />;
|
||||
};
|
||||
|
||||
// Body
|
||||
SkeletonCard.Body = function SkeletonCardCardBody({
|
||||
children,
|
||||
className,
|
||||
}: Props) {
|
||||
return <Skeleton count={3} className={classNames("text-sm", className)} />;
|
||||
};
|
||||
|
||||
// Action
|
||||
SkeletonCard.Action = function SkeletonCardCardAction({ className }: Props) {
|
||||
return (
|
||||
<div className={classNames("w-1/2", className)}>
|
||||
<Skeleton count={1} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SkeletonCard;
|
@ -25,7 +25,7 @@ export function BottomSheetModal({
|
||||
leaveFrom="translate-y-0 opacity-100"
|
||||
leaveTo="translate-y-full opacity-0"
|
||||
>
|
||||
<Dialog className="fixed bottom-0" as="div" onClose={() => {}}>
|
||||
<Dialog className="fixed bottom-0 w-full" as="div" onClose={() => {}}>
|
||||
{children}
|
||||
</Dialog>
|
||||
</Transition>
|
||||
|
@ -5,7 +5,7 @@ import React, { Fragment } from "react";
|
||||
import { Menu, Transition } from "@headlessui/react";
|
||||
import { PropsPartion } from "./ContextMenuItem";
|
||||
import classNames from "classnames";
|
||||
import { ReactComponent as SelectIcon } from "assets/svg/select-arrow.svg";
|
||||
import { SVGCaretDown } from "components/icons";
|
||||
type ChildType = React.ReactElement<any & PropsPartion[]>;
|
||||
type ChildrenType = ChildType[] | ChildType;
|
||||
|
||||
@ -25,13 +25,13 @@ type MenuProps = {
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
const MenuButtonStyle = `
|
||||
items-center
|
||||
inline-flex
|
||||
justify-center w-full
|
||||
cursor-default
|
||||
rounded
|
||||
border border-gray-100
|
||||
outline-8
|
||||
bg-white
|
||||
py-2
|
||||
pl-4
|
||||
pr-1
|
||||
@ -43,7 +43,7 @@ left-0
|
||||
mt-2 w-60
|
||||
origin-top-left
|
||||
rounded
|
||||
bg-white
|
||||
bg-white
|
||||
shadow-lg
|
||||
focus:outline-none
|
||||
py-2 px-4 sm:text-sm`;
|
||||
@ -51,17 +51,17 @@ py-2 px-4 sm:text-sm`;
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component implementation */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/**
|
||||
* Use width ContextMenuAction.tsx , for example:
|
||||
* <ContextMenu button="MyButton" emphasis="low/high">
|
||||
* <ContextMenuAction
|
||||
* caption="First Menu"
|
||||
* icon={icon}
|
||||
* action={() => alert('click')}
|
||||
* ></ContextMenuAction>
|
||||
* ...
|
||||
* </ContextMenu>
|
||||
*/
|
||||
/**
|
||||
* Use width ContextMenuAction.tsx , for example:
|
||||
* <ContextMenu button="MyButton" emphasis="low/high">
|
||||
* <ContextMenuAction
|
||||
* caption="First Menu"
|
||||
* icon={icon}
|
||||
* action={() => alert('click')}
|
||||
* ></ContextMenuAction>
|
||||
* ...
|
||||
* </ContextMenu>
|
||||
*/
|
||||
export default function ContextMenu({
|
||||
button,
|
||||
children,
|
||||
@ -69,48 +69,50 @@ export default function ContextMenu({
|
||||
emphasis = "low",
|
||||
}: MenuProps) {
|
||||
return (
|
||||
<Menu as="div" className="relative inline-block text-right">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Menu.Button
|
||||
<Menu as="div" className="relative inline-block text-right">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<Menu.Button
|
||||
className={classNames([
|
||||
`${MenuButtonStyle}`,
|
||||
{
|
||||
"hover:bg-gray-100 font-bold uppercase": emphasis === "high",
|
||||
},
|
||||
className,
|
||||
])}
|
||||
>
|
||||
{button}
|
||||
<SVGCaretDown
|
||||
className={`${
|
||||
open ? "rotate-180 transform" : "font-normal"
|
||||
} my-2 mx-3 w-4 flex-center fill-gray-900 stroke-gray-900`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
show={open}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
static
|
||||
className={classNames([
|
||||
`${MenuButtonStyle}`,
|
||||
{ "bg-gray-100 font-bold uppercase": emphasis === "high" },
|
||||
className,
|
||||
`${MenuItemStyle}`,
|
||||
{ "ml-2": emphasis === "high" },
|
||||
])}
|
||||
>
|
||||
{button}
|
||||
<SelectIcon
|
||||
className={`${
|
||||
open ? "rotate-180 transform" : "font-normal"
|
||||
} my-2 mx-3 h-2 w-3 flex-center`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</Menu.Button>
|
||||
|
||||
<Transition
|
||||
as={Fragment}
|
||||
show={open}
|
||||
enter="transition ease-out duration-100"
|
||||
enterFrom="transform opacity-0 scale-95"
|
||||
enterTo="transform opacity-100 scale-100"
|
||||
leave="transition ease-in duration-75"
|
||||
leaveFrom="transform opacity-100 scale-100"
|
||||
leaveTo="transform opacity-0 scale-95"
|
||||
>
|
||||
<Menu.Items
|
||||
static
|
||||
className={classNames([
|
||||
className,
|
||||
`${MenuItemStyle}`,
|
||||
{ "ml-2": emphasis === "high" },
|
||||
])}
|
||||
>
|
||||
{children}
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
{children}
|
||||
</Menu.Items>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Menu>
|
||||
);
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
|
||||
type Props = {
|
||||
action: Function;
|
||||
caption: string;
|
||||
@ -18,19 +17,16 @@ export default function ContextMenuAction({
|
||||
className,
|
||||
}: Props) {
|
||||
return (
|
||||
<button
|
||||
<a
|
||||
href="#"
|
||||
onClick={(e) => action(e)}
|
||||
disabled={disabled}
|
||||
className={classNames([
|
||||
"group flex items-center text-base",
|
||||
{ "opacity-50": disabled, "cursor-default": !disabled },
|
||||
"group flex px-2 rounded items-center text-base hover:bg-gray-100",
|
||||
className,
|
||||
])}
|
||||
>
|
||||
{icon && <div className="mr-2 h-5 w-5">{icon}</div>}
|
||||
<span className="px-2 py-2">{caption}</span>
|
||||
</button>
|
||||
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,65 +1,76 @@
|
||||
export { ReactComponent as SVGAgricultural } from "assets/svg/agricultural.svg";
|
||||
export { ReactComponent as SVGArrowBigRight } from "assets/svg/arrow-big-right.svg";
|
||||
export { ReactComponent as SVGArrowDown } from "assets/svg/arrow-down.svg";
|
||||
export { ReactComponent as SVGArrowLeft } from "assets/svg/arrow-left.svg";
|
||||
export { ReactComponent as SVGArrowRight } from "assets/svg/arrow-right.svg";
|
||||
export { ReactComponent as SVGBookmark } from "assets/svg/bookmark.svg";
|
||||
export { ReactComponent as SVGArrowUp } from "assets/svg/arrow-up.svg";
|
||||
export { ReactComponent as SVGBell } from "assets/svg/bell.svg";
|
||||
export { ReactComponent as SVGBellNotification } from "assets/svg/bell-notification.svg";
|
||||
export { ReactComponent as SVGBookmarkFilled } from "assets/svg/bookmark-filled.svg";
|
||||
export { ReactComponent as SVGBookmarkOutlined } from "assets/svg/bookmark-outlined.svg";
|
||||
export { ReactComponent as SVGCaretDown } from "assets/svg/caret-down.svg";
|
||||
export { ReactComponent as SVGCaretLeft } from "assets/svg/caret-left.svg";
|
||||
export { ReactComponent as SVGCaretRight } from "assets/svg/caret-right.svg";
|
||||
export { ReactComponent as SVGCaretUp } from "assets/svg/caret-up.svg";
|
||||
export { ReactComponent as SVGChart } from "assets/svg/chart.svg";
|
||||
export { ReactComponent as SVGChevronesLeft } from "assets/svg/chevrones-left.svg";
|
||||
export { ReactComponent as SVGChevronesRight } from "assets/svg/chevrones-right.svg";
|
||||
export { ReactComponent as SVGCircle } from "assets/svg/circle.svg";
|
||||
export { ReactComponent as SVGCite } from "assets/svg/cite.svg";
|
||||
export { ReactComponent as SVGCopy } from "assets/svg/copy.svg";
|
||||
export { ReactComponent as SVGDelete } from "assets/svg/delete.svg";
|
||||
export { ReactComponent as SVGDownload } from "assets/svg/download.svg";
|
||||
export { ReactComponent as SVGDuplicate } from "assets/svg/duplicate.svg";
|
||||
export { ReactComponent as SVGEdit1 } from "assets/svg/edit1.svg";
|
||||
export { ReactComponent as SVGEdit2 } from "assets/svg/edit2.svg";
|
||||
export { ReactComponent as SVGError } from "assets/svg/error.svg";
|
||||
export { ReactComponent as SVGEye } from "assets/svg/eye.svg";
|
||||
export { ReactComponent as SVGFavorite } from "assets/svg/favorite.svg";
|
||||
export { ReactComponent as SVGFiletext } from "assets/svg/filetext.svg";
|
||||
export { ReactComponent as SVGFolder } from "assets/svg/folder.svg";
|
||||
export { ReactComponent as SVGKey } from "assets/svg/key.svg";
|
||||
export { ReactComponent as SVGList } from "assets/svg/list.svg";
|
||||
export { ReactComponent as SVGMinus } from "assets/svg/minus.svg";
|
||||
export { ReactComponent as SVGMore } from "assets/svg/more.svg";
|
||||
export { ReactComponent as SVGPlus } from "assets/svg/plus.svg";
|
||||
export { ReactComponent as SVGPrinter } from "assets/svg/printer.svg";
|
||||
export { ReactComponent as SVGSearch } from "assets/svg/search.svg";
|
||||
export { ReactComponent as SVGShare } from "assets/svg/share.svg";
|
||||
export { ReactComponent as SVGUser } from "assets/svg/user.svg";
|
||||
export { ReactComponent as SVGXMark } from "assets/svg/xmark.svg";
|
||||
export { ReactComponent as SVGCheckmark } from "assets/svg/checkmark.svg";
|
||||
export { ReactComponent as SVGArrowUp } from "assets/svg/arrow-up.svg";
|
||||
export { ReactComponent as SVGBellNotification } from "assets/svg/bell-notification.svg";
|
||||
export { ReactComponent as SVGBell } from "assets/svg/bell.svg";
|
||||
export { ReactComponent as SVGBookmarkFilled } from "assets/svg/bookmark-filled.svg";
|
||||
export { ReactComponent as SVGBookmarkOutlined } from "assets/svg/bookmark-outlined.svg";
|
||||
export { ReactComponent as SVGChart } from "assets/svg/chart.svg";
|
||||
export { ReactComponent as SVGCircle } from "assets/svg/circle.svg";
|
||||
export { ReactComponent as SVGFacebook } from "assets/svg/facebook.svg";
|
||||
export { ReactComponent as SVGFavoriteFilled } from "assets/svg/favorite-filled.svg";
|
||||
export { ReactComponent as SVGFavoriteOutlined } from "assets/svg/favorite-outlined.svg";
|
||||
export { ReactComponent as SVGFile } from "assets/svg/file.svg";
|
||||
export { ReactComponent as SVGFiletext } from "assets/svg/filetext.svg";
|
||||
export { ReactComponent as SVGFilter } from "assets/svg/filter.svg";
|
||||
export { ReactComponent as SVGFlag } from "assets/svg/flag.svg";
|
||||
export { ReactComponent as SVGFolder } from "assets/svg/folder.svg";
|
||||
export { ReactComponent as SVGFormula } from "assets/svg/formula.svg";
|
||||
export { ReactComponent as SVGFundamental } from "assets/svg/fundamental.svg";
|
||||
export { ReactComponent as SVGGrid } from "assets/svg/grid.svg";
|
||||
export { ReactComponent as SVGHamburger } from "assets/svg/hamburger.svg";
|
||||
export { ReactComponent as SVGHelp } from "assets/svg/help.svg";
|
||||
export { ReactComponent as SVGHorizontal } from "assets/svg/horizontal.svg";
|
||||
export { ReactComponent as SVGHumanitarian } from "assets/svg/humanitarian.svg";
|
||||
export { ReactComponent as SVGImage } from "assets/svg/image.svg";
|
||||
export { ReactComponent as SVGInfo } from "assets/svg/info.svg";
|
||||
export { ReactComponent as SVGInstagram } from "assets/svg/instagram.svg";
|
||||
export { ReactComponent as SVGKey } from "assets/svg/key.svg";
|
||||
export { ReactComponent as SVGLeftMenu } from "assets/svg/left-menu.svg";
|
||||
export { ReactComponent as SVGLink } from "assets/svg/link.svg";
|
||||
export { ReactComponent as SVGList } from "assets/svg/list.svg";
|
||||
export { ReactComponent as SVGLogo } from "assets/svg/logo.svg";
|
||||
export { ReactComponent as SVGMedicine } from "assets/svg/medicine.svg";
|
||||
export { ReactComponent as SVGMinus } from "assets/svg/minus.svg";
|
||||
export { ReactComponent as SVGMore } from "assets/svg/more.svg";
|
||||
export { ReactComponent as SVGPages } from "assets/svg/pages.svg";
|
||||
export { ReactComponent as SVGPalete } from "assets/svg/palete.svg";
|
||||
export { ReactComponent as SVGPlot } from "assets/svg/plot.svg";
|
||||
export { ReactComponent as SVGPlus } from "assets/svg/plus.svg";
|
||||
export { ReactComponent as SVGPrinter } from "assets/svg/printer.svg";
|
||||
export { ReactComponent as SVGRightMenu } from "assets/svg/right-menu.svg";
|
||||
export { ReactComponent as SVGRotate } from "assets/svg/rotate.svg";
|
||||
export { ReactComponent as SVGSearch } from "assets/svg/search.svg";
|
||||
export { ReactComponent as SVGSelection } from "assets/svg/selection.svg";
|
||||
export { ReactComponent as SVGSend } from "assets/svg/send.svg";
|
||||
export { ReactComponent as SVGSettings } from "assets/svg/settings.svg";
|
||||
export { ReactComponent as SVGShape } from "assets/svg/shape.svg";
|
||||
export { ReactComponent as SVGShare } from "assets/svg/share.svg";
|
||||
export { ReactComponent as SVGSmile } from "assets/svg/smile.svg";
|
||||
export { ReactComponent as SVGSocials } from "assets/svg/socials.svg";
|
||||
export { ReactComponent as SVGTable } from "assets/svg/table.svg";
|
||||
export { ReactComponent as SVGTechnicsAndTechology } from "assets/svg/technics-and-techology.svg";
|
||||
export { ReactComponent as SVGText } from "assets/svg/text.svg";
|
||||
export { ReactComponent as SVGTextTransition } from "assets/svg/text-transition.svg";
|
||||
export { ReactComponent as SVGUser } from "assets/svg/user.svg";
|
||||
export { ReactComponent as SVGVertical } from "assets/svg/vertical.svg";
|
||||
export { ReactComponent as SVGVideo } from "assets/svg/video.svg";
|
||||
export { ReactComponent as SVGXmark } from "assets/svg/xmark.svg";
|
||||
|
40
src/components/layouts/ThreeColumn/ColumnLayout.css
Normal file
@ -0,0 +1,40 @@
|
||||
.left-bar {
|
||||
grid-area: lb;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.main-bar {
|
||||
grid-area: main;
|
||||
}
|
||||
|
||||
.right-bar {
|
||||
grid-area: rb;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.column-layout-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-areas: "main" "rb";
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.right-bar {
|
||||
max-width: 100%;
|
||||
}
|
||||
.column-layout-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(300px, 1fr);
|
||||
grid-template-areas:
|
||||
"lb main"
|
||||
"lb rb";
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1280px) {
|
||||
.column-layout-grid {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr auto;
|
||||
grid-template-areas: "lb main rb";
|
||||
}
|
||||
}
|
123
src/components/layouts/ThreeColumn/ColumnLayout.tsx
Normal file
@ -0,0 +1,123 @@
|
||||
import React, {
|
||||
Fragment,
|
||||
useState,
|
||||
useRef,
|
||||
useEffect,
|
||||
FC,
|
||||
isValidElement,
|
||||
} from "react";
|
||||
import { Dialog, Transition } from "@headlessui/react";
|
||||
import { BottomSheetModal } from "components/containers/modal/BottomSheetModal";
|
||||
import { BottomBarAcceptCookies } from "components/containers/modal/BottomBarAcceptCookies";
|
||||
import { Button } from "components/Button/Button";
|
||||
import classNames from "classnames";
|
||||
import "./ColumnLayout.css";
|
||||
import { Children } from "react";
|
||||
import MainColumn from "./Maincolumn";
|
||||
import LeftColumn from "./LeftColumn";
|
||||
import RightColumn from "./RightColumn";
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Custom hook to track container width */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
const useScreenWidth = () => {
|
||||
const ref: any = useRef();
|
||||
const [width, setWidth] = useState<null | number>(null);
|
||||
|
||||
const observer = useRef(
|
||||
new ResizeObserver((entries) => {
|
||||
const { width } = entries[0].contentRect;
|
||||
setWidth(width);
|
||||
})
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
observer.current.observe(ref.current);
|
||||
}, [ref, observer]);
|
||||
|
||||
return [ref, width];
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component extentions */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
type ColumnExtentions = {
|
||||
Left: React.FC<{
|
||||
children: React.ReactNode;
|
||||
openLeftBar?: boolean;
|
||||
widthElement?: number;
|
||||
className?: string;
|
||||
}>;
|
||||
Main: React.FC<{
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}>;
|
||||
Right: React.FC<{
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component properties */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
type ColumnLayoutProps = {
|
||||
children: React.ReactNode; //Column layout gets as children not more than three children
|
||||
} & Omit<React.ComponentPropsWithRef<"div">, "">;
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Component function */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export const ColumnLayout: React.FC<ColumnLayoutProps> & ColumnExtentions = ({
|
||||
children,
|
||||
}) => {
|
||||
const mdScreen = 768;
|
||||
|
||||
//Hooks
|
||||
const [ref, widthElement] = useScreenWidth(); // to track width of screen
|
||||
const [openLeftBar, setOpenLeftBar] = useState(false); //to open or close left bar
|
||||
function leftBar() {
|
||||
return setOpenLeftBar(!openLeftBar);
|
||||
}
|
||||
|
||||
// Change openLeftBar when width of screen > 768
|
||||
useEffect(() => {
|
||||
if (widthElement > mdScreen) {
|
||||
setOpenLeftBar(false);
|
||||
}
|
||||
});
|
||||
|
||||
// TODO
|
||||
const amountChildren = React.Children.count(children);
|
||||
if (amountChildren > 3) {
|
||||
alert("Layout gets only 3 or lesser children");
|
||||
}
|
||||
|
||||
const columns = React.Children.map(children, (child) => {
|
||||
if (
|
||||
child &&
|
||||
React.isValidElement(child) &&
|
||||
React.Children.only(child).type === LeftColumn
|
||||
) {
|
||||
return React.cloneElement(child, {
|
||||
openLeftBar: openLeftBar,
|
||||
widthElement: widthElement,
|
||||
});
|
||||
} else {
|
||||
return child;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div ref={ref} className="flex">
|
||||
<div className="w-full px-2 xl:px-12 column-layout-grid">
|
||||
{amountChildren <= 3 ? columns : undefined}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ColumnLayout.Left = LeftColumn;
|
||||
ColumnLayout.Main = MainColumn;
|
||||
ColumnLayout.Right = RightColumn;
|
49
src/components/layouts/ThreeColumn/LeftColumn.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import React, { Fragment, FunctionComponent as FC } from "react";
|
||||
import { Transition } from "@headlessui/react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type LeftColumnProps = {
|
||||
children: React.ReactNode;
|
||||
openLeftBar?: boolean;
|
||||
widthElement?: number;
|
||||
className?: string;
|
||||
} & Omit<React.ComponentPropsWithoutRef<"div">, "">;
|
||||
|
||||
const LeftColumn: React.FC<LeftColumnProps> = ({
|
||||
className,
|
||||
children,
|
||||
widthElement = 0,
|
||||
openLeftBar = false,
|
||||
}): JSX.Element => {
|
||||
const mdScreen = 768;
|
||||
return (
|
||||
<Transition
|
||||
appear
|
||||
as={Fragment}
|
||||
show={widthElement < mdScreen ? openLeftBar : true}
|
||||
enter={classNames(
|
||||
widthElement < mdScreen ? "ease-in-out duration-150" : "transition-none"
|
||||
)}
|
||||
enterFrom="-translate-x-full"
|
||||
enterTo="translate-x-0"
|
||||
leave="ease-in-out duration-150"
|
||||
leaveFrom="translate-x-0"
|
||||
leaveTo="-translate-x-full"
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
"h-full left-bar",
|
||||
{
|
||||
"fixed inset-0 w-80": widthElement < mdScreen,
|
||||
},
|
||||
className
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</Transition>
|
||||
);
|
||||
};
|
||||
|
||||
LeftColumn.displayName = "LeftColumn";
|
||||
export default LeftColumn;
|
18
src/components/layouts/ThreeColumn/Maincolumn.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
type MainColumnProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function MainColumn({ children, className }: MainColumnProps) {
|
||||
return (
|
||||
<div className={classNames("h-full main-bar overflow-auto", className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MainColumn;
|
||||
MainColumn.displayName = "MainColumn";
|
18
src/components/layouts/ThreeColumn/RightColumn.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import classNames from "classnames";
|
||||
import React from "react";
|
||||
|
||||
type RightColumnProps = {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
function RightColumn({ children, className }: RightColumnProps) {
|
||||
return (
|
||||
<div className={classNames("h-full right-bar overflow-auto", className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default RightColumn;
|
||||
RightColumn.displayName = "RightColumn";
|
60
src/components/parts/FeaturedArticlesCategories.tsx
Normal file
@ -0,0 +1,60 @@
|
||||
import React, { useMemo } from "react";
|
||||
import Typography from "components/typography/Typography";
|
||||
import CategoryCard from "components/Cards/CategoryCard";
|
||||
import {
|
||||
SVGMedicine,
|
||||
SVGAgricultural,
|
||||
SVGHumanitarian,
|
||||
SVGSocials,
|
||||
SVGTechnicsAndTechology,
|
||||
SVGFundamental,
|
||||
} from "components/icons";
|
||||
|
||||
const categories = [
|
||||
{ id: 1, title: "Medical", count: 5617813, icon: <SVGMedicine /> },
|
||||
{
|
||||
id: 2,
|
||||
title: "Technics and Technology",
|
||||
count: 5617813,
|
||||
icon: <SVGTechnicsAndTechology />,
|
||||
},
|
||||
{ id: 3, title: "Fundamental", count: 5617813, icon: <SVGFundamental /> },
|
||||
{ id: 4, title: "Humanitarian", count: 5617813, icon: <SVGHumanitarian /> },
|
||||
{ id: 5, title: "Argicultural", count: 5617813, icon: <SVGAgricultural /> },
|
||||
{ id: 6, title: "Social", count: 5617813, icon: <SVGSocials /> },
|
||||
];
|
||||
|
||||
export function FeaturedArticles() {
|
||||
const categoryCards = useMemo(
|
||||
() =>
|
||||
categories.map((category) => (
|
||||
<CategoryCard
|
||||
title={category.title}
|
||||
count={category.count}
|
||||
iconChild={category.icon}
|
||||
/>
|
||||
)),
|
||||
categories
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="w-full items-center justify-center">
|
||||
<div className="pt-14 lg:pt-16 pb-4 md:pb-8 text-center">
|
||||
<Typography
|
||||
htmlTag="h1"
|
||||
fontWeightVariant="semibold"
|
||||
className="text-3xl mb-2"
|
||||
>
|
||||
Featured articles
|
||||
</Typography>
|
||||
<Typography htmlTag="h2" className="text-base text-gray-500">
|
||||
Select the category of science <br className="visible sm:hidden" />
|
||||
you are interested in
|
||||
</Typography>
|
||||
</div>
|
||||
<div className="py-8 px-10 flex md:flex justify-start md:justify-center md:flex-wrap overflow-x-scroll md:overflow-hidden snap-x scroll-smooth overscroll-x-contain">
|
||||
{categoryCards}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
@ -12,16 +12,26 @@ import Link from "components/typography/Link";
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
const mainLinks = [
|
||||
{ label: "account settings", url: "/account/settings", enabled: true },
|
||||
{ label: "about freeland", url: "/about", enabled: true },
|
||||
{ label: "help", url: "/help", enabled: true },
|
||||
{ label: "contact us", url: "/contact-us", enabled: true },
|
||||
{ label: "account settings", url: "/account/settings", disabled: false },
|
||||
{ label: "about freeland", url: "/about", disabled: false },
|
||||
{ label: "help", url: "/help", disabled: false },
|
||||
{ label: "contact us", url: "/contact-us", disabled: false },
|
||||
];
|
||||
|
||||
const secondaryLinks = [
|
||||
{ index: 1, label: "Terms of Use", url: "/terms-of-use", enabled: true },
|
||||
{ index: 2, label: "Privacy Policy", url: "/privacy-policy", enabled: true },
|
||||
{ index: 3, label: "Cookies Policy", url: "/cookies-policy", enabled: true },
|
||||
{ index: 1, label: "Terms of Use", url: "/terms-of-use", disabled: false },
|
||||
{
|
||||
index: 2,
|
||||
label: "Privacy Policy",
|
||||
url: "/privacy-policy",
|
||||
disabled: false,
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
label: "Cookies Policy",
|
||||
url: "/cookies-policy",
|
||||
disabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
@ -45,7 +55,7 @@ export function Footer() {
|
||||
<RouterLink
|
||||
key={link.url}
|
||||
className="py-1 md:py-2 text-gray-900 px-4"
|
||||
enabled={link.enabled}
|
||||
disabled={link.disabled}
|
||||
to={link.url}
|
||||
>
|
||||
<Typography className="" fontWeightVariant="semibold" htmlTag="p">
|
||||
@ -61,9 +71,9 @@ export function Footer() {
|
||||
secondaryLinks.map((link) => (
|
||||
<div className="flex flex-row items-center">
|
||||
{link.index != 1 && circleDivider}
|
||||
<RouterLink key={link.url} enabled={link.enabled} to={link.url}>
|
||||
<Link key={link.url} disabled={link.disabled} to={link.url}>
|
||||
{link.label}
|
||||
</RouterLink>
|
||||
</Link>
|
||||
</div>
|
||||
)),
|
||||
secondaryLinks
|
||||
@ -76,20 +86,20 @@ export function Footer() {
|
||||
<div className="lg:px-12 px-2 sm:px-8 md:px-4 pb-2 md:pt-4 pt-2 lg:pt-5 bg-gray-200">
|
||||
<section className="w-full grid grid-cols-2 md:grid-rows-2 sm:grid-flow-row-dense sm:grid-rows-2 justify-items-between md:justify-between mb-2 md: items-center md:flex-row md:flex">
|
||||
<div className="sm:col-span-1">
|
||||
<RouterLink to="/">
|
||||
<Link to="*">
|
||||
<Typography className="text-2xl" fontWeightVariant="semibold">
|
||||
Freeland
|
||||
</Typography>
|
||||
</RouterLink>
|
||||
</Link>
|
||||
</div>
|
||||
<div className="order-last md:order-none justify-center justify-items-center items-center flex flex-col col-span-4 sm:flex sm:flex-row text-sm">
|
||||
{mainLinksPart}
|
||||
</div>
|
||||
<div className="flex flex-row sm:col-span-1 justify-end">
|
||||
<Link href="https://www.facebook.com">
|
||||
<Link to="https://www.facebook.com">
|
||||
<SVGFacebook className="w-5 h-5 fill-gray-900 stroke-gray-900 mr-2" />
|
||||
</Link>
|
||||
<Link href="https://www.instagram/com">
|
||||
<Link to="https://www.instagram/com">
|
||||
{" "}
|
||||
<SVGInstagram className="w-5 h-5 fill-gray-900 stroke-gray-900 ml-2 " />
|
||||
</Link>
|
||||
|
@ -1,113 +1,174 @@
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Libs */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import React, { useEffect } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
import { useState } from "react";
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Components */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import ContextMenu from "components/containers/contextmenu/ContextMenu";
|
||||
import Logotype from "components/Logotype";
|
||||
import Avatar from "components/containers/Avatar";
|
||||
import ContextMenuAction from "components/containers/contextmenu/ContextMenuAction";
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* SVG */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Hooks */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
import { useUserStore } from "user/data/userSlice";
|
||||
import { useUserViewModel } from "user/controller/userViewModel";
|
||||
import { useUIStore } from "ui/data/uiSlice";
|
||||
import { useUIViewModel } from "ui/controller/uiViewModel";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import ServicesNavigationContext from "services/views/ServicesNavigationContext";
|
||||
import { useAuthStore } from "auth/data/authSlice";
|
||||
import { useAuthViewModel } from "auth/controller/useAuthViewModel";
|
||||
import ContextMenuAction from "../drop-down-menu/ContextMenuAction";
|
||||
import ContextMenu from "../drop-down-menu/ContextMenu";
|
||||
import Logofreeland from "../Logofreeland";
|
||||
import { Button } from "../Button/Button";
|
||||
import Avatar from "../Avatar";
|
||||
import Navbar from "../Navbar";
|
||||
import Logo from "../Logo";
|
||||
import { RouterLink } from "components/typography/RouterLink";
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Main application header */
|
||||
/* Icons */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
type Props = {
|
||||
title?: string;
|
||||
}
|
||||
/**
|
||||
* Main application header
|
||||
* @return {JSX.Element}
|
||||
*/
|
||||
export default function Header({title}: Props) {
|
||||
import {
|
||||
SVGBellNotification,
|
||||
SVGBell,
|
||||
SVGFavoriteOutlined,
|
||||
SVGFolder,
|
||||
SVGFile,
|
||||
SVGEye,
|
||||
} from "components/icons";
|
||||
|
||||
const {t} = useTranslation();
|
||||
const userStore = useUserStore();
|
||||
const uiStore = useUIStore();
|
||||
const authStore = useAuthStore();
|
||||
const {signOut} = useAuthViewModel(authStore);
|
||||
const { user, isLoading, getUser } = useUserViewModel(userStore);
|
||||
const { showSearchbar } = useUIViewModel(uiStore);
|
||||
const Header = () => {
|
||||
const [authenticated, setAuthenticated] = useState(false);
|
||||
const onClick = () => setAuthenticated(true);
|
||||
const [notification, setNotification] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
getUser();
|
||||
}, [getUser]);
|
||||
|
||||
const navigate = useNavigate();
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Implement Header Component */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
return (
|
||||
<div
|
||||
className="flex items-center flex-row
|
||||
justify-between py-2.5 px-8 bg-gray-300/5 h-14"
|
||||
<header
|
||||
className="header flex justify-between items-center box-border w-full
|
||||
border border-gray-75
|
||||
h-16
|
||||
px-4
|
||||
gap-5
|
||||
md:gap-20
|
||||
lg:gap-40 lg:px-8
|
||||
xl:gap-60 xl:px-16"
|
||||
>
|
||||
<div className="flex-none flex items-center space-x-3">
|
||||
<ServicesNavigationContext />
|
||||
<Logotype name={title} />
|
||||
</div>
|
||||
<div className="flex-none flex items-center space-x-3">
|
||||
|
||||
<ContextMenu
|
||||
button={<Avatar className="p-2" />}
|
||||
className="absolute w-full min-h-14 sm:w-80 -bottom-3
|
||||
sm:top-auto sm:bottom-auto right-0
|
||||
mt-5 z-10 sm:transform -translate-1/2
|
||||
origin-top-center flex flex-col items-center justify-center !pt-8"
|
||||
{/* Logo and Menu */}
|
||||
<div className="flex gap-8 xl:gap-x-16">
|
||||
{/* Logo - start - className="w-7 sm:w-10 " /> */}
|
||||
<a className="Logo flex items-center gap-1 sm:gap-2 " href="/">
|
||||
<Logo
|
||||
className={classNames(authenticated ? "w-10" : "w-7 sm:w-10")}
|
||||
/>
|
||||
<Logofreeland
|
||||
className={classNames(authenticated ? "w-28" : "w-20 sm:w-28")}
|
||||
/>
|
||||
</a>
|
||||
{/* Logo - end - */}
|
||||
|
||||
{/* Menu( Create new - My library - About ) Start */}
|
||||
<div
|
||||
className="hidden lg:flex items-center
|
||||
gap-2"
|
||||
>
|
||||
<Logotype name="techpal" />
|
||||
<div className="w-full my-4 flex justify-center">
|
||||
<Avatar width="27%" className="p-3.5" />
|
||||
</div>
|
||||
<div className="text-xl text-center font-bold leading-relaxed">
|
||||
{isLoading
|
||||
? " "
|
||||
: t("hellousr", {
|
||||
username: [user?.lastname, user?.firstname].join(" "),
|
||||
})}
|
||||
</div>
|
||||
<div className="text-sm text-center text-gray-300 leading-relaxed mb-5">
|
||||
{isLoading ? " " : user?.email}
|
||||
</div>
|
||||
<ContextMenuAction
|
||||
action={() => {
|
||||
navigate('/personal-information');
|
||||
}}
|
||||
disabled={isLoading}
|
||||
className="group w-full py-2 -mx-4 px-4 hover:bg-gray-200/20 rounded-md hover:text-blue-400 transition-colors"
|
||||
caption="Account settings"
|
||||
|
||||
/>
|
||||
<ContextMenuAction
|
||||
action={() => {
|
||||
navigate("");
|
||||
}}
|
||||
disabled={isLoading}
|
||||
className="group w-full py-2 -mx-4 px-4 hover:bg-gray-200/20 rounded-md hover:text-blue-400 transition-colors"
|
||||
caption={t("account.connect")}
|
||||
|
||||
/>
|
||||
<ContextMenuAction
|
||||
action={signOut}
|
||||
className="group w-full py-2 -mx-4 px-4 hover:bg-gray-200/20 rounded-md transition-colors"
|
||||
caption={t('logOut')}
|
||||
|
||||
/>
|
||||
</ContextMenu>
|
||||
{/* Link Create now - start - */}
|
||||
<RouterLink
|
||||
className="text-blue-500 px-4 font-bold uppercase"
|
||||
to="/create-new"
|
||||
>
|
||||
Create new
|
||||
</RouterLink>
|
||||
{/* Link Create now - end - */}
|
||||
|
||||
{/* Dropdown Menu My library - start - */}
|
||||
<ContextMenu
|
||||
emphasis="high"
|
||||
button="My library"
|
||||
className="border-none uppercase"
|
||||
>
|
||||
<ContextMenuAction
|
||||
caption="My Publications"
|
||||
action={() => console.log("My publications")}
|
||||
icon={<SVGFile className="stroke-black " />}
|
||||
></ContextMenuAction>
|
||||
|
||||
<ContextMenuAction
|
||||
caption="My Favorites"
|
||||
action={() => console.log("My Favorites")}
|
||||
icon={<SVGFavoriteOutlined className="stroke-black" />}
|
||||
></ContextMenuAction>
|
||||
|
||||
<ContextMenuAction
|
||||
caption="My Collections"
|
||||
action={() => console.log("My Collections")}
|
||||
icon={<SVGFolder className="stroke-black fill-black" />}
|
||||
></ContextMenuAction>
|
||||
|
||||
<ContextMenuAction
|
||||
caption="Recent Viewed"
|
||||
action={() => console.log("Recent Viewed")}
|
||||
icon={<SVGEye className="stroke-black " />}
|
||||
></ContextMenuAction>
|
||||
</ContextMenu>
|
||||
{/* Dropdown Menu My library - End - */}
|
||||
|
||||
{/* Dropdown Menu About - start - */}
|
||||
<ContextMenu
|
||||
emphasis="high"
|
||||
button="About"
|
||||
className="border-none uppercase"
|
||||
>
|
||||
<ContextMenuAction
|
||||
caption="About Freeland"
|
||||
action={() => console.log("About Freeland")}
|
||||
></ContextMenuAction>
|
||||
|
||||
<ContextMenuAction
|
||||
caption="Contact Us"
|
||||
action={() => console.log("Contact Us")}
|
||||
></ContextMenuAction>
|
||||
|
||||
<ContextMenuAction
|
||||
caption="Help"
|
||||
action={() => console.log("Help")}
|
||||
></ContextMenuAction>
|
||||
</ContextMenu>
|
||||
{/* Dropdown Menu About - End - */}
|
||||
</div>
|
||||
{/* Menu( Create new - My library - About ) End */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sign in - Sign up - Notification - Avatar - Burger */}
|
||||
<div className="flex items-center font-bold text-sm gap-1 md:gap-2 ">
|
||||
{!authenticated
|
||||
? [
|
||||
<Button
|
||||
emphasis="low"
|
||||
onClick={onClick}
|
||||
className="text-xs sm:px-4 sm:text-sm "
|
||||
>
|
||||
Sign in
|
||||
</Button>,
|
||||
<Button
|
||||
emphasis="medium"
|
||||
className="hidden md:flex"
|
||||
onClick={onClick}
|
||||
>
|
||||
Sign up
|
||||
</Button>,
|
||||
]
|
||||
: [
|
||||
<Button emphasis="low">
|
||||
<Button.Icon>
|
||||
{!notification ? (
|
||||
<SVGBell className="h-6 w-6 fill-gray-900 stroke-gray-900" />
|
||||
) : (
|
||||
<SVGBellNotification className="h-6 w-6 fill-gray-900 stroke-gray-900" />
|
||||
)}
|
||||
</Button.Icon>
|
||||
</Button>,
|
||||
|
||||
<Button emphasis="low" className="hidden lg:flex">
|
||||
<Button.Icon>
|
||||
<Avatar className="bg-[rgb(255,122,69)] text-white">K</Avatar>
|
||||
</Button.Icon>
|
||||
</Button>,
|
||||
]}
|
||||
{/* Burger component will be shown for the small screens */}
|
||||
<Navbar className="block lg:hidden" />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default Header;
|
||||
|
@ -1,9 +1,20 @@
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
className?: string | undefined;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
export default function Heading({ children }: Props) {
|
||||
return <h3 className="text-2xl text-current font-medium leading-relaxed">{children}</h3>;
|
||||
export default function Heading({ children, className }: Props) {
|
||||
return (
|
||||
<h3
|
||||
className={classNames([
|
||||
className,
|
||||
"text-2xl text-current font-medium leading-7 ",
|
||||
])}
|
||||
>
|
||||
{children}
|
||||
</h3>
|
||||
);
|
||||
}
|
||||
|
@ -1,21 +1,57 @@
|
||||
import React from "react";
|
||||
import { NavLink, NavLinkProps } from "react-router-dom";
|
||||
import classNames from "classnames";
|
||||
|
||||
type Props = {
|
||||
href?: string;
|
||||
to: string;
|
||||
children: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
} & Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "">;
|
||||
} & NavLinkProps;
|
||||
|
||||
export default function Link({ href, children, disabled, ...props }: Props) {
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/anchor-is-valid
|
||||
<a
|
||||
href={disabled ? undefined : href}
|
||||
aria-disabled={disabled}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
function getURL(to: string): URL {
|
||||
try {
|
||||
return new URL(to);
|
||||
} catch {
|
||||
let outurl = `${window.location.origin}${
|
||||
to.startsWith("/") ? to : "/" + to
|
||||
}`;
|
||||
return new URL(outurl);
|
||||
}
|
||||
}
|
||||
|
||||
export default function Link({
|
||||
to,
|
||||
children,
|
||||
disabled,
|
||||
className,
|
||||
style,
|
||||
...props
|
||||
}: Props) {
|
||||
const link =
|
||||
getURL(to).hostname === window.location.hostname ? (
|
||||
<NavLink
|
||||
to={getURL(to).pathname}
|
||||
style={style}
|
||||
className={classNames({ "pointer-events-none": disabled }, className)}
|
||||
>
|
||||
{children}
|
||||
</NavLink>
|
||||
) : (
|
||||
<a
|
||||
href={disabled ? undefined : getURL(to).origin}
|
||||
style={
|
||||
typeof style === "function"
|
||||
? style({
|
||||
isActive: true,
|
||||
})
|
||||
: style
|
||||
}
|
||||
aria-disabled={disabled}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
return <div>{link}</div>;
|
||||
}
|
||||
|
@ -1,16 +1,21 @@
|
||||
import classNames from "classnames";
|
||||
import { NavLink, NavLinkProps, To } from "react-router-dom";
|
||||
import { NavLink, NavLinkProps } from "react-router-dom";
|
||||
|
||||
type Props = {
|
||||
enabled?: boolean;
|
||||
disabled?: boolean;
|
||||
children?: React.ReactNode;
|
||||
} & NavLinkProps;
|
||||
|
||||
export function RouterLink({ children, enabled = true, className, to }: Props) {
|
||||
export function RouterLink({
|
||||
children,
|
||||
disabled = false,
|
||||
className,
|
||||
to,
|
||||
}: Props) {
|
||||
return (
|
||||
<NavLink
|
||||
to={to}
|
||||
className={classNames({ "pointer-events-none": !enabled }, className)}
|
||||
className={classNames({ "pointer-events-none": disabled }, className)}
|
||||
>
|
||||
{children}
|
||||
</NavLink>
|
||||
|
@ -12,7 +12,9 @@ function Helper() {
|
||||
let obj = {};
|
||||
obj[key] = this.withOpacityValue(`--color-${key}-text-base`);
|
||||
obj[`${key}-muted`] = this.withOpacityValue(`--color-${key}-text-muted`);
|
||||
obj[`${key}-inverted`] = this.withOpacityValue(`--color-${key}-text-inverted`);
|
||||
obj[`${key}-inverted`] = this.withOpacityValue(
|
||||
`--color-${key}-text-inverted`
|
||||
);
|
||||
|
||||
return obj;
|
||||
};
|
||||
@ -22,7 +24,9 @@ function Helper() {
|
||||
obj[key] = this.withOpacityValue(`--color-${key}`);
|
||||
obj[`${key}-disabled`] = this.withOpacityValue(`--color-${key}-disabled`);
|
||||
obj[`${key}-hover`] = this.withOpacityValue(`--color-${key}-hover`);
|
||||
obj[`${key}-disabled-hover`] = this.withOpacityValue(`--color-${key}-disabled-hover`);
|
||||
obj[`${key}-disabled-hover`] = this.withOpacityValue(
|
||||
`--color-${key}-disabled-hover`
|
||||
);
|
||||
|
||||
return obj;
|
||||
};
|
||||
@ -44,7 +48,7 @@ function Helper() {
|
||||
|
||||
this.luminanced = function (key, additionalStops = []) {
|
||||
let obj = {};
|
||||
obj['50'] = this.withOpacityValue(`--color-${key}-50`);
|
||||
obj["50"] = this.withOpacityValue(`--color-${key}-50`);
|
||||
for (let index = 0; index < additionalStops.length; index++) {
|
||||
const element = additionalStops[index];
|
||||
obj[element] = this.withOpacityValue(`--color-${key}-${element}`);
|
||||
@ -62,64 +66,68 @@ const ColorHelper = new Helper();
|
||||
* @type { import('@types/tailwindcss/tailwind-config').TailwindConfig }
|
||||
*/
|
||||
module.exports = {
|
||||
content: ['./src/**/*.{jsx,tsx}'],
|
||||
content: ["./src/**/*.{jsx,tsx}"],
|
||||
plugins: [
|
||||
function ({ addComponents }) {
|
||||
addComponents({
|
||||
'.container': {
|
||||
maxWidth: '840px',
|
||||
paddingLeft: '0.5rem',
|
||||
paddingRight: '0.5rem',
|
||||
'@screen sm': {
|
||||
maxWidth: 'calc(100% - 30px)',
|
||||
".container": {
|
||||
maxWidth: "840px",
|
||||
paddingLeft: "0.5rem",
|
||||
paddingRight: "0.5rem",
|
||||
"@screen sm": {
|
||||
maxWidth: "calc(100% - 30px)",
|
||||
},
|
||||
'@screen md': {
|
||||
maxWidth: 'calc(100% - 30px)',
|
||||
"@screen md": {
|
||||
maxWidth: "calc(100% - 30px)",
|
||||
},
|
||||
'@screen lg': {
|
||||
maxWidth: '840px',
|
||||
"@screen lg": {
|
||||
maxWidth: "840px",
|
||||
},
|
||||
'@screen xl': {
|
||||
maxWidth: '840px',
|
||||
"@screen xl": {
|
||||
maxWidth: "840px",
|
||||
},
|
||||
'@screen 2xl': {
|
||||
maxWidth: '840px',
|
||||
"@screen 2xl": {
|
||||
maxWidth: "840px",
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
require("@tailwindcss/line-clamp"),
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
backgroundImage: {
|
||||
'main': "url('/src/assets/svg/background.svg')",
|
||||
},
|
||||
screens: {
|
||||
tall: { raw: '(min-height: 1200px) and (orientation: portrait)' },
|
||||
skewed: { raw: '(max-height: 600px)' },
|
||||
"1.5xl": '1300px',
|
||||
tall: { raw: "(min-height: 1200px) and (orientation: portrait)" },
|
||||
skewed: { raw: "(max-height: 600px)" },
|
||||
"1.5xl": "1300px",
|
||||
},
|
||||
height: {
|
||||
'half-screen': '50vh',
|
||||
"half-screen": "50vh",
|
||||
},
|
||||
minHeight: {
|
||||
'half-screen': '50vh',
|
||||
"half-screen": "50vh",
|
||||
},
|
||||
colors: {
|
||||
aside: 'var(--color-aside)',
|
||||
main: 'var(--color-body)',
|
||||
header: '#191D2B',
|
||||
transparent: 'transparent',
|
||||
current: 'currentColor',
|
||||
white: '#ffffff',
|
||||
black: '#000000',
|
||||
serv: ColorHelper.luminanced('serv'),
|
||||
blue: ColorHelper.luminanced('blue'),
|
||||
gray: ColorHelper.luminanced('gray', [75]),
|
||||
aside: "var(--color-aside)",
|
||||
main: "var(--color-body)",
|
||||
header: "#191D2B",
|
||||
transparent: "transparent",
|
||||
current: "currentColor",
|
||||
white: "#ffffff",
|
||||
black: "#000000",
|
||||
serv: ColorHelper.luminanced("serv"),
|
||||
blue: ColorHelper.luminanced("blue"),
|
||||
gray: ColorHelper.luminanced("gray", [75]),
|
||||
},
|
||||
gridTemplateColumns: {
|
||||
'layout': 'min-content 7fr',
|
||||
layout: "min-content 7fr",
|
||||
},
|
||||
gridTemplateRows: {
|
||||
'page': '3.5rem calc(100vh - 3.5rem)'
|
||||
}
|
||||
page: "3.5rem calc(100vh - 3.5rem)",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|