chore: import upstream snapshot with attribution
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Leon</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg viewBox="0 0 128 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.9884 7.28647C36.5681 7.43879 38.9101 8.84352 40.259 11.0477C41.608 13.2518 41.7933 15.9765 40.7551 18.343C40.6869 18.4965 40.6147 18.6486 40.5373 18.7982C43.0045 20.2581 44.4873 22.9401 44.4116 25.8059C44.3358 28.6717 42.7135 31.2716 40.1726 32.5992C40.1402 36.232 37.6344 39.3744 34.0999 40.2147C33.9591 42.8611 32.3604 45.2104 29.9502 46.3124C27.5401 47.4145 24.7174 47.0869 22.6236 45.4622L22.1553 45.0988V4.52233L22.4557 4.17993C24.1273 2.27031 26.7399 1.47836 29.1904 2.13845C31.6409 2.79853 33.5022 4.79561 33.9884 7.28647Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3667 9.69371V44.5124C25.2424 45.9667 27.806 46.1581 29.8769 44.9985C31.9478 43.8389 33.1244 41.5532 32.8647 39.194C36.3159 38.8876 38.9623 35.997 38.9636 32.5323C38.9636 32.3407 38.9518 32.1387 38.9322 31.9131L38.9243 31.837C41.5288 30.8301 43.2343 28.3116 43.2021 25.5193C43.1699 22.7271 41.407 20.2485 38.78 19.3019C40.3596 17.2885 40.6523 14.5504 39.5341 12.2485C38.4158 9.94663 36.0824 8.4843 33.5233 8.48155C33.3199 8.48155 33.1126 8.49204 32.9001 8.51172C32.881 6.2539 31.469 4.24266 29.3519 3.45782C27.2347 2.67297 24.8529 3.27775 23.3667 4.97751V9.70028V9.69371Z" fill="#ED297A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.9547 25.5963C41.9555 23.17 40.349 21.0365 38.0169 20.3668C35.6848 19.6971 33.1911 20.6531 31.9044 22.7102L30.8549 22.0542C32.2476 19.8293 34.8126 18.6242 37.4143 18.9726C38.41 17.9573 38.9653 16.5903 38.9597 15.1682C38.9597 12.165 36.5252 9.73045 33.522 9.73045C33.3261 9.73012 33.1303 9.74013 32.9355 9.76063L32.7598 9.78161C32.2087 12.1562 30.1466 13.8753 27.7116 13.9901L27.6578 12.7451C29.5156 12.6654 31.0996 11.373 31.5505 9.56894C32.0014 7.76493 31.2118 5.87925 29.61 4.93479C28.0082 3.99032 25.9759 4.21216 24.6156 5.47996H23.3667V9.69371V4.97751C24.8529 3.27775 27.2347 2.67297 29.3519 3.45782C31.469 4.24266 32.881 6.2539 32.9001 8.51172C33.1126 8.49204 33.3199 8.48155 33.5233 8.48155C36.0824 8.4843 38.4158 9.94663 39.5341 12.2485C40.6523 14.5504 40.3596 17.2885 38.78 19.3019C41.407 20.2485 43.1699 22.7271 43.2021 25.5193C43.2343 28.3116 41.5288 30.8301 38.9243 31.837L38.9322 31.913C38.9518 32.1334 38.9636 32.3407 38.9636 32.5323C38.9623 35.997 36.3159 38.8876 32.8647 39.194C33.1257 41.5536 31.9494 43.8404 29.878 45.0004C27.8065 46.1602 25.2421 45.9681 23.3667 44.5124V40.275V43.8577H24.6156C26.0583 44.707 27.8454 44.7186 29.299 43.8881C30.7526 43.0576 31.6502 41.5122 31.6512 39.8381C31.6517 39.6522 31.6408 39.4664 31.6184 39.2819L31.6066 39.1861C29.904 39.0137 28.3322 38.1954 27.2144 36.8995L28.1564 36.0849C29.6418 37.8025 32.0379 38.4165 34.1662 37.625C36.2946 36.8335 37.7073 34.8031 37.7094 32.5323C37.7094 32.4011 37.7094 32.2869 37.7003 32.178C34.087 32.8255 30.6267 30.4406 29.9458 26.8334L31.1711 26.6038C31.6918 29.3619 34.2257 31.2706 37.0203 31.0102C39.815 30.7498 41.9525 28.4057 41.9547 25.599" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.9547 25.5963C41.9555 23.17 40.349 21.0365 38.0169 20.3668C35.6848 19.6971 33.1911 20.6531 31.9044 22.7102L30.8549 22.0542C32.2476 19.8293 34.8126 18.6242 37.4143 18.9726C38.41 17.9573 38.9653 16.5903 38.9597 15.1682C38.9597 12.165 36.5252 9.73045 33.522 9.73045C33.3261 9.73012 33.1303 9.74013 32.9355 9.76063L32.7598 9.78161C32.2087 12.1562 30.1466 13.8753 27.7116 13.9901L27.6578 12.7451C29.5156 12.6654 31.0996 11.373 31.5505 9.56894C32.0014 7.76493 31.2118 5.87925 29.61 4.93479C28.0082 3.99032 25.9759 4.21216 24.6156 5.47996H23.3667V9.69371V4.97751C24.8529 3.27775 27.2347 2.67297 29.3519 3.45782C31.469 4.24266 32.881 6.2539 32.9001 8.51172C33.1126 8.49204 33.3199 8.48155 33.5233 8.48155C36.0824 8.4843 38.4158 9.94663 39.5341 12.2485C40.6523 14.5504 40.3596 17.2885 38.78 19.3019C41.407 20.2485 43.1699 22.7271 43.2021 25.5193C43.2343 28.3116 41.5288 30.8301 38.9243 31.837L38.9322 31.913C38.9518 32.1334 38.9636 32.3407 38.9636 32.5323C38.9623 35.997 36.3159 38.8876 32.8647 39.194C33.1257 41.5536 31.9494 43.8404 29.878 45.0004C27.8065 46.1602 25.2421 45.9681 23.3667 44.5124V40.275V43.8577H24.6156C26.0583 44.707 27.8454 44.7186 29.299 43.8881C30.7526 43.0576 31.6502 41.5122 31.6512 39.8381C31.6517 39.6522 31.6408 39.4664 31.6184 39.2819L31.6066 39.1861C29.904 39.0137 28.3322 38.1954 27.2144 36.8995L28.1564 36.0849C29.6418 37.8025 32.0379 38.4165 34.1662 37.625C36.2946 36.8335 37.7073 34.8031 37.7094 32.5323C37.7094 32.4011 37.7094 32.2869 37.7003 32.178C34.087 32.8255 30.6267 30.4406 29.9458 26.8334L31.1711 26.6038C31.6918 29.3619 34.2257 31.2706 37.0203 31.0102C39.815 30.7498 41.9525 28.4057 41.9547 25.599" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5332 7.32519C8.95369 7.47707 6.6116 8.88153 5.26259 11.0855C3.91357 13.2895 3.72832 16.0141 4.76658 18.3804C4.8348 18.5352 4.90696 18.6874 4.98567 18.8369C2.51982 20.2978 1.03815 22.9791 1.11359 25.8442C1.18903 28.7092 2.80975 31.3089 5.34906 32.6379C5.38061 36.2719 7.88639 39.4159 11.4217 40.2573C11.5625 42.9037 13.1613 45.253 15.5715 46.355C17.9816 47.4571 20.8044 47.1295 22.8981 45.5049L23.3664 45.1415V4.56237L23.066 4.21996C21.3946 2.31037 18.7822 1.51826 16.3318 2.17806C13.8813 2.83785 12.0199 4.83454 11.5332 7.32519Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.1555 9.73183V44.5518C20.2799 46.0098 17.7138 46.2033 15.6407 45.0431C13.5677 43.883 12.3906 41.5945 12.6523 39.2334C9.20154 38.9263 6.55597 36.036 6.55465 32.5717C6.55465 32.3802 6.56515 32.1781 6.58483 31.9525L6.5927 31.8764C3.98803 30.8677 2.2841 28.347 2.31888 25.554C2.35366 22.7611 4.11984 20.2836 6.74881 19.3401C5.17098 17.3279 4.87799 14.5924 5.99386 12.2918C7.10975 9.99111 9.43937 8.52763 11.9963 8.52098C12.1996 8.52098 12.4082 8.53148 12.6195 8.55116C12.6359 6.29133 14.0483 4.27722 16.1674 3.49187C18.2864 2.70652 20.6703 3.31366 22.1555 5.01695V9.73183Z" fill="#1C75DB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.56743 25.6357C3.56664 23.2094 5.17311 21.0759 7.50524 20.4062C9.83737 19.7365 12.331 20.6926 13.6177 22.7496L14.6672 22.0937C13.2735 19.8663 10.7051 18.6609 8.10129 19.0121C7.1056 17.9967 6.55033 16.6297 6.5559 15.2077C6.55624 13.7652 7.12966 12.382 8.14994 11.3625C9.17029 10.3429 10.5538 9.77047 11.9963 9.77121C12.1925 9.77047 12.3887 9.78007 12.584 9.80003L12.7585 9.82236C13.3105 12.1978 15.3744 13.9166 17.8105 14.0295L17.8643 12.7846C16.0065 12.7048 14.4226 11.4124 13.9717 9.60835C13.5208 7.80436 14.3104 5.91867 15.9121 4.97421C17.514 4.02975 19.5462 4.25159 20.9066 5.51939H22.1555V9.73183V5.01694C20.6692 3.31717 18.2874 2.7124 16.1703 3.49725C14.0532 4.28209 12.6411 6.29333 12.622 8.55115C12.4108 8.53146 12.2022 8.52097 11.9989 8.52097C9.43904 8.52147 7.10435 9.98394 5.98679 12.2869C4.86922 14.5899 5.16511 17.3289 6.74874 19.3401C4.12064 20.2858 2.35662 22.7648 2.32419 25.5577C2.29174 28.3506 3.99771 30.8698 6.60312 31.8764L6.59525 31.9525C6.57557 32.1729 6.56508 32.3802 6.56508 32.5717C6.5659 36.0323 9.20567 38.9213 12.6522 39.2334C12.3912 41.5931 13.5675 43.8799 15.639 45.0398C17.7104 46.1996 20.2748 46.0075 22.1502 44.5518V40.3144V43.8972H20.9013C19.4586 44.7464 17.6715 44.7581 16.2179 43.9276C14.7643 43.097 13.8667 41.5517 13.8657 39.8775C13.8658 39.6917 13.8767 39.5059 13.8985 39.3213L13.9103 39.2256C15.6154 39.0534 17.1893 38.2335 18.3077 36.935L17.3658 36.119C15.8818 37.8411 13.4833 38.4583 11.3525 37.6665C9.22157 36.8747 7.80808 34.841 7.80874 32.5677C7.80874 32.4365 7.80874 32.3224 7.81792 32.2135C11.4335 32.8633 14.8967 30.4764 15.5764 26.8663L14.3577 26.6407C13.8399 29.402 11.3044 31.3146 8.50713 31.054C5.70983 30.7934 3.57132 28.4452 3.57268 25.6357" fill="white"/>
|
||||
<path d="M24.1587 5.18921V44.4998H21.1587V5.18921H24.1587Z" fill="white"/>
|
||||
<path d="M59 34.5856V12H63.037V31.2012H72.5031V34.5856H59ZM82.9874 35C81.8041 35 80.7021 34.8043 79.6812 34.4129C78.6604 33.9985 77.7671 33.4114 77.0015 32.6517C76.2358 31.8919 75.6326 30.9709 75.1918 29.8889C74.7741 28.7838 74.5653 27.5291 74.5653 26.1246C74.5653 24.7432 74.7858 23.5 75.2266 22.3949C75.6674 21.2898 76.2475 20.3574 76.9667 19.5976C77.7092 18.8378 78.556 18.2508 79.5072 17.8363C80.4585 17.4219 81.433 17.2147 82.4305 17.2147C83.5907 17.2147 84.6115 17.4105 85.4931 17.8018C86.3748 18.1932 87.1056 18.7457 87.6856 19.4595C88.2889 20.1732 88.7413 21.025 89.0429 22.015C89.3445 23.005 89.4953 24.0871 89.4953 25.2613C89.4953 25.6526 89.4722 26.021 89.4257 26.3664C89.4026 26.7117 89.3678 26.988 89.3213 27.1952H78.4631C78.6488 28.7147 79.1824 29.8889 80.064 30.7177C80.9688 31.5235 82.1173 31.9264 83.5094 31.9264C84.2519 31.9264 84.9362 31.8228 85.5627 31.6156C86.2124 31.3853 86.8503 31.0745 87.4768 30.6832L88.834 33.1697C88.022 33.6992 87.1172 34.1366 86.1195 34.482C85.1219 34.8273 84.0779 35 82.9874 35ZM78.4283 24.5015H86.0151C86.0151 23.1892 85.7251 22.1646 85.1451 21.4279C84.5882 20.6682 83.7182 20.2883 82.5349 20.2883C81.5141 20.2883 80.6208 20.6451 79.8552 21.3589C79.0896 22.0726 78.614 23.1201 78.4283 24.5015ZM100.313 35C99.2456 35 98.2252 34.8043 97.2507 34.4129C96.2763 34.0216 95.4062 33.4459 94.6406 32.6862C93.8973 31.9264 93.3057 30.994 92.8657 29.8889C92.4245 28.7838 92.2045 27.5291 92.2045 26.1246C92.2045 24.7202 92.4245 23.4655 92.8657 22.3604C93.3057 21.2553 93.8973 20.3228 94.6406 19.5631C95.4062 18.8033 96.2763 18.2277 97.2507 17.8363C98.2252 17.4219 99.2456 17.2147 100.313 17.2147C101.38 17.2147 102.401 17.4219 103.376 17.8363C104.373 18.2277 105.243 18.8033 105.986 19.5631C106.728 20.3228 107.32 21.2553 107.761 22.3604C108.224 23.4655 108.457 24.7202 108.457 26.1246C108.457 27.5291 108.224 28.7838 107.761 29.8889C107.32 30.994 106.728 31.9264 105.986 32.6862C105.243 33.4459 104.373 34.0216 103.376 34.4129C102.401 34.8043 101.38 35 100.313 35ZM100.313 31.7538C101.566 31.7538 102.552 31.2472 103.271 30.2342C103.99 29.1982 104.35 27.8283 104.35 26.1246C104.35 24.3979 103.99 23.028 103.271 22.015C102.552 20.979 101.566 20.461 100.313 20.461C99.0604 20.461 98.0735 20.979 97.3551 22.015C96.6591 23.028 96.3111 24.3979 96.3111 26.1246C96.3111 27.8283 96.6591 29.1982 97.3551 30.2342C98.0735 31.2472 99.0604 31.7538 100.313 31.7538ZM112.418 34.5856V17.6291H115.724L116.003 19.9084H116.142C116.907 19.1717 117.732 18.5385 118.613 18.009C119.518 17.4795 120.562 17.2147 121.745 17.2147C123.578 17.2147 124.912 17.8018 125.747 18.976C126.582 20.1271 127 21.7847 127 23.9489V34.5856H122.998V24.467C122.998 23.0625 122.789 22.0726 122.371 21.497C121.954 20.9214 121.269 20.6336 120.318 20.6336C119.576 20.6336 118.915 20.8178 118.334 21.1862C117.778 21.5315 117.14 22.0496 116.42 22.7402V34.5856H112.418Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg viewBox="0 0 44 46" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.8776 5.37716C35.4573 5.52949 37.7993 6.93422 39.1482 9.13838C40.4972 11.3425 40.6824 14.0672 39.6443 16.4337C39.5761 16.5872 39.5039 16.7393 39.4265 16.8889C41.8937 18.3488 43.3765 21.0308 43.3007 23.8966C43.225 26.7624 41.6027 29.3623 39.0618 30.6899C39.0294 34.3227 36.5236 37.4651 32.9891 38.3054C32.8483 40.9518 31.2496 43.3011 28.8394 44.4031C26.4293 45.5052 23.6065 45.1776 21.5128 43.5529L21.0444 43.1895V2.61303L21.3449 2.27063C23.0164 0.361007 25.629 -0.430945 28.0795 0.229143C30.5301 0.88923 32.3914 2.8863 32.8776 5.37716Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.2559 7.78441V42.6031C24.1315 44.0574 26.6952 44.2488 28.7661 43.0892C30.837 41.9296 32.0135 39.6439 31.7539 37.2847C35.2051 36.9783 37.8514 34.0877 37.8528 30.623C37.8528 30.4314 37.8409 30.2294 37.8213 30.0038L37.8134 29.9277C40.418 28.9208 42.1234 26.4023 42.0913 23.61C42.0591 20.8178 40.2962 18.3392 37.6692 17.3926C39.2488 15.3792 39.5415 12.6411 38.4232 10.3392C37.3049 8.03733 34.9715 6.575 32.4124 6.57224C32.2091 6.57224 32.0018 6.58274 31.7892 6.60242C31.7702 4.3446 30.3581 2.33336 28.241 1.54851C26.1239 0.763668 23.7421 1.36845 22.2559 3.06821V7.79098V7.78441Z" fill="#ED297A"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.8439 23.687C40.8447 21.2607 39.2382 19.1272 36.9061 18.4575C34.574 17.7878 32.0803 18.7438 30.7935 20.8009L29.744 20.1449C31.1367 17.92 33.7018 16.7149 36.3035 17.0633C37.2991 16.048 37.8544 14.681 37.8489 13.2589C37.8489 10.2557 35.4143 7.82115 32.4111 7.82115C32.2153 7.82081 32.0195 7.83083 31.8247 7.85133L31.6489 7.8723C31.0978 10.2469 29.0358 11.966 26.6008 12.0808L26.547 10.8358C28.4048 10.7561 29.9888 9.46365 30.4397 7.65964C30.8906 5.85563 30.1009 3.96995 28.4991 3.02548C26.8973 2.08102 24.865 2.30286 23.5048 3.57066H22.2559V7.78441V3.06821C23.7421 1.36845 26.1239 0.763668 28.241 1.54851C30.3581 2.33336 31.7702 4.3446 31.7892 6.60242C32.0018 6.58274 32.2091 6.57224 32.4124 6.57224C34.9715 6.575 37.3049 8.03733 38.4232 10.3392C39.5415 12.6411 39.2488 15.3792 37.6692 17.3926C40.2962 18.3392 42.0591 20.8178 42.0913 23.61C42.1234 26.4023 40.418 28.9208 37.8134 29.9277L37.8213 30.0037C37.8409 30.2241 37.8528 30.4314 37.8528 30.623C37.8514 34.0877 35.2051 36.9783 31.7539 37.2847C32.0148 39.6443 30.8385 41.9311 28.7671 43.091C26.6956 44.2509 24.1313 44.0588 22.2559 42.6031V38.3657V41.9484H23.5048C24.9475 42.7977 26.7346 42.8093 28.1882 41.9788C29.6418 41.1483 30.5393 39.6029 30.5403 37.9288C30.5409 37.7429 30.5299 37.5571 30.5076 37.3726L30.4958 37.2768C28.7932 37.1044 27.2213 36.2861 26.1036 34.9902L27.0455 34.1756C28.5309 35.8932 30.927 36.5072 33.0554 35.7157C35.1838 34.9242 36.5964 32.8938 36.5986 30.623C36.5986 30.4918 36.5986 30.3776 36.5895 30.2687C32.9761 30.9162 29.5159 28.5313 28.8349 24.9241L30.0602 24.6945C30.581 27.4526 33.1149 29.3613 35.9095 29.1009C38.7041 28.8405 40.8416 26.4964 40.8439 23.6897" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.8439 23.687C40.8447 21.2607 39.2382 19.1272 36.9061 18.4575C34.574 17.7878 32.0803 18.7438 30.7935 20.8009L29.744 20.1449C31.1367 17.92 33.7018 16.7149 36.3035 17.0633C37.2991 16.048 37.8544 14.681 37.8489 13.2589C37.8489 10.2557 35.4143 7.82115 32.4111 7.82115C32.2153 7.82081 32.0195 7.83083 31.8247 7.85133L31.6489 7.8723C31.0978 10.2469 29.0358 11.966 26.6008 12.0808L26.547 10.8358C28.4048 10.7561 29.9888 9.46365 30.4397 7.65964C30.8906 5.85563 30.1009 3.96995 28.4991 3.02548C26.8973 2.08102 24.865 2.30286 23.5048 3.57066H22.2559V7.78441V3.06821C23.7421 1.36845 26.1239 0.763668 28.241 1.54851C30.3581 2.33336 31.7702 4.3446 31.7892 6.60242C32.0018 6.58274 32.2091 6.57224 32.4124 6.57224C34.9715 6.575 37.3049 8.03733 38.4232 10.3392C39.5415 12.6411 39.2488 15.3792 37.6692 17.3926C40.2962 18.3392 42.0591 20.8178 42.0913 23.61C42.1234 26.4023 40.418 28.9208 37.8134 29.9277L37.8213 30.0037C37.8409 30.2241 37.8528 30.4314 37.8528 30.623C37.8514 34.0877 35.2051 36.9783 31.7539 37.2847C32.0148 39.6443 30.8385 41.9311 28.7671 43.091C26.6956 44.2509 24.1313 44.0588 22.2559 42.6031V38.3657V41.9484H23.5048C24.9475 42.7977 26.7346 42.8093 28.1882 41.9788C29.6418 41.1483 30.5393 39.6029 30.5403 37.9288C30.5409 37.7429 30.5299 37.5571 30.5076 37.3726L30.4958 37.2768C28.7932 37.1044 27.2213 36.2861 26.1036 34.9902L27.0455 34.1756C28.5309 35.8932 30.927 36.5072 33.0554 35.7157C35.1838 34.9242 36.5964 32.8938 36.5986 30.623C36.5986 30.4918 36.5986 30.3776 36.5895 30.2687C32.9761 30.9162 29.5159 28.5313 28.8349 24.9241L30.0602 24.6945C30.581 27.4526 33.1149 29.3613 35.9095 29.1009C38.7041 28.8405 40.8416 26.4964 40.8439 23.6897" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4224 5.41589C7.84285 5.56777 5.50076 6.97222 4.15175 9.17618C2.80273 11.3802 2.61748 14.1048 3.65574 16.4711C3.72396 16.6259 3.79612 16.7781 3.87483 16.9276C1.40898 18.3884 -0.0726901 21.0698 0.00274939 23.9349C0.078189 26.7999 1.69892 29.3996 4.23822 30.7286C4.26977 34.3626 6.77555 37.5066 10.3109 38.348C10.4517 40.9944 12.0505 43.3437 14.4606 44.4457C16.8707 45.5478 19.6935 45.2202 21.7873 43.5956L22.2556 43.2322V2.65306L21.9551 2.31066C20.2838 0.401067 17.6714 -0.391038 15.2209 0.268755C12.7705 0.928546 10.909 2.92524 10.4224 5.41589Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.0447 7.82253V42.6425C19.1691 44.1005 16.6029 44.294 14.5299 43.1338C12.4569 41.9737 11.2797 39.6852 11.5415 37.3241C8.0907 37.017 5.44513 34.1267 5.44381 30.6624C5.44381 30.4709 5.45431 30.2688 5.47399 30.0432L5.48186 29.9671C2.87719 28.9584 1.17326 26.4377 1.20804 23.6447C1.24282 20.8518 3.009 18.3743 5.63797 17.4308C4.06014 15.4186 3.76715 12.6831 4.88302 10.3825C5.99891 8.08181 8.32853 6.61833 10.8855 6.61168C11.0888 6.61168 11.2974 6.62218 11.5086 6.64186C11.5251 4.38203 12.9375 2.36792 15.0565 1.58257C17.1756 0.79722 19.5595 1.40436 21.0447 3.10765V7.82253Z" fill="#1C75DB"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.45659 23.7264C2.4558 21.3001 4.06227 19.1666 6.3944 18.4969C8.72653 17.8272 11.2201 18.7833 12.5069 20.8403L13.5564 20.1844C12.1627 17.957 9.59429 16.7516 6.99045 17.1028C5.99476 16.0874 5.43949 14.7204 5.44506 13.2984C5.4454 11.8559 6.01882 10.4727 7.0391 9.45319C8.05945 8.43359 9.443 7.86117 10.8854 7.86191C11.0817 7.86117 11.2779 7.87077 11.4731 7.89073L11.6476 7.91306C12.1996 10.2885 14.2636 12.0073 16.6997 12.1202L16.7535 10.8753C14.8957 10.7955 13.3117 9.50305 12.8608 7.69905C12.4099 5.89506 13.1995 4.00937 14.8013 3.06491C16.4031 2.12045 18.4354 2.34229 19.7957 3.61009H21.0446V7.82253V3.10764C19.5584 1.40787 17.1765 0.803098 15.0594 1.58794C12.9424 2.37279 11.5303 4.38403 11.5112 6.64185C11.3 6.62216 11.0914 6.61167 10.8881 6.61167C8.3282 6.61217 5.99351 8.07464 4.87595 10.3776C3.75838 12.6806 4.05427 15.4196 5.6379 17.4308C3.0098 18.3765 1.24578 20.8555 1.21335 23.6484C1.1809 26.4413 2.88687 28.9605 5.49228 29.9671L5.48441 30.0432C5.46473 30.2636 5.45424 30.4709 5.45424 30.6624C5.45506 34.123 8.09483 37.012 11.5413 37.3241C11.2804 39.6838 12.4567 41.9706 14.5281 43.1305C16.5996 44.2903 19.1639 44.0982 21.0393 42.6425V38.4051V41.9879H19.7904C18.3477 42.8371 16.5607 42.8488 15.1071 42.0183C13.6534 41.1877 12.7559 39.6424 12.7549 37.9682C12.7549 37.7824 12.7659 37.5966 12.7877 37.412L12.7994 37.3162C14.5045 37.1441 16.0785 36.3242 17.1969 35.0257L16.255 34.2097C14.771 35.9318 12.3724 36.549 10.2416 35.7572C8.11073 34.9654 6.69725 32.9317 6.6979 30.6584C6.6979 30.5272 6.6979 30.4131 6.70708 30.3042C10.3227 30.954 13.7859 28.5671 14.4656 24.957L13.2468 24.7314C12.7291 27.4927 10.1936 29.4053 7.39629 29.1447C4.59899 28.8841 2.46048 26.5359 2.46184 23.7264" fill="white"/>
|
||||
<path d="M23.0479 3.27991V42.5905H20.0479V3.27991H23.0479Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg viewBox="0 0 128 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.9884 7.28647C36.5681 7.43879 38.9101 8.84352 40.259 11.0477C41.608 13.2518 41.7933 15.9765 40.7551 18.343C40.6869 18.4965 40.6147 18.6486 40.5373 18.7982C43.0045 20.2581 44.4873 22.9401 44.4116 25.8059C44.3358 28.6717 42.7135 31.2716 40.1726 32.5992C40.1402 36.232 37.6344 39.3744 34.0999 40.2147C33.9591 42.8611 32.3604 45.2104 29.9502 46.3124C27.5401 47.4145 24.7174 47.0869 22.6236 45.4622L22.1553 45.0988V4.52233L22.4557 4.17993C24.1273 2.27031 26.7399 1.47836 29.1904 2.13845C31.6409 2.79853 33.5022 4.79561 33.9884 7.28647Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3667 9.69371V44.5124C25.2424 45.9667 27.806 46.1581 29.8769 44.9985C31.9478 43.8389 33.1244 41.5532 32.8647 39.194C36.3159 38.8876 38.9623 35.997 38.9636 32.5323C38.9636 32.3407 38.9518 32.1387 38.9322 31.9131L38.9243 31.837C41.5288 30.8301 43.2343 28.3116 43.2021 25.5193C43.1699 22.7271 41.407 20.2485 38.78 19.3019C40.3596 17.2885 40.6523 14.5504 39.5341 12.2485C38.4158 9.94663 36.0824 8.4843 33.5233 8.48155C33.3199 8.48155 33.1126 8.49204 32.9001 8.51172C32.881 6.2539 31.469 4.24266 29.3519 3.45782C27.2347 2.67297 24.8529 3.27775 23.3667 4.97751V9.70028V9.69371Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.9547 25.5963C41.9555 23.17 40.349 21.0365 38.0169 20.3668C35.6848 19.6971 33.1911 20.6531 31.9044 22.7102L30.8549 22.0542C32.2476 19.8293 34.8126 18.6242 37.4143 18.9726C38.41 17.9573 38.9653 16.5903 38.9597 15.1682C38.9597 12.165 36.5252 9.73045 33.522 9.73045C33.3261 9.73012 33.1303 9.74013 32.9355 9.76063L32.7598 9.78161C32.2087 12.1562 30.1466 13.8753 27.7116 13.9901L27.6578 12.7451C29.5156 12.6654 31.0996 11.373 31.5505 9.56894C32.0014 7.76493 31.2118 5.87925 29.61 4.93479C28.0082 3.99032 25.9759 4.21216 24.6156 5.47996H23.3667V9.69371V4.97751C24.8529 3.27775 27.2347 2.67297 29.3519 3.45782C31.469 4.24266 32.881 6.2539 32.9001 8.51172C33.1126 8.49204 33.3199 8.48155 33.5233 8.48155C36.0824 8.4843 38.4158 9.94663 39.5341 12.2485C40.6523 14.5504 40.3596 17.2885 38.78 19.3019C41.407 20.2485 43.1699 22.7271 43.2021 25.5193C43.2343 28.3116 41.5288 30.8301 38.9243 31.837L38.9322 31.913C38.9518 32.1334 38.9636 32.3407 38.9636 32.5323C38.9623 35.997 36.3159 38.8876 32.8647 39.194C33.1257 41.5536 31.9494 43.8404 29.878 45.0004C27.8065 46.1602 25.2421 45.9681 23.3667 44.5124V40.275V43.8577H24.6156C26.0583 44.707 27.8454 44.7186 29.299 43.8881C30.7526 43.0576 31.6502 41.5122 31.6512 39.8381C31.6517 39.6522 31.6408 39.4664 31.6184 39.2819L31.6066 39.1861C29.904 39.0137 28.3322 38.1954 27.2144 36.8995L28.1564 36.0849C29.6418 37.8025 32.0379 38.4165 34.1662 37.625C36.2946 36.8335 37.7073 34.8031 37.7094 32.5323C37.7094 32.4011 37.7094 32.2869 37.7003 32.178C34.087 32.8255 30.6267 30.4406 29.9458 26.8334L31.1711 26.6038C31.6918 29.3619 34.2257 31.2706 37.0203 31.0102C39.815 30.7498 41.9525 28.4057 41.9547 25.599" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.9547 25.5963C41.9555 23.17 40.349 21.0365 38.0169 20.3668C35.6848 19.6971 33.1911 20.6531 31.9044 22.7102L30.8549 22.0542C32.2476 19.8293 34.8126 18.6242 37.4143 18.9726C38.41 17.9573 38.9653 16.5903 38.9597 15.1682C38.9597 12.165 36.5252 9.73045 33.522 9.73045C33.3261 9.73012 33.1303 9.74013 32.9355 9.76063L32.7598 9.78161C32.2087 12.1562 30.1466 13.8753 27.7116 13.9901L27.6578 12.7451C29.5156 12.6654 31.0996 11.373 31.5505 9.56894C32.0014 7.76493 31.2118 5.87925 29.61 4.93479C28.0082 3.99032 25.9759 4.21216 24.6156 5.47996H23.3667V9.69371V4.97751C24.8529 3.27775 27.2347 2.67297 29.3519 3.45782C31.469 4.24266 32.881 6.2539 32.9001 8.51172C33.1126 8.49204 33.3199 8.48155 33.5233 8.48155C36.0824 8.4843 38.4158 9.94663 39.5341 12.2485C40.6523 14.5504 40.3596 17.2885 38.78 19.3019C41.407 20.2485 43.1699 22.7271 43.2021 25.5193C43.2343 28.3116 41.5288 30.8301 38.9243 31.837L38.9322 31.913C38.9518 32.1334 38.9636 32.3407 38.9636 32.5323C38.9623 35.997 36.3159 38.8876 32.8647 39.194C33.1257 41.5536 31.9494 43.8404 29.878 45.0004C27.8065 46.1602 25.2421 45.9681 23.3667 44.5124V40.275V43.8577H24.6156C26.0583 44.707 27.8454 44.7186 29.299 43.8881C30.7526 43.0576 31.6502 41.5122 31.6512 39.8381C31.6517 39.6522 31.6408 39.4664 31.6184 39.2819L31.6066 39.1861C29.904 39.0137 28.3322 38.1954 27.2144 36.8995L28.1564 36.0849C29.6418 37.8025 32.0379 38.4165 34.1662 37.625C36.2946 36.8335 37.7073 34.8031 37.7094 32.5323C37.7094 32.4011 37.7094 32.2869 37.7003 32.178C34.087 32.8255 30.6267 30.4406 29.9458 26.8334L31.1711 26.6038C31.6918 29.3619 34.2257 31.2706 37.0203 31.0102C39.815 30.7498 41.9525 28.4057 41.9547 25.599" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5332 7.32519C8.95369 7.47707 6.6116 8.88153 5.26259 11.0855C3.91357 13.2895 3.72832 16.0141 4.76658 18.3804C4.8348 18.5352 4.90696 18.6874 4.98567 18.8369C2.51982 20.2978 1.03815 22.9791 1.11359 25.8442C1.18903 28.7092 2.80975 31.3089 5.34906 32.6379C5.38061 36.2719 7.88639 39.4159 11.4217 40.2573C11.5625 42.9037 13.1613 45.253 15.5715 46.355C17.9816 47.4571 20.8044 47.1295 22.8981 45.5049L23.3664 45.1415V4.56237L23.066 4.21996C21.3946 2.31037 18.7822 1.51826 16.3318 2.17806C13.8813 2.83785 12.0199 4.83454 11.5332 7.32519Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.1555 9.73183V44.5518C20.2799 46.0098 17.7138 46.2033 15.6407 45.0431C13.5677 43.883 12.3906 41.5945 12.6523 39.2334C9.20154 38.9263 6.55597 36.036 6.55465 32.5717C6.55465 32.3802 6.56515 32.1781 6.58483 31.9525L6.5927 31.8764C3.98803 30.8677 2.2841 28.347 2.31888 25.554C2.35366 22.7611 4.11984 20.2836 6.74881 19.3401C5.17098 17.3279 4.87799 14.5924 5.99386 12.2918C7.10975 9.99111 9.43937 8.52763 11.9963 8.52098C12.1996 8.52098 12.4082 8.53148 12.6195 8.55116C12.6359 6.29133 14.0483 4.27722 16.1674 3.49187C18.2864 2.70652 20.6703 3.31366 22.1555 5.01695V9.73183Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.56743 25.6357C3.56664 23.2094 5.17311 21.0759 7.50524 20.4062C9.83737 19.7365 12.331 20.6926 13.6177 22.7496L14.6672 22.0937C13.2735 19.8663 10.7051 18.6609 8.10129 19.0121C7.1056 17.9967 6.55033 16.6297 6.5559 15.2077C6.55624 13.7652 7.12966 12.382 8.14994 11.3625C9.17029 10.3429 10.5538 9.77047 11.9963 9.77121C12.1925 9.77047 12.3887 9.78007 12.584 9.80003L12.7585 9.82236C13.3105 12.1978 15.3744 13.9166 17.8105 14.0295L17.8643 12.7846C16.0065 12.7048 14.4226 11.4124 13.9717 9.60835C13.5208 7.80436 14.3104 5.91867 15.9121 4.97421C17.514 4.02975 19.5462 4.25159 20.9066 5.51939H22.1555V9.73183V5.01694C20.6692 3.31717 18.2874 2.7124 16.1703 3.49725C14.0532 4.28209 12.6411 6.29333 12.622 8.55115C12.4108 8.53146 12.2022 8.52097 11.9989 8.52097C9.43904 8.52147 7.10435 9.98394 5.98679 12.2869C4.86922 14.5899 5.16511 17.3289 6.74874 19.3401C4.12064 20.2858 2.35662 22.7648 2.32419 25.5577C2.29174 28.3506 3.99771 30.8698 6.60312 31.8764L6.59525 31.9525C6.57557 32.1729 6.56508 32.3802 6.56508 32.5717C6.5659 36.0323 9.20567 38.9213 12.6522 39.2334C12.3912 41.5931 13.5675 43.8799 15.639 45.0398C17.7104 46.1996 20.2748 46.0075 22.1502 44.5518V40.3144V43.8972H20.9013C19.4586 44.7464 17.6715 44.7581 16.2179 43.9276C14.7643 43.097 13.8667 41.5517 13.8657 39.8775C13.8658 39.6917 13.8767 39.5059 13.8985 39.3213L13.9103 39.2256C15.6154 39.0534 17.1893 38.2335 18.3077 36.935L17.3658 36.119C15.8818 37.8411 13.4833 38.4583 11.3525 37.6665C9.22157 36.8747 7.80808 34.841 7.80874 32.5677C7.80874 32.4365 7.80874 32.3224 7.81792 32.2135C11.4335 32.8633 14.8967 30.4764 15.5764 26.8663L14.3577 26.6407C13.8399 29.402 11.3044 31.3146 8.50713 31.054C5.70983 30.7934 3.57132 28.4452 3.57268 25.6357" fill="white"/>
|
||||
<path d="M24.1587 5.18921V44.4998H21.1587V5.18921H24.1587Z" fill="white"/>
|
||||
<path d="M59 34.5856V12H63.037V31.2012H72.5031V34.5856H59ZM82.9874 35C81.8041 35 80.7021 34.8043 79.6812 34.4129C78.6604 33.9985 77.7671 33.4114 77.0015 32.6517C76.2358 31.8919 75.6326 30.9709 75.1918 29.8889C74.7741 28.7838 74.5653 27.5291 74.5653 26.1246C74.5653 24.7432 74.7858 23.5 75.2266 22.3949C75.6674 21.2898 76.2475 20.3574 76.9667 19.5976C77.7092 18.8378 78.556 18.2508 79.5072 17.8363C80.4585 17.4219 81.433 17.2147 82.4305 17.2147C83.5907 17.2147 84.6115 17.4105 85.4931 17.8018C86.3748 18.1932 87.1056 18.7457 87.6856 19.4595C88.2889 20.1732 88.7413 21.025 89.0429 22.015C89.3445 23.005 89.4953 24.0871 89.4953 25.2613C89.4953 25.6526 89.4722 26.021 89.4257 26.3664C89.4026 26.7117 89.3678 26.988 89.3213 27.1952H78.4631C78.6488 28.7147 79.1824 29.8889 80.064 30.7177C80.9688 31.5235 82.1173 31.9264 83.5094 31.9264C84.2519 31.9264 84.9362 31.8228 85.5627 31.6156C86.2124 31.3853 86.8503 31.0745 87.4768 30.6832L88.834 33.1697C88.022 33.6992 87.1172 34.1366 86.1195 34.482C85.1219 34.8273 84.0779 35 82.9874 35ZM78.4283 24.5015H86.0151C86.0151 23.1892 85.7251 22.1646 85.1451 21.4279C84.5882 20.6682 83.7182 20.2883 82.5349 20.2883C81.5141 20.2883 80.6208 20.6451 79.8552 21.3589C79.0896 22.0726 78.614 23.1201 78.4283 24.5015ZM100.313 35C99.2456 35 98.2252 34.8043 97.2507 34.4129C96.2763 34.0216 95.4062 33.4459 94.6406 32.6862C93.8973 31.9264 93.3057 30.994 92.8657 29.8889C92.4245 28.7838 92.2045 27.5291 92.2045 26.1246C92.2045 24.7202 92.4245 23.4655 92.8657 22.3604C93.3057 21.2553 93.8973 20.3228 94.6406 19.5631C95.4062 18.8033 96.2763 18.2277 97.2507 17.8363C98.2252 17.4219 99.2456 17.2147 100.313 17.2147C101.38 17.2147 102.401 17.4219 103.376 17.8363C104.373 18.2277 105.243 18.8033 105.986 19.5631C106.728 20.3228 107.32 21.2553 107.761 22.3604C108.224 23.4655 108.457 24.7202 108.457 26.1246C108.457 27.5291 108.224 28.7838 107.761 29.8889C107.32 30.994 106.728 31.9264 105.986 32.6862C105.243 33.4459 104.373 34.0216 103.376 34.4129C102.401 34.8043 101.38 35 100.313 35ZM100.313 31.7538C101.566 31.7538 102.552 31.2472 103.271 30.2342C103.99 29.1982 104.35 27.8283 104.35 26.1246C104.35 24.3979 103.99 23.028 103.271 22.015C102.552 20.979 101.566 20.461 100.313 20.461C99.0604 20.461 98.0735 20.979 97.3551 22.015C96.6591 23.028 96.3111 24.3979 96.3111 26.1246C96.3111 27.8283 96.6591 29.1982 97.3551 30.2342C98.0735 31.2472 99.0604 31.7538 100.313 31.7538ZM112.418 34.5856V17.6291H115.724L116.003 19.9084H116.142C116.907 19.1717 117.732 18.5385 118.613 18.009C119.518 17.4795 120.562 17.2147 121.745 17.2147C123.578 17.2147 124.912 17.8018 125.747 18.976C126.582 20.1271 127 21.7847 127 23.9489V34.5856H122.998V24.467C122.998 23.0625 122.789 22.0726 122.371 21.497C121.954 20.9214 121.269 20.6336 120.318 20.6336C119.576 20.6336 118.915 20.8178 118.334 21.1862C117.778 21.5315 117.14 22.0496 116.42 22.7402V34.5856H112.418Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg viewBox="0 0 44 46" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.8776 5.37716C35.4573 5.52949 37.7993 6.93422 39.1482 9.13838C40.4972 11.3425 40.6824 14.0672 39.6443 16.4337C39.5761 16.5872 39.5039 16.7393 39.4265 16.8889C41.8937 18.3488 43.3765 21.0308 43.3007 23.8966C43.225 26.7624 41.6027 29.3623 39.0618 30.6899C39.0294 34.3227 36.5236 37.4651 32.9891 38.3054C32.8483 40.9518 31.2496 43.3011 28.8394 44.4031C26.4293 45.5052 23.6065 45.1776 21.5128 43.5529L21.0444 43.1895V2.61303L21.3449 2.27063C23.0164 0.361007 25.629 -0.430945 28.0795 0.229143C30.5301 0.88923 32.3914 2.8863 32.8776 5.37716Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.2559 7.78441V42.6031C24.1315 44.0574 26.6952 44.2488 28.7661 43.0892C30.837 41.9296 32.0135 39.6439 31.7539 37.2847C35.2051 36.9783 37.8514 34.0877 37.8528 30.623C37.8528 30.4314 37.8409 30.2294 37.8213 30.0038L37.8134 29.9277C40.418 28.9208 42.1234 26.4023 42.0913 23.61C42.0591 20.8178 40.2962 18.3392 37.6692 17.3926C39.2488 15.3792 39.5415 12.6411 38.4232 10.3392C37.3049 8.03733 34.9715 6.575 32.4124 6.57224C32.2091 6.57224 32.0018 6.58274 31.7892 6.60242C31.7702 4.3446 30.3581 2.33336 28.241 1.54851C26.1239 0.763668 23.7421 1.36845 22.2559 3.06821V7.79098V7.78441Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.8439 23.687C40.8447 21.2607 39.2382 19.1272 36.9061 18.4575C34.574 17.7878 32.0803 18.7438 30.7935 20.8009L29.744 20.1449C31.1367 17.92 33.7018 16.7149 36.3035 17.0633C37.2991 16.048 37.8544 14.681 37.8489 13.2589C37.8489 10.2557 35.4143 7.82115 32.4111 7.82115C32.2153 7.82081 32.0195 7.83083 31.8247 7.85133L31.6489 7.8723C31.0978 10.2469 29.0358 11.966 26.6008 12.0808L26.547 10.8358C28.4048 10.7561 29.9888 9.46365 30.4397 7.65964C30.8906 5.85563 30.1009 3.96995 28.4991 3.02548C26.8973 2.08102 24.865 2.30286 23.5048 3.57066H22.2559V7.78441V3.06821C23.7421 1.36845 26.1239 0.763668 28.241 1.54851C30.3581 2.33336 31.7702 4.3446 31.7892 6.60242C32.0018 6.58274 32.2091 6.57224 32.4124 6.57224C34.9715 6.575 37.3049 8.03733 38.4232 10.3392C39.5415 12.6411 39.2488 15.3792 37.6692 17.3926C40.2962 18.3392 42.0591 20.8178 42.0913 23.61C42.1234 26.4023 40.418 28.9208 37.8134 29.9277L37.8213 30.0037C37.8409 30.2241 37.8528 30.4314 37.8528 30.623C37.8514 34.0877 35.2051 36.9783 31.7539 37.2847C32.0148 39.6443 30.8385 41.9311 28.7671 43.091C26.6956 44.2509 24.1313 44.0588 22.2559 42.6031V38.3657V41.9484H23.5048C24.9475 42.7977 26.7346 42.8093 28.1882 41.9788C29.6418 41.1483 30.5393 39.6029 30.5403 37.9288C30.5409 37.7429 30.5299 37.5571 30.5076 37.3726L30.4958 37.2768C28.7932 37.1044 27.2213 36.2861 26.1036 34.9902L27.0455 34.1756C28.5309 35.8932 30.927 36.5072 33.0554 35.7157C35.1838 34.9242 36.5964 32.8938 36.5986 30.623C36.5986 30.4918 36.5986 30.3776 36.5895 30.2687C32.9761 30.9162 29.5159 28.5313 28.8349 24.9241L30.0602 24.6945C30.581 27.4526 33.1149 29.3613 35.9095 29.1009C38.7041 28.8405 40.8416 26.4964 40.8439 23.6897" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.8439 23.687C40.8447 21.2607 39.2382 19.1272 36.9061 18.4575C34.574 17.7878 32.0803 18.7438 30.7935 20.8009L29.744 20.1449C31.1367 17.92 33.7018 16.7149 36.3035 17.0633C37.2991 16.048 37.8544 14.681 37.8489 13.2589C37.8489 10.2557 35.4143 7.82115 32.4111 7.82115C32.2153 7.82081 32.0195 7.83083 31.8247 7.85133L31.6489 7.8723C31.0978 10.2469 29.0358 11.966 26.6008 12.0808L26.547 10.8358C28.4048 10.7561 29.9888 9.46365 30.4397 7.65964C30.8906 5.85563 30.1009 3.96995 28.4991 3.02548C26.8973 2.08102 24.865 2.30286 23.5048 3.57066H22.2559V7.78441V3.06821C23.7421 1.36845 26.1239 0.763668 28.241 1.54851C30.3581 2.33336 31.7702 4.3446 31.7892 6.60242C32.0018 6.58274 32.2091 6.57224 32.4124 6.57224C34.9715 6.575 37.3049 8.03733 38.4232 10.3392C39.5415 12.6411 39.2488 15.3792 37.6692 17.3926C40.2962 18.3392 42.0591 20.8178 42.0913 23.61C42.1234 26.4023 40.418 28.9208 37.8134 29.9277L37.8213 30.0037C37.8409 30.2241 37.8528 30.4314 37.8528 30.623C37.8514 34.0877 35.2051 36.9783 31.7539 37.2847C32.0148 39.6443 30.8385 41.9311 28.7671 43.091C26.6956 44.2509 24.1313 44.0588 22.2559 42.6031V38.3657V41.9484H23.5048C24.9475 42.7977 26.7346 42.8093 28.1882 41.9788C29.6418 41.1483 30.5393 39.6029 30.5403 37.9288C30.5409 37.7429 30.5299 37.5571 30.5076 37.3726L30.4958 37.2768C28.7932 37.1044 27.2213 36.2861 26.1036 34.9902L27.0455 34.1756C28.5309 35.8932 30.927 36.5072 33.0554 35.7157C35.1838 34.9242 36.5964 32.8938 36.5986 30.623C36.5986 30.4918 36.5986 30.3776 36.5895 30.2687C32.9761 30.9162 29.5159 28.5313 28.8349 24.9241L30.0602 24.6945C30.581 27.4526 33.1149 29.3613 35.9095 29.1009C38.7041 28.8405 40.8416 26.4964 40.8439 23.6897" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4224 5.41589C7.84285 5.56777 5.50076 6.97222 4.15175 9.17618C2.80273 11.3802 2.61748 14.1048 3.65574 16.4711C3.72396 16.6259 3.79612 16.7781 3.87483 16.9276C1.40898 18.3884 -0.0726901 21.0698 0.00274939 23.9349C0.078189 26.7999 1.69892 29.3996 4.23822 30.7286C4.26977 34.3626 6.77555 37.5066 10.3109 38.348C10.4517 40.9944 12.0505 43.3437 14.4606 44.4457C16.8707 45.5478 19.6935 45.2202 21.7873 43.5956L22.2556 43.2322V2.65306L21.9551 2.31066C20.2838 0.401067 17.6714 -0.391038 15.2209 0.268755C12.7705 0.928546 10.909 2.92524 10.4224 5.41589Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.0447 7.82253V42.6425C19.1691 44.1005 16.6029 44.294 14.5299 43.1338C12.4569 41.9737 11.2797 39.6852 11.5415 37.3241C8.0907 37.017 5.44513 34.1267 5.44381 30.6624C5.44381 30.4709 5.45431 30.2688 5.47399 30.0432L5.48186 29.9671C2.87719 28.9584 1.17326 26.4377 1.20804 23.6447C1.24282 20.8518 3.009 18.3743 5.63797 17.4308C4.06014 15.4186 3.76715 12.6831 4.88302 10.3825C5.99891 8.08181 8.32853 6.61833 10.8855 6.61168C11.0888 6.61168 11.2974 6.62218 11.5086 6.64186C11.5251 4.38203 12.9375 2.36792 15.0565 1.58257C17.1756 0.79722 19.5595 1.40436 21.0447 3.10765V7.82253Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.45659 23.7264C2.4558 21.3001 4.06227 19.1666 6.3944 18.4969C8.72653 17.8272 11.2201 18.7833 12.5069 20.8403L13.5564 20.1844C12.1627 17.957 9.59429 16.7516 6.99045 17.1028C5.99476 16.0874 5.43949 14.7204 5.44506 13.2984C5.4454 11.8559 6.01882 10.4727 7.0391 9.45319C8.05945 8.43359 9.443 7.86117 10.8854 7.86191C11.0817 7.86117 11.2779 7.87077 11.4731 7.89073L11.6476 7.91306C12.1996 10.2885 14.2636 12.0073 16.6997 12.1202L16.7535 10.8753C14.8957 10.7955 13.3117 9.50305 12.8608 7.69905C12.4099 5.89506 13.1995 4.00937 14.8013 3.06491C16.4031 2.12045 18.4354 2.34229 19.7957 3.61009H21.0446V7.82253V3.10764C19.5584 1.40787 17.1765 0.803098 15.0594 1.58794C12.9424 2.37279 11.5303 4.38403 11.5112 6.64185C11.3 6.62216 11.0914 6.61167 10.8881 6.61167C8.3282 6.61217 5.99351 8.07464 4.87595 10.3776C3.75838 12.6806 4.05427 15.4196 5.6379 17.4308C3.0098 18.3765 1.24578 20.8555 1.21335 23.6484C1.1809 26.4413 2.88687 28.9605 5.49228 29.9671L5.48441 30.0432C5.46473 30.2636 5.45424 30.4709 5.45424 30.6624C5.45506 34.123 8.09483 37.012 11.5413 37.3241C11.2804 39.6838 12.4567 41.9706 14.5281 43.1305C16.5996 44.2903 19.1639 44.0982 21.0393 42.6425V38.4051V41.9879H19.7904C18.3477 42.8371 16.5607 42.8488 15.1071 42.0183C13.6534 41.1877 12.7559 39.6424 12.7549 37.9682C12.7549 37.7824 12.7659 37.5966 12.7877 37.412L12.7994 37.3162C14.5045 37.1441 16.0785 36.3242 17.1969 35.0257L16.255 34.2097C14.771 35.9318 12.3724 36.549 10.2416 35.7572C8.11073 34.9654 6.69725 32.9317 6.6979 30.6584C6.6979 30.5272 6.6979 30.4131 6.70708 30.3042C10.3227 30.954 13.7859 28.5671 14.4656 24.957L13.2468 24.7314C12.7291 27.4927 10.1936 29.4053 7.39629 29.1447C4.59899 28.8841 2.46048 26.5359 2.46184 23.7264" fill="white"/>
|
||||
<path d="M23.0479 3.27991V42.5905H20.0479V3.27991H23.0479Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg viewBox="0 0 128 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M33.9884 7.28647C36.5681 7.43879 38.9101 8.84352 40.259 11.0477C41.608 13.2518 41.7933 15.9765 40.7551 18.343C40.6869 18.4965 40.6147 18.6486 40.5373 18.7982C43.0045 20.2581 44.4873 22.9401 44.4116 25.8059C44.3358 28.6717 42.7135 31.2716 40.1726 32.5992C40.1402 36.232 37.6344 39.3744 34.0999 40.2147C33.9591 42.8611 32.3604 45.2104 29.9502 46.3124C27.5401 47.4145 24.7174 47.0869 22.6236 45.4622L22.1553 45.0988V4.52233L22.4557 4.17993C24.1273 2.27031 26.7399 1.47836 29.1904 2.13845C31.6409 2.79853 33.5022 4.79561 33.9884 7.28647Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.3667 9.69371V44.5124C25.2424 45.9667 27.806 46.1581 29.8769 44.9985C31.9478 43.8389 33.1244 41.5532 32.8647 39.194C36.3159 38.8876 38.9623 35.997 38.9636 32.5323C38.9636 32.3407 38.9518 32.1387 38.9322 31.9131L38.9243 31.837C41.5288 30.8301 43.2343 28.3116 43.2021 25.5193C43.1699 22.7271 41.407 20.2485 38.78 19.3019C40.3596 17.2885 40.6523 14.5504 39.5341 12.2485C38.4158 9.94663 36.0824 8.4843 33.5233 8.48155C33.3199 8.48155 33.1126 8.49204 32.9001 8.51172C32.881 6.2539 31.469 4.24266 29.3519 3.45782C27.2347 2.67297 24.8529 3.27775 23.3667 4.97751V9.70028V9.69371Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.9547 25.5963C41.9555 23.17 40.349 21.0365 38.0169 20.3668C35.6848 19.6971 33.1911 20.6531 31.9044 22.7102L30.8549 22.0542C32.2476 19.8293 34.8126 18.6242 37.4143 18.9726C38.41 17.9573 38.9653 16.5903 38.9597 15.1682C38.9597 12.165 36.5252 9.73045 33.522 9.73045C33.3261 9.73012 33.1303 9.74013 32.9355 9.76063L32.7598 9.78161C32.2087 12.1562 30.1466 13.8753 27.7116 13.9901L27.6578 12.7451C29.5156 12.6654 31.0996 11.373 31.5505 9.56894C32.0014 7.76493 31.2118 5.87925 29.61 4.93479C28.0082 3.99032 25.9759 4.21216 24.6156 5.47996H23.3667V9.69371V4.97751C24.8529 3.27775 27.2347 2.67297 29.3519 3.45782C31.469 4.24266 32.881 6.2539 32.9001 8.51172C33.1126 8.49204 33.3199 8.48155 33.5233 8.48155C36.0824 8.4843 38.4158 9.94663 39.5341 12.2485C40.6523 14.5504 40.3596 17.2885 38.78 19.3019C41.407 20.2485 43.1699 22.7271 43.2021 25.5193C43.2343 28.3116 41.5288 30.8301 38.9243 31.837L38.9322 31.913C38.9518 32.1334 38.9636 32.3407 38.9636 32.5323C38.9623 35.997 36.3159 38.8876 32.8647 39.194C33.1257 41.5536 31.9494 43.8404 29.878 45.0004C27.8065 46.1602 25.2421 45.9681 23.3667 44.5124V40.275V43.8577H24.6156C26.0583 44.707 27.8454 44.7186 29.299 43.8881C30.7526 43.0576 31.6502 41.5122 31.6512 39.8381C31.6517 39.6522 31.6408 39.4664 31.6184 39.2819L31.6066 39.1861C29.904 39.0137 28.3322 38.1954 27.2144 36.8995L28.1564 36.0849C29.6418 37.8025 32.0379 38.4165 34.1662 37.625C36.2946 36.8335 37.7073 34.8031 37.7094 32.5323C37.7094 32.4011 37.7094 32.2869 37.7003 32.178C34.087 32.8255 30.6267 30.4406 29.9458 26.8334L31.1711 26.6038C31.6918 29.3619 34.2257 31.2706 37.0203 31.0102C39.815 30.7498 41.9525 28.4057 41.9547 25.599" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M41.9547 25.5963C41.9555 23.17 40.349 21.0365 38.0169 20.3668C35.6848 19.6971 33.1911 20.6531 31.9044 22.7102L30.8549 22.0542C32.2476 19.8293 34.8126 18.6242 37.4143 18.9726C38.41 17.9573 38.9653 16.5903 38.9597 15.1682C38.9597 12.165 36.5252 9.73045 33.522 9.73045C33.3261 9.73012 33.1303 9.74013 32.9355 9.76063L32.7598 9.78161C32.2087 12.1562 30.1466 13.8753 27.7116 13.9901L27.6578 12.7451C29.5156 12.6654 31.0996 11.373 31.5505 9.56894C32.0014 7.76493 31.2118 5.87925 29.61 4.93479C28.0082 3.99032 25.9759 4.21216 24.6156 5.47996H23.3667V9.69371V4.97751C24.8529 3.27775 27.2347 2.67297 29.3519 3.45782C31.469 4.24266 32.881 6.2539 32.9001 8.51172C33.1126 8.49204 33.3199 8.48155 33.5233 8.48155C36.0824 8.4843 38.4158 9.94663 39.5341 12.2485C40.6523 14.5504 40.3596 17.2885 38.78 19.3019C41.407 20.2485 43.1699 22.7271 43.2021 25.5193C43.2343 28.3116 41.5288 30.8301 38.9243 31.837L38.9322 31.913C38.9518 32.1334 38.9636 32.3407 38.9636 32.5323C38.9623 35.997 36.3159 38.8876 32.8647 39.194C33.1257 41.5536 31.9494 43.8404 29.878 45.0004C27.8065 46.1602 25.2421 45.9681 23.3667 44.5124V40.275V43.8577H24.6156C26.0583 44.707 27.8454 44.7186 29.299 43.8881C30.7526 43.0576 31.6502 41.5122 31.6512 39.8381C31.6517 39.6522 31.6408 39.4664 31.6184 39.2819L31.6066 39.1861C29.904 39.0137 28.3322 38.1954 27.2144 36.8995L28.1564 36.0849C29.6418 37.8025 32.0379 38.4165 34.1662 37.625C36.2946 36.8335 37.7073 34.8031 37.7094 32.5323C37.7094 32.4011 37.7094 32.2869 37.7003 32.178C34.087 32.8255 30.6267 30.4406 29.9458 26.8334L31.1711 26.6038C31.6918 29.3619 34.2257 31.2706 37.0203 31.0102C39.815 30.7498 41.9525 28.4057 41.9547 25.599" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.5332 7.32519C8.95369 7.47707 6.6116 8.88153 5.26259 11.0855C3.91357 13.2895 3.72832 16.0141 4.76658 18.3804C4.8348 18.5352 4.90696 18.6874 4.98567 18.8369C2.51982 20.2978 1.03815 22.9791 1.11359 25.8442C1.18903 28.7092 2.80975 31.3089 5.34906 32.6379C5.38061 36.2719 7.88639 39.4159 11.4217 40.2573C11.5625 42.9037 13.1613 45.253 15.5715 46.355C17.9816 47.4571 20.8044 47.1295 22.8981 45.5049L23.3664 45.1415V4.56237L23.066 4.21996C21.3946 2.31037 18.7822 1.51826 16.3318 2.17806C13.8813 2.83785 12.0199 4.83454 11.5332 7.32519Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.1555 9.73183V44.5518C20.2799 46.0098 17.7138 46.2033 15.6407 45.0431C13.5677 43.883 12.3906 41.5945 12.6523 39.2334C9.20154 38.9263 6.55597 36.036 6.55465 32.5717C6.55465 32.3802 6.56515 32.1781 6.58483 31.9525L6.5927 31.8764C3.98803 30.8677 2.2841 28.347 2.31888 25.554C2.35366 22.7611 4.11984 20.2836 6.74881 19.3401C5.17098 17.3279 4.87799 14.5924 5.99386 12.2918C7.10975 9.99111 9.43937 8.52763 11.9963 8.52098C12.1996 8.52098 12.4082 8.53148 12.6195 8.55116C12.6359 6.29133 14.0483 4.27722 16.1674 3.49187C18.2864 2.70652 20.6703 3.31366 22.1555 5.01695V9.73183Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.56743 25.6357C3.56664 23.2094 5.17311 21.0759 7.50524 20.4062C9.83737 19.7365 12.331 20.6926 13.6177 22.7496L14.6672 22.0937C13.2735 19.8663 10.7051 18.6609 8.10129 19.0121C7.1056 17.9967 6.55033 16.6297 6.5559 15.2077C6.55624 13.7652 7.12966 12.382 8.14994 11.3625C9.17029 10.3429 10.5538 9.77047 11.9963 9.77121C12.1925 9.77047 12.3887 9.78007 12.584 9.80003L12.7585 9.82236C13.3105 12.1978 15.3744 13.9166 17.8105 14.0295L17.8643 12.7846C16.0065 12.7048 14.4226 11.4124 13.9717 9.60835C13.5208 7.80436 14.3104 5.91867 15.9121 4.97421C17.514 4.02975 19.5462 4.25159 20.9066 5.51939H22.1555V9.73183V5.01694C20.6692 3.31717 18.2874 2.7124 16.1703 3.49725C14.0532 4.28209 12.6411 6.29333 12.622 8.55115C12.4108 8.53146 12.2022 8.52097 11.9989 8.52097C9.43904 8.52147 7.10435 9.98394 5.98679 12.2869C4.86922 14.5899 5.16511 17.3289 6.74874 19.3401C4.12064 20.2858 2.35662 22.7648 2.32419 25.5577C2.29174 28.3506 3.99771 30.8698 6.60312 31.8764L6.59525 31.9525C6.57557 32.1729 6.56508 32.3802 6.56508 32.5717C6.5659 36.0323 9.20567 38.9213 12.6522 39.2334C12.3912 41.5931 13.5675 43.8799 15.639 45.0398C17.7104 46.1996 20.2748 46.0075 22.1502 44.5518V40.3144V43.8972H20.9013C19.4586 44.7464 17.6715 44.7581 16.2179 43.9276C14.7643 43.097 13.8667 41.5517 13.8657 39.8775C13.8658 39.6917 13.8767 39.5059 13.8985 39.3213L13.9103 39.2256C15.6154 39.0534 17.1893 38.2335 18.3077 36.935L17.3658 36.119C15.8818 37.8411 13.4833 38.4583 11.3525 37.6665C9.22157 36.8747 7.80808 34.841 7.80874 32.5677C7.80874 32.4365 7.80874 32.3224 7.81792 32.2135C11.4335 32.8633 14.8967 30.4764 15.5764 26.8663L14.3577 26.6407C13.8399 29.402 11.3044 31.3146 8.50713 31.054C5.70983 30.7934 3.57132 28.4452 3.57268 25.6357" fill="black"/>
|
||||
<path d="M24.1587 5.18921V44.4998H21.1587V5.18921H24.1587Z" fill="black"/>
|
||||
<path d="M59 34.5856V12H63.037V31.2012H72.5031V34.5856H59ZM82.9874 35C81.8041 35 80.7021 34.8043 79.6812 34.4129C78.6604 33.9985 77.7671 33.4114 77.0015 32.6517C76.2358 31.8919 75.6326 30.9709 75.1918 29.8889C74.7741 28.7838 74.5653 27.5291 74.5653 26.1246C74.5653 24.7432 74.7858 23.5 75.2266 22.3949C75.6674 21.2898 76.2475 20.3574 76.9667 19.5976C77.7092 18.8378 78.556 18.2508 79.5072 17.8363C80.4585 17.4219 81.433 17.2147 82.4305 17.2147C83.5907 17.2147 84.6115 17.4105 85.4931 17.8018C86.3748 18.1932 87.1056 18.7457 87.6856 19.4595C88.2889 20.1732 88.7413 21.025 89.0429 22.015C89.3445 23.005 89.4953 24.0871 89.4953 25.2613C89.4953 25.6526 89.4722 26.021 89.4257 26.3664C89.4026 26.7117 89.3678 26.988 89.3213 27.1952H78.4631C78.6488 28.7147 79.1824 29.8889 80.064 30.7177C80.9688 31.5235 82.1173 31.9264 83.5094 31.9264C84.2519 31.9264 84.9362 31.8228 85.5627 31.6156C86.2124 31.3853 86.8503 31.0745 87.4768 30.6832L88.834 33.1697C88.022 33.6992 87.1172 34.1366 86.1195 34.482C85.1219 34.8273 84.0779 35 82.9874 35ZM78.4283 24.5015H86.0151C86.0151 23.1892 85.7251 22.1646 85.1451 21.4279C84.5882 20.6682 83.7182 20.2883 82.5349 20.2883C81.5141 20.2883 80.6208 20.6451 79.8552 21.3589C79.0896 22.0726 78.614 23.1201 78.4283 24.5015ZM100.313 35C99.2456 35 98.2252 34.8043 97.2507 34.4129C96.2763 34.0216 95.4062 33.4459 94.6406 32.6862C93.8973 31.9264 93.3057 30.994 92.8657 29.8889C92.4245 28.7838 92.2045 27.5291 92.2045 26.1246C92.2045 24.7202 92.4245 23.4655 92.8657 22.3604C93.3057 21.2553 93.8973 20.3228 94.6406 19.5631C95.4062 18.8033 96.2763 18.2277 97.2507 17.8363C98.2252 17.4219 99.2456 17.2147 100.313 17.2147C101.38 17.2147 102.401 17.4219 103.376 17.8363C104.373 18.2277 105.243 18.8033 105.986 19.5631C106.728 20.3228 107.32 21.2553 107.761 22.3604C108.224 23.4655 108.457 24.7202 108.457 26.1246C108.457 27.5291 108.224 28.7838 107.761 29.8889C107.32 30.994 106.728 31.9264 105.986 32.6862C105.243 33.4459 104.373 34.0216 103.376 34.4129C102.401 34.8043 101.38 35 100.313 35ZM100.313 31.7538C101.566 31.7538 102.552 31.2472 103.271 30.2342C103.99 29.1982 104.35 27.8283 104.35 26.1246C104.35 24.3979 103.99 23.028 103.271 22.015C102.552 20.979 101.566 20.461 100.313 20.461C99.0604 20.461 98.0735 20.979 97.3551 22.015C96.6591 23.028 96.3111 24.3979 96.3111 26.1246C96.3111 27.8283 96.6591 29.1982 97.3551 30.2342C98.0735 31.2472 99.0604 31.7538 100.313 31.7538ZM112.418 34.5856V17.6291H115.724L116.003 19.9084H116.142C116.907 19.1717 117.732 18.5385 118.613 18.009C119.518 17.4795 120.562 17.2147 121.745 17.2147C123.578 17.2147 124.912 17.8018 125.747 18.976C126.582 20.1271 127 21.7847 127 23.9489V34.5856H122.998V24.467C122.998 23.0625 122.789 22.0726 122.371 21.497C121.954 20.9214 121.269 20.6336 120.318 20.6336C119.576 20.6336 118.915 20.8178 118.334 21.1862C117.778 21.5315 117.14 22.0496 116.42 22.7402V34.5856H112.418Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,10 @@
|
||||
<svg viewBox="0 0 44 46" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M32.8776 5.37716C35.4573 5.52949 37.7993 6.93422 39.1482 9.13838C40.4972 11.3425 40.6824 14.0672 39.6443 16.4337C39.5761 16.5872 39.5039 16.7393 39.4265 16.8889C41.8937 18.3488 43.3765 21.0308 43.3007 23.8966C43.225 26.7624 41.6027 29.3623 39.0618 30.6899C39.0294 34.3227 36.5236 37.4651 32.9891 38.3054C32.8483 40.9518 31.2496 43.3011 28.8394 44.4031C26.4293 45.5052 23.6065 45.1776 21.5128 43.5529L21.0444 43.1895V2.61303L21.3449 2.27063C23.0164 0.361007 25.629 -0.430945 28.0795 0.229143C30.5301 0.88923 32.3914 2.8863 32.8776 5.37716Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.2559 7.78441V42.6031C24.1315 44.0574 26.6952 44.2488 28.7661 43.0892C30.837 41.9296 32.0135 39.6439 31.7539 37.2847C35.2051 36.9783 37.8514 34.0877 37.8528 30.623C37.8528 30.4314 37.8409 30.2294 37.8213 30.0038L37.8134 29.9277C40.418 28.9208 42.1234 26.4023 42.0913 23.61C42.0591 20.8178 40.2962 18.3392 37.6692 17.3926C39.2488 15.3792 39.5415 12.6411 38.4232 10.3392C37.3049 8.03733 34.9715 6.575 32.4124 6.57224C32.2091 6.57224 32.0018 6.58274 31.7892 6.60242C31.7702 4.3446 30.3581 2.33336 28.241 1.54851C26.1239 0.763668 23.7421 1.36845 22.2559 3.06821V7.79098V7.78441Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.8439 23.687C40.8447 21.2607 39.2382 19.1272 36.9061 18.4575C34.574 17.7878 32.0803 18.7438 30.7935 20.8009L29.744 20.1449C31.1367 17.92 33.7018 16.7149 36.3035 17.0633C37.2991 16.048 37.8544 14.681 37.8489 13.2589C37.8489 10.2557 35.4143 7.82115 32.4111 7.82115C32.2153 7.82081 32.0195 7.83083 31.8247 7.85133L31.6489 7.8723C31.0978 10.2469 29.0358 11.966 26.6008 12.0808L26.547 10.8358C28.4048 10.7561 29.9888 9.46365 30.4397 7.65964C30.8906 5.85563 30.1009 3.96995 28.4991 3.02548C26.8973 2.08102 24.865 2.30286 23.5048 3.57066H22.2559V7.78441V3.06821C23.7421 1.36845 26.1239 0.763668 28.241 1.54851C30.3581 2.33336 31.7702 4.3446 31.7892 6.60242C32.0018 6.58274 32.2091 6.57224 32.4124 6.57224C34.9715 6.575 37.3049 8.03733 38.4232 10.3392C39.5415 12.6411 39.2488 15.3792 37.6692 17.3926C40.2962 18.3392 42.0591 20.8178 42.0913 23.61C42.1234 26.4023 40.418 28.9208 37.8134 29.9277L37.8213 30.0037C37.8409 30.2241 37.8528 30.4314 37.8528 30.623C37.8514 34.0877 35.2051 36.9783 31.7539 37.2847C32.0148 39.6443 30.8385 41.9311 28.7671 43.091C26.6956 44.2509 24.1313 44.0588 22.2559 42.6031V38.3657V41.9484H23.5048C24.9475 42.7977 26.7346 42.8093 28.1882 41.9788C29.6418 41.1483 30.5393 39.6029 30.5403 37.9288C30.5409 37.7429 30.5299 37.5571 30.5076 37.3726L30.4958 37.2768C28.7932 37.1044 27.2213 36.2861 26.1036 34.9902L27.0455 34.1756C28.5309 35.8932 30.927 36.5072 33.0554 35.7157C35.1838 34.9242 36.5964 32.8938 36.5986 30.623C36.5986 30.4918 36.5986 30.3776 36.5895 30.2687C32.9761 30.9162 29.5159 28.5313 28.8349 24.9241L30.0602 24.6945C30.581 27.4526 33.1149 29.3613 35.9095 29.1009C38.7041 28.8405 40.8416 26.4964 40.8439 23.6897" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M40.8439 23.687C40.8447 21.2607 39.2382 19.1272 36.9061 18.4575C34.574 17.7878 32.0803 18.7438 30.7935 20.8009L29.744 20.1449C31.1367 17.92 33.7018 16.7149 36.3035 17.0633C37.2991 16.048 37.8544 14.681 37.8489 13.2589C37.8489 10.2557 35.4143 7.82115 32.4111 7.82115C32.2153 7.82081 32.0195 7.83083 31.8247 7.85133L31.6489 7.8723C31.0978 10.2469 29.0358 11.966 26.6008 12.0808L26.547 10.8358C28.4048 10.7561 29.9888 9.46365 30.4397 7.65964C30.8906 5.85563 30.1009 3.96995 28.4991 3.02548C26.8973 2.08102 24.865 2.30286 23.5048 3.57066H22.2559V7.78441V3.06821C23.7421 1.36845 26.1239 0.763668 28.241 1.54851C30.3581 2.33336 31.7702 4.3446 31.7892 6.60242C32.0018 6.58274 32.2091 6.57224 32.4124 6.57224C34.9715 6.575 37.3049 8.03733 38.4232 10.3392C39.5415 12.6411 39.2488 15.3792 37.6692 17.3926C40.2962 18.3392 42.0591 20.8178 42.0913 23.61C42.1234 26.4023 40.418 28.9208 37.8134 29.9277L37.8213 30.0037C37.8409 30.2241 37.8528 30.4314 37.8528 30.623C37.8514 34.0877 35.2051 36.9783 31.7539 37.2847C32.0148 39.6443 30.8385 41.9311 28.7671 43.091C26.6956 44.2509 24.1313 44.0588 22.2559 42.6031V38.3657V41.9484H23.5048C24.9475 42.7977 26.7346 42.8093 28.1882 41.9788C29.6418 41.1483 30.5393 39.6029 30.5403 37.9288C30.5409 37.7429 30.5299 37.5571 30.5076 37.3726L30.4958 37.2768C28.7932 37.1044 27.2213 36.2861 26.1036 34.9902L27.0455 34.1756C28.5309 35.8932 30.927 36.5072 33.0554 35.7157C35.1838 34.9242 36.5964 32.8938 36.5986 30.623C36.5986 30.4918 36.5986 30.3776 36.5895 30.2687C32.9761 30.9162 29.5159 28.5313 28.8349 24.9241L30.0602 24.6945C30.581 27.4526 33.1149 29.3613 35.9095 29.1009C38.7041 28.8405 40.8416 26.4964 40.8439 23.6897" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4224 5.41589C7.84285 5.56777 5.50076 6.97222 4.15175 9.17618C2.80273 11.3802 2.61748 14.1048 3.65574 16.4711C3.72396 16.6259 3.79612 16.7781 3.87483 16.9276C1.40898 18.3884 -0.0726901 21.0698 0.00274939 23.9349C0.078189 26.7999 1.69892 29.3996 4.23822 30.7286C4.26977 34.3626 6.77555 37.5066 10.3109 38.348C10.4517 40.9944 12.0505 43.3437 14.4606 44.4457C16.8707 45.5478 19.6935 45.2202 21.7873 43.5956L22.2556 43.2322V2.65306L21.9551 2.31066C20.2838 0.401067 17.6714 -0.391038 15.2209 0.268755C12.7705 0.928546 10.909 2.92524 10.4224 5.41589Z" fill="black"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M21.0447 7.82253V42.6425C19.1691 44.1005 16.6029 44.294 14.5299 43.1338C12.4569 41.9737 11.2797 39.6852 11.5415 37.3241C8.0907 37.017 5.44513 34.1267 5.44381 30.6624C5.44381 30.4709 5.45431 30.2688 5.47399 30.0432L5.48186 29.9671C2.87719 28.9584 1.17326 26.4377 1.20804 23.6447C1.24282 20.8518 3.009 18.3743 5.63797 17.4308C4.06014 15.4186 3.76715 12.6831 4.88302 10.3825C5.99891 8.08181 8.32853 6.61833 10.8855 6.61168C11.0888 6.61168 11.2974 6.62218 11.5086 6.64186C11.5251 4.38203 12.9375 2.36792 15.0565 1.58257C17.1756 0.79722 19.5595 1.40436 21.0447 3.10765V7.82253Z" fill="white"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M2.45659 23.7264C2.4558 21.3001 4.06227 19.1666 6.3944 18.4969C8.72653 17.8272 11.2201 18.7833 12.5069 20.8403L13.5564 20.1844C12.1627 17.957 9.59429 16.7516 6.99045 17.1028C5.99476 16.0874 5.43949 14.7204 5.44506 13.2984C5.4454 11.8559 6.01882 10.4727 7.0391 9.45319C8.05945 8.43359 9.443 7.86117 10.8854 7.86191C11.0817 7.86117 11.2779 7.87077 11.4731 7.89073L11.6476 7.91306C12.1996 10.2885 14.2636 12.0073 16.6997 12.1202L16.7535 10.8753C14.8957 10.7955 13.3117 9.50305 12.8608 7.69905C12.4099 5.89506 13.1995 4.00937 14.8013 3.06491C16.4031 2.12045 18.4354 2.34229 19.7957 3.61009H21.0446V7.82253V3.10764C19.5584 1.40787 17.1765 0.803098 15.0594 1.58794C12.9424 2.37279 11.5303 4.38403 11.5112 6.64185C11.3 6.62216 11.0914 6.61167 10.8881 6.61167C8.3282 6.61217 5.99351 8.07464 4.87595 10.3776C3.75838 12.6806 4.05427 15.4196 5.6379 17.4308C3.0098 18.3765 1.24578 20.8555 1.21335 23.6484C1.1809 26.4413 2.88687 28.9605 5.49228 29.9671L5.48441 30.0432C5.46473 30.2636 5.45424 30.4709 5.45424 30.6624C5.45506 34.123 8.09483 37.012 11.5413 37.3241C11.2804 39.6838 12.4567 41.9706 14.5281 43.1305C16.5996 44.2903 19.1639 44.0982 21.0393 42.6425V38.4051V41.9879H19.7904C18.3477 42.8371 16.5607 42.8488 15.1071 42.0183C13.6534 41.1877 12.7559 39.6424 12.7549 37.9682C12.7549 37.7824 12.7659 37.5966 12.7877 37.412L12.7994 37.3162C14.5045 37.1441 16.0785 36.3242 17.1969 35.0257L16.255 34.2097C14.771 35.9318 12.3724 36.549 10.2416 35.7572C8.11073 34.9654 6.69725 32.9317 6.6979 30.6584C6.6979 30.5272 6.6979 30.4131 6.70708 30.3042C10.3227 30.954 13.7859 28.5671 14.4656 24.957L13.2468 24.7314C12.7291 27.4927 10.1936 29.4053 7.39629 29.1447C4.59899 28.8841 2.46048 26.5359 2.46184 23.7264" fill="black"/>
|
||||
<path d="M23.0479 3.27991V42.5905H20.0479V3.27991H23.0479Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.6 KiB |
@@ -0,0 +1,25 @@
|
||||
.badge
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
gap: 6px
|
||||
padding: 4px 0
|
||||
width: 56px
|
||||
border-radius: var(--border-radius-pill)
|
||||
background-color: var(--color-accent-soft)
|
||||
color: var(--color-accent)
|
||||
font-size: var(--font-size-xs)
|
||||
line-height: var(--line-height-xs)
|
||||
transition: opacity var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.badge-dot
|
||||
display: inline-block
|
||||
position: relative
|
||||
width: 4px
|
||||
height: 4px
|
||||
border-radius: var(--border-radius-pill)
|
||||
background-color: currentColor
|
||||
|
||||
&.badge-secondary
|
||||
background-color: var(--color-surface)
|
||||
color: var(--color-text-secondary)
|
||||
@@ -0,0 +1,20 @@
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import './badge.sass'
|
||||
|
||||
interface BadgeProps {
|
||||
label: string
|
||||
variant?: 'primary' | 'secondary'
|
||||
}
|
||||
|
||||
export function Badge({
|
||||
label,
|
||||
variant = 'primary'
|
||||
}: BadgeProps) {
|
||||
return (
|
||||
<div className={clsx('badge', `badge-${variant}`)}>
|
||||
{variant === 'primary' && <span className="badge-dot" aria-hidden="true" />}
|
||||
{label}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './badge'
|
||||
@@ -0,0 +1,84 @@
|
||||
.button
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
gap: var(--space-sm)
|
||||
box-sizing: border-box
|
||||
border: 1px solid transparent
|
||||
border-radius: var(--border-radius-lg)
|
||||
color: var(--color-text-secondary)
|
||||
font-weight: var(--font-weight-semi-bold)
|
||||
text-decoration: none
|
||||
transition: color var(--transition-duration-main) var(--transition-timing-main), background-color var(--transition-duration-main) var(--transition-timing-main), border-color var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
.button-icon
|
||||
flex: 0 0 auto
|
||||
font-weight: var(--font-weight-regular)
|
||||
line-height: 1
|
||||
|
||||
.button-label
|
||||
min-width: 0
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
&.button-md
|
||||
min-height: 34px
|
||||
padding: 0 12px
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
|
||||
&.button-icon-only
|
||||
box-sizing: border-box
|
||||
color: var(--color-text-secondary)
|
||||
width: var(--button-md-icon-only-size)
|
||||
height: var(--button-md-icon-only-size)
|
||||
min-height: 0
|
||||
padding: 8px
|
||||
|
||||
&.button-ghost:hover:not(:disabled):not([aria-disabled='true']),
|
||||
&.button-secondary:hover:not(:disabled):not([aria-disabled='true'])
|
||||
background-color: var(--color-surface-hover)
|
||||
color: var(--color-text)
|
||||
|
||||
&.button-secondary
|
||||
background-color: transparent
|
||||
border-color: var(--color-border)
|
||||
color: var(--color-text)
|
||||
|
||||
&.button-ghost:active:not(:disabled):not([aria-disabled='true']),
|
||||
&.button-secondary:active:not(:disabled):not([aria-disabled='true'])
|
||||
background-color: var(--color-surface-active)
|
||||
color: var(--color-text)
|
||||
|
||||
&.button-primary
|
||||
background-color: var(--color-primary-button)
|
||||
color: var(--color-primary-button-text)
|
||||
|
||||
&.button-primary:hover:not(:disabled):not([aria-disabled='true'])
|
||||
background-color: var(--color-primary-button-hover)
|
||||
|
||||
&.button-primary:active:not(:disabled):not([aria-disabled='true'])
|
||||
background-color: var(--color-primary-button-active)
|
||||
|
||||
&.button-danger
|
||||
background-color: var(--color-danger)
|
||||
color: var(--color-danger-contrast)
|
||||
|
||||
&.button-danger:hover:not(:disabled):not([aria-disabled='true'])
|
||||
background-color: var(--color-danger-hover)
|
||||
|
||||
&.button-danger:active:not(:disabled):not([aria-disabled='true'])
|
||||
background-color: var(--color-danger-pressed)
|
||||
|
||||
&:disabled
|
||||
color: var(--color-text-disabled)
|
||||
|
||||
&:not([aria-busy='true'])
|
||||
cursor: not-allowed !important
|
||||
|
||||
&[aria-disabled='true']
|
||||
color: var(--color-text-disabled)
|
||||
|
||||
&:not([aria-busy='true'])
|
||||
cursor: not-allowed !important
|
||||
@@ -0,0 +1,157 @@
|
||||
import type { CSSProperties, MouseEvent, ReactNode } from 'react'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import { Tooltip } from '../tooltip'
|
||||
import type { TooltipPosition } from '../tooltip'
|
||||
|
||||
import './button.sass'
|
||||
|
||||
type AppRouteTo = '/'
|
||||
type ButtonIconType = 'line' | 'fill'
|
||||
type ButtonIconPosition = 'left' | 'right'
|
||||
type ButtonSize = 'sm' | 'md'
|
||||
export type ButtonVariant = 'ghost' | 'secondary' | 'primary' | 'danger'
|
||||
|
||||
interface ButtonBaseProps {
|
||||
children?: ReactNode
|
||||
iconName?: string
|
||||
iconType?: ButtonIconType
|
||||
iconPosition?: ButtonIconPosition
|
||||
tooltipMessage?: string
|
||||
tooltipPosition?: TooltipPosition
|
||||
ariaLabel?: string
|
||||
variant?: ButtonVariant
|
||||
size?: ButtonSize
|
||||
cursorStyle?: CSSProperties['cursor']
|
||||
disabled?: boolean
|
||||
loading?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
type ButtonProps = ButtonBaseProps & (
|
||||
| {
|
||||
to?: undefined
|
||||
href?: undefined
|
||||
type?: 'button' | 'submit'
|
||||
target?: never
|
||||
rel?: never
|
||||
}
|
||||
| {
|
||||
to: AppRouteTo
|
||||
href?: never
|
||||
type?: never
|
||||
target?: never
|
||||
rel?: never
|
||||
}
|
||||
| {
|
||||
to?: never
|
||||
href: string
|
||||
type?: never
|
||||
target?: string
|
||||
rel?: string
|
||||
}
|
||||
)
|
||||
|
||||
export function Button({
|
||||
children,
|
||||
type = 'button',
|
||||
to,
|
||||
href,
|
||||
target,
|
||||
rel,
|
||||
iconName,
|
||||
iconType = 'line',
|
||||
iconPosition = 'left',
|
||||
tooltipMessage,
|
||||
tooltipPosition = 'bottom',
|
||||
ariaLabel,
|
||||
variant = 'ghost',
|
||||
size = 'md',
|
||||
cursorStyle = 'pointer',
|
||||
disabled = false,
|
||||
loading = false,
|
||||
onClick
|
||||
}: ButtonProps) {
|
||||
const isDisabled = disabled || loading
|
||||
const isIconOnly = children === undefined
|
||||
const accessibleLabel = ariaLabel ?? tooltipMessage
|
||||
const iconClassName = `ri-${iconName}-${iconType}`
|
||||
const className = clsx('button', `button-${variant}`, `button-${size}`, {
|
||||
'button-icon-only': isIconOnly
|
||||
})
|
||||
const content = (
|
||||
<>
|
||||
{iconName && iconPosition === 'left' && (
|
||||
<i className={clsx('button-icon', iconClassName)} aria-hidden="true" />
|
||||
)}
|
||||
{children && <span className="button-label">{children}</span>}
|
||||
{iconName && iconPosition === 'right' && (
|
||||
<i className={clsx('button-icon', iconClassName)} aria-hidden="true" />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
const button = to !== undefined ? (
|
||||
<Link
|
||||
to={to}
|
||||
className={className}
|
||||
aria-label={isIconOnly ? accessibleLabel : ariaLabel}
|
||||
aria-busy={loading}
|
||||
aria-disabled={isDisabled}
|
||||
style={{ cursor: loading ? 'wait' : cursorStyle }}
|
||||
onClick={(event: MouseEvent<HTMLAnchorElement>) => {
|
||||
if (isDisabled) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
onClick?.()
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
) : href === undefined ? (
|
||||
<button
|
||||
type={type}
|
||||
className={className}
|
||||
aria-label={isIconOnly ? accessibleLabel : ariaLabel}
|
||||
aria-busy={loading}
|
||||
disabled={isDisabled}
|
||||
style={{ cursor: loading ? 'wait' : cursorStyle }}
|
||||
onClick={onClick}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
) : (
|
||||
<a
|
||||
href={href}
|
||||
className={className}
|
||||
aria-label={isIconOnly ? accessibleLabel : ariaLabel}
|
||||
aria-busy={loading}
|
||||
aria-disabled={isDisabled}
|
||||
target={target}
|
||||
rel={rel}
|
||||
style={{ cursor: loading ? 'wait' : cursorStyle }}
|
||||
onClick={(event: MouseEvent<HTMLAnchorElement>) => {
|
||||
if (isDisabled) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
onClick?.()
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
)
|
||||
|
||||
if (tooltipMessage === undefined) {
|
||||
return button
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip message={tooltipMessage} position={tooltipPosition}>
|
||||
{button}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './button'
|
||||
@@ -0,0 +1,128 @@
|
||||
.dialog-overlay
|
||||
position: fixed
|
||||
inset: 0
|
||||
z-index: 105
|
||||
display: flex
|
||||
align-items: flex-start
|
||||
justify-content: center
|
||||
padding-top: 112px
|
||||
background-color: rgba(0, 0, 0, .2)
|
||||
backdrop-filter: blur(2px)
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
transition: opacity var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.dialog-overlay-open
|
||||
opacity: 1
|
||||
pointer-events: auto
|
||||
|
||||
.dialog
|
||||
overflow: hidden
|
||||
background: linear-gradient(var(--color-surface-elevated), var(--color-surface-elevated)) padding-box, var(--gradient-border) border-box
|
||||
border: 1px solid transparent
|
||||
border-radius: var(--border-radius-lg)
|
||||
color: var(--color-text)
|
||||
opacity: 0
|
||||
scale: 1
|
||||
scrollbar-color: var(--color-scrollbar-thumb-muted) transparent
|
||||
scrollbar-width: thin
|
||||
transform-origin: center
|
||||
transition: opacity var(--transition-duration-sidebar) var(--transition-timing-sidebar), scale var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.dialog-open
|
||||
opacity: 1
|
||||
animation: dialog-enter-pulse .75s cubic-bezier(.16, 1, .3, 1) both
|
||||
scale: 1
|
||||
|
||||
.dialog-medium
|
||||
width: min(464px, calc(100vw - 32px))
|
||||
|
||||
.dialog-large
|
||||
width: min(720px, calc(100vw - 32px))
|
||||
|
||||
@starting-style
|
||||
.dialog-overlay-open
|
||||
opacity: 0
|
||||
|
||||
.dialog-open
|
||||
opacity: 0
|
||||
scale: 1
|
||||
|
||||
.dialog:not(.dialog-open)
|
||||
opacity: 0
|
||||
scale: 1
|
||||
|
||||
@keyframes dialog-enter-pulse
|
||||
0%
|
||||
scale: .75
|
||||
|
||||
25%
|
||||
scale: 1.03
|
||||
|
||||
100%
|
||||
scale: 1
|
||||
|
||||
.dialog-header
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: space-between
|
||||
gap: var(--space-lg)
|
||||
padding: var(--space-lg) var(--space-xl) 0
|
||||
|
||||
.button
|
||||
color: var(--color-text-secondary)
|
||||
|
||||
&:hover:not(:disabled):not([aria-disabled='true'])
|
||||
color: var(--color-text)
|
||||
|
||||
.dialog-title
|
||||
margin: 0
|
||||
color: var(--color-text)
|
||||
font-size: var(--font-size-xl)
|
||||
font-weight: var(--font-weight-semi-bold)
|
||||
line-height: var(--line-height-xl)
|
||||
|
||||
.dialog-body
|
||||
padding: var(--space-lg) var(--space-xl) var(--space-lg)
|
||||
color: var(--color-text-secondary)
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
|
||||
.dialog,
|
||||
.dialog *
|
||||
&::-webkit-scrollbar
|
||||
width: 8px
|
||||
height: 8px
|
||||
|
||||
&::-webkit-scrollbar-track
|
||||
background-color: transparent
|
||||
|
||||
&::-webkit-scrollbar-thumb
|
||||
border: 2px solid transparent
|
||||
border-radius: var(--border-radius-pill)
|
||||
background-clip: content-box
|
||||
background-color: var(--color-scrollbar-thumb-muted)
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover
|
||||
background-color: var(--color-scrollbar-thumb)
|
||||
|
||||
.dialog-description
|
||||
margin: 0
|
||||
|
||||
.dialog-footer
|
||||
display: flex
|
||||
justify-content: flex-end
|
||||
gap: var(--space-lg)
|
||||
padding: var(--space-lg) var(--space-xl)
|
||||
border-top: 1px solid var(--color-border)
|
||||
background-color: var(--color-surface)
|
||||
|
||||
@media (prefers-reduced-motion: reduce)
|
||||
.dialog-overlay,
|
||||
.dialog
|
||||
animation: none
|
||||
transition: none
|
||||
|
||||
.dialog
|
||||
opacity: 1
|
||||
transform: none
|
||||
@@ -0,0 +1,143 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import { useEffect, useId, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import { Button, type ButtonVariant } from '../button'
|
||||
|
||||
import './dialog.sass'
|
||||
|
||||
type DialogRole = 'dialog' | 'alertdialog'
|
||||
type DialogSize = 'medium' | 'large'
|
||||
|
||||
interface DialogAction {
|
||||
label: string
|
||||
variant?: ButtonVariant
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
interface DialogProps {
|
||||
actions?: DialogAction[]
|
||||
children?: ReactNode
|
||||
closeOnOverlayClick?: boolean
|
||||
hideFooter?: boolean
|
||||
hideHeader?: boolean
|
||||
description?: ReactNode
|
||||
open: boolean
|
||||
role?: DialogRole
|
||||
size?: DialogSize
|
||||
title: string
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export function Dialog({
|
||||
actions = [],
|
||||
children,
|
||||
closeOnOverlayClick = true,
|
||||
description,
|
||||
hideFooter = false,
|
||||
hideHeader = false,
|
||||
open,
|
||||
role = 'dialog',
|
||||
size = 'medium',
|
||||
title,
|
||||
onClose
|
||||
}: DialogProps) {
|
||||
const titleId = useId()
|
||||
const descriptionId = useId()
|
||||
const [mounted, setMounted] = useState(open)
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
setMounted(true)
|
||||
}
|
||||
}, [open])
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent): void {
|
||||
if (event.key === 'Escape') {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [onClose, open])
|
||||
|
||||
if (!mounted) {
|
||||
return null
|
||||
}
|
||||
|
||||
return createPortal(
|
||||
<div
|
||||
className={clsx('dialog-overlay', {
|
||||
'dialog-overlay-open': open
|
||||
})}
|
||||
onTransitionEnd={(event) => {
|
||||
if (
|
||||
event.target === event.currentTarget &&
|
||||
event.propertyName === 'opacity' &&
|
||||
!open
|
||||
) {
|
||||
setMounted(false)
|
||||
}
|
||||
}}
|
||||
onMouseDown={(event) => {
|
||||
if (closeOnOverlayClick && event.target === event.currentTarget) {
|
||||
onClose()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<section
|
||||
className={clsx('dialog', `dialog-${size}`, { 'dialog-open': open })}
|
||||
role={role}
|
||||
aria-label={hideHeader ? title : undefined}
|
||||
aria-labelledby={hideHeader ? undefined : titleId}
|
||||
aria-describedby={description === undefined ? undefined : descriptionId}
|
||||
aria-modal="true"
|
||||
>
|
||||
{!hideHeader && (
|
||||
<header className="dialog-header">
|
||||
<h2 className="dialog-title" id={titleId}>
|
||||
{title}
|
||||
</h2>
|
||||
<Button
|
||||
iconName="close"
|
||||
ariaLabel="Close dialog"
|
||||
onClick={onClose}
|
||||
/>
|
||||
</header>
|
||||
)}
|
||||
<div className="dialog-body">
|
||||
{description !== undefined && (
|
||||
<p className="dialog-description" id={descriptionId}>
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
{!hideFooter && actions.length > 0 && (
|
||||
<footer className="dialog-footer">
|
||||
{actions.map((action) => (
|
||||
<Button
|
||||
key={action.label}
|
||||
variant={action.variant ?? 'ghost'}
|
||||
onClick={action.onClick}
|
||||
>
|
||||
{action.label}
|
||||
</Button>
|
||||
))}
|
||||
</footer>
|
||||
)}
|
||||
</section>
|
||||
</div>,
|
||||
document.body
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './dialog'
|
||||
@@ -0,0 +1,89 @@
|
||||
.dropdown-trigger
|
||||
display: inline-flex
|
||||
|
||||
.dropdown-content
|
||||
position: fixed
|
||||
z-index: 110
|
||||
display: flex
|
||||
flex-direction: column
|
||||
box-sizing: border-box
|
||||
gap: 1px
|
||||
padding: var(--space-xs)
|
||||
background: linear-gradient(var(--color-surface-elevated), var(--color-surface-elevated)) padding-box, var(--gradient-border) border-box
|
||||
border: 1px solid transparent
|
||||
border-radius: var(--border-radius-md)
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
transition: opacity var(--transition-duration-main) var(--transition-timing-main), transform var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
.dropdown-content-open
|
||||
opacity: 1
|
||||
pointer-events: auto
|
||||
|
||||
.dropdown-content-bottom
|
||||
transform: translate3d(0, -4px, 0)
|
||||
|
||||
&.dropdown-content-open
|
||||
transform: translate3d(0, 0, 0)
|
||||
|
||||
.dropdown-content-top
|
||||
transform: translate3d(0, 4px, 0)
|
||||
|
||||
&.dropdown-content-open
|
||||
transform: translate3d(0, 0, 0)
|
||||
|
||||
@starting-style
|
||||
.dropdown-content-open
|
||||
opacity: 0
|
||||
|
||||
.dropdown-content-bottom.dropdown-content-open
|
||||
transform: translate3d(0, -4px, 0)
|
||||
|
||||
.dropdown-content-top.dropdown-content-open
|
||||
transform: translate3d(0, 4px, 0)
|
||||
|
||||
.dropdown-item
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: var(--space-sm)
|
||||
box-sizing: border-box
|
||||
width: 100%
|
||||
min-height: 40px
|
||||
padding: var(--space-sm)
|
||||
border-radius: var(--border-radius-md)
|
||||
color: var(--color-text)
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
text-align: left
|
||||
transition: color var(--transition-duration-main) var(--transition-timing-main), background-color var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
&:hover
|
||||
background-color: var(--color-surface-hover)
|
||||
|
||||
&:active
|
||||
background-color: var(--color-surface-active)
|
||||
|
||||
.dropdown-item-danger
|
||||
color: var(--color-danger)
|
||||
|
||||
&:hover
|
||||
background-color: var(--color-danger-soft)
|
||||
|
||||
&:active
|
||||
background-color: var(--color-danger-active)
|
||||
|
||||
.dropdown-item-icon
|
||||
flex: 0 0 auto
|
||||
color: currentColor
|
||||
font-size: var(--font-size-xl)
|
||||
|
||||
.dropdown-item-label
|
||||
min-width: 0
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
@media (prefers-reduced-motion: reduce)
|
||||
.dropdown-content,
|
||||
.dropdown-item
|
||||
transition: none
|
||||
@@ -0,0 +1,209 @@
|
||||
import type { CSSProperties, ReactNode } from 'react'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import './dropdown.sass'
|
||||
|
||||
type DropdownPlacement = 'top' | 'bottom'
|
||||
type DropdownItemVariant = 'default' | 'danger'
|
||||
|
||||
interface DropdownItem {
|
||||
iconName: string
|
||||
label: string
|
||||
variant?: DropdownItemVariant
|
||||
onSelect?: () => void
|
||||
}
|
||||
|
||||
interface DropdownProps {
|
||||
children: ReactNode
|
||||
items: DropdownItem[]
|
||||
}
|
||||
|
||||
const DROPDOWN_OFFSET = 6
|
||||
const DROPDOWN_WIDTH = 180
|
||||
const DROPDOWN_ESTIMATED_ITEM_HEIGHT = 40
|
||||
const DROPDOWN_VERTICAL_PADDING = 8
|
||||
|
||||
function getDropdownHeight(itemCount: number): number {
|
||||
return (itemCount * DROPDOWN_ESTIMATED_ITEM_HEIGHT) + DROPDOWN_VERTICAL_PADDING
|
||||
}
|
||||
|
||||
function getDropdownPosition(
|
||||
triggerRect: DOMRect,
|
||||
itemCount: number
|
||||
): {
|
||||
placement: DropdownPlacement
|
||||
style: CSSProperties
|
||||
} {
|
||||
const dropdownHeight = getDropdownHeight(itemCount)
|
||||
const spaceBelow = window.innerHeight - triggerRect.bottom
|
||||
const placement =
|
||||
spaceBelow >= dropdownHeight + DROPDOWN_OFFSET ? 'bottom' : 'top'
|
||||
const preferredTop = placement === 'bottom'
|
||||
? triggerRect.bottom + DROPDOWN_OFFSET
|
||||
: triggerRect.top - dropdownHeight - DROPDOWN_OFFSET
|
||||
const top = Math.min(
|
||||
Math.max(preferredTop, DROPDOWN_OFFSET),
|
||||
window.innerHeight - dropdownHeight - DROPDOWN_OFFSET
|
||||
)
|
||||
const left = Math.min(
|
||||
Math.max(triggerRect.right - DROPDOWN_WIDTH, DROPDOWN_OFFSET),
|
||||
window.innerWidth - DROPDOWN_WIDTH - DROPDOWN_OFFSET
|
||||
)
|
||||
|
||||
return {
|
||||
placement,
|
||||
style: {
|
||||
top,
|
||||
left,
|
||||
width: DROPDOWN_WIDTH
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function Dropdown({
|
||||
children,
|
||||
items
|
||||
}: DropdownProps) {
|
||||
const triggerRef = useRef<HTMLSpanElement>(null)
|
||||
const dropdownRef = useRef<HTMLDivElement>(null)
|
||||
const [mounted, setMounted] = useState(false)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [placement, setPlacement] = useState<DropdownPlacement>('bottom')
|
||||
const [dropdownStyle, setDropdownStyle] = useState<CSSProperties>()
|
||||
|
||||
const updateDropdownPosition = useCallback((): void => {
|
||||
if (triggerRef.current === null) {
|
||||
return
|
||||
}
|
||||
|
||||
const nextPosition = getDropdownPosition(
|
||||
triggerRef.current.getBoundingClientRect(),
|
||||
items.length
|
||||
)
|
||||
|
||||
setPlacement(nextPosition.placement)
|
||||
setDropdownStyle(nextPosition.style)
|
||||
}, [items.length])
|
||||
|
||||
const closeDropdown = useCallback((): void => {
|
||||
if (!open) {
|
||||
return
|
||||
}
|
||||
|
||||
setOpen(false)
|
||||
}, [open])
|
||||
|
||||
const openDropdown = useCallback((): void => {
|
||||
updateDropdownPosition()
|
||||
setMounted(true)
|
||||
setOpen(true)
|
||||
}, [updateDropdownPosition])
|
||||
|
||||
function toggleDropdown(): void {
|
||||
if (mounted && open) {
|
||||
closeDropdown()
|
||||
return
|
||||
}
|
||||
|
||||
openDropdown()
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function handlePointerDown(event: PointerEvent): void {
|
||||
const target = event.target
|
||||
|
||||
if (!(target instanceof Node)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (
|
||||
triggerRef.current?.contains(target) ||
|
||||
dropdownRef.current?.contains(target)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
closeDropdown()
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent): void {
|
||||
if (event.key === 'Escape') {
|
||||
closeDropdown()
|
||||
}
|
||||
}
|
||||
|
||||
updateDropdownPosition()
|
||||
document.addEventListener('pointerdown', handlePointerDown)
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
window.addEventListener('resize', updateDropdownPosition)
|
||||
window.addEventListener('scroll', updateDropdownPosition, true)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('pointerdown', handlePointerDown)
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
window.removeEventListener('resize', updateDropdownPosition)
|
||||
window.removeEventListener('scroll', updateDropdownPosition, true)
|
||||
}
|
||||
}, [closeDropdown, open, updateDropdownPosition])
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
ref={triggerRef}
|
||||
className="dropdown-trigger"
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
toggleDropdown()
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
{mounted && createPortal(
|
||||
<div
|
||||
ref={dropdownRef}
|
||||
className={clsx(
|
||||
'dropdown-content',
|
||||
`dropdown-content-${placement}`,
|
||||
{ 'dropdown-content-open': open }
|
||||
)}
|
||||
role="menu"
|
||||
style={dropdownStyle}
|
||||
onTransitionEnd={(event) => {
|
||||
if (event.propertyName === 'opacity' && !open) {
|
||||
setMounted(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{items.map((item) => (
|
||||
<button
|
||||
type="button"
|
||||
className={clsx(
|
||||
'dropdown-item',
|
||||
`dropdown-item-${item.variant ?? 'default'}`
|
||||
)}
|
||||
key={item.label}
|
||||
role="menuitem"
|
||||
onClick={() => {
|
||||
item.onSelect?.()
|
||||
closeDropdown()
|
||||
}}
|
||||
>
|
||||
<i
|
||||
className={`dropdown-item-icon ri-${item.iconName}-line`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="dropdown-item-label">{item.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './dropdown'
|
||||
@@ -0,0 +1,7 @@
|
||||
.feed
|
||||
display: grid
|
||||
align-content: start
|
||||
gap: var(--space-xl)
|
||||
width: min(100%, var(--content-width))
|
||||
padding: 52px 0 150px
|
||||
color: var(--color-text-secondary)
|
||||
@@ -0,0 +1,5 @@
|
||||
import './feed.sass'
|
||||
|
||||
export function Feed() {
|
||||
return <section className="feed" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './feed'
|
||||
@@ -0,0 +1 @@
|
||||
export * from './input'
|
||||
@@ -0,0 +1,70 @@
|
||||
.input
|
||||
position: relative
|
||||
display: block
|
||||
width: 100%
|
||||
|
||||
.input-icon
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: var(--space-lg)
|
||||
z-index: 1
|
||||
color: var(--color-text-secondary)
|
||||
font-size: var(--font-size-lg)
|
||||
line-height: 1
|
||||
pointer-events: none
|
||||
transform: translateY(-50%)
|
||||
transition: color var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
.input-field
|
||||
box-sizing: border-box
|
||||
display: block
|
||||
width: 100%
|
||||
min-height: 40px
|
||||
padding: 0 var(--space-lg)
|
||||
border: 1px solid var(--color-input-border)
|
||||
border-radius: var(--border-radius-pill)
|
||||
outline: 0
|
||||
background-color: var(--color-background)
|
||||
color: var(--color-text)
|
||||
font-family: var(--font-family)
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
transition: background-color var(--transition-duration-main) var(--transition-timing-main), border-color var(--transition-duration-main) var(--transition-timing-main), color var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
&::placeholder
|
||||
color: var(--color-text-secondary)
|
||||
|
||||
&:hover
|
||||
border-color: var(--color-border-strong)
|
||||
|
||||
&:focus,
|
||||
&:active
|
||||
border-color: var(--color-accent)
|
||||
|
||||
&[type='search']
|
||||
appearance: textfield
|
||||
|
||||
&::-webkit-search-cancel-button,
|
||||
&::-webkit-search-decoration
|
||||
appearance: none
|
||||
|
||||
.input-medium
|
||||
.input-field
|
||||
min-height: 40px
|
||||
|
||||
.input-large
|
||||
.input-field
|
||||
min-height: 56px
|
||||
|
||||
.input-with-icon
|
||||
&.input-medium .input-field
|
||||
// 16px left; 16px right; + icon font-size
|
||||
padding-left: calc((var(--space-lg) * 2) + var(--font-size-lg))
|
||||
|
||||
&.input-large .input-field
|
||||
// 16px left; 16px right; + icon font-size
|
||||
padding-left: calc((var(--space-lg) * 2) + var(--font-size-lg))
|
||||
|
||||
&:focus-within
|
||||
.input-icon
|
||||
color: var(--color-accent)
|
||||
@@ -0,0 +1,78 @@
|
||||
import type {
|
||||
InputHTMLAttributes,
|
||||
Ref,
|
||||
TextareaHTMLAttributes
|
||||
} from 'react'
|
||||
import { useId } from 'react'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import './input.sass'
|
||||
|
||||
type InputIconType = 'line' | 'fill'
|
||||
type InputSize = 'medium' | 'large'
|
||||
|
||||
interface InputSharedProps {
|
||||
ariaLabel: string
|
||||
className?: string
|
||||
fieldRef?: Ref<HTMLInputElement | HTMLTextAreaElement>
|
||||
iconName?: string
|
||||
iconType?: InputIconType
|
||||
size?: InputSize
|
||||
}
|
||||
|
||||
type NativeInputProps = InputSharedProps &
|
||||
Omit<InputHTMLAttributes<HTMLInputElement>, 'aria-label' | 'className' | 'size'> & {
|
||||
multiline?: false
|
||||
}
|
||||
|
||||
type NativeTextareaProps = InputSharedProps &
|
||||
Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'aria-label' | 'className'> & {
|
||||
multiline: true
|
||||
}
|
||||
|
||||
type InputProps = NativeInputProps | NativeTextareaProps
|
||||
|
||||
export function Input({
|
||||
ariaLabel,
|
||||
className,
|
||||
fieldRef,
|
||||
iconName,
|
||||
iconType = 'line',
|
||||
multiline = false,
|
||||
size = 'medium',
|
||||
...fieldProps
|
||||
}: InputProps) {
|
||||
const generatedId = useId()
|
||||
const inputId = fieldProps.id ?? generatedId
|
||||
const inputClassName = clsx('input', `input-${size}`, className, {
|
||||
'input-with-icon': iconName !== undefined
|
||||
})
|
||||
|
||||
return (
|
||||
<span className={inputClassName}>
|
||||
{iconName !== undefined && (
|
||||
<i
|
||||
className={`input-icon ri-${iconName}-${iconType}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
{multiline ? (
|
||||
<textarea
|
||||
{...(fieldProps as TextareaHTMLAttributes<HTMLTextAreaElement>)}
|
||||
id={inputId}
|
||||
className="input-field"
|
||||
aria-label={ariaLabel}
|
||||
ref={fieldRef as Ref<HTMLTextAreaElement>}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
{...(fieldProps as InputHTMLAttributes<HTMLInputElement>)}
|
||||
id={inputId}
|
||||
className="input-field"
|
||||
aria-label={ariaLabel}
|
||||
ref={fieldRef as Ref<HTMLInputElement>}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './leon-message'
|
||||
@@ -0,0 +1,10 @@
|
||||
.leon-message
|
||||
display: grid
|
||||
gap: var(--space-sm)
|
||||
max-width: 760px
|
||||
color: var(--color-text)
|
||||
font-size: var(--font-size-md)
|
||||
line-height: 28px
|
||||
|
||||
p
|
||||
color: var(--color-text)
|
||||
@@ -0,0 +1,5 @@
|
||||
import './leon-message.sass'
|
||||
|
||||
export function LeonMessage() {
|
||||
return <div className="leon-message" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './markdown-content'
|
||||
@@ -0,0 +1,23 @@
|
||||
.markdown-content
|
||||
display: grid
|
||||
gap: var(--space-sm)
|
||||
padding-top: var(--space-lg)
|
||||
border-top: 1px solid var(--color-border)
|
||||
color: var(--color-text)
|
||||
|
||||
h2,
|
||||
h3
|
||||
margin-top: var(--space-md)
|
||||
color: var(--color-text)
|
||||
font-size: 24px
|
||||
font-weight: var(--font-weight-bold)
|
||||
line-height: 30px
|
||||
|
||||
p
|
||||
color: var(--color-text-secondary)
|
||||
font-size: var(--font-size-md)
|
||||
line-height: 28px
|
||||
|
||||
strong
|
||||
color: var(--color-text)
|
||||
font-weight: var(--font-weight-bold)
|
||||
@@ -0,0 +1,5 @@
|
||||
import './markdown-content.sass'
|
||||
|
||||
export function MarkdownContent() {
|
||||
return <div className="markdown-content" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './message'
|
||||
@@ -0,0 +1,9 @@
|
||||
.message
|
||||
display: grid
|
||||
max-width: 100%
|
||||
|
||||
&.end
|
||||
justify-content: end
|
||||
|
||||
&.start
|
||||
justify-content: start
|
||||
@@ -0,0 +1,5 @@
|
||||
import './message.sass'
|
||||
|
||||
export function Message() {
|
||||
return <article className="message" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './owner-message'
|
||||
@@ -0,0 +1,8 @@
|
||||
.owner-message
|
||||
max-width: 520px
|
||||
padding: 12px 24px
|
||||
border: 1px solid rgba(245, 245, 247, .18)
|
||||
border-radius: var(--border-radius-pill)
|
||||
background-color: var(--color-accent)
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
@@ -0,0 +1,5 @@
|
||||
import './owner-message.sass'
|
||||
|
||||
export function OwnerMessage() {
|
||||
return <p className="owner-message" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './query-input'
|
||||
@@ -0,0 +1,53 @@
|
||||
.query-input
|
||||
position: fixed
|
||||
right: max(var(--space-xl), calc((100vw - var(--sidebar-width) - var(--content-width)) / 2))
|
||||
bottom: var(--space-xl)
|
||||
z-index: 3
|
||||
display: grid
|
||||
grid-template-columns: 40px 1fr 40px 48px
|
||||
align-items: center
|
||||
gap: var(--space-sm)
|
||||
width: min(var(--content-width), calc(100vw - var(--sidebar-width) - (var(--space-xl) * 2)))
|
||||
min-height: var(--query-input-height)
|
||||
padding: 8px 10px 8px 20px
|
||||
border: 1px solid var(--color-query-border)
|
||||
border-radius: var(--border-radius-xl)
|
||||
background: linear-gradient(var(--color-query-background), var(--color-query-background)) padding-box, var(--gradient-query) border-box
|
||||
box-shadow: 0 22px 80px rgba(28, 117, 219, .12)
|
||||
backdrop-filter: blur(18px)
|
||||
|
||||
input
|
||||
border: 0
|
||||
background: transparent
|
||||
padding: 0
|
||||
color: var(--color-text)
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active
|
||||
border: 0
|
||||
|
||||
button
|
||||
display: grid
|
||||
place-items: center
|
||||
color: var(--color-text-secondary)
|
||||
font-size: 28px
|
||||
|
||||
.add,
|
||||
.voice
|
||||
width: 40px
|
||||
height: 40px
|
||||
|
||||
.send
|
||||
width: 48px
|
||||
height: 48px
|
||||
border: 1px solid var(--color-border-strong)
|
||||
border-radius: var(--border-radius-pill)
|
||||
|
||||
@media (max-width: 960px)
|
||||
.query-input
|
||||
right: var(--space-lg)
|
||||
left: var(--space-lg)
|
||||
width: auto
|
||||
@@ -0,0 +1,5 @@
|
||||
import './query-input.sass'
|
||||
|
||||
export function QueryInput() {
|
||||
return <form className="query-input" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './search-sessions-dialog'
|
||||
@@ -0,0 +1,98 @@
|
||||
.search-sessions-dialog
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: var(--space-lg)
|
||||
min-height: 360px
|
||||
|
||||
input
|
||||
font-size: var(--font-size-md)
|
||||
|
||||
.search-sessions-dialog-results
|
||||
overflow: auto
|
||||
min-height: 0
|
||||
max-height: 320px
|
||||
|
||||
.search-sessions-dialog-list
|
||||
position: relative
|
||||
|
||||
.search-sessions-dialog-item
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
width: 100%
|
||||
|
||||
.search-sessions-dialog-link
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: space-between
|
||||
gap: var(--space-lg)
|
||||
box-sizing: border-box
|
||||
width: 100%
|
||||
height: 46px
|
||||
padding: 0 var(--space-md)
|
||||
border-radius: var(--border-radius-md)
|
||||
color: var(--color-text)
|
||||
text-decoration: none
|
||||
transition: background-color var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
&:hover
|
||||
background-color: var(--color-surface-hover)
|
||||
|
||||
&:active
|
||||
background-color: var(--color-surface-active)
|
||||
|
||||
.search-sessions-dialog-title
|
||||
min-width: 0
|
||||
overflow: hidden
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
.search-sessions-dialog-title-highlight
|
||||
border-radius: var(--border-radius-sm)
|
||||
background-color: var(--color-accent-soft)
|
||||
color: var(--color-accent)
|
||||
|
||||
.search-sessions-dialog-date
|
||||
flex: 0 0 auto
|
||||
color: var(--color-text-secondary)
|
||||
font-size: var(--font-size-sm)
|
||||
line-height: var(--line-height-sm)
|
||||
|
||||
.search-sessions-dialog-empty
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
flex: 1 1 auto
|
||||
flex-direction: column
|
||||
gap: var(--space-md)
|
||||
min-height: 220px
|
||||
color: var(--color-text-secondary)
|
||||
|
||||
.search-sessions-dialog-empty-text
|
||||
margin: 0
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
|
||||
.search-sessions-dialog-empty-link
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
min-height: 34px
|
||||
padding: 0 12px
|
||||
border: 1px solid transparent
|
||||
border-radius: var(--border-radius-lg)
|
||||
background-color: var(--color-primary-button)
|
||||
color: var(--color-primary-button-text)
|
||||
font-size: var(--font-size-md)
|
||||
font-weight: var(--font-weight-semi-bold)
|
||||
line-height: var(--line-height-md)
|
||||
text-decoration: none
|
||||
transition: background-color var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
&:hover
|
||||
background-color: var(--color-primary-button-hover)
|
||||
|
||||
&:active
|
||||
background-color: var(--color-primary-button-active)
|
||||
@@ -0,0 +1,267 @@
|
||||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
|
||||
import { Input } from '../input'
|
||||
import {
|
||||
sessionIndex,
|
||||
type ConversationSession
|
||||
} from '../../data/sessions'
|
||||
|
||||
import './search-sessions-dialog.sass'
|
||||
|
||||
interface SearchSessionsDialogProps {
|
||||
onSessionSelect?: () => void
|
||||
}
|
||||
|
||||
interface HighlightedTitleSegment {
|
||||
highlighted: boolean
|
||||
text: string
|
||||
}
|
||||
|
||||
const SEARCH_DEBOUNCE_MS = 300
|
||||
const SEARCH_RESULT_ITEM_HEIGHT = 46
|
||||
const SEARCH_RESULT_OVERSCAN = 6
|
||||
const MINUTE_MS = 60_000
|
||||
const HOUR_MS = 60 * MINUTE_MS
|
||||
const DAY_MS = 24 * HOUR_MS
|
||||
const DATE_FORMATTER = new Intl.DateTimeFormat('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short'
|
||||
})
|
||||
const DATE_WITH_YEAR_FORMATTER = new Intl.DateTimeFormat('en-US', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
})
|
||||
|
||||
function useDebouncedValue<TValue>(
|
||||
value: TValue,
|
||||
delayMs: number
|
||||
): TValue {
|
||||
const [debouncedValue, setDebouncedValue] = useState(value)
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
setDebouncedValue(value)
|
||||
}, delayMs)
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId)
|
||||
}
|
||||
}, [delayMs, value])
|
||||
|
||||
return debouncedValue
|
||||
}
|
||||
|
||||
function getSessionTimestamp(session: ConversationSession): number {
|
||||
return session.lastMessageAt ?? session.updatedAt
|
||||
}
|
||||
|
||||
function getStartOfDay(date: Date): number {
|
||||
return new Date(
|
||||
date.getFullYear(),
|
||||
date.getMonth(),
|
||||
date.getDate()
|
||||
).getTime()
|
||||
}
|
||||
|
||||
function formatSessionDate(timestamp: number): string {
|
||||
const now = Date.now()
|
||||
const elapsedMs = Math.max(0, now - timestamp)
|
||||
const nowDate = new Date(now)
|
||||
const date = new Date(timestamp)
|
||||
const todayStart = getStartOfDay(nowDate)
|
||||
const yesterdayStart = todayStart - DAY_MS
|
||||
|
||||
if (timestamp >= todayStart) {
|
||||
if (elapsedMs < MINUTE_MS) {
|
||||
return 'Just now'
|
||||
}
|
||||
|
||||
if (elapsedMs < HOUR_MS) {
|
||||
const minutes = Math.floor(elapsedMs / MINUTE_MS)
|
||||
|
||||
return `${minutes} minute${minutes === 1 ? '' : 's'} ago`
|
||||
}
|
||||
|
||||
const hours = Math.floor(elapsedMs / HOUR_MS)
|
||||
|
||||
return `${hours} hour${hours === 1 ? '' : 's'} ago`
|
||||
}
|
||||
|
||||
if (timestamp >= yesterdayStart) {
|
||||
return 'Yesterday'
|
||||
}
|
||||
|
||||
if (date.getFullYear() === nowDate.getFullYear()) {
|
||||
return DATE_FORMATTER.format(date)
|
||||
}
|
||||
|
||||
return DATE_WITH_YEAR_FORMATTER.format(date)
|
||||
}
|
||||
|
||||
function sortSessionsByLastMessage(
|
||||
sessions: ConversationSession[]
|
||||
): ConversationSession[] {
|
||||
return [...sessions].sort((firstSession, secondSession) =>
|
||||
getSessionTimestamp(secondSession) - getSessionTimestamp(firstSession)
|
||||
)
|
||||
}
|
||||
|
||||
function getHighlightedTitleSegments(
|
||||
title: string,
|
||||
query: string
|
||||
): HighlightedTitleSegment[] {
|
||||
const normalizedQuery = query.trim().toLowerCase()
|
||||
|
||||
if (normalizedQuery.length === 0) {
|
||||
return [{ highlighted: false, text: title }]
|
||||
}
|
||||
|
||||
const normalizedTitle = title.toLowerCase()
|
||||
const segments: HighlightedTitleSegment[] = []
|
||||
let searchIndex = 0
|
||||
|
||||
while (searchIndex < title.length) {
|
||||
const matchIndex = normalizedTitle.indexOf(normalizedQuery, searchIndex)
|
||||
|
||||
if (matchIndex === -1) {
|
||||
segments.push({
|
||||
highlighted: false,
|
||||
text: title.slice(searchIndex)
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
if (matchIndex > searchIndex) {
|
||||
segments.push({
|
||||
highlighted: false,
|
||||
text: title.slice(searchIndex, matchIndex)
|
||||
})
|
||||
}
|
||||
|
||||
segments.push({
|
||||
highlighted: true,
|
||||
text: title.slice(matchIndex, matchIndex + normalizedQuery.length)
|
||||
})
|
||||
searchIndex = matchIndex + normalizedQuery.length
|
||||
}
|
||||
|
||||
return segments
|
||||
}
|
||||
|
||||
export function SearchSessionsDialog({
|
||||
onSessionSelect
|
||||
}: SearchSessionsDialogProps) {
|
||||
const scrollElementRef = useRef<HTMLDivElement>(null)
|
||||
const [query, setQuery] = useState('')
|
||||
const debouncedQuery = useDebouncedValue(query, SEARCH_DEBOUNCE_MS)
|
||||
const searchResults = useMemo(() => {
|
||||
const normalizedQuery = debouncedQuery.trim().toLowerCase()
|
||||
const sessions = sortSessionsByLastMessage(sessionIndex.sessions)
|
||||
|
||||
if (normalizedQuery.length === 0) {
|
||||
return sessions
|
||||
}
|
||||
|
||||
return sessions.filter((session) =>
|
||||
session.title.toLowerCase().includes(normalizedQuery)
|
||||
)
|
||||
}, [debouncedQuery])
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: searchResults.length,
|
||||
getScrollElement: () => scrollElementRef.current,
|
||||
estimateSize: () => SEARCH_RESULT_ITEM_HEIGHT,
|
||||
overscan: SEARCH_RESULT_OVERSCAN
|
||||
})
|
||||
|
||||
return (
|
||||
<div className="search-sessions-dialog">
|
||||
<Input
|
||||
ariaLabel="Search sessions"
|
||||
autoFocus
|
||||
iconName="search"
|
||||
placeholder="Search sessions..."
|
||||
size="large"
|
||||
type="search"
|
||||
value={query}
|
||||
onChange={(event) => setQuery(event.target.value)}
|
||||
/>
|
||||
<div
|
||||
className="search-sessions-dialog-results"
|
||||
ref={scrollElementRef}
|
||||
>
|
||||
{searchResults.length > 0 ? (
|
||||
<ul
|
||||
className="search-sessions-dialog-list"
|
||||
style={{ height: `${rowVirtualizer.getTotalSize()}px` }}
|
||||
>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||
const session = searchResults[virtualRow.index]
|
||||
|
||||
if (session === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
className="search-sessions-dialog-item"
|
||||
key={session.id}
|
||||
style={{
|
||||
height: `${virtualRow.size}px`,
|
||||
transform: `translateY(${virtualRow.start}px)`
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to="/session/$sessionId"
|
||||
params={{ sessionId: session.id }}
|
||||
className="search-sessions-dialog-link"
|
||||
onClick={onSessionSelect}
|
||||
>
|
||||
<span className="search-sessions-dialog-title">
|
||||
{getHighlightedTitleSegments(
|
||||
session.title,
|
||||
debouncedQuery
|
||||
).map((segment, segmentIndex) => (
|
||||
<span
|
||||
className={segment.highlighted
|
||||
? 'search-sessions-dialog-title-highlight'
|
||||
: undefined}
|
||||
key={`${segment.text}-${segmentIndex}`}
|
||||
>
|
||||
{segment.text}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
<span className="search-sessions-dialog-date">
|
||||
{formatSessionDate(getSessionTimestamp(session))}
|
||||
</span>
|
||||
</Link>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
<div className="search-sessions-dialog-empty">
|
||||
<p className="search-sessions-dialog-empty-text">
|
||||
No session found.
|
||||
</p>
|
||||
<Link
|
||||
to="/"
|
||||
className="search-sessions-dialog-empty-link"
|
||||
onClick={onSessionSelect}
|
||||
>
|
||||
New session
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './shortcut'
|
||||
@@ -0,0 +1,18 @@
|
||||
.shortcut
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
gap: var(--space-xs)
|
||||
color: var(--color-text-tertiary)
|
||||
|
||||
kbd
|
||||
display: grid
|
||||
min-width: 22px
|
||||
height: 22px
|
||||
place-items: center
|
||||
border-radius: 6px
|
||||
background-color: var(--color-background)
|
||||
color: var(--color-text-secondary)
|
||||
font-family: var(--font-family)
|
||||
font-size: 13px
|
||||
font-weight: var(--font-weight-semi-bold)
|
||||
line-height: 1
|
||||
@@ -0,0 +1,5 @@
|
||||
import './shortcut.sass'
|
||||
|
||||
export function Shortcut() {
|
||||
return <span className="shortcut" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './task-list-item'
|
||||
@@ -0,0 +1,55 @@
|
||||
.task-list-item
|
||||
position: relative
|
||||
display: grid
|
||||
grid-template-columns: 24px 1fr
|
||||
gap: var(--space-sm)
|
||||
color: var(--color-text-secondary)
|
||||
|
||||
&:not(:last-child)::after
|
||||
position: absolute
|
||||
top: 24px
|
||||
bottom: -18px
|
||||
left: 11px
|
||||
width: 1px
|
||||
background-color: rgba(245, 245, 247, .2)
|
||||
content: ''
|
||||
|
||||
.marker
|
||||
z-index: 1
|
||||
display: grid
|
||||
width: 22px
|
||||
height: 22px
|
||||
place-items: center
|
||||
border-radius: var(--border-radius-pill)
|
||||
background-color: var(--color-surface)
|
||||
color: var(--color-text-secondary)
|
||||
font-size: 14px
|
||||
|
||||
&.done .marker,
|
||||
&.active .marker
|
||||
background-color: var(--color-accent-soft)
|
||||
color: var(--color-accent)
|
||||
|
||||
&.active .marker::before
|
||||
width: 10px
|
||||
height: 10px
|
||||
border-radius: var(--border-radius-pill)
|
||||
background-color: var(--color-accent)
|
||||
content: ''
|
||||
|
||||
.content
|
||||
display: grid
|
||||
gap: var(--space-sm)
|
||||
|
||||
p
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
gap: var(--space-sm)
|
||||
min-height: 22px
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
|
||||
span
|
||||
color: var(--color-text-tertiary)
|
||||
font-size: var(--font-size-sm)
|
||||
line-height: var(--line-height-sm)
|
||||
@@ -0,0 +1,5 @@
|
||||
import './task-list-item.sass'
|
||||
|
||||
export function TaskListItem() {
|
||||
return <li className="task-list-item" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './task-list'
|
||||
@@ -0,0 +1,3 @@
|
||||
.task-list
|
||||
display: grid
|
||||
gap: 18px
|
||||
@@ -0,0 +1,5 @@
|
||||
import './task-list.sass'
|
||||
|
||||
export function TaskList() {
|
||||
return <ol className="task-list" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './thinking-message'
|
||||
@@ -0,0 +1,16 @@
|
||||
.thinking-message
|
||||
display: grid
|
||||
gap: var(--space-md)
|
||||
|
||||
h2
|
||||
color: var(--color-text)
|
||||
font-size: var(--font-size-md)
|
||||
font-weight: var(--font-weight-semi-bold)
|
||||
line-height: var(--line-height-md)
|
||||
|
||||
.lines
|
||||
display: grid
|
||||
gap: var(--space-sm)
|
||||
color: var(--color-text-secondary)
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
@@ -0,0 +1,5 @@
|
||||
import './thinking-message.sass'
|
||||
|
||||
export function ThinkingMessage() {
|
||||
return <section className="thinking-message" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './toast'
|
||||
@@ -0,0 +1,167 @@
|
||||
.toast-region
|
||||
--toast-stack-gap: var(--space-sm)
|
||||
--toast-stack-item-height: 96px
|
||||
position: fixed
|
||||
top: var(--space-xl)
|
||||
left: 50%
|
||||
z-index: 120
|
||||
height: 152px
|
||||
width: min(396px, calc(100vw - 32px))
|
||||
pointer-events: none
|
||||
transform: translateX(-50%)
|
||||
transition: height var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
&.toast-region-expanded,
|
||||
&:focus-within
|
||||
height: var(--toast-region-expanded-height)
|
||||
|
||||
.toast
|
||||
pointer-events: auto
|
||||
opacity: 1
|
||||
transform: translate3d(0, calc((var(--toast-stack-item-height) + var(--toast-stack-gap)) * var(--toast-index)), 0) scale(1)
|
||||
|
||||
.toast
|
||||
--toast-close-button-size: var(--button-md-icon-only-size)
|
||||
--toast-icon-size: 20px
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
display: flex
|
||||
align-items: flex-start
|
||||
justify-content: space-between
|
||||
gap: var(--space-lg)
|
||||
box-sizing: border-box
|
||||
width: 100%
|
||||
height: var(--toast-stack-item-height)
|
||||
padding: var(--space-md)
|
||||
background: linear-gradient(var(--color-surface-elevated), var(--color-surface-elevated)) padding-box, var(--gradient-border) border-box
|
||||
border: 1px solid transparent
|
||||
border-radius: var(--border-radius-lg)
|
||||
color: var(--color-text)
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
transform-origin: top center
|
||||
transition: opacity var(--transition-duration-sidebar) var(--transition-timing-sidebar), transform var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
will-change: transform, opacity
|
||||
|
||||
.toast-stack-0
|
||||
z-index: 3
|
||||
opacity: 1
|
||||
pointer-events: auto
|
||||
transform: translate3d(0, 0, 0) scale(1)
|
||||
|
||||
.toast-stack-1
|
||||
z-index: 2
|
||||
opacity: 1
|
||||
transform: translate3d(0, 16px, 0) scale(.96)
|
||||
|
||||
.toast-stack-2
|
||||
z-index: 1
|
||||
opacity: 1
|
||||
transform: translate3d(0, 32px, 0) scale(.92)
|
||||
|
||||
.toast-hidden-stack
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
transform: translate3d(0, 32px, 0) scale(.92)
|
||||
|
||||
.toast-region .toast.toast-exiting
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
transform: translate3d(0, -14px, 0) scale(.98)
|
||||
|
||||
@starting-style
|
||||
.toast-stack-0,
|
||||
.toast-stack-1,
|
||||
.toast-stack-2
|
||||
opacity: 0
|
||||
transform: translate3d(0, -14px, 0) scale(.98)
|
||||
|
||||
.toast-content-container
|
||||
display: flex
|
||||
flex: 1 1 auto
|
||||
flex-direction: column
|
||||
gap: var(--space-sm)
|
||||
min-width: 0
|
||||
|
||||
.toast-header
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 10px
|
||||
|
||||
.toast-icon
|
||||
display: inline-flex
|
||||
flex: 0 0 auto
|
||||
align-items: center
|
||||
justify-content: center
|
||||
width: var(--toast-icon-size)
|
||||
height: var(--toast-icon-size)
|
||||
border-radius: var(--border-radius-pill)
|
||||
|
||||
.toast-icon-symbol
|
||||
font-size: var(--font-size-md)
|
||||
line-height: 1
|
||||
|
||||
.toast-success
|
||||
.toast-icon
|
||||
background-color: var(--color-success-soft)
|
||||
color: var(--color-success)
|
||||
|
||||
.toast-info
|
||||
.toast-icon
|
||||
background-color: var(--color-accent-soft)
|
||||
color: var(--color-accent)
|
||||
|
||||
.toast-warning
|
||||
.toast-icon
|
||||
background-color: var(--color-warning-soft)
|
||||
color: var(--color-warning)
|
||||
|
||||
.toast-error
|
||||
.toast-icon
|
||||
background-color: var(--color-danger-soft)
|
||||
color: var(--color-danger)
|
||||
|
||||
.toast-title
|
||||
display: block
|
||||
min-width: 0
|
||||
overflow: hidden
|
||||
color: var(--color-text)
|
||||
font-size: var(--font-size-md)
|
||||
font-weight: var(--font-weight-semi-bold)
|
||||
line-height: var(--line-height-md)
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
.toast-description
|
||||
display: -webkit-box
|
||||
overflow: hidden
|
||||
margin: 0
|
||||
padding-left: 30px
|
||||
color: var(--color-text-secondary)
|
||||
font-size: var(--font-size-sm)
|
||||
font-weight: var(--font-weight-regular)
|
||||
line-height: var(--line-height-sm)
|
||||
-webkit-box-orient: vertical
|
||||
-webkit-line-clamp: 2
|
||||
|
||||
.toast-close-button
|
||||
flex: 0 0 auto
|
||||
margin-top: calc((var(--toast-icon-size) - var(--toast-close-button-size)) / 2)
|
||||
|
||||
.button
|
||||
color: var(--color-text-tertiary)
|
||||
|
||||
&:hover:not(:disabled):not([aria-disabled='true'])
|
||||
color: var(--color-text)
|
||||
|
||||
@media (prefers-reduced-motion: reduce)
|
||||
.toast-region
|
||||
transition: none
|
||||
|
||||
.toast
|
||||
animation: none
|
||||
transition: none
|
||||
|
||||
.toast-close-button .button
|
||||
transition: none
|
||||
@@ -0,0 +1,240 @@
|
||||
import type { CSSProperties, ReactNode } from 'react'
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState
|
||||
} from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import { Button } from '../button'
|
||||
|
||||
import './toast.sass'
|
||||
|
||||
type ToastType = 'success' | 'info' | 'warning' | 'error'
|
||||
|
||||
interface ToastInput {
|
||||
type?: ToastType
|
||||
title: string
|
||||
description: string
|
||||
}
|
||||
|
||||
interface ToastItem extends Required<ToastInput> {
|
||||
exiting: boolean
|
||||
id: string
|
||||
}
|
||||
|
||||
interface ToastContextValue {
|
||||
showToast: (toast: ToastInput) => void
|
||||
}
|
||||
|
||||
interface ToastProviderProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
interface ToastCardProps {
|
||||
stackIndex: number
|
||||
toast: ToastItem
|
||||
toastCount: number
|
||||
onActivate: () => void
|
||||
onClose: (toastId: string) => void
|
||||
onExited: (toastId: string) => void
|
||||
}
|
||||
|
||||
const DEFAULT_TOAST_DURATION_MS = 9_000
|
||||
const MAX_STACKED_TOASTS = 3
|
||||
const TOAST_ICONS: Record<ToastType, string> = {
|
||||
success: 'check-line',
|
||||
info: 'info-i',
|
||||
warning: 'alert-line',
|
||||
error: 'close-circle-line'
|
||||
}
|
||||
const ToastContext = createContext<ToastContextValue | null>(null)
|
||||
|
||||
function createToastId(): string {
|
||||
return window.crypto.randomUUID()
|
||||
}
|
||||
|
||||
function ToastCard({
|
||||
stackIndex,
|
||||
toast,
|
||||
toastCount,
|
||||
onActivate,
|
||||
onClose,
|
||||
onExited
|
||||
}: ToastCardProps) {
|
||||
useEffect(() => {
|
||||
const timeoutId = window.setTimeout(() => {
|
||||
onClose(toast.id)
|
||||
}, DEFAULT_TOAST_DURATION_MS)
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timeoutId)
|
||||
}
|
||||
}, [onClose, toast.id])
|
||||
|
||||
return (
|
||||
<article
|
||||
className={clsx(
|
||||
'toast',
|
||||
`toast-${toast.type}`,
|
||||
`toast-stack-${stackIndex}`,
|
||||
{ 'toast-hidden-stack': stackIndex >= MAX_STACKED_TOASTS },
|
||||
{ 'toast-exiting': toast.exiting }
|
||||
)}
|
||||
style={{
|
||||
'--toast-index': stackIndex,
|
||||
zIndex: toastCount - stackIndex
|
||||
} as CSSProperties}
|
||||
onPointerEnter={onActivate}
|
||||
onFocus={onActivate}
|
||||
onTransitionEnd={(event) => {
|
||||
if (event.propertyName === 'opacity' && toast.exiting) {
|
||||
onExited(toast.id)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="toast-content-container">
|
||||
<div className="toast-header">
|
||||
<span className="toast-icon" aria-hidden="true">
|
||||
<i className={`toast-icon-symbol ri-${TOAST_ICONS[toast.type]}`} />
|
||||
</span>
|
||||
<strong className="toast-title">{toast.title}</strong>
|
||||
</div>
|
||||
<p className="toast-description">{toast.description}</p>
|
||||
</div>
|
||||
<div
|
||||
className="toast-close-button"
|
||||
>
|
||||
<Button
|
||||
iconName="close"
|
||||
ariaLabel="Close notification"
|
||||
onClick={() => onClose(toast.id)}
|
||||
/>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
export function ToastProvider({
|
||||
children
|
||||
}: ToastProviderProps) {
|
||||
const regionRef = useRef<HTMLElement>(null)
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const [toasts, setToasts] = useState<ToastItem[]>([])
|
||||
|
||||
const closeToast = useCallback((toastId: string): void => {
|
||||
setToasts((currentToasts) =>
|
||||
currentToasts.map((toast) =>
|
||||
toast.id === toastId
|
||||
? { ...toast, exiting: true }
|
||||
: toast
|
||||
)
|
||||
)
|
||||
|
||||
}, [])
|
||||
|
||||
const removeToast = useCallback((toastId: string): void => {
|
||||
setToasts((currentToasts) =>
|
||||
currentToasts.filter((toast) => toast.id !== toastId)
|
||||
)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!expanded) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
function handlePointerMove(event: PointerEvent): void {
|
||||
const region = regionRef.current
|
||||
|
||||
if (region === null) {
|
||||
setExpanded(false)
|
||||
return
|
||||
}
|
||||
|
||||
const regionRect = region.getBoundingClientRect()
|
||||
const isInsideRegion =
|
||||
event.clientX >= regionRect.left &&
|
||||
event.clientX <= regionRect.right &&
|
||||
event.clientY >= regionRect.top &&
|
||||
event.clientY <= regionRect.bottom
|
||||
|
||||
if (!isInsideRegion) {
|
||||
setExpanded(false)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('pointermove', handlePointerMove)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('pointermove', handlePointerMove)
|
||||
}
|
||||
}, [expanded])
|
||||
|
||||
const contextValue = useMemo<ToastContextValue>(() => ({
|
||||
showToast: (toast) => {
|
||||
setToasts((currentToasts) => [
|
||||
{
|
||||
id: createToastId(),
|
||||
exiting: false,
|
||||
type: toast.type ?? 'info',
|
||||
title: toast.title,
|
||||
description: toast.description
|
||||
},
|
||||
...currentToasts
|
||||
])
|
||||
}
|
||||
}), [])
|
||||
|
||||
return (
|
||||
<ToastContext.Provider value={contextValue}>
|
||||
{children}
|
||||
{toasts.length > 0 && createPortal(
|
||||
<section
|
||||
ref={regionRef}
|
||||
className={clsx('toast-region', {
|
||||
'toast-region-expanded': expanded
|
||||
})}
|
||||
aria-label="Notifications"
|
||||
aria-live="polite"
|
||||
style={{
|
||||
'--toast-region-expanded-height': `calc((var(--toast-stack-item-height) * ${toasts.length}) + (var(--toast-stack-gap) * ${Math.max(toasts.length - 1, 0)}))`
|
||||
} as CSSProperties}
|
||||
onBlur={(event) => {
|
||||
if (!event.currentTarget.contains(event.relatedTarget)) {
|
||||
setExpanded(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
{toasts.map((toast, stackIndex) => (
|
||||
<ToastCard
|
||||
key={toast.id}
|
||||
stackIndex={stackIndex}
|
||||
toast={toast}
|
||||
toastCount={toasts.length}
|
||||
onActivate={() => setExpanded(true)}
|
||||
onClose={closeToast}
|
||||
onExited={removeToast}
|
||||
/>
|
||||
))}
|
||||
</section>,
|
||||
document.body
|
||||
)}
|
||||
</ToastContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useToast(): ToastContextValue {
|
||||
const contextValue = useContext(ToastContext)
|
||||
|
||||
if (contextValue === null) {
|
||||
throw new Error('useToast must be used within ToastProvider.')
|
||||
}
|
||||
|
||||
return contextValue
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './tool-call'
|
||||
@@ -0,0 +1,28 @@
|
||||
.tool-call
|
||||
display: grid
|
||||
gap: var(--space-sm)
|
||||
|
||||
> p
|
||||
color: var(--color-text-tertiary)
|
||||
font-size: var(--font-size-sm)
|
||||
line-height: var(--line-height-sm)
|
||||
|
||||
.panes
|
||||
display: grid
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr))
|
||||
overflow: hidden
|
||||
border: 1px solid var(--color-border)
|
||||
border-radius: var(--border-radius-md)
|
||||
background-color: rgba(0, 0, 0, .34)
|
||||
|
||||
pre
|
||||
overflow: hidden
|
||||
min-height: 150px
|
||||
padding: var(--space-md)
|
||||
color: var(--color-text-secondary)
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace
|
||||
font-size: 13px
|
||||
line-height: 20px
|
||||
|
||||
+ pre
|
||||
border-left: 1px solid var(--color-border)
|
||||
@@ -0,0 +1,5 @@
|
||||
import './tool-call.sass'
|
||||
|
||||
export function ToolCall() {
|
||||
return <section className="tool-call" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './tooltip'
|
||||
@@ -0,0 +1,80 @@
|
||||
.tooltip
|
||||
display: inline-flex
|
||||
|
||||
.tooltip-content
|
||||
position: fixed
|
||||
z-index: 100
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
gap: var(--space-md)
|
||||
width: max-content
|
||||
max-width: 220px
|
||||
padding: 6px 10px
|
||||
background: linear-gradient(var(--color-surface-elevated), var(--color-surface-elevated)) padding-box, var(--gradient-border) border-box
|
||||
border: 1px solid transparent
|
||||
border-radius: var(--border-radius-md)
|
||||
color: var(--color-text)
|
||||
font-size: var(--font-size-sm)
|
||||
font-weight: var(--font-weight-semi-bold)
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
transition: opacity var(--transition-duration-main) var(--transition-timing-main), transform var(--transition-duration-main) var(--transition-timing-main)
|
||||
white-space: nowrap
|
||||
|
||||
.tooltip-message,
|
||||
.tooltip-hint
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
gap: var(--space-xs)
|
||||
|
||||
.tooltip-hint
|
||||
color: var(--color-text-secondary)
|
||||
font-weight: var(--font-weight-regular)
|
||||
|
||||
.tooltip-hint-icon
|
||||
color: currentColor
|
||||
font-size: calc(var(--font-size-sm) - 1px)
|
||||
line-height: var(--line-height-sm)
|
||||
|
||||
.tooltip-content-open
|
||||
opacity: 1
|
||||
|
||||
.tooltip-content-top
|
||||
transform: translate3d(-50%, 4px, 0)
|
||||
|
||||
&.tooltip-content-open
|
||||
transform: translate3d(-50%, 0, 0)
|
||||
|
||||
.tooltip-content-right
|
||||
transform: translate3d(-4px, -50%, 0)
|
||||
|
||||
&.tooltip-content-open
|
||||
transform: translate3d(0, -50%, 0)
|
||||
|
||||
.tooltip-content-bottom
|
||||
transform: translate3d(-50%, -4px, 0)
|
||||
|
||||
&.tooltip-content-open
|
||||
transform: translate3d(-50%, 0, 0)
|
||||
|
||||
.tooltip-content-left
|
||||
transform: translate3d(4px, -50%, 0)
|
||||
|
||||
&.tooltip-content-open
|
||||
transform: translate3d(0, -50%, 0)
|
||||
|
||||
@starting-style
|
||||
.tooltip-content-open
|
||||
opacity: 0
|
||||
|
||||
.tooltip-content-top.tooltip-content-open
|
||||
transform: translate3d(-50%, 4px, 0)
|
||||
|
||||
.tooltip-content-right.tooltip-content-open
|
||||
transform: translate3d(-4px, -50%, 0)
|
||||
|
||||
.tooltip-content-bottom.tooltip-content-open
|
||||
transform: translate3d(-50%, -4px, 0)
|
||||
|
||||
.tooltip-content-left.tooltip-content-open
|
||||
transform: translate3d(4px, -50%, 0)
|
||||
@@ -0,0 +1,152 @@
|
||||
import type { CSSProperties, ReactNode } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { createPortal } from 'react-dom'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import './tooltip.sass'
|
||||
|
||||
export type TooltipPosition = 'top' | 'right' | 'bottom' | 'left'
|
||||
|
||||
interface TooltipProps {
|
||||
children: ReactNode
|
||||
hint?: ReactNode
|
||||
message: string
|
||||
position?: TooltipPosition
|
||||
}
|
||||
|
||||
const TOOLTIP_OFFSET = 8
|
||||
|
||||
function getTooltipStyle(
|
||||
triggerRect: DOMRect,
|
||||
position: TooltipPosition
|
||||
): CSSProperties {
|
||||
if (position === 'top') {
|
||||
return {
|
||||
bottom: window.innerHeight - triggerRect.top + TOOLTIP_OFFSET,
|
||||
left: triggerRect.left + (triggerRect.width / 2)
|
||||
}
|
||||
}
|
||||
|
||||
if (position === 'right') {
|
||||
return {
|
||||
top: triggerRect.top + (triggerRect.height / 2),
|
||||
left: triggerRect.right + TOOLTIP_OFFSET
|
||||
}
|
||||
}
|
||||
|
||||
if (position === 'left') {
|
||||
return {
|
||||
top: triggerRect.top + (triggerRect.height / 2),
|
||||
right: window.innerWidth - triggerRect.left + TOOLTIP_OFFSET
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
top: triggerRect.bottom + TOOLTIP_OFFSET,
|
||||
left: triggerRect.left + (triggerRect.width / 2)
|
||||
}
|
||||
}
|
||||
|
||||
export function Tooltip({
|
||||
children,
|
||||
hint,
|
||||
message,
|
||||
position = 'bottom'
|
||||
}: TooltipProps) {
|
||||
const [rendered, setRendered] = useState(false)
|
||||
const [open, setOpen] = useState(false)
|
||||
const [tooltipStyle, setTooltipStyle] = useState<CSSProperties>()
|
||||
const triggerRef = useRef<HTMLSpanElement>(null)
|
||||
|
||||
function openTooltip(): void {
|
||||
if (triggerRef.current === null) {
|
||||
return
|
||||
}
|
||||
|
||||
setTooltipStyle(getTooltipStyle(
|
||||
triggerRef.current.getBoundingClientRect(),
|
||||
position
|
||||
))
|
||||
setRendered(true)
|
||||
setOpen(true)
|
||||
}
|
||||
|
||||
function closeTooltip(): void {
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
function closeTooltipAndBlurTrigger(): void {
|
||||
closeTooltip()
|
||||
|
||||
if (
|
||||
document.activeElement instanceof HTMLElement &&
|
||||
triggerRef.current?.contains(document.activeElement)
|
||||
) {
|
||||
document.activeElement.blur()
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || triggerRef.current === null) {
|
||||
return
|
||||
}
|
||||
|
||||
function updateTooltipPosition(): void {
|
||||
if (triggerRef.current === null) {
|
||||
return
|
||||
}
|
||||
|
||||
setTooltipStyle(getTooltipStyle(
|
||||
triggerRef.current.getBoundingClientRect(),
|
||||
position
|
||||
))
|
||||
}
|
||||
|
||||
updateTooltipPosition()
|
||||
window.addEventListener('resize', updateTooltipPosition)
|
||||
window.addEventListener('scroll', updateTooltipPosition, true)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', updateTooltipPosition)
|
||||
window.removeEventListener('scroll', updateTooltipPosition, true)
|
||||
}
|
||||
}, [open, position])
|
||||
|
||||
return (
|
||||
<>
|
||||
<span
|
||||
ref={triggerRef}
|
||||
className="tooltip"
|
||||
onMouseEnter={openTooltip}
|
||||
onMouseLeave={closeTooltip}
|
||||
onFocus={openTooltip}
|
||||
onBlur={closeTooltip}
|
||||
onClick={closeTooltipAndBlurTrigger}
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
{rendered && createPortal(
|
||||
<span
|
||||
className={clsx(
|
||||
'tooltip-content',
|
||||
`tooltip-content-${position}`,
|
||||
{ 'tooltip-content-open': open }
|
||||
)}
|
||||
role="tooltip"
|
||||
style={tooltipStyle}
|
||||
onTransitionEnd={(event) => {
|
||||
if (event.propertyName === 'opacity' && !open) {
|
||||
setRendered(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="tooltip-message">{message}</span>
|
||||
{hint !== undefined && (
|
||||
<span className="tooltip-hint">{hint}</span>
|
||||
)}
|
||||
</span>,
|
||||
document.body
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './vibes'
|
||||
@@ -0,0 +1,98 @@
|
||||
.vibes
|
||||
position: fixed
|
||||
inset: 0
|
||||
z-index: 0
|
||||
overflow: hidden
|
||||
// background: radial-gradient(circle at 58% 56%, rgba(28, 117, 219, .1), transparent 23%), #000000
|
||||
pointer-events: none
|
||||
|
||||
/* &::before,
|
||||
&::after
|
||||
position: absolute
|
||||
inset: 0
|
||||
content: ''
|
||||
|
||||
&::before
|
||||
background: radial-gradient(circle at 28% 72%, rgba(255, 246, 152, .2), transparent 18%), radial-gradient(circle at 78% 72%, rgba(28, 117, 219, .18), transparent 24%)
|
||||
opacity: .7
|
||||
|
||||
&::after
|
||||
background: linear-gradient(90deg, rgba(0, 0, 0, .08), rgba(0, 0, 0, .8) 38%, rgba(0, 0, 0, .2) 64%, rgba(0, 0, 0, .82)), linear-gradient(0deg, rgba(0, 0, 0, .75), transparent 28%, rgba(0, 0, 0, .8))
|
||||
|
||||
.mark
|
||||
position: absolute
|
||||
top: 11%
|
||||
left: 48%
|
||||
width: 380px
|
||||
height: 380px
|
||||
border: 18px solid rgba(245, 245, 247, .04)
|
||||
border-radius: 44%
|
||||
transform: translateX(-50%)
|
||||
|
||||
.desk
|
||||
position: absolute
|
||||
bottom: 0
|
||||
left: var(--sidebar-width)
|
||||
width: calc(100% - var(--sidebar-width))
|
||||
height: 34%
|
||||
background: linear-gradient(180deg, transparent, rgba(92, 45, 18, .2) 45%, rgba(0, 0, 0, .9))
|
||||
|
||||
.lamp
|
||||
position: absolute
|
||||
bottom: 34%
|
||||
left: 4%
|
||||
width: 170px
|
||||
height: 130px
|
||||
border-radius: 60% 60% 18% 18%
|
||||
background: radial-gradient(circle at 50% 70%, rgba(255, 246, 152, .85), rgba(255, 246, 152, .16) 22%, transparent 45%), linear-gradient(135deg, rgba(245, 245, 247, .18), rgba(21, 21, 21, .9))
|
||||
filter: blur(.2px)
|
||||
transform: rotate(-18deg)
|
||||
|
||||
.plant
|
||||
position: absolute
|
||||
bottom: 16%
|
||||
left: 6%
|
||||
width: 170px
|
||||
height: 160px
|
||||
border-radius: 48% 48% 12px 12px
|
||||
background: radial-gradient(ellipse at 52% 18%, rgba(120, 173, 45, .72), transparent 35%), radial-gradient(ellipse at 30% 44%, rgba(120, 173, 45, .7), transparent 28%), linear-gradient(180deg, transparent 45%, rgba(115, 58, 21, .9) 46%, rgba(58, 24, 10, .9) 75%, transparent 76%)
|
||||
|
||||
.keyboard
|
||||
position: absolute
|
||||
right: 26%
|
||||
bottom: 0
|
||||
width: 720px
|
||||
height: 120px
|
||||
border-radius: 18px 18px 0 0
|
||||
background: repeating-linear-gradient(90deg, rgba(245, 245, 247, .05) 0 32px, transparent 32px 42px), linear-gradient(180deg, rgba(167, 179, 200, .08), transparent)
|
||||
opacity: .6
|
||||
|
||||
.window
|
||||
position: absolute
|
||||
top: 22%
|
||||
right: 3%
|
||||
width: 360px
|
||||
height: 300px
|
||||
border: 18px solid rgba(78, 91, 116, .24)
|
||||
background: radial-gradient(circle at 82% 20%, rgba(245, 245, 247, .75), rgba(245, 245, 247, .18) 4%, transparent 8%), linear-gradient(135deg, rgba(28, 117, 219, .22), rgba(0, 0, 0, .65))
|
||||
box-shadow: 0 0 80px rgba(28, 117, 219, .08)
|
||||
|
||||
.mug
|
||||
position: absolute
|
||||
right: 7%
|
||||
bottom: 14%
|
||||
width: 170px
|
||||
height: 150px
|
||||
border-radius: 0 0 36px 36px
|
||||
background: linear-gradient(90deg, rgba(245, 245, 247, .04), rgba(21, 21, 21, .86))
|
||||
box-shadow: inset 0 16px 0 rgba(245, 245, 247, .08)
|
||||
|
||||
.glow
|
||||
position: absolute
|
||||
right: 21%
|
||||
bottom: 2%
|
||||
width: 720px
|
||||
height: 180px
|
||||
background: var(--gradient-vibe-a)
|
||||
filter: blur(90px)
|
||||
opacity: .4*/
|
||||
@@ -0,0 +1,5 @@
|
||||
import './vibes.sass'
|
||||
|
||||
export function Vibes() {
|
||||
return <div className="vibes" aria-hidden="true" />
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
{
|
||||
"activeSessionId": "2c5238f6-6828-413c-b784-07d71dafd2aa",
|
||||
"sessions": [
|
||||
{
|
||||
"id": "2c5238f6-6828-413c-b784-07d71dafd2aa",
|
||||
"title": "Leon 2.0 web app",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": true,
|
||||
"createdAt": 1780736400000,
|
||||
"updatedAt": 1780740000000,
|
||||
"lastMessageAt": 1780740000000,
|
||||
"messageCount": 18,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "f4954e94-a885-466a-935e-843632521803",
|
||||
"title": "Local model setup and a long title",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": true,
|
||||
"createdAt": 1780648200000,
|
||||
"updatedAt": 1780652400000,
|
||||
"lastMessageAt": 1780652400000,
|
||||
"messageCount": 12,
|
||||
"modelTarget": "openai:gpt-5"
|
||||
},
|
||||
{
|
||||
"id": "3e75529a-61e9-4d7c-a8d8-2f7f97e20e1c",
|
||||
"title": "Home automation workflow",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1780563600000,
|
||||
"updatedAt": 1780567800000,
|
||||
"lastMessageAt": 1780567800000,
|
||||
"messageCount": 9,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "bd1d0321-9510-4f21-9f1c-bac97d078d92",
|
||||
"title": "Research notes summary",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1780479000000,
|
||||
"updatedAt": 1780483500000,
|
||||
"lastMessageAt": 1780483500000,
|
||||
"messageCount": 15,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "80867bd0-64d2-4051-8fec-9006381019e5",
|
||||
"title": "Plugin ideas",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1780392600000,
|
||||
"updatedAt": 1780398000000,
|
||||
"lastMessageAt": 1780398000000,
|
||||
"messageCount": 7,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "fdbb4695-b8b9-4688-b3a3-ec66f4177115",
|
||||
"title": "Calendar cleanup",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1780306200000,
|
||||
"updatedAt": 1780311600000,
|
||||
"lastMessageAt": 1780311600000,
|
||||
"messageCount": 6,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "59ab5f57-14a1-48c7-9e62-fc5b56c8327c",
|
||||
"title": "Memory design review",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": true,
|
||||
"createdAt": 1780219800000,
|
||||
"updatedAt": 1780225200000,
|
||||
"lastMessageAt": 1780225200000,
|
||||
"messageCount": 21,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "cab0a30d-a1b8-4e70-a179-ff77909166f1",
|
||||
"title": "Agent traces interface",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1780133400000,
|
||||
"updatedAt": 1780138800000,
|
||||
"lastMessageAt": 1780138800000,
|
||||
"messageCount": 10,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "5afd0d14-0132-48f4-828d-8e2335c1f835",
|
||||
"title": "Documentation refresh",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1780047000000,
|
||||
"updatedAt": 1780052400000,
|
||||
"lastMessageAt": 1780052400000,
|
||||
"messageCount": 8,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "4971d150-cdcc-4740-b49d-eccf65e24a23",
|
||||
"title": "Command shortcuts",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779960600000,
|
||||
"updatedAt": 1779966000000,
|
||||
"lastMessageAt": 1779966000000,
|
||||
"messageCount": 5,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "1e1e87c6-39b1-40d6-a81f-83161badf65d",
|
||||
"title": "Tool output rendering",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779874200000,
|
||||
"updatedAt": 1779879600000,
|
||||
"lastMessageAt": 1779879600000,
|
||||
"messageCount": 13,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "0fa9eef4-bd0f-488c-8d11-d790606cca21",
|
||||
"title": "Settings modal structure",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779787800000,
|
||||
"updatedAt": 1779793200000,
|
||||
"lastMessageAt": 1779793200000,
|
||||
"messageCount": 11,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "a0f50ed2-afd7-425c-b981-59adc0dc08e6",
|
||||
"title": "Search dialog states",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779701400000,
|
||||
"updatedAt": 1779706800000,
|
||||
"lastMessageAt": 1779706800000,
|
||||
"messageCount": 14,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "81f66744-b020-4c21-92a1-8df9946492b9",
|
||||
"title": "Sidebar collapse details",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779615000000,
|
||||
"updatedAt": 1779620400000,
|
||||
"lastMessageAt": 1779620400000,
|
||||
"messageCount": 19,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "f78e3de8-84ef-480a-830d-9eb27f6685bd",
|
||||
"title": "Theme color tokens",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": true,
|
||||
"createdAt": 1779528600000,
|
||||
"updatedAt": 1779534000000,
|
||||
"lastMessageAt": 1779534000000,
|
||||
"messageCount": 16,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "df776fd4-f2de-4db4-9d36-75a3da0299f7",
|
||||
"title": "Tooltip layering fix",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779442200000,
|
||||
"updatedAt": 1779447600000,
|
||||
"lastMessageAt": 1779447600000,
|
||||
"messageCount": 8,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "a9c5483f-1430-49d1-a32a-2816f7c341bd",
|
||||
"title": "Keyboard flow",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779355800000,
|
||||
"updatedAt": 1779361200000,
|
||||
"lastMessageAt": 1779361200000,
|
||||
"messageCount": 6,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "7283436c-8574-4037-94e4-f639ca68ed32",
|
||||
"title": "Server state caching",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779269400000,
|
||||
"updatedAt": 1779274800000,
|
||||
"lastMessageAt": 1779274800000,
|
||||
"messageCount": 12,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "69e875e3-6442-4fb5-ad04-a1358fb1510a",
|
||||
"title": "Voice feedback controls",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779183000000,
|
||||
"updatedAt": 1779188400000,
|
||||
"lastMessageAt": 1779188400000,
|
||||
"messageCount": 9,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "20174d50-9429-4cee-8464-bc999ccf6d04",
|
||||
"title": "Agent timeline layout",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779096600000,
|
||||
"updatedAt": 1779102000000,
|
||||
"lastMessageAt": 1779102000000,
|
||||
"messageCount": 17,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "cff6a6ff-2335-4553-b8c4-fa4db2d7af60",
|
||||
"title": "Widget surface",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1779010200000,
|
||||
"updatedAt": 1779015600000,
|
||||
"lastMessageAt": 1779015600000,
|
||||
"messageCount": 10,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "73af64cb-eeef-4c31-ab5f-4c21c325b2d3",
|
||||
"title": "Aurora migration notes",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1778923800000,
|
||||
"updatedAt": 1778929200000,
|
||||
"lastMessageAt": 1778929200000,
|
||||
"messageCount": 20,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "239f7f12-886f-4915-a116-986c8c21a268",
|
||||
"title": "Command parser edge cases",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1778837400000,
|
||||
"updatedAt": 1778842800000,
|
||||
"lastMessageAt": 1778842800000,
|
||||
"messageCount": 7,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "d63d9b86-3587-414f-9e2d-33b61b18865d",
|
||||
"title": "Run action payloads",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1778751000000,
|
||||
"updatedAt": 1778756400000,
|
||||
"lastMessageAt": 1778756400000,
|
||||
"messageCount": 15,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "33267797-a7b1-4555-b2b9-b0fbb4b37786",
|
||||
"title": "Model routing",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": true,
|
||||
"createdAt": 1778664600000,
|
||||
"updatedAt": 1778670000000,
|
||||
"lastMessageAt": 1778670000000,
|
||||
"messageCount": 18,
|
||||
"modelTarget": "openrouter:anthropic/claude-sonnet-4"
|
||||
},
|
||||
{
|
||||
"id": "ec7f1a0f-25d8-4ecb-9fe2-b78c115b1550",
|
||||
"title": "Feed virtualization",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1778578200000,
|
||||
"updatedAt": 1778583600000,
|
||||
"lastMessageAt": 1778583600000,
|
||||
"messageCount": 11,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "2c8d5920-018f-4697-ab74-33a02539b826",
|
||||
"title": "Query input polish",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1778491800000,
|
||||
"updatedAt": 1778497200000,
|
||||
"lastMessageAt": 1778497200000,
|
||||
"messageCount": 13,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "1c413db5-705f-479b-86d4-36dd4ac4f226",
|
||||
"title": "Dialog accessibility",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1778405400000,
|
||||
"updatedAt": 1778410800000,
|
||||
"lastMessageAt": 1778410800000,
|
||||
"messageCount": 9,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "e986679e-2fa4-4fc2-b281-92d038bae7b8",
|
||||
"title": "Profile memory sync",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1778319000000,
|
||||
"updatedAt": 1778324400000,
|
||||
"lastMessageAt": 1778324400000,
|
||||
"messageCount": 16,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "977fab1b-7ef8-4d71-becb-56eb13dd64d1",
|
||||
"title": "Log viewer",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1778232600000,
|
||||
"updatedAt": 1778238000000,
|
||||
"lastMessageAt": 1778238000000,
|
||||
"messageCount": 5,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "88cf2ef6-96a0-4ed9-8802-713c33912d22",
|
||||
"title": "Widget fetching",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1778146200000,
|
||||
"updatedAt": 1778151600000,
|
||||
"lastMessageAt": 1778151600000,
|
||||
"messageCount": 14,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "3f0e56c2-91c6-4f35-ba88-3931767a1d87",
|
||||
"title": "Title generation",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1778059800000,
|
||||
"updatedAt": 1778065200000,
|
||||
"lastMessageAt": 1778065200000,
|
||||
"messageCount": 12,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "b99f9982-62c3-43cb-93ec-4358fab7550d",
|
||||
"title": "Provider error states",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1777973400000,
|
||||
"updatedAt": 1777978800000,
|
||||
"lastMessageAt": 1777978800000,
|
||||
"messageCount": 6,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "52443ebc-2344-47b3-a177-1ebc29b7b117",
|
||||
"title": "Command result cards",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1777887000000,
|
||||
"updatedAt": 1777892400000,
|
||||
"lastMessageAt": 1777892400000,
|
||||
"messageCount": 10,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "22c493f9-3ea5-4fd3-a729-054e300d76cf",
|
||||
"title": "Navigation states",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1777800600000,
|
||||
"updatedAt": 1777806000000,
|
||||
"lastMessageAt": 1777806000000,
|
||||
"messageCount": 8,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "19d7f6ed-670f-4b1d-8740-be1fc672afa6",
|
||||
"title": "Quick actions",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1777714200000,
|
||||
"updatedAt": 1777719600000,
|
||||
"lastMessageAt": 1777719600000,
|
||||
"messageCount": 7,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "6b6414d8-ff7b-423e-ac43-5bdb23268892",
|
||||
"title": "Desktop shell prep",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1777627800000,
|
||||
"updatedAt": 1777633200000,
|
||||
"lastMessageAt": 1777633200000,
|
||||
"messageCount": 11,
|
||||
"modelTarget": null
|
||||
},
|
||||
{
|
||||
"id": "8df75bfd-c5ad-450e-82ca-561ce54a68e6",
|
||||
"title": "Developer preview release",
|
||||
"isTitleGenerated": true,
|
||||
"isPinned": false,
|
||||
"createdAt": 1777541400000,
|
||||
"updatedAt": 1777546800000,
|
||||
"lastMessageAt": 1777546800000,
|
||||
"messageCount": 22,
|
||||
"modelTarget": null
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import sessionsData from './sessions.json'
|
||||
|
||||
export interface ConversationSession {
|
||||
id: string
|
||||
title: string
|
||||
isTitleGenerated: boolean
|
||||
isPinned: boolean
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
lastMessageAt: number | null
|
||||
messageCount: number
|
||||
modelTarget: string | null
|
||||
}
|
||||
|
||||
export interface ConversationSessionIndex {
|
||||
activeSessionId: string
|
||||
sessions: ConversationSession[]
|
||||
}
|
||||
|
||||
export const sessionIndex = sessionsData satisfies ConversationSessionIndex
|
||||
@@ -0,0 +1,25 @@
|
||||
.app-layout
|
||||
min-height: 100vh
|
||||
background-color: var(--color-background)
|
||||
|
||||
> main
|
||||
position: relative
|
||||
z-index: 2
|
||||
display: grid
|
||||
justify-items: center
|
||||
height: 100vh
|
||||
min-height: 100vh
|
||||
margin-left: var(--sidebar-width)
|
||||
overflow: auto
|
||||
scrollbar-width: none
|
||||
|
||||
&::-webkit-scrollbar
|
||||
display: none
|
||||
|
||||
@media (max-width: 960px)
|
||||
.app-layout
|
||||
.sidebar
|
||||
transform: translateX(calc(var(--sidebar-width) * -1))
|
||||
|
||||
> main
|
||||
margin-left: 0
|
||||
@@ -0,0 +1,17 @@
|
||||
import { QueryInput } from '../../components/query-input'
|
||||
import { Vibes } from '../../components/vibes'
|
||||
import { Sidebar } from '../sidebar'
|
||||
|
||||
import './app-layout.sass'
|
||||
|
||||
export function AppLayout() {
|
||||
return (
|
||||
<div className="app-layout">
|
||||
<Vibes />
|
||||
<Sidebar />
|
||||
<main>
|
||||
<QueryInput />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './app-layout'
|
||||
@@ -0,0 +1 @@
|
||||
export * from './sidebar'
|
||||
@@ -0,0 +1 @@
|
||||
export * from './logo'
|
||||
@@ -0,0 +1,7 @@
|
||||
.logo
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
text-decoration: none
|
||||
|
||||
img
|
||||
display: block
|
||||
@@ -0,0 +1,25 @@
|
||||
import { Link } from '@tanstack/react-router'
|
||||
|
||||
import './logo.sass'
|
||||
|
||||
interface LogoProps {
|
||||
src: string
|
||||
width: number
|
||||
height: number
|
||||
alt?: string
|
||||
to?: '/'
|
||||
}
|
||||
|
||||
export function Logo({
|
||||
src,
|
||||
width,
|
||||
height,
|
||||
alt = 'Leon',
|
||||
to = '/'
|
||||
}: LogoProps) {
|
||||
return (
|
||||
<Link className="logo" to={to} aria-label="Leon home">
|
||||
<img src={src} width={width} height={height} alt={alt} />
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './menu-item'
|
||||
@@ -0,0 +1,140 @@
|
||||
.menu-item
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: var(--space-sm)
|
||||
box-sizing: border-box
|
||||
width: 100%
|
||||
min-height: 40px
|
||||
padding: var(--space-sm)
|
||||
border-radius: var(--border-radius-md)
|
||||
color: var(--color-text)
|
||||
text-align: left
|
||||
text-decoration: none
|
||||
transition: color var(--transition-duration-main) var(--transition-timing-main), background-color var(--transition-duration-main) var(--transition-timing-main), gap var(--transition-duration-sidebar) var(--transition-timing-sidebar), padding var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.menu-item-icon
|
||||
flex: 0 0 auto
|
||||
color: var(--color-text)
|
||||
font-size: var(--font-size-xl)
|
||||
transform-origin: center
|
||||
transition: transform var(--transition-duration-main) var(--transition-timing-main), color var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.menu-item-label
|
||||
flex: 1 1 auto
|
||||
min-width: 0
|
||||
overflow: hidden
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
transition: opacity var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.menu-item-shortcut
|
||||
display: none
|
||||
align-items: center
|
||||
gap: var(--space-xs)
|
||||
transition: opacity var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.menu-item-shortcut-key
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
min-width: 20px
|
||||
min-height: 20px
|
||||
padding: 0 2px
|
||||
border-radius: var(--border-radius-sm)
|
||||
background-color: var(--color-surface-elevated)
|
||||
color: var(--color-text-secondary)
|
||||
font-size: var(--font-size-xs)
|
||||
filter: var(--shortcut-shadow-contrast)
|
||||
|
||||
.menu-item-shortcut-key-wide
|
||||
padding: 0 6px
|
||||
|
||||
.menu-item-shortcut-icon
|
||||
color: currentColor
|
||||
|
||||
&:hover:not(:disabled):not([aria-disabled='true'])
|
||||
background-color: var(--color-surface-hover)
|
||||
color: var(--color-text)
|
||||
|
||||
.menu-item-shortcut
|
||||
display: inline-flex
|
||||
|
||||
&.menu-item-active
|
||||
background-color: var(--color-surface-hover)
|
||||
color: var(--color-text)
|
||||
|
||||
&:active:not(:disabled):not([aria-disabled='true'])
|
||||
background-color: var(--color-surface-active)
|
||||
color: var(--color-text)
|
||||
|
||||
&.menu-item-animation-write:hover:not(:disabled):not([aria-disabled='true']) .menu-item-icon
|
||||
transform: rotate(-8deg) translate(1px, -1px)
|
||||
|
||||
&.menu-item-animation-search:hover:not(:disabled):not([aria-disabled='true']) .menu-item-icon
|
||||
transform: scale(1.1)
|
||||
|
||||
&.menu-item-animation-settings:hover:not(:disabled):not([aria-disabled='true']) .menu-item-icon
|
||||
transform: rotate(90deg)
|
||||
|
||||
&.menu-item-animation-download:hover:not(:disabled):not([aria-disabled='true']) .menu-item-icon
|
||||
transform: translate(0, -2px)
|
||||
|
||||
&.menu-item-animation-book:hover:not(:disabled):not([aria-disabled='true']) .menu-item-icon
|
||||
transform: perspective(120px) rotateY(18deg) translateX(1px)
|
||||
|
||||
&:hover:active:not(:disabled):not([aria-disabled='true']) .menu-item-icon
|
||||
animation: none
|
||||
transform: none
|
||||
|
||||
&:disabled
|
||||
color: var(--color-text-disabled)
|
||||
cursor: not-allowed
|
||||
|
||||
.menu-item-icon
|
||||
color: var(--color-text-disabled)
|
||||
|
||||
&[aria-disabled='true']
|
||||
color: var(--color-text-disabled)
|
||||
cursor: not-allowed
|
||||
|
||||
.menu-item-icon
|
||||
color: var(--color-text-disabled)
|
||||
|
||||
&.menu-item-collapsed
|
||||
.menu-item-label,
|
||||
.menu-item-shortcut,
|
||||
.badge
|
||||
opacity: 0
|
||||
display: none
|
||||
|
||||
&:hover:not(:disabled):not([aria-disabled='true']) .menu-item-shortcut
|
||||
display: none
|
||||
|
||||
.sidebar-closing .menu-item
|
||||
.menu-item-label,
|
||||
.menu-item-shortcut,
|
||||
.badge
|
||||
opacity: 0
|
||||
|
||||
.menu-item-tooltip-shortcut
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
gap: var(--space-xs)
|
||||
|
||||
.menu-item-tooltip-shortcut-separator
|
||||
color: var(--color-text-secondary)
|
||||
|
||||
@media (prefers-reduced-motion: reduce)
|
||||
.menu-item .menu-item-icon
|
||||
transition: none
|
||||
|
||||
.menu-item,
|
||||
.menu-item .menu-item-label
|
||||
transition: none
|
||||
|
||||
.menu-item:hover:not(:disabled):not([aria-disabled='true']) .menu-item-icon,
|
||||
.menu-item:hover:active:not(:disabled):not([aria-disabled='true']) .menu-item-icon
|
||||
animation: none
|
||||
transform: none
|
||||
@@ -0,0 +1,191 @@
|
||||
import type { ComponentType, MouseEvent , ReactNode } from 'react'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import { Tooltip } from '../../../components/tooltip'
|
||||
|
||||
import './menu-item.sass'
|
||||
|
||||
type AppRouteTo = '/'
|
||||
type MenuItemIconAnimation = 'write' | 'search' | 'settings' | 'download' | 'book'
|
||||
type MenuItemShortcutKey = string | {
|
||||
iconName: string
|
||||
label: string
|
||||
}
|
||||
|
||||
function renderShortcutHint(
|
||||
shortcutKeys: MenuItemShortcutKey[] | undefined
|
||||
): ReactNode {
|
||||
if (shortcutKeys === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
return shortcutKeys.map((shortcutKey, shortcutKeyIndex) => (
|
||||
<span className="menu-item-tooltip-shortcut" key={
|
||||
typeof shortcutKey === 'string'
|
||||
? `${shortcutKey}-${shortcutKeyIndex}`
|
||||
: shortcutKey.label
|
||||
}>
|
||||
{shortcutKeyIndex > 0 && (
|
||||
<span className="menu-item-tooltip-shortcut-separator">
|
||||
+
|
||||
</span>
|
||||
)}
|
||||
{typeof shortcutKey === 'string' ? (
|
||||
<span>{shortcutKey}</span>
|
||||
) : (
|
||||
<i
|
||||
className={`tooltip-hint-icon ri-${shortcutKey.iconName}-line`}
|
||||
aria-label={shortcutKey.label}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
|
||||
interface MenuItemBaseProps {
|
||||
iconName: string
|
||||
label: string
|
||||
iconAnimation: MenuItemIconAnimation
|
||||
badge?: ComponentType
|
||||
shortcutKeys?: MenuItemShortcutKey[]
|
||||
collapsed?: boolean
|
||||
disabled?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
type MenuItemProps = MenuItemBaseProps & (
|
||||
| {
|
||||
to?: undefined
|
||||
href?: undefined
|
||||
}
|
||||
| {
|
||||
to: AppRouteTo
|
||||
href?: never
|
||||
}
|
||||
| {
|
||||
to?: never
|
||||
href: string
|
||||
}
|
||||
)
|
||||
|
||||
export function MenuItem({
|
||||
iconName,
|
||||
label,
|
||||
iconAnimation,
|
||||
to,
|
||||
href,
|
||||
badge: Badge,
|
||||
shortcutKeys,
|
||||
collapsed = false,
|
||||
disabled = false,
|
||||
onClick
|
||||
}: MenuItemProps) {
|
||||
const className = clsx(
|
||||
'menu-item',
|
||||
`menu-item-animation-${iconAnimation}`,
|
||||
{ 'menu-item-collapsed': collapsed }
|
||||
)
|
||||
const content = (
|
||||
<>
|
||||
<i className={`menu-item-icon ri-${iconName}-line`} aria-hidden="true" />
|
||||
<span className="menu-item-label">{label}</span>
|
||||
{shortcutKeys !== undefined && (
|
||||
<span className="menu-item-shortcut" aria-hidden="true">
|
||||
{shortcutKeys.map((shortcutKey) => {
|
||||
if (typeof shortcutKey === 'string') {
|
||||
return (
|
||||
<span
|
||||
className={clsx('menu-item-shortcut-key', {
|
||||
'menu-item-shortcut-key-wide': shortcutKey.length > 3
|
||||
})}
|
||||
key={shortcutKey}
|
||||
>
|
||||
{shortcutKey}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="menu-item-shortcut-key" key={shortcutKey.label}>
|
||||
<i
|
||||
className={`menu-item-shortcut-icon ri-${shortcutKey.iconName}-line`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
{Badge && <Badge />}
|
||||
</>
|
||||
)
|
||||
|
||||
const item = (() => {
|
||||
if (to !== undefined) {
|
||||
return (
|
||||
<Link
|
||||
to={to}
|
||||
className={className}
|
||||
activeProps={{ className: 'menu-item-active' }}
|
||||
aria-disabled={disabled}
|
||||
onClick={(event: MouseEvent<HTMLAnchorElement>) => {
|
||||
if (disabled) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
onClick?.()
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
if (href !== undefined) {
|
||||
return (
|
||||
<a
|
||||
href={href}
|
||||
className={className}
|
||||
aria-disabled={disabled}
|
||||
onClick={(event: MouseEvent<HTMLAnchorElement>) => {
|
||||
if (disabled) {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
onClick?.()
|
||||
}}
|
||||
>
|
||||
{content}
|
||||
</a>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
>
|
||||
{content}
|
||||
</button>
|
||||
)
|
||||
})()
|
||||
|
||||
if (!collapsed) {
|
||||
return item
|
||||
}
|
||||
|
||||
return (
|
||||
<Tooltip
|
||||
message={label}
|
||||
hint={renderShortcutHint(shortcutKeys)}
|
||||
position="right"
|
||||
>
|
||||
{item}
|
||||
</Tooltip>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './menu'
|
||||
@@ -0,0 +1,12 @@
|
||||
.menu
|
||||
padding: 0 var(--space-sm)
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 1px
|
||||
transition: padding var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.tooltip
|
||||
display: flex
|
||||
|
||||
.menu-scrollable
|
||||
padding-bottom: var(--space-sm)
|
||||
@@ -0,0 +1,101 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { MenuItem } from '../menu-item'
|
||||
import { Badge } from '../../../components/badge'
|
||||
|
||||
import './menu.sass'
|
||||
|
||||
const DEFAULT_MODIFIER_KEY = 'Ctrl'
|
||||
const MACOS_PLATFORM_KEYWORD = 'mac'
|
||||
const NAVIGATOR_USER_AGENT_DATA_KEY = 'userAgentData'
|
||||
const MACOS_MODIFIER_KEY = {
|
||||
iconName: 'command',
|
||||
label: 'Command'
|
||||
}
|
||||
|
||||
interface NavigatorUserAgentData {
|
||||
platform: string
|
||||
}
|
||||
|
||||
interface NavigatorWithUserAgentData extends Navigator {
|
||||
userAgentData?: NavigatorUserAgentData
|
||||
}
|
||||
|
||||
function SoonBadge() {
|
||||
return (
|
||||
<Badge
|
||||
variant="secondary"
|
||||
label="Soon"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function NewBadge() {
|
||||
return (
|
||||
<Badge
|
||||
label="New"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function getPlatform(): string | undefined {
|
||||
if (NAVIGATOR_USER_AGENT_DATA_KEY in navigator) {
|
||||
return (navigator as NavigatorWithUserAgentData).userAgentData?.platform
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
interface MenuProps {
|
||||
collapsed?: boolean
|
||||
onSearchSessions?: () => void
|
||||
variant?: 'fixed' | 'scrollable'
|
||||
}
|
||||
|
||||
export function Menu({
|
||||
collapsed = false,
|
||||
onSearchSessions,
|
||||
variant = 'fixed'
|
||||
}: MenuProps) {
|
||||
const [modifierKey, setModifierKey] = useState<string | typeof MACOS_MODIFIER_KEY>(
|
||||
MACOS_MODIFIER_KEY
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
const platform = getPlatform()
|
||||
|
||||
if (
|
||||
platform !== undefined &&
|
||||
!platform.toLowerCase().includes(MACOS_PLATFORM_KEYWORD)
|
||||
) {
|
||||
setModifierKey(DEFAULT_MODIFIER_KEY)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const fixedItems = (
|
||||
<>
|
||||
<MenuItem iconName="edit-box" label="New session" iconAnimation="write" to="/" shortcutKeys={[modifierKey, 'Shift', 'O']} collapsed={collapsed} />
|
||||
<MenuItem
|
||||
iconName="search"
|
||||
label="Search sessions"
|
||||
iconAnimation="search"
|
||||
shortcutKeys={[modifierKey, 'K']}
|
||||
collapsed={collapsed}
|
||||
{...(onSearchSessions === undefined ? {} : { onClick: onSearchSessions })}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
const scrollableItems = (
|
||||
<>
|
||||
<MenuItem iconName="settings" label="Settings" iconAnimation="settings" shortcutKeys={['/']} collapsed={collapsed} />
|
||||
<MenuItem iconName="download-cloud-2" label="Update" iconAnimation="download" badge={NewBadge} collapsed={collapsed} />
|
||||
<MenuItem iconName="book-open" label="Docs" iconAnimation="book" disabled badge={SoonBadge} collapsed={collapsed} />
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<nav className={`menu menu-${variant}`} aria-label={variant === 'fixed' ? 'Main menu' : 'More menu'}>
|
||||
{variant === 'fixed' ? fixedItems : scrollableItems}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './session-list-item'
|
||||
@@ -0,0 +1,126 @@
|
||||
.session-list-item
|
||||
position: absolute
|
||||
top: 0
|
||||
left: 0
|
||||
width: 100%
|
||||
|
||||
.session-list-item-link
|
||||
display: flex
|
||||
align-items: center
|
||||
|
||||
.session-list-item-link
|
||||
box-sizing: border-box
|
||||
width: 100%
|
||||
height: 40px
|
||||
min-height: 40px
|
||||
padding: var(--space-sm)
|
||||
border-radius: var(--border-radius-md)
|
||||
transition: color var(--transition-duration-main) var(--transition-timing-main), background-color var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
.session-list-item-link
|
||||
color: var(--color-text)
|
||||
text-decoration: none
|
||||
|
||||
&:hover,
|
||||
&.session-list-item-active
|
||||
padding-right: 44px
|
||||
background-color: var(--color-surface-hover)
|
||||
color: var(--color-text)
|
||||
&:active
|
||||
background-color: var(--color-surface-active)
|
||||
|
||||
.session-list-item-actions
|
||||
position: absolute
|
||||
top: 50%
|
||||
right: var(--space-sm)
|
||||
display: inline-flex
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
transform: translateY(-50%)
|
||||
transition: opacity var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
.session-list-item-pinned-icon
|
||||
position: absolute
|
||||
top: 50%
|
||||
right: 18px
|
||||
width: 16px
|
||||
height: 16px
|
||||
color: var(--color-text-tertiary)
|
||||
font-size: var(--font-size-md)
|
||||
line-height: 16px
|
||||
transform: translateY(-50%)
|
||||
|
||||
&:hover,
|
||||
&:focus-within
|
||||
.session-list-item-link
|
||||
padding-right: 44px
|
||||
background-color: var(--color-surface-hover)
|
||||
|
||||
.session-list-item-actions
|
||||
opacity: 1
|
||||
pointer-events: auto
|
||||
|
||||
.session-list-item-pinned-icon
|
||||
right: 44px
|
||||
|
||||
.session-list-item-active + .session-list-item-actions
|
||||
opacity: 1
|
||||
pointer-events: auto
|
||||
|
||||
.session-list-item-active ~ .session-list-item-actions
|
||||
opacity: 1
|
||||
pointer-events: auto
|
||||
|
||||
.session-list-item-active ~ .session-list-item-pinned-icon
|
||||
right: 44px
|
||||
|
||||
.session-list-item-link:active
|
||||
background-color: var(--color-surface-active)
|
||||
|
||||
.session-list-item-input
|
||||
height: 100%
|
||||
|
||||
.input-field
|
||||
height: 100%
|
||||
padding: calc(var(--space-sm) - 1px)
|
||||
|
||||
&.session-list-item-pinned
|
||||
.session-list-item-link,
|
||||
.session-list-item-input .input-field
|
||||
padding-right: 44px
|
||||
|
||||
&:hover,
|
||||
&:focus-within
|
||||
.session-list-item-link,
|
||||
.session-list-item-input .input-field
|
||||
padding-right: 64px
|
||||
|
||||
.session-list-item-active
|
||||
padding-right: 64px
|
||||
|
||||
&.session-list-item-editing
|
||||
.session-list-item-actions,
|
||||
.session-list-item-pinned-icon
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
|
||||
.session-list-item-input .input-field
|
||||
padding-right: calc(var(--space-sm) - 1px)
|
||||
|
||||
.session-list-item-title
|
||||
min-width: 0
|
||||
overflow: hidden
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
@media (prefers-reduced-motion: reduce)
|
||||
.session-list-item .session-list-item-link
|
||||
transition: none
|
||||
|
||||
.session-list-item .session-list-item-actions
|
||||
transition: none
|
||||
|
||||
.session-list-item .session-list-item-pinned-icon
|
||||
transition: none
|
||||
@@ -0,0 +1,183 @@
|
||||
import {
|
||||
useEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type CSSProperties,
|
||||
type KeyboardEvent
|
||||
} from 'react'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import { Button } from '../../../components/button'
|
||||
import { Dialog } from '../../../components/dialog'
|
||||
import { Dropdown } from '../../../components/dropdown'
|
||||
import { Input } from '../../../components/input'
|
||||
|
||||
import './session-list-item.sass'
|
||||
|
||||
interface SessionListItemProps {
|
||||
id: string
|
||||
isPinned: boolean
|
||||
onDelete: (sessionId: string) => void
|
||||
onRename: (sessionId: string, title: string) => void
|
||||
title: string
|
||||
style?: CSSProperties
|
||||
}
|
||||
|
||||
export function SessionListItem({
|
||||
id,
|
||||
isPinned,
|
||||
onDelete,
|
||||
onRename,
|
||||
title,
|
||||
style
|
||||
}: SessionListItemProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
const [editing, setEditing] = useState(false)
|
||||
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
|
||||
const [draftTitle, setDraftTitle] = useState(title)
|
||||
const pinDropdownItem = isPinned
|
||||
? {
|
||||
iconName: 'unpin',
|
||||
label: 'Unpin session'
|
||||
}
|
||||
: {
|
||||
iconName: 'pushpin',
|
||||
label: 'Pin session'
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!editing) {
|
||||
setDraftTitle(title)
|
||||
return
|
||||
}
|
||||
|
||||
inputRef.current?.focus()
|
||||
inputRef.current?.select()
|
||||
}, [editing, title])
|
||||
|
||||
function startEditing(): void {
|
||||
setDraftTitle(title)
|
||||
setEditing(true)
|
||||
}
|
||||
|
||||
function cancelEditing(): void {
|
||||
setDraftTitle(title)
|
||||
setEditing(false)
|
||||
}
|
||||
|
||||
function commitEditing(): void {
|
||||
const titleToCommit = draftTitle.trim()
|
||||
|
||||
if (titleToCommit.length > 0) {
|
||||
onRename(id, titleToCommit)
|
||||
}
|
||||
|
||||
setEditing(false)
|
||||
}
|
||||
|
||||
function handleInputKeyDown(event: KeyboardEvent<HTMLInputElement>): void {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault()
|
||||
commitEditing()
|
||||
return
|
||||
}
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
cancelEditing()
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDelete(): void {
|
||||
onDelete(id)
|
||||
setDeleteDialogOpen(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<li
|
||||
className={clsx('session-list-item', {
|
||||
'session-list-item-editing': editing,
|
||||
'session-list-item-pinned': isPinned
|
||||
})}
|
||||
style={style}
|
||||
>
|
||||
{editing ? (
|
||||
<Input
|
||||
ariaLabel="Session title"
|
||||
className="session-list-item-input"
|
||||
fieldRef={(element) => {
|
||||
inputRef.current = element instanceof HTMLInputElement ? element : null
|
||||
}}
|
||||
value={draftTitle}
|
||||
onBlur={commitEditing}
|
||||
onChange={(event) => setDraftTitle(event.target.value)}
|
||||
onKeyDown={handleInputKeyDown}
|
||||
/>
|
||||
) : (
|
||||
<Link
|
||||
to="/session/$sessionId"
|
||||
params={{ sessionId: id }}
|
||||
className="session-list-item-link"
|
||||
activeProps={{
|
||||
className: clsx('session-list-item-link', 'session-list-item-active')
|
||||
}}
|
||||
onDoubleClick={(event) => {
|
||||
event.preventDefault()
|
||||
startEditing()
|
||||
}}
|
||||
>
|
||||
<span className="session-list-item-title">{title}</span>
|
||||
</Link>
|
||||
)}
|
||||
{isPinned && (
|
||||
<i
|
||||
className="session-list-item-pinned-icon ri-unpin-fill"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<div className="session-list-item-actions">
|
||||
<Dropdown
|
||||
items={[
|
||||
{
|
||||
iconName: 'edit',
|
||||
label: 'Rename',
|
||||
onSelect: startEditing
|
||||
},
|
||||
pinDropdownItem,
|
||||
{
|
||||
iconName: 'delete-bin',
|
||||
label: 'Delete',
|
||||
onSelect: () => setDeleteDialogOpen(true),
|
||||
variant: 'danger'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Button
|
||||
iconName="more-2"
|
||||
ariaLabel="Session options"
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<Dialog
|
||||
open={deleteDialogOpen}
|
||||
role="alertdialog"
|
||||
title="You sure?"
|
||||
description="This action cannot be undone. This will permanently delete the session."
|
||||
actions={[
|
||||
{
|
||||
label: 'Cancel',
|
||||
variant: 'secondary',
|
||||
onClick: () => setDeleteDialogOpen(false)
|
||||
},
|
||||
{
|
||||
label: 'Delete session',
|
||||
variant: 'danger',
|
||||
onClick: confirmDelete
|
||||
}
|
||||
]}
|
||||
onClose={() => setDeleteDialogOpen(false)}
|
||||
/>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './session-list'
|
||||
@@ -0,0 +1,37 @@
|
||||
.session-list
|
||||
padding: var(--space-sm)
|
||||
transition: opacity var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.session-list-title
|
||||
margin: 0 0 var(--space-sm)
|
||||
padding: 0 var(--space-sm)
|
||||
color: var(--color-text)
|
||||
font-size: var(--font-size-md)
|
||||
font-weight: var(--font-weight-semi-bold)
|
||||
line-height: var(--line-height-md)
|
||||
|
||||
.session-list-items
|
||||
position: relative
|
||||
margin: 0
|
||||
padding: 0
|
||||
list-style: none
|
||||
|
||||
.session-list-mask
|
||||
position: sticky
|
||||
bottom: 0
|
||||
display: block
|
||||
width: 100%
|
||||
height: 54px
|
||||
margin-top: -54px
|
||||
background-color: var(--color-sidebar-background)
|
||||
mask-image: linear-gradient(to bottom, transparent, #000 74%, #000)
|
||||
-webkit-mask-image: linear-gradient(to bottom, transparent, #000 74%, #000)
|
||||
pointer-events: none
|
||||
transition: background-color var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
@media (prefers-reduced-motion: reduce)
|
||||
.session-list
|
||||
transition: none
|
||||
|
||||
.session-list-mask
|
||||
transition: none
|
||||
@@ -0,0 +1,159 @@
|
||||
import {
|
||||
useLayoutEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type RefObject
|
||||
} from 'react'
|
||||
import { useVirtualizer } from '@tanstack/react-virtual'
|
||||
|
||||
import { sessionIndex, type ConversationSession } from '../../../data/sessions'
|
||||
import { useToast } from '../../../components/toast'
|
||||
import { SessionListItem } from '../session-list-item'
|
||||
|
||||
import './session-list.sass'
|
||||
|
||||
const SESSION_ITEM_ESTIMATED_HEIGHT = 41
|
||||
const SESSION_LIST_OVERSCAN = 5
|
||||
|
||||
interface SessionListProps {
|
||||
collapsed?: boolean
|
||||
scrollElementRef: RefObject<HTMLDivElement | null>
|
||||
}
|
||||
|
||||
function sortPinnedSessionsFirst(
|
||||
sessions: ConversationSession[]
|
||||
): ConversationSession[] {
|
||||
return [...sessions].sort((firstSession, secondSession) => {
|
||||
if (firstSession.isPinned === secondSession.isPinned) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return firstSession.isPinned ? -1 : 1
|
||||
})
|
||||
}
|
||||
|
||||
export function SessionList({
|
||||
collapsed = false,
|
||||
scrollElementRef
|
||||
}: SessionListProps) {
|
||||
const { showToast } = useToast()
|
||||
const virtualListRef = useRef<HTMLUListElement>(null)
|
||||
const [scrollMargin, setScrollMargin] = useState(0)
|
||||
const [sessions, setSessions] = useState<ConversationSession[]>(
|
||||
() => sessionIndex.sessions
|
||||
)
|
||||
const sortedSessions = useMemo(
|
||||
() => sortPinnedSessionsFirst(sessions),
|
||||
[sessions]
|
||||
)
|
||||
const rowVirtualizer = useVirtualizer({
|
||||
count: sortedSessions.length,
|
||||
getScrollElement: () => scrollElementRef.current,
|
||||
estimateSize: () => SESSION_ITEM_ESTIMATED_HEIGHT,
|
||||
overscan: SESSION_LIST_OVERSCAN,
|
||||
scrollMargin
|
||||
})
|
||||
|
||||
function renameSession(sessionId: string, nextTitle: string): void {
|
||||
const title = nextTitle.trim()
|
||||
const session = sessions.find((currentSession) =>
|
||||
currentSession.id === sessionId
|
||||
)
|
||||
|
||||
if (
|
||||
session === undefined ||
|
||||
title.length === 0 ||
|
||||
title === session.title
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
setSessions((currentSessions) =>
|
||||
currentSessions.map((currentSession) =>
|
||||
currentSession.id === sessionId
|
||||
? {
|
||||
...currentSession,
|
||||
title,
|
||||
updatedAt: Date.now()
|
||||
}
|
||||
: currentSession
|
||||
)
|
||||
)
|
||||
showToast({
|
||||
type: 'success',
|
||||
title: 'Session title edited',
|
||||
description: `I edited your session title to "${title}".`
|
||||
})
|
||||
}
|
||||
|
||||
function deleteSession(sessionId: string): void {
|
||||
setSessions((currentSessions) =>
|
||||
currentSessions.filter((currentSession) => currentSession.id !== sessionId)
|
||||
)
|
||||
}
|
||||
|
||||
useLayoutEffect(() => {
|
||||
const virtualList = virtualListRef.current
|
||||
|
||||
if (virtualList === null) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
// The sidebar scroll container starts above this list, so the virtual rows
|
||||
// need the list offset to keep their transforms local to the list element.
|
||||
function updateScrollMargin(): void {
|
||||
setScrollMargin(virtualList?.offsetTop ?? 0)
|
||||
}
|
||||
|
||||
updateScrollMargin()
|
||||
|
||||
const resizeObserver = new ResizeObserver(updateScrollMargin)
|
||||
resizeObserver.observe(virtualList)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (collapsed) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<nav className="session-list" aria-labelledby="session-list-title">
|
||||
<h2 className="session-list-title" id="session-list-title">
|
||||
Recents
|
||||
</h2>
|
||||
<ul
|
||||
className="session-list-items"
|
||||
ref={virtualListRef}
|
||||
style={{ height: `${rowVirtualizer.getTotalSize()}px` }}
|
||||
>
|
||||
{rowVirtualizer.getVirtualItems().map((virtualRow) => {
|
||||
const session = sortedSessions[virtualRow.index]
|
||||
|
||||
if (session === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<SessionListItem
|
||||
key={session.id}
|
||||
id={session.id}
|
||||
isPinned={session.isPinned}
|
||||
title={session.title}
|
||||
onDelete={deleteSession}
|
||||
onRename={renameSession}
|
||||
style={{
|
||||
height: `${virtualRow.size}px`,
|
||||
transform: `translateY(${virtualRow.start - rowVirtualizer.options.scrollMargin}px)`
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
<div className="session-list-mask" aria-hidden="true" />
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './session-search'
|
||||
@@ -0,0 +1,23 @@
|
||||
.session-search
|
||||
display: grid
|
||||
grid-template-columns: 28px 1fr auto
|
||||
align-items: center
|
||||
gap: var(--space-sm)
|
||||
width: 100%
|
||||
min-height: 46px
|
||||
padding: 0 var(--space-md)
|
||||
border-radius: var(--border-radius-md)
|
||||
background-color: var(--color-surface)
|
||||
color: var(--color-text-secondary)
|
||||
text-align: left
|
||||
|
||||
.session-search-icon
|
||||
color: var(--color-text)
|
||||
font-size: 26px
|
||||
|
||||
.session-search-label
|
||||
overflow: hidden
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
@@ -0,0 +1,5 @@
|
||||
import './session-search.sass'
|
||||
|
||||
export function SessionSearch() {
|
||||
return <button type="button" className="session-search" />
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from './sidebar-footer'
|
||||
@@ -0,0 +1,40 @@
|
||||
.sidebar-footer
|
||||
display: grid
|
||||
gap: var(--space-xl)
|
||||
margin: 0 calc(var(--space-lg) * -1) calc(var(--space-lg) * -1)
|
||||
border-top: 1px solid var(--color-border)
|
||||
|
||||
.sidebar-footer-link
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
justify-self: center
|
||||
gap: var(--space-sm)
|
||||
padding-top: var(--space-xl)
|
||||
color: var(--color-text-secondary)
|
||||
font-size: var(--font-size-md)
|
||||
line-height: var(--line-height-md)
|
||||
text-decoration: none
|
||||
|
||||
.sidebar-footer-link-icon
|
||||
color: var(--color-text)
|
||||
font-size: 24px
|
||||
|
||||
.sidebar-footer-status
|
||||
display: grid
|
||||
grid-template-columns: 1fr 72px
|
||||
min-height: 44px
|
||||
border-top: 1px solid var(--color-border)
|
||||
|
||||
.sidebar-footer-status-label
|
||||
align-self: center
|
||||
padding-left: var(--space-lg)
|
||||
color: var(--color-text-tertiary)
|
||||
font-size: var(--font-size-sm)
|
||||
line-height: var(--line-height-sm)
|
||||
|
||||
.sidebar-footer-status-button
|
||||
display: grid
|
||||
place-items: center
|
||||
border-left: 1px solid var(--color-border)
|
||||
color: var(--color-text)
|
||||
font-size: 22px
|
||||
@@ -0,0 +1,5 @@
|
||||
import './sidebar-footer.sass'
|
||||
|
||||
export function SidebarFooter() {
|
||||
return <footer className="sidebar-footer" />
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
.sidebar
|
||||
--sidebar-collapsed-logo-width: 32px
|
||||
--sidebar-footer-height: 120px
|
||||
--sidebar-open-button-width: 40px
|
||||
position: fixed
|
||||
inset: 0 auto 0 0
|
||||
z-index: 5
|
||||
display: flex
|
||||
flex-direction: column
|
||||
width: var(--sidebar-width)
|
||||
border-right: 1px solid var(--color-border)
|
||||
background-color: var(--color-sidebar-background)
|
||||
backdrop-filter: blur(18px)
|
||||
overflow: hidden
|
||||
transition: width var(--transition-duration-sidebar) var(--transition-timing-sidebar), background-color var(--transition-duration-sidebar) var(--transition-timing-sidebar), border-color var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
will-change: width
|
||||
|
||||
&.sidebar-collapsed
|
||||
width: var(--sidebar-collapsed-width)
|
||||
|
||||
.sidebar-header
|
||||
position: relative
|
||||
display: flex
|
||||
align-items: center
|
||||
justify-content: flex-end
|
||||
min-height: 72px
|
||||
box-sizing: border-box
|
||||
padding: var(--space-lg)
|
||||
transition: gap var(--transition-duration-sidebar) var(--transition-timing-sidebar), padding var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.sidebar-logo-slot
|
||||
position: absolute
|
||||
top: 50%
|
||||
left: var(--space-lg)
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
justify-content: flex-start
|
||||
overflow: hidden
|
||||
transform: translate3d(0, -50%, 0)
|
||||
width: 96px
|
||||
height: 36px
|
||||
transition: left var(--transition-duration-sidebar) var(--transition-timing-sidebar), width var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.sidebar-logo-open-button
|
||||
position: absolute
|
||||
inset: 0
|
||||
z-index: 1
|
||||
display: inline-flex
|
||||
align-items: center
|
||||
justify-content: center
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
transition: opacity var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.sidebar-controls
|
||||
display: inline-flex
|
||||
flex: 0 1 auto
|
||||
max-width: 132px
|
||||
opacity: 1
|
||||
overflow: hidden
|
||||
transform: translate3d(0, 0, 0)
|
||||
transition: max-width var(--transition-duration-sidebar) var(--transition-timing-sidebar), opacity var(--transition-duration-sidebar) var(--transition-timing-sidebar), transform var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.sidebar-primary
|
||||
display: grid
|
||||
gap: var(--space-sm)
|
||||
|
||||
.sidebar-scroll-area
|
||||
position: relative
|
||||
flex: 1 1 auto
|
||||
min-height: 0
|
||||
overflow-x: hidden
|
||||
overflow-y: auto
|
||||
scrollbar-color: var(--color-scrollbar-thumb-muted) transparent
|
||||
scrollbar-width: thin
|
||||
transition: box-shadow var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
&::-webkit-scrollbar
|
||||
width: 8px
|
||||
|
||||
&::-webkit-scrollbar-track
|
||||
background: transparent
|
||||
|
||||
&::-webkit-scrollbar-thumb
|
||||
border: 2px solid transparent
|
||||
border-radius: var(--border-radius-pill)
|
||||
background-color: var(--color-scrollbar-thumb-muted)
|
||||
background-clip: content-box
|
||||
transition: background-color var(--transition-duration-main) var(--transition-timing-main)
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover
|
||||
background-color: var(--color-scrollbar-thumb-hover)
|
||||
|
||||
&.sidebar-scroll-area-scrolled
|
||||
box-shadow: inset 0 1px 0 var(--color-border)
|
||||
|
||||
&:hover
|
||||
scrollbar-color: var(--color-scrollbar-thumb) transparent
|
||||
|
||||
&::-webkit-scrollbar-thumb
|
||||
background-color: var(--color-scrollbar-thumb)
|
||||
|
||||
.sidebar-collapsed-unfold-hit
|
||||
flex: 0 0 0
|
||||
min-height: 0
|
||||
width: 100%
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
transition: opacity var(--transition-duration-sidebar) var(--transition-timing-sidebar)
|
||||
|
||||
.sidebar-footer-slot
|
||||
flex: 0 0 var(--sidebar-footer-height)
|
||||
min-height: var(--sidebar-footer-height)
|
||||
|
||||
&.sidebar-content-collapsed
|
||||
.sidebar-scroll-area
|
||||
flex: 0 0 auto
|
||||
min-height: auto
|
||||
overflow: visible
|
||||
scrollbar-width: none
|
||||
|
||||
&::-webkit-scrollbar
|
||||
display: none
|
||||
|
||||
.sidebar-logo-slot:hover,
|
||||
.sidebar-logo-slot:focus-within
|
||||
.logo
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
|
||||
.sidebar-logo-open-button
|
||||
opacity: 1
|
||||
pointer-events: auto
|
||||
|
||||
.sidebar-collapsed-unfold-hit
|
||||
cursor: e-resize
|
||||
flex: 1 1 auto
|
||||
opacity: 1
|
||||
pointer-events: auto
|
||||
|
||||
&.sidebar-collapsed
|
||||
.sidebar-logo-slot
|
||||
left: calc((var(--sidebar-collapsed-width) - 36px) / 2)
|
||||
width: 40px
|
||||
|
||||
.sidebar-logo-open-button
|
||||
left: calc(((var(--sidebar-collapsed-width) - var(--sidebar-open-button-width)) / 2) - ((var(--sidebar-collapsed-width) - var(--sidebar-collapsed-logo-width)) / 2))
|
||||
right: auto
|
||||
width: var(--sidebar-open-button-width)
|
||||
|
||||
.sidebar-controls
|
||||
max-width: 0
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
transform: translate3d(10px, 0, 0)
|
||||
|
||||
&.sidebar-closing
|
||||
.sidebar-controls
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
|
||||
.sidebar-scroll-area
|
||||
scrollbar-width: none
|
||||
|
||||
&::-webkit-scrollbar
|
||||
display: none
|
||||
|
||||
.session-list
|
||||
opacity: 0
|
||||
pointer-events: none
|
||||
|
||||
@media (prefers-reduced-motion: reduce)
|
||||
.sidebar,
|
||||
.sidebar .sidebar-header,
|
||||
.sidebar .sidebar-logo-slot,
|
||||
.sidebar .sidebar-logo-open-button,
|
||||
.sidebar .sidebar-controls,
|
||||
.sidebar .sidebar-scroll-area,
|
||||
.sidebar .sidebar-collapsed-unfold-hit
|
||||
transition: none
|
||||
@@ -0,0 +1,231 @@
|
||||
import { useEffect, useRef, useState, type TransitionEvent } from 'react'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import './sidebar.sass'
|
||||
import { Button } from '../../components/button'
|
||||
import { Dialog } from '../../components/dialog'
|
||||
import { SearchSessionsDialog } from '../../components/search-sessions-dialog'
|
||||
import {
|
||||
getStoredSidebarExpanded,
|
||||
getStoredSoundsEnabled,
|
||||
getStoredTheme,
|
||||
saveSidebarExpanded,
|
||||
saveSoundsEnabled,
|
||||
saveTheme,
|
||||
type Theme
|
||||
} from '../../theme'
|
||||
|
||||
import { Logo } from './logo'
|
||||
import { Menu } from './menu'
|
||||
import { SessionList } from './session-list'
|
||||
|
||||
const DARK_THEME_LOGO_SRC = '/img/logo-for-dark-bg-text.svg'
|
||||
const LIGHT_THEME_LOGO_SRC = '/img/logo-for-light-bg-text.svg'
|
||||
const REDUCED_MOTION_MEDIA_QUERY = '(prefers-reduced-motion: reduce)'
|
||||
|
||||
function isEditableTarget(target: EventTarget | null): boolean {
|
||||
return (
|
||||
target instanceof HTMLInputElement ||
|
||||
target instanceof HTMLTextAreaElement ||
|
||||
target instanceof HTMLSelectElement ||
|
||||
(
|
||||
target instanceof HTMLElement &&
|
||||
target.isContentEditable
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
function shouldReduceMotion(): boolean {
|
||||
return window.matchMedia(REDUCED_MOTION_MEDIA_QUERY).matches
|
||||
}
|
||||
|
||||
export function Sidebar() {
|
||||
const sidebarScrollAreaRef = useRef<HTMLDivElement>(null)
|
||||
const [soundsEnabled, setSoundsEnabled] = useState(getStoredSoundsEnabled)
|
||||
const [theme, setTheme] = useState<Theme>(getStoredTheme)
|
||||
const [sidebarExpanded, setSidebarExpanded] = useState(getStoredSidebarExpanded)
|
||||
const [sidebarContentCollapsed, setSidebarContentCollapsed] = useState(
|
||||
() => !getStoredSidebarExpanded()
|
||||
)
|
||||
const [sidebarClosing, setSidebarClosing] = useState(false)
|
||||
const [sidebarScrollAreaScrolled, setSidebarScrollAreaScrolled] =
|
||||
useState(false)
|
||||
const [searchSessionsDialogOpen, setSearchSessionsDialogOpen] =
|
||||
useState(false)
|
||||
|
||||
function toggleTheme(): void {
|
||||
const nextTheme = theme === 'dark' ? 'light' : 'dark'
|
||||
|
||||
setTheme(nextTheme)
|
||||
saveTheme(nextTheme)
|
||||
}
|
||||
|
||||
function updateSoundsEnabled(nextSoundsEnabled: boolean): void {
|
||||
setSoundsEnabled(nextSoundsEnabled)
|
||||
saveSoundsEnabled(nextSoundsEnabled)
|
||||
}
|
||||
|
||||
function updateSidebarExpanded(nextSidebarExpanded: boolean): void {
|
||||
if (nextSidebarExpanded) {
|
||||
setSidebarClosing(false)
|
||||
setSidebarContentCollapsed(false)
|
||||
setSidebarExpanded(true)
|
||||
saveSidebarExpanded(true)
|
||||
return
|
||||
}
|
||||
|
||||
setSidebarClosing(true)
|
||||
setSidebarExpanded(nextSidebarExpanded)
|
||||
saveSidebarExpanded(nextSidebarExpanded)
|
||||
|
||||
if (shouldReduceMotion()) {
|
||||
setSidebarContentCollapsed(true)
|
||||
setSidebarClosing(false)
|
||||
}
|
||||
}
|
||||
|
||||
function handleSidebarTransitionEnd(
|
||||
event: TransitionEvent<HTMLElement>
|
||||
): void {
|
||||
if (
|
||||
event.propertyName !== 'width' ||
|
||||
sidebarExpanded
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
setSidebarContentCollapsed(true)
|
||||
setSidebarClosing(false)
|
||||
}
|
||||
|
||||
function handleSidebarScroll(): void {
|
||||
const scrollArea = sidebarScrollAreaRef.current
|
||||
|
||||
if (scrollArea === null) {
|
||||
return
|
||||
}
|
||||
|
||||
setSidebarScrollAreaScrolled(scrollArea.scrollTop > 0)
|
||||
}
|
||||
|
||||
function openSearchSessionsDialog(): void {
|
||||
setSearchSessionsDialogOpen(true)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
function handleKeyDown(event: KeyboardEvent): void {
|
||||
if (
|
||||
isEditableTarget(event.target) ||
|
||||
event.altKey ||
|
||||
event.shiftKey ||
|
||||
event.key.toLowerCase() !== 'k' ||
|
||||
(!event.ctrlKey && !event.metaKey)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
openSearchSessionsDialog()
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown)
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const logoSrc = theme === 'dark' ? DARK_THEME_LOGO_SRC : LIGHT_THEME_LOGO_SRC
|
||||
|
||||
return (
|
||||
<aside className={clsx(
|
||||
'sidebar',
|
||||
sidebarExpanded ? 'sidebar-expanded' : 'sidebar-collapsed',
|
||||
{
|
||||
'sidebar-closing': sidebarClosing,
|
||||
'sidebar-content-collapsed': sidebarContentCollapsed
|
||||
}
|
||||
)}
|
||||
onTransitionEnd={handleSidebarTransitionEnd}>
|
||||
<header className="sidebar-header">
|
||||
<div className="sidebar-logo-slot">
|
||||
<Logo
|
||||
src={logoSrc}
|
||||
width={96}
|
||||
height={36}
|
||||
/>
|
||||
<div className="sidebar-logo-open-button">
|
||||
<Button
|
||||
iconName="sidebar-unfold"
|
||||
tooltipMessage="Open sidebar"
|
||||
tooltipPosition="right"
|
||||
onClick={() => updateSidebarExpanded(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="sidebar-controls">
|
||||
<Button
|
||||
iconName={soundsEnabled ? 'volume-up' : 'volume-mute'}
|
||||
tooltipMessage={soundsEnabled ? 'Mute sounds' : 'Unmute sounds'}
|
||||
onClick={() => updateSoundsEnabled(!soundsEnabled)}
|
||||
/>
|
||||
<Button
|
||||
iconName={theme === 'dark' ? 'moon' : 'sun'}
|
||||
tooltipMessage={theme === 'dark' ? 'Apply light theme' : 'Apply dark theme'}
|
||||
onClick={toggleTheme}
|
||||
/>
|
||||
<Button
|
||||
iconName={sidebarExpanded ? 'sidebar-fold' : 'sidebar-unfold'}
|
||||
tooltipMessage={sidebarExpanded ? 'Close sidebar' : 'Open sidebar'}
|
||||
onClick={() => updateSidebarExpanded(!sidebarExpanded)}
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
<Menu
|
||||
collapsed={sidebarContentCollapsed}
|
||||
variant="fixed"
|
||||
onSearchSessions={openSearchSessionsDialog}
|
||||
/>
|
||||
<div
|
||||
className={clsx('sidebar-scroll-area', {
|
||||
'sidebar-scroll-area-scrolled': sidebarScrollAreaScrolled
|
||||
})}
|
||||
ref={sidebarScrollAreaRef}
|
||||
onScroll={handleSidebarScroll}
|
||||
>
|
||||
<Menu collapsed={sidebarContentCollapsed} variant="scrollable" />
|
||||
<SessionList
|
||||
collapsed={sidebarContentCollapsed}
|
||||
scrollElementRef={sidebarScrollAreaRef}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
className="sidebar-collapsed-unfold-hit"
|
||||
aria-label="Open sidebar"
|
||||
onClick={() => updateSidebarExpanded(true)}
|
||||
/>
|
||||
<footer className="sidebar-footer-slot">
|
||||
|
||||
</footer>
|
||||
<Dialog
|
||||
hideHeader
|
||||
open={searchSessionsDialogOpen}
|
||||
title="Search sessions"
|
||||
size="large"
|
||||
actions={[
|
||||
{
|
||||
label: 'Close',
|
||||
variant: 'primary',
|
||||
onClick: () => setSearchSessionsDialogOpen(false)
|
||||
}
|
||||
]}
|
||||
onClose={() => setSearchSessionsDialogOpen(false)}
|
||||
>
|
||||
<SearchSessionsDialog
|
||||
onSessionSelect={() => setSearchSessionsDialogOpen(false)}
|
||||
/>
|
||||
</Dialog>
|
||||
</aside>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import '@fontsource/source-sans-pro/latin-400.css'
|
||||
import '@fontsource/source-sans-pro/latin-600.css'
|
||||
import '@fontsource/source-sans-pro/latin-700.css'
|
||||
import 'remixicon/fonts/remixicon.css'
|
||||
import './styles/main.sass'
|
||||
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { QueryClientProvider } from '@tanstack/react-query'
|
||||
import { RouterProvider } from '@tanstack/react-router'
|
||||
|
||||
import { ToastProvider } from './components/toast'
|
||||
import { queryClient } from './query-client'
|
||||
import { router } from './router'
|
||||
import { applyStoredTheme } from './theme'
|
||||
|
||||
applyStoredTheme()
|
||||
|
||||
const rootElement = document.querySelector('#root')
|
||||
|
||||
if (rootElement === null) {
|
||||
throw new Error('Root element not found.')
|
||||
}
|
||||
|
||||
createRoot(rootElement).render(
|
||||
<StrictMode>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<ToastProvider>
|
||||
<RouterProvider router={router} />
|
||||
</ToastProvider>
|
||||
</QueryClientProvider>
|
||||
</StrictMode>
|
||||
)
|
||||
@@ -0,0 +1,11 @@
|
||||
import { QueryClient } from '@tanstack/react-query'
|
||||
|
||||
const QUERY_STALE_TIME = 60_000
|
||||
|
||||
export const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: QUERY_STALE_TIME
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
createRootRoute,
|
||||
createRoute,
|
||||
createRouter,
|
||||
Outlet
|
||||
} from '@tanstack/react-router'
|
||||
|
||||
import { HomeRoute } from './routes/home-route'
|
||||
|
||||
const rootRoute = createRootRoute({
|
||||
component: () => <Outlet />
|
||||
})
|
||||
|
||||
const indexRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/',
|
||||
component: HomeRoute
|
||||
})
|
||||
|
||||
const sessionRoute = createRoute({
|
||||
getParentRoute: () => rootRoute,
|
||||
path: '/session/$sessionId',
|
||||
component: HomeRoute
|
||||
})
|
||||
|
||||
const routeTree = rootRoute.addChildren([indexRoute, sessionRoute])
|
||||
|
||||
export const router = createRouter({ routeTree })
|
||||
|
||||
declare module '@tanstack/react-router' {
|
||||
interface Register {
|
||||
router: typeof router
|
||||
}
|
||||
}
|
||||