Initial import: Music_Server, MusicFree, catalog-sync
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12.0001 22.0001C7.02956 22.0001 3.00012 17.9707 3.00012 13.0001C3.00012 8.02956 7.02956 4.00012 12.0001 4.00012C16.9707 4.00012 21.0001 8.02956 21.0001 13.0001C21.0001 17.9707 16.9707 22.0001 12.0001 22.0001ZM12.0001 20.0001C15.8661 20.0001 19.0001 16.8661 19.0001 13.0001C19.0001 9.13412 15.8661 6.00012 12.0001 6.00012C8.13412 6.00012 5.00012 9.13412 5.00012 13.0001C5.00012 16.8661 8.13412 20.0001 12.0001 20.0001ZM13.0001 13.0001H16.0001V15.0001H11.0001V8.00012H13.0001V13.0001ZM1.74707 6.2826L5.2826 2.74707L6.69682 4.16128L3.16128 7.69682L1.74707 6.2826ZM18.7176 2.74707L22.2532 6.2826L20.839 7.69682L17.3034 4.16128L18.7176 2.74707Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 744 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20ZM12 22C6.47715 22 2 17.5228 2 12C2 6.47715 6.47715 2 12 2C17.5228 2 22 6.47715 22 12C22 17.5228 17.5228 22 12 22ZM12 14C13.1046 14 14 13.1046 14 12C14 10.8954 13.1046 10 12 10C10.8954 10 10 10.8954 10 12C10 13.1046 10.8954 14 12 14ZM12 16C9.79086 16 8 14.2091 8 12C8 9.79086 9.79086 8 12 8C14.2091 8 16 9.79086 16 12C16 14.2091 14.2091 16 12 16Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 562 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5m6 4.125 2.25 2.25m0 0 2.25 2.25M12 13.875l2.25-2.25M12 13.875l-2.25 2.25M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 516 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 19.5 3 12m0 0 7.5-7.5M3 12h18" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 234 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 15.75 3 12m0 0 3.75-3.75M3 12h18" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 237 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992v-.001M2.985 19.644v-4.992m0 0h4.992m-4.993 0 3.181 3.183a8.25 8.25 0 0 0 13.803-3.7M4.031 9.865a8.25 8.25 0 0 1 13.803-3.7l3.181 3.182m0-4.991v4.99" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 366 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15M12 9l3 3m0 0-3 3m3-3H2.25" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 349 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5m-13.5-9L12 3m0 0 4.5 4.5M12 3v13.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 307 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 15 3 9m0 0 6-6M3 9h12a6 6 0 0 1 0 12h-3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 241 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7.5 21 3 16.5m0 0L7.5 12M3 16.5h13.5m0-13.5L21 7.5m0 0L16.5 12M21 7.5H7.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 273 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 243 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 3.75V16.5L12 14.25 7.5 16.5V3.75m9 0H18A2.25 2.25 0 0 1 20.25 6v12A2.25 2.25 0 0 1 18 20.25H6A2.25 2.25 0 0 1 3.75 18V6A2.25 2.25 0 0 1 6 3.75h1.5m9 0h-9" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 358 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.625 12a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H8.25m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0H12m4.125 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm0 0h-.375M21 12c0 4.556-4.03 8.25-9 8.25a9.764 9.764 0 0 1-2.555-.337A5.972 5.972 0 0 1 5.41 20.97a5.969 5.969 0 0 1-.474-.065 4.48 4.48 0 0 0 .978-2.025c.09-.457-.133-.901-.467-1.226C3.93 16.178 3 14.189 3 12c0-4.556 4.03-8.25 9-8.25s9 3.694 9 8.25Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 622 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 261 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm13.36-1.814a.75.75 0 1 0-1.22-.872l-3.236 4.53L9.53 12.22a.75.75 0 0 0-1.06 1.06l2.25 2.25a.75.75 0 0 0 1.14-.094l3.75-5.25Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 384 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 220 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375m16.5 0v3.75m-16.5-3.75v3.75m16.5 0v3.75C20.25 16.153 16.556 18 12 18s-8.25-1.847-8.25-4.125v-3.75m16.5 0c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 556 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 6v6h4.5m4.5 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 247 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9.75 16.5 12l-2.25 2.25m-4.5 0L7.5 12l2.25-2.25M6 20.25h12A2.25 2.25 0 0 0 20.25 18V6A2.25 2.25 0 0 0 18 3.75H6A2.25 2.25 0 0 0 3.75 6v12A2.25 2.25 0 0 0 6 20.25Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 368 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 0 1 1.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204.165.397.505.71.93.78l.893.15c.543.09.94.559.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.894.149c-.424.07-.764.383-.929.78-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 0 1-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107-.398.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 0 1-.12-1.45l.527-.737c.25-.35.272-.806.108-1.204-.165-.397-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 0 1 .12-1.45l.773-.773a1.125 1.125 0 0 1 1.45-.12l.737.527c.35.25.807.272 1.204.107.397-.165.71-.505.78-.929l.15-.894Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M11 5.07089C7.93431 5.5094 5.5094 7.93431 5.07089 11H7V13H5.07089C5.5094 16.0657 7.93431 18.4906 11 18.9291V17H13V18.9291C16.0657 18.4906 18.4906 16.0657 18.9291 13H17V11H18.9291C18.4906 7.93431 16.0657 5.5094 13 5.07089V7H11V5.07089ZM3.05493 11C3.51608 6.82838 6.82838 3.51608 11 3.05493V1H13V3.05493C17.1716 3.51608 20.4839 6.82838 20.9451 11H23V13H20.9451C20.4839 17.1716 17.1716 20.4839 13 20.9451V23H11V20.9451C6.82838 20.4839 3.51608 17.1716 3.05493 13H1V11H3.05493ZM14 12C14 13.1046 13.1046 14 12 14C10.8954 14 10 13.1046 10 12C10 10.8954 10.8954 10 12 10C13.1046 10 14 10.8954 14 12Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 695 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 456 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||
<path fill-rule="evenodd" d="M10.5 6a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Zm0 6a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Zm0 6a1.5 1.5 0 1 1 3 0 1.5 1.5 0 0 1-3 0Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 285 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 380 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.362 5.214A8.252 8.252 0 0 1 12 21 8.25 8.25 0 0 1 6.038 7.047 8.287 8.287 0 0 0 9 9.601a8.983 8.983 0 0 1 3.361-6.867 8.21 8.21 0 0 0 3 2.48Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18a3.75 3.75 0 0 0 .495-7.468 5.99 5.99 0 0 0-1.925 3.547 5.975 5.975 0 0 1-2.133-1.001A3.75 3.75 0 0 0 12 18Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 521 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.362 5.214A8.252 8.252 0 0 1 12 21 8.25 8.25 0 0 1 6.038 7.047 8.287 8.287 0 0 0 9 9.601a8.983 8.983 0 0 1 3.361-6.867 8.21 8.21 0 0 0 3 2.48Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18a3.75 3.75 0 0 0 .495-7.468 5.99 5.99 0 0 0-1.925 3.547 5.975 5.975 0 0 1-2.133-1.001A3.75 3.75 0 0 0 12 18Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 521 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12.4142 5H21C21.5523 5 22 5.44772 22 6V20C22 20.5523 21.5523 21 21 21H3C2.44772 21 2 20.5523 2 20V4C2 3.44772 2.44772 3 3 3H10.4142L12.4142 5ZM4 5V19H20V7H11.5858L9.58579 5H4ZM11 13.05V9H16V11H13V15.5C13 16.8807 11.8807 18 10.5 18C9.11929 18 8 16.8807 8 15.5C8 14.1193 9.11929 13 10.5 13C10.6712 13 10.8384 13.0172 11 13.05Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 429 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 458 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 10.5v6m3-3H9m4.06-7.19-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 402 B |
@@ -0,0 +1 @@
|
||||
<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M2.78233 2.21707C2.70732 2.14206 2.60557 2.09991 2.49949 2.09991C2.3934 2.09991 2.29166 2.14206 2.21664 2.21707L0.216645 4.21707C0.0604351 4.37328 0.0604351 4.62655 0.216645 4.78276C0.372855 4.93897 0.626121 4.93897 0.78233 4.78276L2.09949 3.4656L2.09949 11.5342L0.78233 10.2171C0.62612 10.0609 0.372854 10.0609 0.216645 10.2171C0.0604349 10.3733 0.0604349 10.6265 0.216645 10.7828L2.21664 12.7828C2.29166 12.8578 2.3934 12.8999 2.49949 12.8999C2.60557 12.8999 2.70731 12.8578 2.78233 12.7828L4.78233 10.7828C4.93854 10.6265 4.93854 10.3733 4.78233 10.2171C4.62612 10.0609 4.37285 10.0609 4.21664 10.2171L2.89949 11.5342L2.89949 3.4656L4.21664 4.78276C4.37285 4.93897 4.62612 4.93897 4.78233 4.78276C4.93854 4.62655 4.93854 4.37328 4.78233 4.21707L2.78233 2.21707ZM10.5 2.74997C10.7107 2.74997 10.8988 2.88211 10.9703 3.08036L13.9703 11.3999C14.064 11.6597 13.9293 11.9462 13.6696 12.0399C13.4098 12.1336 13.1233 11.9989 13.0296 11.7392L12.0477 9.016H8.95228L7.97033 11.7392C7.87666 11.9989 7.59013 12.1336 7.33036 12.0399C7.07059 11.9462 6.93595 11.6597 7.02962 11.3999L10.0296 3.08036C10.1011 2.88211 10.2892 2.74997 10.5 2.74997ZM10.5 4.72396L11.7412 8.166H9.25879L10.5 4.72396Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6.633 10.25c.806 0 1.533-.446 2.031-1.08a9.041 9.041 0 0 1 2.861-2.4c.723-.384 1.35-.956 1.653-1.715a4.498 4.498 0 0 0 .322-1.672V2.75a.75.75 0 0 1 .75-.75 2.25 2.25 0 0 1 2.25 2.25c0 1.152-.26 2.243-.723 3.218-.266.558.107 1.282.725 1.282m0 0h3.126c1.026 0 1.945.694 2.054 1.715.045.422.068.85.068 1.285a11.95 11.95 0 0 1-2.649 7.521c-.388.482-.987.729-1.605.729H13.48c-.483 0-.964-.078-1.423-.23l-3.114-1.04a4.501 4.501 0 0 0-1.423-.23H5.904m10.598-9.75H14.25M5.904 18.5c.083.205.173.405.27.602.197.4-.078.898-.523.898h-.908c-.889 0-1.713-.518-1.972-1.368a12 12 0 0 1-.521-3.507c0-1.553.295-3.036.831-4.398C3.387 9.953 4.167 9.5 5 9.5h1.053c.472 0 .745.556.5.96a8.958 8.958 0 0 0-1.302 4.665c0 1.194.232 2.333.654 3.375Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 924 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 356 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||
<path d="m11.645 20.91-.007-.003-.022-.012a15.247 15.247 0 0 1-.383-.218 25.18 25.18 0 0 1-4.244-3.17C4.688 15.36 2.25 12.174 2.25 8.25 2.25 5.322 4.714 3 7.688 3A5.5 5.5 0 0 1 12 5.052 5.5 5.5 0 0 1 16.313 3c2.973 0 5.437 2.322 5.437 5.25 0 3.925-2.438 7.111-4.739 9.256a25.175 25.175 0 0 1-4.244 3.17 15.247 15.247 0 0 1-.383.219l-.022.012-.007.004-.003.001a.752.752 0 0 1-.704 0l-.003-.001Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 503 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m2.25 12 8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 433 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 515 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 3.75H6.912a2.25 2.25 0 0 0-2.15 1.588L2.35 13.177a2.25 2.25 0 0 0-.1.661V18a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 0 0-2.15-1.588H15M2.25 13.5h3.86a2.25 2.25 0 0 1 2.012 1.244l.256.512a2.25 2.25 0 0 0 2.013 1.244h3.218a2.25 2.25 0 0 0 2.013-1.244l.256-.512a2.25 2.25 0 0 1 2.013-1.244h3.859M12 3v8.25m0 0-3-3m3 3 3-3" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 589 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m11.25 11.25.041-.02a.75.75 0 0 1 1.063.852l-.708 2.836a.75.75 0 0 0 1.063.853l.041-.021M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9-3.75h.008v.008H12V8.25Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 351 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M13.3344 16.055 12.4764 17.243C13.2904 17.969 14.3024 18.332 15.5124 18.332 16.4364 18.31 17.1404 18.0717 17.6244 17.617 18.1157 17.155 18.3614 16.605 18.3614 15.967 18.3614 15.3437 18.1891 14.8303 17.8444 14.427 17.4997 14.0237 16.9204 13.701 16.1064 13.459 15.4317 13.2537 14.9551 13.0667 14.6764 12.898 14.3977 12.722 14.2584 12.5093 14.2584 12.26 14.2584 12.0327 14.3721 11.8493 14.5994 11.71 14.8267 11.5707 15.1311 11.501 15.5124 11.501 15.7911 11.501 16.1064 11.556 16.4584 11.666 16.8104 11.7613 17.1221 11.9153 17.3934 12.128L18.1634 10.929C17.4887 10.3863 16.5941 10.115 15.4794 10.115 14.6801 10.115 14.0237 10.3203 13.5104 10.731 12.9824 11.1417 12.7184 11.6513 12.7184 12.26 12.7257 12.9053 12.9384 13.4077 13.3564 13.767 13.7817 14.1263 14.3867 14.4197 15.1714 14.647 15.8241 14.8523 16.2677 15.0577 16.5024 15.263 16.7297 15.4683 16.8434 15.7177 16.8434 16.011 16.8434 16.297 16.7297 16.517 16.5024 16.671 16.2677 16.8323 15.9304 16.913 15.4904 16.913 14.7717 16.9203 14.0531 16.6343 13.3344 16.055ZM7.80405 16.693C7.58405 16.561 7.37872 16.3667 7.18805 16.11L6.15405 16.957C6.46205 17.4777 6.84339 17.8407 7.29805 18.046 7.72339 18.2367 8.21105 18.332 8.76105 18.332 9.06172 18.332 9.37339 18.2843 9.69605 18.189 10.0187 18.0937 10.3157 17.9323 10.5871 17.705 11.0637 17.3237 11.3131 16.7003 11.3351 15.835V10.247H9.85005V15.549C9.85005 16.055 9.73639 16.4107 9.50905 16.616 9.28172 16.814 8.99572 16.913 8.65105 16.913 8.32105 16.913 8.03872 16.8397 7.80405 16.693ZM3 6C3 4.34315 4.34315 3 6 3H18C19.6569 3 21 4.34315 21 6V18C21 19.6569 19.6569 21 18 21H6C4.34315 21 3 19.6569 3 18V6ZM6 5C5.44772 5 5 5.44772 5 6V18C5 18.5523 5.44772 19 6 19H18C18.5523 19 19 18.5523 19 18V6C19 5.44772 18.5523 5 18 5H6Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m10.5 21 5.25-11.25L21 21m-9-3h7.5M3 5.621a48.474 48.474 0 0 1 6-.371m0 0c1.12 0 2.233.038 3.334.114M9 5.25V3m3.334 2.364C11.176 10.658 7.69 15.08 3 17.502m9.334-12.138c.896.061 1.785.147 2.666.257m-4.589 8.495a18.023 18.023 0 0 1-3.827-5.802" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 441 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.181 8.68a4.503 4.503 0 0 1 1.903 6.405m-9.768-2.782L3.56 14.06a4.5 4.5 0 0 0 6.364 6.365l3.129-3.129m5.614-5.615 1.757-1.757a4.5 4.5 0 0 0-6.364-6.365l-4.5 4.5c-.258.26-.479.541-.661.84m1.903 6.405a4.495 4.495 0 0 1-1.242-.88 4.483 4.483 0 0 1-1.062-1.683m6.587 2.345 5.907 5.907m-5.907-5.907L8.898 8.898M2.991 2.99 8.898 8.9" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 528 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 368 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path id="矢量 488" d="M6.14453 4C6.49023 4.38379 6.9209 4.91553 7.43652 5.59521C7.95508 6.27197 8.33447 6.81396 8.57471 7.22119L7.4585 8.13965C7.2417 7.68555 6.89014 7.11719 6.40381 6.43457C5.91748 5.74902 5.48242 5.18359 5.09863 4.73828L6.14453 4ZM9.40088 4.95361L18.959 4.95361L18.959 17.645C18.959 18.2075 18.8872 18.6382 18.7437 18.937C18.6001 19.2388 18.3452 19.4614 17.979 19.605C17.6128 19.7485 17.0635 19.8452 16.3311 19.895L15.25 19.9653L14.7842 18.6162L16.1157 18.5459C16.5815 18.5195 16.9199 18.4668 17.1309 18.3877C17.3418 18.3115 17.4795 18.187 17.5439 18.0142C17.6113 17.8384 17.645 17.5718 17.645 17.2144L17.645 6.23242L9.40088 6.23242L9.40088 4.95361ZM8.95264 9.58105L8.95264 8.35498L16.9595 8.35498L16.9595 9.58105L8.95264 9.58105ZM4 9.5459L7.40137 9.5459L7.40137 17.0122L8.89551 15.6455L9.3833 16.7793C9.23975 16.8994 8.93945 17.1763 8.48242 17.6099C7.84668 18.2075 7.43213 18.5913 7.23877 18.7612C6.85498 19.145 6.59131 19.4146 6.44775 19.5698L5.56445 18.6338C5.75781 18.4287 5.89551 18.2222 5.97754 18.0142C6.0625 17.8032 6.10498 17.5366 6.10498 17.2144L6.10498 10.8774L4 10.8774L4 9.5459ZM9.81396 17.1748L9.81396 11.6157L15.5752 11.6157L15.5752 17.1748L9.81396 17.1748ZM14.3491 12.8022L11.04 12.8022L11.04 15.9355L14.3491 15.9355L14.3491 12.8022Z" fill-rule="nonzero" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 276 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||
<path fill-rule="evenodd" d="M4.25 12a.75.75 0 0 1 .75-.75h14a.75.75 0 0 1 0 1.5H5a.75.75 0 0 1-.75-.75Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 234 B |
@@ -0,0 +1,5 @@
|
||||
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path id="path" d="M8.55658 3.68789C9.659 3.23137 10.8068 3.00311 12 3.00311C13.1932 3.00311 14.341 3.23137 15.4434 3.68789C16.5458 4.14442 17.519 4.79446 18.3629 5.63805C19.2067 6.48162 19.8571 7.45454 20.314 8.5568C20.7709 9.65906 20.9996 10.8068 21 12C21.0004 13.1937 20.7723 14.3421 20.3158 15.4451C19.8593 16.5481 19.209 17.5217 18.3651 18.366C17.5211 19.2102 16.5477 19.8608 15.4449 20.3177C14.342 20.7746 13.1938 21.0031 12 21.0031C10.8062 21.0031 9.65796 20.7746 8.55515 20.3177C7.4523 19.8608 6.47891 19.2102 5.63495 18.366C4.79099 17.5217 4.14075 16.5481 3.6842 15.4451C3.22766 14.3421 2.9996 13.1937 3 12C3.00043 10.8068 3.22906 9.65906 3.68597 8.5568" stroke="currentColor" stroke-width="1.500000" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path id="path" d="M16.1032 12C16.1032 12.1473 16.0388 12.2566 15.91 12.328L10.307 15.441C10.182 15.5104 10.0579 15.5088 9.93472 15.4363C9.81152 15.3637 9.74994 15.256 9.75 15.113L9.75 8.887C9.75 8.60101 10.057 8.42101 10.307 8.56001L15.91 11.672C16.0388 11.7434 16.1032 11.8527 16.1032 12Z" stroke="currentColor" stroke-width="1.500000" stroke-linejoin="round"/>
|
||||
<circle id="椭圆 3" cx="5.549988" cy="5.550003" r="0.750000" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9 9l10.5-3m0 6.553v3.75a2.25 2.25 0 01-1.632 2.163l-1.32.377a1.803 1.803 0 11-.99-3.467l2.31-.66a2.25 2.25 0 001.632-2.163zm0 0V2.25L9 5.25v10.303m0 0v3.75a2.25 2.25 0 01-1.632 2.163l-1.32.377a1.803 1.803 0 01-.99-3.467l2.31-.66A2.25 2.25 0 009 15.553z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 453 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M14.25 9v6m-4.5 0V9M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 255 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||
<path fill-rule="evenodd" d="M6.75 5.25a.75.75 0 0 1 .75-.75H9a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H7.5a.75.75 0 0 1-.75-.75V5.25Zm7.5 0A.75.75 0 0 1 15 4.5h1.5a.75.75 0 0 1 .75.75v13.5a.75.75 0 0 1-.75.75H15a.75.75 0 0 1-.75-.75V5.25Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 373 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.832 19.82a4.5 4.5 0 0 1-1.897 1.13l-2.685.8.8-2.685a4.5 4.5 0 0 1 1.13-1.897L16.863 4.487Zm0 0L19.5 7.125" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 362 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L10.582 16.07a4.5 4.5 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 455 B |
@@ -0,0 +1,4 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.91 11.672a.375.375 0 0 1 0 .656l-5.603 3.113a.375.375 0 0 1-.557-.328V8.887c0-.286.307-.466.557-.327l5.603 3.112Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 416 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||
<path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm14.024-.983a1.125 1.125 0 0 1 0 1.966l-5.603 3.113A1.125 1.125 0 0 1 9 15.113V8.887c0-.857.921-1.4 1.671-.983l5.603 3.113Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 382 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="size-6">
|
||||
<path fill-rule="evenodd" d="M4.5 5.653c0-1.427 1.529-2.33 2.779-1.643l11.54 6.347c1.295.712 1.295 2.573 0 3.286L7.28 19.99c-1.25.687-2.779-.217-2.779-1.643V5.653Z" clip-rule="evenodd" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 293 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path id="vector" d="M3.62791 5C3.28113 5 3 5.28491 3 5.63635C3 5.98779 3.28113 6.27271 3.62791 6.27271L18.6977 6.27271C19.0445 6.27271 19.3256 5.98779 19.3256 5.63635C19.3256 5.28491 19.0445 5 18.6977 5L3.62791 5ZM20.3721 10.5151L19.3256 10.5151L19.3256 17.0909C19.3256 18.1453 18.4822 19 17.4419 19C16.4015 19 15.5581 18.1453 15.5581 17.0909C15.5581 16.0366 16.4015 15.1819 17.4419 15.1819C17.662 15.1819 17.8734 15.2201 18.0698 15.2904L18.0698 10.3879C18.0698 9.75525 18.5758 9.24243 19.2 9.24243L20.3721 9.24243C20.7189 9.24243 21 9.52734 21 9.87878C21 10.2302 20.7189 10.5151 20.3721 10.5151ZM3 10.7273C3 10.3759 3.28113 10.0909 3.62791 10.0909L15.3488 10.0909C15.6956 10.0909 15.9767 10.3759 15.9767 10.7273C15.9767 11.0787 15.6956 11.3636 15.3488 11.3636L3.62791 11.3636C3.28113 11.3636 3 11.0787 3 10.7273ZM12.8372 16.4545L3.62791 16.4545C3.28113 16.4545 3 16.1696 3 15.8181C3 15.4667 3.28113 15.1818 3.62791 15.1818L12.8372 15.1818C13.184 15.1818 13.4651 15.4667 13.4651 15.8181C13.4651 16.1696 13.184 16.4545 12.8372 16.4545Z" clip-rule="evenodd" fill-rule="evenodd" fill="currentColor" fill-opacity="1.000000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 221 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M5.636 5.636a9 9 0 1 0 12.728 0M12 3v9" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 237 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 398 B |
@@ -0,0 +1,6 @@
|
||||
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path id="Vector" d="M5 19L3 17L5 15M3 17L17 17" stroke="currentColor" stroke-width="1.500000" stroke-linejoin="round"/>
|
||||
<path id="Ellipse 57" d="M7 7C4.79086 7 3 8.79089 3 11" stroke="currentColor" stroke-width="1.500000" stroke-linecap="round"/>
|
||||
<path id="Ellipse 58" d="M17 17C19.2091 17 21 15.2091 21 13" stroke="currentColor" stroke-width="1.500000"/>
|
||||
<path id="Vector" d="M19 5L21 7L19 9M21 7L7 7" stroke="currentColor" stroke-width="1.500000" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 640 B |
@@ -0,0 +1,7 @@
|
||||
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path id="Vector" d="M5 19L3 17L5 15M3 17L17 17" stroke="currentColor" stroke-width="1.500000" stroke-linejoin="round"/>
|
||||
<path id="Ellipse 57" d="M7 7C4.79086 7 3 8.79089 3 11" stroke="currentColor" stroke-width="1.500000" stroke-linecap="round"/>
|
||||
<path id="Ellipse 58" d="M17 17C19.2091 17 21 15.2091 21 13" stroke="currentColor" stroke-width="1.500000" stroke-linecap="round"/>
|
||||
<path id="Vector" d="M19 5L21 7L19 9M21 7L7 7" stroke="currentColor" stroke-width="1.500000" stroke-linejoin="round"/>
|
||||
<path id="Vector" d="M12 15L12 10L10 11" stroke="currentColor" stroke-width="1.200000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 753 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M7.217 10.907a2.25 2.25 0 1 0 0 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186 9.566-5.314m-9.566 7.5 9.566 5.314m0 0a2.25 2.25 0 1 0 3.935 2.186 2.25 2.25 0 0 0-3.935-2.186Zm0-12.814a2.25 2.25 0 1 0 3.933-2.185 2.25 2.25 0 0 0-3.933 2.185Z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 460 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 1L20.2169 2.82598C20.6745 2.92766 21 3.33347 21 3.80217V13.7889C21 15.795 19.9974 17.6684 18.3282 18.7812L12 23L5.6718 18.7812C4.00261 17.6684 3 15.795 3 13.7889V3.80217C3 3.33347 3.32553 2.92766 3.78307 2.82598L12 1ZM12 3.04879L5 4.60434V13.7889C5 15.1263 5.6684 16.3752 6.7812 17.1171L12 20.5963L17.2188 17.1171C18.3316 16.3752 19 15.1263 19 13.7889V4.60434L12 3.04879ZM12 7C13.1046 7 14 7.89543 14 9C14 9.73984 13.5983 10.3858 13.0011 10.7318L13 15H11L10.9999 10.7324C10.4022 10.3866 10 9.74025 10 9C10 7.89543 10.8954 7 12 7Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 637 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path id="Vector 149" d="M3 17L5.73524 17C7.14029 17 8.44231 16.2628 9.16521 15.058L12.8348 8.94202C13.5577 7.73718 14.8597 7 16.2648 7L21 7M19 5L21 7L19 9" stroke="currentColor" stroke-width="1.500000" stroke-linejoin="round"/>
|
||||
<path id="Vector" d="M2.99994 7L5.73518 7C6.82978 7 7.86186 7.44739 8.6063 8.21484M13.3936 15.7852C14.138 16.5526 15.1701 17 16.2647 17L20.9999 17M18.9999 19L20.9999 17L18.9999 15" stroke="currentColor" stroke-width="1.500000" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 644 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path id="path" d="M5.84871 6.29553C6.34871 6.29553 6.59871 6.54553 6.59871 7.04553L6.59871 17.5455C6.59871 18.0455 6.34871 18.2955 5.84871 18.2955L5.09871 18.2955C4.59871 18.2955 4.34871 18.0455 4.34871 17.5455L4.34871 7.04553C4.34871 6.54553 4.59871 6.29553 5.09871 6.29553L5.84871 6.29553ZM15.7937 6.60559C17.0437 5.89258 18.5987 6.79553 18.5987 8.23547L18.5987 16.3575C18.5987 17.7975 17.0437 18.7006 15.7937 17.9855L8.68571 13.9246C7.4257 13.2046 7.4257 11.3885 8.68571 10.6686L15.7937 6.60559Z" fill-rule="nonzero" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 704 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path id="path" d="M18.1513 6.29553C17.6513 6.29553 17.4013 6.54553 17.4013 7.04553L17.4013 17.5455C17.4013 18.0455 17.6513 18.2955 18.1513 18.2955L18.9013 18.2955C19.4013 18.2955 19.6513 18.0455 19.6513 17.5455L19.6513 7.04553C19.6513 6.54553 19.4013 6.29553 18.9013 6.29553L18.1513 6.29553ZM8.20628 6.60559C6.95628 5.89258 5.40129 6.79553 5.40129 8.23547L5.40129 16.3575C5.40129 17.7975 6.95628 18.7006 8.20628 17.9855L15.3143 13.9246C16.5743 13.2046 16.5743 11.3885 15.3143 10.6686L8.20628 6.60559Z" fill-rule="nonzero" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 706 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M20 4V16H23L19 21L15 16H18V4H20ZM12 18V20H3V18H12ZM14 11V13H3V11H14ZM14 4V6H3V4H14Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 187 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><svg width="24px" height="24px" viewBox="0 0 24 24" stroke-width="1.5" fill="none" xmlns="http://www.w3.org/2000/svg" color="currentColor"><path d="M6 20.5C7 11 11.5 8 20 6" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M15.9086 3.80941L20.3946 5.90126L18.3028 10.3873" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M5 7C6.10457 7 7 6.10457 7 5C7 3.89543 6.10457 3 5 3C3.89543 3 3 3.89543 3 5C3 6.10457 3.89543 7 5 7Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M16 20.2426L18.1213 18.1213M18.1213 18.1213L20.2426 16M18.1213 18.1213L16 16M18.1213 18.1213L20.2426 20.2426" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></svg>
|
||||
|
After Width: | Height: | Size: 888 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M14.5135 5.00008L17.1201 2.39348C17.5106 2.00295 18.1438 2.00295 18.5343 2.39348L22.777 6.63612C23.1675 7.02664 23.1675 7.65981 22.777 8.05033L18.9988 11.8285V21.0001C18.9988 21.5524 18.5511 22.0001 17.9988 22.0001H5.9988C5.44652 22.0001 4.9988 21.5524 4.9988 21.0001V11.8285L1.22063 8.05033C0.830103 7.65981 0.830103 7.02664 1.22063 6.63612L5.46327 2.39348C5.85379 2.00295 6.48696 2.00295 6.87748 2.39348L9.48408 5.00008H14.5135ZM15.3419 7.00008H8.65566L6.17037 4.5148L3.34195 7.34323L6.9988 11.0001V20.0001H16.9988V11.0001L20.6557 7.34323L17.8272 4.5148L15.3419 7.00008Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 676 B |
@@ -0,0 +1,4 @@
|
||||
<svg width="24.000000" height="24.000000" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs/>
|
||||
<path id="矢量 489" d="M7.13965 6.98828C6.70898 6.17969 5.77295 4.91846 4.97754 4L3.97119 4.646C4.72705 5.604 5.64551 6.93555 6.05859 7.76172L7.13965 6.98828ZM14.395 14.0063L17.9766 14.0063L17.9766 12.8198L14.395 12.8198L14.395 11.2378L13.0811 11.2378L13.0811 12.8198L9.53467 12.8198L9.53467 14.0063L13.0811 14.0063L13.0811 15.896L8.5415 15.896L8.5415 16.4189C8.43604 16.186 8.32617 15.9355 8.25586 15.7158L6.81445 16.7793L6.81445 9.16797L3 9.16797L3 10.46L5.51807 10.46L5.51807 16.8496C5.51807 17.7329 4.97754 18.3438 4.63916 18.5986C4.88965 18.814 5.23242 19.3018 5.39502 19.5698C5.62793 19.2622 6.05859 18.8843 8.5415 16.9771L8.5415 17.1045L13.0811 17.1045L13.0811 20.0576L14.395 20.0576L14.395 17.1045L19.1455 17.1045L19.1455 15.896L14.395 15.896L14.395 14.0063ZM16.5352 5.72705C15.832 6.69824 14.9136 7.54639 13.8369 8.28467C12.8262 7.56396 12 6.69824 11.3672 5.72705L16.5352 5.72705ZM17.5591 4.48779L17.3438 4.54053L8.77881 4.54053L8.77881 5.72705L10.0532 5.72705C10.7563 6.9707 11.6748 8.02979 12.7734 8.94824C11.2617 9.83154 9.55225 10.4995 7.89551 10.895C8.12842 11.1631 8.45361 11.686 8.59863 12.0288C10.3784 11.541 12.2153 10.7852 13.8369 9.76123C15.2739 10.6973 16.9131 11.418 18.6973 11.8486C18.873 11.5059 19.2334 10.9829 19.5234 10.75C17.8315 10.3896 16.2847 9.79639 14.9312 9.00537C16.4077 7.90674 17.6162 6.5752 18.4072 4.97119L17.5591 4.48779Z" fill-rule="nonzero" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 612 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M16.5 18.75h-9m9 0a3 3 0 013 3h-15a3 3 0 013-3m9 0v-3.375c0-.621-.503-1.125-1.125-1.125h-.871M7.5 18.75v-3.375c0-.621.504-1.125 1.125-1.125h.872m5.007 0H9.497m5.007 0a7.454 7.454 0 01-.982-3.172M9.497 14.25a7.454 7.454 0 00.981-3.172M5.25 4.236c-.982.143-1.954.317-2.916.52A6.003 6.003 0 007.73 9.728M5.25 4.236V4.5c0 2.108.966 3.99 2.48 5.228M5.25 4.236V2.721C7.456 2.41 9.71 2.25 12 2.25c2.291 0 4.545.16 6.75.47v1.516M7.73 9.728a6.726 6.726 0 002.748 1.35m8.272-6.842V4.5c0 2.108-.966 3.99-2.48 5.228m2.48-5.492a46.32 46.32 0 012.916.52 6.003 6.003 0 01-5.395 4.972m0 0a6.726 6.726 0 01-2.749 1.35m0 0a6.772 6.772 0 01-3.044 0" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 829 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 344 B |
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 219 B |
@@ -0,0 +1,465 @@
|
||||
/**
|
||||
* 支持长按拖拽排序的flatlist,右边加个固定的按钮,拖拽排序。
|
||||
* 考虑到方便实现+节省性能,整个app内的拖拽排序都遵守以下实现。
|
||||
* 点击会出现
|
||||
*/
|
||||
|
||||
import globalStyle from "@/constants/globalStyle";
|
||||
import { iconSizeConst } from "@/constants/uiConst";
|
||||
import useTextColor from "@/hooks/useTextColor";
|
||||
import rpx from "@/utils/rpx";
|
||||
import { FlashList } from "@shopify/flash-list";
|
||||
import React, {
|
||||
ForwardedRef,
|
||||
forwardRef,
|
||||
memo,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { LayoutRectangle, Pressable, StyleSheet, View } from "react-native";
|
||||
import {
|
||||
runOnJS,
|
||||
useDerivedValue,
|
||||
useSharedValue,
|
||||
} from "react-native-reanimated";
|
||||
import Icon from "@/components/base/icon.tsx";
|
||||
|
||||
const defaultZIndex = 10;
|
||||
|
||||
interface ISortableFlatListProps<T> {
|
||||
data: T[];
|
||||
renderItem: (props: { item: T; index: number }) => JSX.Element;
|
||||
// 高度
|
||||
itemHeight: number;
|
||||
itemJustifyContent?:
|
||||
| "flex-start"
|
||||
| "flex-end"
|
||||
| "center"
|
||||
| "space-between"
|
||||
| "space-around"
|
||||
| "space-evenly";
|
||||
// 滚动list距离顶部的距离, 这里写的不好
|
||||
marginTop: number;
|
||||
/** 拖拽时的背景色 */
|
||||
activeBackgroundColor?: string;
|
||||
/** 交换结束 */
|
||||
onSortEnd?: (newData: T[]) => void;
|
||||
}
|
||||
|
||||
export default function SortableFlatList<T extends any = any>(
|
||||
props: ISortableFlatListProps<T>,
|
||||
) {
|
||||
const {
|
||||
data,
|
||||
renderItem,
|
||||
itemHeight,
|
||||
itemJustifyContent,
|
||||
marginTop,
|
||||
activeBackgroundColor,
|
||||
onSortEnd,
|
||||
} = props;
|
||||
|
||||
// 不要干扰原始数据
|
||||
const [_data, _setData] = useState([...(data ?? [])]);
|
||||
// 是否禁止滚动
|
||||
const [scrollEnabled, setScrollEnabled] = useState(true);
|
||||
// 是否处在激活状态, -1表示无,其他表示当前激活的下标
|
||||
const activeRef = useRef(-1);
|
||||
const [activeItem, setActiveItem] = useState<T | null>(null);
|
||||
|
||||
const layoutRef = useRef<LayoutRectangle>();
|
||||
// listref
|
||||
const listRef = useRef<FlashList<T> | null>(null);
|
||||
// fakeref
|
||||
const fakeItemRef = useRef<View | null>(null);
|
||||
// contentoffset
|
||||
const contentOffsetYRef = useRef<number>(-1);
|
||||
const targetOffsetYRef = useRef<number>(0);
|
||||
|
||||
const direction = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
_setData([...(data ?? [])]);
|
||||
}, [data]);
|
||||
|
||||
const initDragPageY = useRef<number>(0);
|
||||
const initDragLocationY = useRef<number>(0);
|
||||
const offsetRef = useRef<number>(0);
|
||||
|
||||
//#region 滚动
|
||||
const scrollingRef = useRef(false);
|
||||
|
||||
// 列表整体的高度
|
||||
const listContentHeight = useMemo(
|
||||
() => itemHeight * data.length,
|
||||
[data, itemHeight],
|
||||
);
|
||||
|
||||
function scrollToTarget(forceScroll = false) {
|
||||
// 未选中
|
||||
if (activeRef.current === -1) {
|
||||
scrollingRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// 滚动中就不滚了 /
|
||||
if (scrollingRef.current && !forceScroll) {
|
||||
scrollingRef.current = true;
|
||||
return;
|
||||
}
|
||||
// 方向是0
|
||||
if (direction.value === 0) {
|
||||
scrollingRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const nextTarget =
|
||||
Math.sign(direction.value) *
|
||||
Math.max(Math.abs(direction.value), 0.3) *
|
||||
300 +
|
||||
contentOffsetYRef.current;
|
||||
// 当前到极限了
|
||||
if (
|
||||
(contentOffsetYRef.current <= 2 &&
|
||||
nextTarget < contentOffsetYRef.current) ||
|
||||
(contentOffsetYRef.current >=
|
||||
listContentHeight - (layoutRef.current?.height ?? 0) - 2 &&
|
||||
nextTarget > contentOffsetYRef.current)
|
||||
) {
|
||||
scrollingRef.current = false;
|
||||
return;
|
||||
}
|
||||
scrollingRef.current = true;
|
||||
// 超出区域
|
||||
targetOffsetYRef.current = Math.min(
|
||||
Math.max(0, nextTarget),
|
||||
listContentHeight - (layoutRef.current?.height ?? 0),
|
||||
);
|
||||
listRef.current?.scrollToOffset({
|
||||
animated: true,
|
||||
offset: targetOffsetYRef.current,
|
||||
});
|
||||
}
|
||||
|
||||
useDerivedValue(() => {
|
||||
// 正在滚动
|
||||
if (scrollingRef.current) {
|
||||
return;
|
||||
} else if (direction.value !== 0) {
|
||||
// 开始滚动
|
||||
runOnJS(scrollToTarget)();
|
||||
}
|
||||
}, []);
|
||||
|
||||
//#endregion
|
||||
|
||||
return (
|
||||
<View style={globalStyle.fwflex1}>
|
||||
{/* 纯展示 */}
|
||||
<FakeFlatListItem
|
||||
ref={_ => (fakeItemRef.current = _)}
|
||||
backgroundColor={activeBackgroundColor}
|
||||
renderItem={renderItem}
|
||||
itemHeight={itemHeight}
|
||||
item={activeItem}
|
||||
itemJustifyContent={itemJustifyContent}
|
||||
/>
|
||||
<FlashList
|
||||
scrollEnabled={scrollEnabled}
|
||||
ref={_ => {
|
||||
listRef.current = _;
|
||||
}}
|
||||
onLayout={evt => {
|
||||
layoutRef.current = evt.nativeEvent.layout;
|
||||
}}
|
||||
data={_data}
|
||||
estimatedItemSize={itemHeight}
|
||||
scrollEventThrottle={16}
|
||||
onTouchStart={e => {
|
||||
if (activeRef.current !== -1) {
|
||||
// 相对于整个页面顶部的距离
|
||||
initDragPageY.current = e.nativeEvent.pageY;
|
||||
initDragLocationY.current = e.nativeEvent.locationY;
|
||||
}
|
||||
}}
|
||||
onTouchMove={e => {
|
||||
if (activeRef.current !== -1) {
|
||||
offsetRef.current =
|
||||
e.nativeEvent.pageY -
|
||||
(marginTop ?? layoutRef.current?.y ?? 0) -
|
||||
itemHeight / 2;
|
||||
|
||||
if (offsetRef.current < 0) {
|
||||
offsetRef.current = 0;
|
||||
} else if (
|
||||
offsetRef.current >
|
||||
(layoutRef.current?.height ?? 0) - itemHeight
|
||||
) {
|
||||
offsetRef.current =
|
||||
(layoutRef.current?.height ?? 0) - itemHeight;
|
||||
}
|
||||
fakeItemRef.current!.setNativeProps({
|
||||
top: offsetRef.current,
|
||||
opacity: 1,
|
||||
zIndex: 100,
|
||||
});
|
||||
|
||||
// 如果超出范围,停止
|
||||
if (offsetRef.current < itemHeight * 2) {
|
||||
// 上滑
|
||||
direction.value =
|
||||
offsetRef.current / itemHeight / 2 - 1;
|
||||
} else if (
|
||||
offsetRef.current >
|
||||
(layoutRef.current?.height ?? 0) - 3 * itemHeight
|
||||
) {
|
||||
// 下滑
|
||||
direction.value =
|
||||
(offsetRef.current -
|
||||
(layoutRef.current?.height ?? 0) +
|
||||
3 * itemHeight) /
|
||||
itemHeight /
|
||||
2;
|
||||
} else {
|
||||
// 不滑动
|
||||
direction.value = 0;
|
||||
}
|
||||
}
|
||||
}}
|
||||
onTouchEnd={e => {
|
||||
if (activeRef.current !== -1) {
|
||||
// 计算最终的位置,触发onSortEnd
|
||||
let index = activeRef.current;
|
||||
if (contentOffsetYRef.current !== -1) {
|
||||
index = Math.round(
|
||||
(contentOffsetYRef.current +
|
||||
offsetRef.current) /
|
||||
itemHeight,
|
||||
);
|
||||
} else {
|
||||
// 拖动的距离
|
||||
index =
|
||||
activeRef.current +
|
||||
Math.round(
|
||||
(e.nativeEvent.pageY -
|
||||
initDragPageY.current +
|
||||
initDragLocationY.current) /
|
||||
itemHeight,
|
||||
);
|
||||
}
|
||||
index = Math.min(data.length, Math.max(index, 0));
|
||||
// from: activeRef.current to: index
|
||||
if (activeRef.current !== index) {
|
||||
let nData = _data
|
||||
.slice(0, activeRef.current)
|
||||
.concat(_data.slice(activeRef.current + 1));
|
||||
nData.splice(index, 0, activeItem as T);
|
||||
onSortEnd?.(nData);
|
||||
// 测试用,正式时移除掉
|
||||
// _setData(nData);
|
||||
}
|
||||
}
|
||||
scrollingRef.current = false;
|
||||
activeRef.current = -1;
|
||||
setScrollEnabled(true);
|
||||
setActiveItem(null);
|
||||
fakeItemRef.current!.setNativeProps({
|
||||
top: 0,
|
||||
opacity: 0,
|
||||
zIndex: -1,
|
||||
});
|
||||
}}
|
||||
onTouchCancel={() => {
|
||||
// todo: 滑动很快的时候会触发取消,native的flatlist就这样
|
||||
activeRef.current = -1;
|
||||
scrollingRef.current = false;
|
||||
setScrollEnabled(true);
|
||||
setActiveItem(null);
|
||||
fakeItemRef.current!.setNativeProps({
|
||||
top: 0,
|
||||
opacity: 0,
|
||||
zIndex: -1,
|
||||
});
|
||||
contentOffsetYRef.current = -1;
|
||||
}}
|
||||
onScroll={e => {
|
||||
contentOffsetYRef.current = e.nativeEvent.contentOffset.y;
|
||||
if (
|
||||
activeRef.current !== -1 &&
|
||||
Math.abs(
|
||||
contentOffsetYRef.current -
|
||||
targetOffsetYRef.current,
|
||||
) < 2
|
||||
) {
|
||||
scrollToTarget(true);
|
||||
}
|
||||
}}
|
||||
renderItem={({ item, index }) => {
|
||||
return (
|
||||
<SortableFlatListItem
|
||||
setScrollEnabled={setScrollEnabled}
|
||||
activeRef={activeRef}
|
||||
renderItem={renderItem}
|
||||
item={item}
|
||||
index={index}
|
||||
setActiveItem={setActiveItem}
|
||||
itemJustifyContent={itemJustifyContent}
|
||||
itemHeight={itemHeight}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
interface ISortableFlatListItemProps<T extends any = any> {
|
||||
item: T;
|
||||
index: number;
|
||||
// 高度
|
||||
itemHeight: number;
|
||||
itemJustifyContent?:
|
||||
| "flex-start"
|
||||
| "flex-end"
|
||||
| "center"
|
||||
| "space-between"
|
||||
| "space-around"
|
||||
| "space-evenly";
|
||||
setScrollEnabled: (scrollEnabled: boolean) => void;
|
||||
renderItem: (props: { item: T; index: number }) => JSX.Element;
|
||||
setActiveItem: (item: T | null) => void;
|
||||
activeRef: React.MutableRefObject<number>;
|
||||
}
|
||||
function _SortableFlatListItem(props: ISortableFlatListItemProps) {
|
||||
const {
|
||||
itemHeight,
|
||||
setScrollEnabled,
|
||||
renderItem,
|
||||
setActiveItem,
|
||||
itemJustifyContent,
|
||||
item,
|
||||
index,
|
||||
activeRef,
|
||||
} = props;
|
||||
|
||||
// 省一点性能,height是顺着传下来的,放ref就好了
|
||||
const styleRef = useRef(
|
||||
StyleSheet.create({
|
||||
viewWrapper: {
|
||||
height: itemHeight,
|
||||
width: "100%",
|
||||
flexDirection: "row",
|
||||
justifyContent: itemJustifyContent ?? "flex-end",
|
||||
zIndex: defaultZIndex,
|
||||
},
|
||||
btn: {
|
||||
height: itemHeight,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
width: rpx(100),
|
||||
textAlignVertical: "center",
|
||||
},
|
||||
}),
|
||||
);
|
||||
const textColor = useTextColor();
|
||||
|
||||
return (
|
||||
<View style={styleRef.current.viewWrapper}>
|
||||
{renderItem({ item, index })}
|
||||
<Pressable
|
||||
onTouchStart={() => {
|
||||
if (activeRef.current !== -1) {
|
||||
return;
|
||||
}
|
||||
/** 使用ref避免其它组件重新渲染; 由于事件冒泡,这里会先触发 */
|
||||
activeRef.current = index;
|
||||
/** 锁定滚动 */
|
||||
setScrollEnabled(false);
|
||||
setActiveItem(item);
|
||||
}}
|
||||
style={styleRef.current.btn}>
|
||||
<Icon
|
||||
name="bars-3"
|
||||
size={iconSizeConst.normal}
|
||||
color={textColor}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const SortableFlatListItem = memo(
|
||||
_SortableFlatListItem,
|
||||
(prev, curr) => prev.index === curr.index && prev.item === curr.item,
|
||||
);
|
||||
|
||||
const FakeFlatListItem = forwardRef(function (
|
||||
props: Pick<
|
||||
ISortableFlatListItemProps,
|
||||
"itemHeight" | "renderItem" | "item" | "itemJustifyContent"
|
||||
> & {
|
||||
backgroundColor?: string;
|
||||
},
|
||||
ref: ForwardedRef<View>,
|
||||
) {
|
||||
const { itemHeight, renderItem, item, backgroundColor, itemJustifyContent } =
|
||||
props;
|
||||
|
||||
const styleRef = useRef(
|
||||
StyleSheet.create({
|
||||
viewWrapper: {
|
||||
height: itemHeight,
|
||||
width: "100%",
|
||||
flexDirection: "row",
|
||||
justifyContent: itemJustifyContent ?? "flex-end",
|
||||
zIndex: defaultZIndex,
|
||||
},
|
||||
btn: {
|
||||
height: itemHeight,
|
||||
paddingHorizontal: rpx(28),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
right: 0,
|
||||
width: rpx(100),
|
||||
textAlignVertical: "center",
|
||||
},
|
||||
}),
|
||||
);
|
||||
const textColor = useTextColor();
|
||||
|
||||
return (
|
||||
<View
|
||||
ref={ref}
|
||||
style={[
|
||||
styleRef.current.viewWrapper,
|
||||
style.activeItemDefault,
|
||||
backgroundColor ? { backgroundColor } : {},
|
||||
]}>
|
||||
{item ? renderItem({ item, index: -1 }) : null}
|
||||
<Pressable style={styleRef.current.btn}>
|
||||
<Icon
|
||||
name="bars-3"
|
||||
size={iconSizeConst.normal}
|
||||
color={textColor}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
const style = StyleSheet.create({
|
||||
activeItemDefault: {
|
||||
opacity: 0,
|
||||
zIndex: -1,
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,287 @@
|
||||
import React, { ReactNode, useEffect, useState } from "react";
|
||||
import {
|
||||
LayoutRectangle,
|
||||
StatusBar as OriginalStatusBar,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
TouchableWithoutFeedback,
|
||||
View,
|
||||
ViewStyle,
|
||||
} from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import StatusBar from "./statusBar";
|
||||
import color from "color";
|
||||
import IconButton from "./iconButton";
|
||||
import globalStyle from "@/constants/globalStyle";
|
||||
import ThemeText from "./themeText";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import Animated, {
|
||||
Easing,
|
||||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
withTiming,
|
||||
} from "react-native-reanimated";
|
||||
import Portal from "./portal";
|
||||
import ListItem from "./listItem";
|
||||
import { IIconName } from "@/components/base/icon.tsx";
|
||||
|
||||
interface IAppBarProps {
|
||||
titleTextOpacity?: number;
|
||||
withStatusBar?: boolean;
|
||||
color?: string;
|
||||
actions?: Array<{
|
||||
icon: IIconName;
|
||||
onPress?: () => void;
|
||||
}>;
|
||||
menu?: Array<{
|
||||
icon: IIconName;
|
||||
title: string;
|
||||
show?: boolean;
|
||||
onPress?: () => void;
|
||||
}>;
|
||||
menuWithStatusBar?: boolean;
|
||||
children?: string | ReactNode;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
contentStyle?: StyleProp<ViewStyle>;
|
||||
actionComponent?: ReactNode;
|
||||
onBackPress?: () => void;
|
||||
}
|
||||
|
||||
const ANIMATION_EASING: Animated.EasingFunction = Easing.out(Easing.exp);
|
||||
const ANIMATION_DURATION = 500;
|
||||
|
||||
const timingConfig = {
|
||||
duration: ANIMATION_DURATION,
|
||||
easing: ANIMATION_EASING,
|
||||
};
|
||||
|
||||
export default function AppBar(props: IAppBarProps) {
|
||||
const {
|
||||
titleTextOpacity = 1,
|
||||
withStatusBar,
|
||||
color: _color,
|
||||
actions = [],
|
||||
menu = [],
|
||||
menuWithStatusBar = true,
|
||||
containerStyle,
|
||||
contentStyle,
|
||||
children,
|
||||
actionComponent,
|
||||
onBackPress,
|
||||
} = props;
|
||||
|
||||
const colors = useColors();
|
||||
const navigation = useNavigation();
|
||||
|
||||
const bgColor = color(colors.appBar ?? colors.primary).toString();
|
||||
const contentColor = _color ?? colors.appBarText;
|
||||
|
||||
const [showMenu, setShowMenu] = useState(false);
|
||||
const [menuIconLayout, setMenuIconLayout] =
|
||||
useState<LayoutRectangle | null>(null);
|
||||
const scaleRate = useSharedValue(0);
|
||||
|
||||
useEffect(() => {
|
||||
if (showMenu) {
|
||||
scaleRate.value = withTiming(1, timingConfig);
|
||||
} else {
|
||||
scaleRate.value = withTiming(0, timingConfig);
|
||||
}
|
||||
}, [showMenu]);
|
||||
|
||||
const transformStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
opacity: scaleRate.value,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
{withStatusBar ? <StatusBar backgroundColor={bgColor} /> : null}
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
containerStyle,
|
||||
{ backgroundColor: bgColor },
|
||||
]}>
|
||||
<IconButton
|
||||
name="arrow-left"
|
||||
sizeType="normal"
|
||||
color={contentColor}
|
||||
style={globalStyle.notShrink}
|
||||
onPress={
|
||||
onBackPress ||
|
||||
(() => {
|
||||
navigation.goBack();
|
||||
})
|
||||
}
|
||||
/>
|
||||
<View style={[globalStyle.grow, styles.content, contentStyle]}>
|
||||
{typeof children === "string" ? (
|
||||
<ThemeText
|
||||
fontSize="title"
|
||||
fontWeight="bold"
|
||||
numberOfLines={1}
|
||||
color={
|
||||
titleTextOpacity !== 1
|
||||
? color(contentColor)
|
||||
.alpha(titleTextOpacity)
|
||||
.toString()
|
||||
: contentColor
|
||||
}>
|
||||
{children}
|
||||
</ThemeText>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</View>
|
||||
{actions.map((action, index) => (
|
||||
<IconButton
|
||||
key={index}
|
||||
name={action.icon}
|
||||
sizeType="normal"
|
||||
color={contentColor}
|
||||
style={[globalStyle.notShrink, styles.rightButton]}
|
||||
onPress={action.onPress}
|
||||
/>
|
||||
))}
|
||||
{actionComponent ?? null}
|
||||
{menu?.length ? (
|
||||
<IconButton
|
||||
name="ellipsis-vertical"
|
||||
sizeType="normal"
|
||||
onLayout={evt => {
|
||||
setMenuIconLayout(evt.nativeEvent.layout);
|
||||
}}
|
||||
color={contentColor}
|
||||
style={[globalStyle.notShrink, styles.rightButton]}
|
||||
onPress={() => {
|
||||
setShowMenu(true);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
</View>
|
||||
<Portal>
|
||||
{showMenu ? (
|
||||
<TouchableWithoutFeedback
|
||||
onPress={() => {
|
||||
setShowMenu(false);
|
||||
}}>
|
||||
<View style={styles.blocker} />
|
||||
</TouchableWithoutFeedback>
|
||||
) : null}
|
||||
<>
|
||||
<Animated.View
|
||||
pointerEvents={showMenu ? "auto" : "none"}
|
||||
style={[
|
||||
{
|
||||
borderBottomColor: colors.background,
|
||||
left:
|
||||
(menuIconLayout?.x ?? 0) +
|
||||
(menuIconLayout?.width ?? 0) / 2 -
|
||||
rpx(10),
|
||||
top:
|
||||
(menuIconLayout?.y ?? 0) +
|
||||
(menuIconLayout?.height ?? 0) +
|
||||
(menuWithStatusBar
|
||||
? OriginalStatusBar.currentHeight ?? 0
|
||||
: 0),
|
||||
},
|
||||
transformStyle,
|
||||
styles.bubbleCorner,
|
||||
]}
|
||||
/>
|
||||
<Animated.View
|
||||
pointerEvents={showMenu ? "auto" : "none"}
|
||||
style={[
|
||||
{
|
||||
backgroundColor: colors.background,
|
||||
right: rpx(24),
|
||||
top:
|
||||
(menuIconLayout?.y ?? 0) +
|
||||
(menuIconLayout?.height ?? 0) +
|
||||
rpx(20) +
|
||||
(menuWithStatusBar
|
||||
? OriginalStatusBar.currentHeight ?? 0
|
||||
: 0),
|
||||
shadowColor: colors.shadow,
|
||||
},
|
||||
transformStyle,
|
||||
styles.menu,
|
||||
]}>
|
||||
{menu.map(it =>
|
||||
it.show !== false ? (
|
||||
<ListItem
|
||||
key={it.title}
|
||||
withHorizontalPadding
|
||||
heightType="small"
|
||||
onPress={() => {
|
||||
setShowMenu(false);
|
||||
// async
|
||||
setTimeout(() => {
|
||||
it.onPress?.();
|
||||
}, 20);
|
||||
}}>
|
||||
<ListItem.ListItemIcon icon={it.icon} />
|
||||
<ListItem.Content title={it.title} />
|
||||
</ListItem>
|
||||
) : null,
|
||||
)}
|
||||
</Animated.View>
|
||||
</>
|
||||
</Portal>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
width: "100%",
|
||||
zIndex: 10000,
|
||||
height: rpx(88),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: rpx(24),
|
||||
},
|
||||
content: {
|
||||
flexDirection: "row",
|
||||
flexBasis: 0,
|
||||
alignItems: "center",
|
||||
paddingHorizontal: rpx(24),
|
||||
},
|
||||
rightButton: {
|
||||
marginLeft: rpx(28),
|
||||
},
|
||||
blocker: {
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
zIndex: 10010,
|
||||
},
|
||||
bubbleCorner: {
|
||||
position: "absolute",
|
||||
borderColor: "transparent",
|
||||
borderWidth: rpx(10),
|
||||
zIndex: 10012,
|
||||
transformOrigin: "right top",
|
||||
opacity: 0,
|
||||
},
|
||||
menu: {
|
||||
width: rpx(340),
|
||||
maxHeight: rpx(600),
|
||||
borderRadius: rpx(8),
|
||||
zIndex: 10011,
|
||||
position: "absolute",
|
||||
opacity: 0,
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 2,
|
||||
},
|
||||
shadowOpacity: 0.23,
|
||||
shadowRadius: 2.62,
|
||||
elevation: 4,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
GestureResponderEvent,
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
ViewStyle,
|
||||
} from "react-native";
|
||||
import useColors from "@/hooks/useColors.ts";
|
||||
import ThemeText from "@/components/base/themeText.tsx";
|
||||
import React from "react";
|
||||
import rpx from "@/utils/rpx.ts";
|
||||
|
||||
export function Button(props: {
|
||||
type?: "normal" | "primary";
|
||||
text: string;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
onPress?: (evt: GestureResponderEvent) => void;
|
||||
}) {
|
||||
const { type = "normal", text, style, onPress } = props;
|
||||
const colors = useColors();
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.6}
|
||||
onPress={onPress}
|
||||
style={[
|
||||
styles.bottomBtn,
|
||||
{
|
||||
backgroundColor:
|
||||
type === "normal" ? colors.placeholder : colors.primary,
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
<ThemeText color={type === "normal" ? undefined : "white"}>
|
||||
{text}
|
||||
</ThemeText>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
bottomBtn: {
|
||||
borderRadius: rpx(8),
|
||||
flexShrink: 0,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
height: rpx(72),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
import React from "react";
|
||||
import { StyleProp, StyleSheet, View, ViewProps } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import { TouchableOpacity } from "react-native-gesture-handler";
|
||||
import Icon from "@/components/base/icon.tsx";
|
||||
|
||||
interface ICheckboxProps {
|
||||
checked?: boolean;
|
||||
onPress?: () => void;
|
||||
style?: StyleProp<ViewProps>;
|
||||
}
|
||||
|
||||
const slop = rpx(24);
|
||||
|
||||
export default function Checkbox(props: ICheckboxProps) {
|
||||
const { checked, onPress, style } = props;
|
||||
const colors = useColors();
|
||||
|
||||
const innerNode = (
|
||||
<View
|
||||
style={[
|
||||
styles.container,
|
||||
checked
|
||||
? {
|
||||
backgroundColor: colors.primary,
|
||||
borderColor: colors.primary,
|
||||
}
|
||||
: {
|
||||
borderColor: colors.text,
|
||||
},
|
||||
style,
|
||||
]}>
|
||||
{checked ? (
|
||||
<Icon name="check" color={colors.appBarText} size={rpx(34)} />
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
|
||||
return onPress ? (
|
||||
<TouchableOpacity
|
||||
hitSlop={{
|
||||
left: slop,
|
||||
right: slop,
|
||||
top: slop,
|
||||
bottom: slop,
|
||||
}}
|
||||
onPress={onPress}>
|
||||
{innerNode}
|
||||
</TouchableOpacity>
|
||||
) : (
|
||||
innerNode
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
width: rpx(36),
|
||||
height: rpx(36),
|
||||
borderRadius: rpx(2),
|
||||
borderWidth: rpx(1),
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,57 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import { Pressable, StyleProp, StyleSheet, ViewStyle } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import ThemeText from "./themeText";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import IconButton from "./iconButton";
|
||||
|
||||
interface IChipProps {
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
children?: ReactNode;
|
||||
onPress?: () => void;
|
||||
onClose?: () => void;
|
||||
}
|
||||
export default function Chip(props: IChipProps) {
|
||||
const { containerStyle, children, onPress, onClose } = props;
|
||||
const colors = useColors();
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
backgroundColor: colors.placeholder,
|
||||
},
|
||||
containerStyle,
|
||||
]}>
|
||||
{typeof children === "string" ? (
|
||||
<ThemeText fontSize="subTitle" numberOfLines={1}>
|
||||
{children}
|
||||
</ThemeText>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
<IconButton
|
||||
onPress={onClose}
|
||||
name="x-mark"
|
||||
sizeType="small"
|
||||
style={styles.icon}
|
||||
/>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
height: rpx(56),
|
||||
paddingHorizontal: rpx(18),
|
||||
borderRadius: rpx(28),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
icon: {
|
||||
marginLeft: rpx(8),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
import { Image, StyleSheet, View } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import { ImgAsset } from "@/constants/assetsConst";
|
||||
|
||||
interface IColorBlockProps {
|
||||
color: string;
|
||||
}
|
||||
export default function ColorBlock(props: IColorBlockProps) {
|
||||
const { color } = props;
|
||||
|
||||
return (
|
||||
<View style={[styles.showBar]}>
|
||||
<Image
|
||||
resizeMode="repeat"
|
||||
source={ImgAsset.transparentBg}
|
||||
style={styles.transparentBg}
|
||||
/>
|
||||
<View
|
||||
style={[
|
||||
styles.showBarContent,
|
||||
{
|
||||
backgroundColor: color,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
showBar: {
|
||||
width: rpx(76),
|
||||
height: rpx(50),
|
||||
borderWidth: 1,
|
||||
borderStyle: "solid",
|
||||
borderColor: "#ccc",
|
||||
},
|
||||
showBarContent: {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
transparentBg: {
|
||||
position: "absolute",
|
||||
zIndex: -1,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import React from "react";
|
||||
import { StyleProp, StyleSheet, View, ViewStyle } from "react-native";
|
||||
import useColors from "@/hooks/useColors";
|
||||
|
||||
interface IDividerProps {
|
||||
vertical?: boolean;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
export default function Divider(props: IDividerProps) {
|
||||
const { vertical, style } = props;
|
||||
const colors = useColors();
|
||||
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
vertical ? css.dividerVertical : css.divider,
|
||||
{
|
||||
backgroundColor: colors.divider ?? "#999999",
|
||||
},
|
||||
style,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const css = StyleSheet.create({
|
||||
divider: {
|
||||
width: "100%",
|
||||
height: 1,
|
||||
},
|
||||
dividerVertical: {
|
||||
height: "100%",
|
||||
width: 1,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import ThemeText from "./themeText";
|
||||
import { useI18N } from "@/core/i18n";
|
||||
|
||||
interface IEmptyProps {
|
||||
content?: string;
|
||||
}
|
||||
export default function Empty(props: IEmptyProps) {
|
||||
const { t } = useI18N();
|
||||
|
||||
return (
|
||||
<View style={style.wrapper}>
|
||||
<ThemeText fontSize="title">
|
||||
{props?.content ?? t("common.emptyList")}
|
||||
</ThemeText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
wrapper: {
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
minHeight: rpx(300),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import React from "react";
|
||||
import { Pressable, StyleSheet } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import { iconSizeConst } from "@/constants/uiConst";
|
||||
import Icon, { IIconName } from "@/components/base/icon.tsx";
|
||||
|
||||
interface IFabProps {
|
||||
icon?: IIconName;
|
||||
onPress?: () => void;
|
||||
}
|
||||
export default function Fab(props: IFabProps) {
|
||||
const { icon, onPress } = props;
|
||||
|
||||
const colors = useColors();
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
style={[
|
||||
styles.container,
|
||||
{
|
||||
backgroundColor: colors.backdrop,
|
||||
shadowColor: colors.shadow,
|
||||
},
|
||||
]}>
|
||||
{icon ? (
|
||||
<Icon
|
||||
name={icon}
|
||||
color={colors.text}
|
||||
size={iconSizeConst.normal}
|
||||
/>
|
||||
) : null}
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
width: rpx(108),
|
||||
height: rpx(108),
|
||||
borderRadius: rpx(54),
|
||||
position: "absolute",
|
||||
zIndex: 10010,
|
||||
right: rpx(36),
|
||||
bottom: rpx(72),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 5,
|
||||
},
|
||||
shadowOpacity: 0.34,
|
||||
shadowRadius: 6.27,
|
||||
|
||||
elevation: 10,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ImageRequireSource } from "react-native";
|
||||
import FastImage, { FastImageProps } from "react-native-fast-image";
|
||||
|
||||
interface IFastImageProps {
|
||||
style: FastImageProps["style"];
|
||||
defaultSource?: FastImageProps["defaultSource"];
|
||||
placeholderSource?: ImageRequireSource;
|
||||
source?: FastImageProps["source"] | string;
|
||||
}
|
||||
export default function (props: IFastImageProps) {
|
||||
const { style, placeholderSource, defaultSource, source } = props ?? {};
|
||||
const [isError, setIsError] = useState(false);
|
||||
|
||||
|
||||
let realSource: FastImageProps["source"];
|
||||
if (typeof source === "string") {
|
||||
realSource = { uri: source };
|
||||
if (source.length === 0) {
|
||||
realSource = placeholderSource;
|
||||
}
|
||||
} else if (source){
|
||||
realSource = source;
|
||||
} else {
|
||||
realSource = placeholderSource;
|
||||
}
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setIsError(false);
|
||||
}, [source]);
|
||||
|
||||
|
||||
return (
|
||||
<FastImage
|
||||
style={style}
|
||||
source={isError ? placeholderSource : realSource}
|
||||
onError={() => {
|
||||
setIsError(true);
|
||||
console.error("Image load error:", realSource);
|
||||
}}
|
||||
defaultSource={defaultSource}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import React from "react";
|
||||
import { StyleProp, ViewStyle } from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
|
||||
interface IHorizontalSafeAreaViewProps {
|
||||
mode?: "margin" | "padding";
|
||||
children: JSX.Element | JSX.Element[];
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
export default function HorizontalSafeAreaView(
|
||||
props: IHorizontalSafeAreaViewProps,
|
||||
) {
|
||||
const { children, style, mode } = props;
|
||||
return (
|
||||
<SafeAreaView style={style} mode={mode} edges={["right", "left"]}>
|
||||
{children}
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
// This file is generated by generate-assets.mjs. DO NOT MODIFY.
|
||||
import { SvgProps } from "react-native-svg";
|
||||
|
||||
import AlarmOutlineIcon from "@/assets/icons/alarm-outline.svg";
|
||||
import AlbumOutlineIcon from "@/assets/icons/album-outline.svg";
|
||||
import ArchiveBoxXMarkIcon from "@/assets/icons/archive-box-x-mark.svg";
|
||||
import ArrowDownTrayIcon from "@/assets/icons/arrow-down-tray.svg";
|
||||
import ArrowLeftIcon from "@/assets/icons/arrow-left.svg";
|
||||
import ArrowLongLeftIcon from "@/assets/icons/arrow-long-left.svg";
|
||||
import ArrowPathIcon from "@/assets/icons/arrow-path.svg";
|
||||
import ArrowRightEndOnRectangleIcon from "@/assets/icons/arrow-right-end-on-rectangle.svg";
|
||||
import ArrowUpTrayIcon from "@/assets/icons/arrow-up-tray.svg";
|
||||
import ArrowUturnLeftIcon from "@/assets/icons/arrow-uturn-left.svg";
|
||||
import ArrowsLeftRightIcon from "@/assets/icons/arrows-left-right.svg";
|
||||
import Bars3Icon from "@/assets/icons/bars-3.svg";
|
||||
import BookmarkSquareIcon from "@/assets/icons/bookmark-square.svg";
|
||||
import ChatBubbleOvalLeftEllipsisIcon from "@/assets/icons/chat-bubble-oval-left-ellipsis.svg";
|
||||
import CheckCircleOutlineIcon from "@/assets/icons/check-circle-outline.svg";
|
||||
import CheckCircleIcon from "@/assets/icons/check-circle.svg";
|
||||
import CheckIcon from "@/assets/icons/check.svg";
|
||||
import CircleStackIcon from "@/assets/icons/circle-stack.svg";
|
||||
import ClockOutlineIcon from "@/assets/icons/clock-outline.svg";
|
||||
import CodeBracketSquareIcon from "@/assets/icons/code-bracket-square.svg";
|
||||
import Cog8ToothIcon from "@/assets/icons/cog-8-tooth.svg";
|
||||
import CrossHairIcon from "@/assets/icons/crosshair.svg";
|
||||
import DocumentOutlineIcon from "@/assets/icons/document-outline.svg";
|
||||
import EllipsisVerticalIcon from "@/assets/icons/ellipsis-vertical.svg";
|
||||
import ExclamationCircleIcon from "@/assets/icons/exclamation-circle.svg";
|
||||
import FireOutlineIcon from "@/assets/icons/fire-outline.svg";
|
||||
import FireIcon from "@/assets/icons/fire.svg";
|
||||
import FolderMusicOutlineIcon from "@/assets/icons/folder-music-outline.svg";
|
||||
import FolderOutlineIcon from "@/assets/icons/folder-outline.svg";
|
||||
import FolderPlusIcon from "@/assets/icons/folder-plus.svg";
|
||||
import FontSizeIcon from "@/assets/icons/font-size.svg";
|
||||
import HandThumbUpIcon from "@/assets/icons/hand-thumb-up.svg";
|
||||
import HeartOutlineIcon from "@/assets/icons/heart-outline.svg";
|
||||
import HeartIcon from "@/assets/icons/heart.svg";
|
||||
import HomeOutlineIcon from "@/assets/icons/home-outline.svg";
|
||||
import IdentificationIcon from "@/assets/icons/identification.svg";
|
||||
import InboxArrowDownIcon from "@/assets/icons/inbox-arrow-down.svg";
|
||||
import InformationCircleIcon from "@/assets/icons/information-circle.svg";
|
||||
import JavascriptIcon from "@/assets/icons/javascript.svg";
|
||||
import LanguageIcon from "@/assets/icons/language.svg";
|
||||
import LinkSlashIcon from "@/assets/icons/link-slash.svg";
|
||||
import LinkIcon from "@/assets/icons/link.svg";
|
||||
import LyricIcon from "@/assets/icons/lyric.svg";
|
||||
import MagnifyingGlassIcon from "@/assets/icons/magnifying-glass.svg";
|
||||
import MinusIcon from "@/assets/icons/minus.svg";
|
||||
import MotionPlayIcon from "@/assets/icons/motion-play.svg";
|
||||
import MusicalNoteIcon from "@/assets/icons/musical-note.svg";
|
||||
import PauseCircleOutlineIcon from "@/assets/icons/pause-circle-outline.svg";
|
||||
import PauseIcon from "@/assets/icons/pause.svg";
|
||||
import PencilOutlineIcon from "@/assets/icons/pencil-outline.svg";
|
||||
import PencilSquareIcon from "@/assets/icons/pencil-square.svg";
|
||||
import PlayCircleOutlineIcon from "@/assets/icons/play-circle-outline.svg";
|
||||
import PlayCircleIcon from "@/assets/icons/play-circle.svg";
|
||||
import PlayIcon from "@/assets/icons/play.svg";
|
||||
import PlaylistIcon from "@/assets/icons/playlist.svg";
|
||||
import PlusIcon from "@/assets/icons/plus.svg";
|
||||
import PowerOutlineIcon from "@/assets/icons/power-outline.svg";
|
||||
import QuestionMarkCircleIcon from "@/assets/icons/question-mark-circle.svg";
|
||||
import RepeatSong1Icon from "@/assets/icons/repeat-song-1.svg";
|
||||
import RepeatSongIcon from "@/assets/icons/repeat-song.svg";
|
||||
import ShareIcon from "@/assets/icons/share.svg";
|
||||
import ShieldKeyholeOutlineIcon from "@/assets/icons/shield-keyhole-outline.svg";
|
||||
import ShuffleIcon from "@/assets/icons/shuffle.svg";
|
||||
import SkipLeftIcon from "@/assets/icons/skip-left.svg";
|
||||
import SkipRightIcon from "@/assets/icons/skip-right.svg";
|
||||
import SortOutlineIcon from "@/assets/icons/sort-outline.svg";
|
||||
import StrategyIcon from "@/assets/icons/strategy.svg";
|
||||
import TShirtOutlineIcon from "@/assets/icons/t-shirt-outline.svg";
|
||||
import TranslationIcon from "@/assets/icons/translation.svg";
|
||||
import TrashOutlineIcon from "@/assets/icons/trash-outline.svg";
|
||||
import TrophyIcon from "@/assets/icons/trophy.svg";
|
||||
import UserIcon from "@/assets/icons/user.svg";
|
||||
import XMarkIcon from "@/assets/icons/x-mark.svg";
|
||||
|
||||
export type IIconName =
|
||||
| "alarm-outline"
|
||||
| "album-outline"
|
||||
| "archive-box-x-mark"
|
||||
| "arrow-down-tray"
|
||||
| "arrow-left"
|
||||
| "arrow-long-left"
|
||||
| "arrow-path"
|
||||
| "arrow-right-end-on-rectangle"
|
||||
| "arrow-up-tray"
|
||||
| "arrow-uturn-left"
|
||||
| "arrows-left-right"
|
||||
| "bars-3"
|
||||
| "bookmark-square"
|
||||
| "chat-bubble-oval-left-ellipsis"
|
||||
| "check-circle-outline"
|
||||
| "check-circle"
|
||||
| "check"
|
||||
| "circle-stack"
|
||||
| "clock-outline"
|
||||
| "code-bracket-square"
|
||||
| "cog-8-tooth"
|
||||
| "crosshair"
|
||||
| "document-outline"
|
||||
| "ellipsis-vertical"
|
||||
| "exclamation-circle"
|
||||
| "fire-outline"
|
||||
| "fire"
|
||||
| "folder-music-outline"
|
||||
| "folder-outline"
|
||||
| "folder-plus"
|
||||
| "font-size"
|
||||
| "hand-thumb-up"
|
||||
| "heart-outline"
|
||||
| "heart"
|
||||
| "home-outline"
|
||||
| "identification"
|
||||
| "inbox-arrow-down"
|
||||
| "information-circle"
|
||||
| "javascript"
|
||||
| "language"
|
||||
| "link-slash"
|
||||
| "link"
|
||||
| "lyric"
|
||||
| "magnifying-glass"
|
||||
| "minus"
|
||||
| "motion-play"
|
||||
| "musical-note"
|
||||
| "pause-circle-outline"
|
||||
| "pause"
|
||||
| "pencil-outline"
|
||||
| "pencil-square"
|
||||
| "play-circle-outline"
|
||||
| "play-circle"
|
||||
| "play"
|
||||
| "playlist"
|
||||
| "plus"
|
||||
| "power-outline"
|
||||
| "question-mark-circle"
|
||||
| "repeat-song-1"
|
||||
| "repeat-song"
|
||||
| "share"
|
||||
| "shield-keyhole-outline"
|
||||
| "shuffle"
|
||||
| "skip-left"
|
||||
| "skip-right"
|
||||
| "sort-outline"
|
||||
| "strategy"
|
||||
| "t-shirt-outline"
|
||||
| "translation"
|
||||
| "trash-outline"
|
||||
| "trophy"
|
||||
| "user"
|
||||
| "x-mark";
|
||||
|
||||
interface IProps extends SvgProps {
|
||||
/** 图标名称 */
|
||||
name: IIconName;
|
||||
/** 图标大小 */
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const iconMap = {
|
||||
"alarm-outline": AlarmOutlineIcon,
|
||||
"album-outline": AlbumOutlineIcon,
|
||||
"archive-box-x-mark": ArchiveBoxXMarkIcon,
|
||||
"arrow-down-tray": ArrowDownTrayIcon,
|
||||
"arrow-left": ArrowLeftIcon,
|
||||
"arrow-long-left": ArrowLongLeftIcon,
|
||||
"arrow-path": ArrowPathIcon,
|
||||
"arrow-right-end-on-rectangle": ArrowRightEndOnRectangleIcon,
|
||||
"arrow-up-tray": ArrowUpTrayIcon,
|
||||
"arrow-uturn-left": ArrowUturnLeftIcon,
|
||||
"arrows-left-right": ArrowsLeftRightIcon,
|
||||
"bars-3": Bars3Icon,
|
||||
"bookmark-square": BookmarkSquareIcon,
|
||||
"chat-bubble-oval-left-ellipsis": ChatBubbleOvalLeftEllipsisIcon,
|
||||
"check-circle-outline": CheckCircleOutlineIcon,
|
||||
"check-circle": CheckCircleIcon,
|
||||
check: CheckIcon,
|
||||
"circle-stack": CircleStackIcon,
|
||||
"clock-outline": ClockOutlineIcon,
|
||||
"code-bracket-square": CodeBracketSquareIcon,
|
||||
"cog-8-tooth": Cog8ToothIcon,
|
||||
crosshair: CrossHairIcon,
|
||||
"document-outline": DocumentOutlineIcon,
|
||||
"ellipsis-vertical": EllipsisVerticalIcon,
|
||||
"exclamation-circle": ExclamationCircleIcon,
|
||||
"fire-outline": FireOutlineIcon,
|
||||
fire: FireIcon,
|
||||
"folder-music-outline": FolderMusicOutlineIcon,
|
||||
"folder-outline": FolderOutlineIcon,
|
||||
"folder-plus": FolderPlusIcon,
|
||||
"font-size": FontSizeIcon,
|
||||
"hand-thumb-up": HandThumbUpIcon,
|
||||
"heart-outline": HeartOutlineIcon,
|
||||
heart: HeartIcon,
|
||||
"home-outline": HomeOutlineIcon,
|
||||
identification: IdentificationIcon,
|
||||
"inbox-arrow-down": InboxArrowDownIcon,
|
||||
"information-circle": InformationCircleIcon,
|
||||
javascript: JavascriptIcon,
|
||||
"link-slash": LinkSlashIcon,
|
||||
link: LinkIcon,
|
||||
language: LanguageIcon,
|
||||
lyric: LyricIcon,
|
||||
"magnifying-glass": MagnifyingGlassIcon,
|
||||
minus: MinusIcon,
|
||||
"motion-play": MotionPlayIcon,
|
||||
"musical-note": MusicalNoteIcon,
|
||||
"pause-circle-outline": PauseCircleOutlineIcon,
|
||||
pause: PauseIcon,
|
||||
"pencil-outline": PencilOutlineIcon,
|
||||
"pencil-square": PencilSquareIcon,
|
||||
"play-circle-outline": PlayCircleOutlineIcon,
|
||||
"play-circle": PlayCircleIcon,
|
||||
play: PlayIcon,
|
||||
playlist: PlaylistIcon,
|
||||
plus: PlusIcon,
|
||||
"power-outline": PowerOutlineIcon,
|
||||
"question-mark-circle": QuestionMarkCircleIcon,
|
||||
"repeat-song-1": RepeatSong1Icon,
|
||||
"repeat-song": RepeatSongIcon,
|
||||
share: ShareIcon,
|
||||
"shield-keyhole-outline": ShieldKeyholeOutlineIcon,
|
||||
shuffle: ShuffleIcon,
|
||||
"skip-left": SkipLeftIcon,
|
||||
"skip-right": SkipRightIcon,
|
||||
"sort-outline": SortOutlineIcon,
|
||||
strategy: StrategyIcon,
|
||||
"t-shirt-outline": TShirtOutlineIcon,
|
||||
translation: TranslationIcon,
|
||||
"trash-outline": TrashOutlineIcon,
|
||||
trophy: TrophyIcon,
|
||||
user: UserIcon,
|
||||
"x-mark": XMarkIcon,
|
||||
} as const;
|
||||
|
||||
export default function Icon(props: IProps) {
|
||||
const { name, size } = props;
|
||||
|
||||
const newProps = {
|
||||
...props,
|
||||
width: props.width ?? size,
|
||||
height: props.width ?? size,
|
||||
} as SvgProps;
|
||||
|
||||
const Component = iconMap[name];
|
||||
|
||||
return <Component {...newProps} />;
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import { ColorKey, colorMap, iconSizeConst } from "@/constants/uiConst";
|
||||
import { TapGestureHandler } from "react-native-gesture-handler";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import { SvgProps } from "react-native-svg";
|
||||
import Icon, { IIconName } from "@/components/base/icon.tsx";
|
||||
|
||||
interface IIconButtonProps extends SvgProps {
|
||||
name: IIconName;
|
||||
style?: SvgProps["style"];
|
||||
sizeType?: keyof typeof iconSizeConst;
|
||||
fontColor?: ColorKey;
|
||||
color?: string;
|
||||
onPress?: () => void;
|
||||
accessibilityLabel?: string;
|
||||
}
|
||||
export function IconButtonWithGesture(props: IIconButtonProps) {
|
||||
const {
|
||||
name,
|
||||
sizeType: size = "normal",
|
||||
fontColor = "normal",
|
||||
onPress,
|
||||
style,
|
||||
accessibilityLabel,
|
||||
} = props;
|
||||
const colors = useColors();
|
||||
const textSize = iconSizeConst[size];
|
||||
const color = colors[colorMap[fontColor]];
|
||||
return (
|
||||
<TapGestureHandler onActivated={onPress}>
|
||||
<View>
|
||||
<Icon
|
||||
accessible
|
||||
accessibilityLabel={accessibilityLabel}
|
||||
name={name}
|
||||
color={color}
|
||||
style={[{ minWidth: textSize }, styles.textCenter, style]}
|
||||
size={textSize}
|
||||
/>
|
||||
</View>
|
||||
</TapGestureHandler>
|
||||
);
|
||||
}
|
||||
|
||||
export default function IconButton(props: IIconButtonProps) {
|
||||
const { sizeType = "normal", fontColor = "normal", style, color } = props;
|
||||
const colors = useColors();
|
||||
const size = iconSizeConst[sizeType];
|
||||
|
||||
return (
|
||||
<Icon
|
||||
{...props}
|
||||
color={color ?? colors[colorMap[fontColor]]}
|
||||
style={[{ minWidth: size }, styles.textCenter, style]}
|
||||
size={size}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
textCenter: {
|
||||
height: "100%",
|
||||
textAlignVertical: "center",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
import { StyleProp, StyleSheet, ViewStyle } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import ThemeText from "./themeText";
|
||||
import { iconSizeConst } from "@/constants/uiConst";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import { TouchableOpacity } from "react-native-gesture-handler";
|
||||
import Icon, { IIconName } from "@/components/base/icon.tsx";
|
||||
|
||||
interface IProps {
|
||||
icon: IIconName;
|
||||
onPress?: () => void;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
children?: string;
|
||||
}
|
||||
export default function (props: IProps) {
|
||||
const { icon, children, onPress, containerStyle } = props;
|
||||
const colors = useColors();
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.7}
|
||||
style={[style.container, containerStyle]}
|
||||
onPress={onPress}>
|
||||
<Icon name={icon} size={iconSizeConst.light} color={colors.text} />
|
||||
<ThemeText style={style.text} fontSize={"content"}>
|
||||
{children}
|
||||
</ThemeText>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
container: {
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
paddingHorizontal: rpx(16),
|
||||
paddingVertical: rpx(8),
|
||||
},
|
||||
text: {
|
||||
marginLeft: rpx(8),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
import { Image, ImageProps } from "react-native";
|
||||
|
||||
interface IImageProps extends ImageProps {
|
||||
uri?: string | null;
|
||||
emptySrc?: any;
|
||||
}
|
||||
export default function (props: Omit<IImageProps, "source">) {
|
||||
const { uri, emptySrc } = props;
|
||||
const source = typeof uri === "string"
|
||||
? {
|
||||
uri,
|
||||
}
|
||||
: emptySrc;
|
||||
return <Image {...props} source={source} />;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import { StyleProp, StyleSheet, TouchableOpacity, ViewStyle } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import Image from "./image";
|
||||
import { ImgAsset } from "@/constants/assetsConst";
|
||||
import ThemeText from "./themeText";
|
||||
|
||||
interface IImageBtnProps {
|
||||
uri?: string;
|
||||
title?: string;
|
||||
onPress?: () => void;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
}
|
||||
export default function ImageBtn(props: IImageBtnProps) {
|
||||
const { onPress, uri, title, style: _style } = props ?? {};
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.5}
|
||||
onPress={onPress}
|
||||
style={[style.wrapper, _style]}>
|
||||
<Image
|
||||
style={style.image}
|
||||
uri={uri}
|
||||
emptySrc={ImgAsset.albumDefault}
|
||||
/>
|
||||
<ThemeText
|
||||
fontSize="subTitle"
|
||||
numberOfLines={2}
|
||||
ellipsizeMode="tail">
|
||||
{title ?? ""}
|
||||
</ThemeText>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
wrapper: {
|
||||
width: rpx(210),
|
||||
height: rpx(290),
|
||||
flexGrow: 0,
|
||||
flexShrink: 0,
|
||||
},
|
||||
image: {
|
||||
width: rpx(210),
|
||||
height: rpx(210),
|
||||
borderRadius: rpx(12),
|
||||
marginBottom: rpx(16),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import useColors from "@/hooks/useColors";
|
||||
import rpx from "@/utils/rpx";
|
||||
import Color from "color";
|
||||
import React from "react";
|
||||
import { StyleSheet, TextInput, TextInputProps } from "react-native";
|
||||
|
||||
interface IInputProps extends TextInputProps {
|
||||
fontColor?: string;
|
||||
hasHorizontalPadding?: boolean;
|
||||
}
|
||||
|
||||
export default function Input(props: IInputProps) {
|
||||
const { fontColor, hasHorizontalPadding = true } = props;
|
||||
const colors = useColors();
|
||||
|
||||
const currentColor = fontColor ?? colors.text;
|
||||
|
||||
const defaultStyle = {
|
||||
color: currentColor,
|
||||
};
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
placeholderTextColor={Color(currentColor).alpha(0.7).toString()}
|
||||
{...props}
|
||||
style={[
|
||||
hasHorizontalPadding
|
||||
? styles.container
|
||||
: styles.containerWithoutPadding,
|
||||
defaultStyle,
|
||||
props?.style,
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
paddingVertical: 0,
|
||||
paddingHorizontal: rpx(24),
|
||||
},
|
||||
containerWithoutPadding: {
|
||||
padding: 0,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import React, { useState } from "react";
|
||||
import { GestureResponderEvent, StyleSheet, TextProps } from "react-native";
|
||||
import { fontSizeConst, fontWeightConst } from "@/constants/uiConst";
|
||||
import openUrl from "@/utils/openUrl";
|
||||
import ThemeText from "./themeText";
|
||||
import Color from "color";
|
||||
|
||||
type ILinkTextProps = TextProps & {
|
||||
fontSize?: keyof typeof fontSizeConst;
|
||||
fontWeight?: keyof typeof fontWeightConst;
|
||||
linkTo?: string;
|
||||
onPress?: (event: GestureResponderEvent) => void;
|
||||
};
|
||||
|
||||
export default function LinkText(props: ILinkTextProps) {
|
||||
const [isPressed, setIsPressed] = useState(false);
|
||||
|
||||
return (
|
||||
<ThemeText
|
||||
{...props}
|
||||
style={[style.linkText, isPressed ? style.pressed : null]}
|
||||
onPressIn={() => {
|
||||
setIsPressed(true);
|
||||
}}
|
||||
onPress={evt => {
|
||||
if (props.onPress) {
|
||||
props.onPress(evt);
|
||||
} else {
|
||||
props?.linkTo && openUrl(props.linkTo);
|
||||
}
|
||||
}}
|
||||
onPressOut={() => {
|
||||
setIsPressed(false);
|
||||
}}>
|
||||
{props.children}
|
||||
</ThemeText>
|
||||
);
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
linkText: {
|
||||
color: "#66ccff",
|
||||
textDecorationLine: "underline",
|
||||
},
|
||||
pressed: {
|
||||
color: Color("#66ccff").alpha(0.4).toString(),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { RequestStateCode } from "@/constants/commonConst";
|
||||
import { fontSizeConst } from "@/constants/uiConst";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import rpx from "@/utils/rpx";
|
||||
import React from "react";
|
||||
import { ActivityIndicator, StyleSheet, TouchableOpacity, View } from "react-native";
|
||||
import ThemeText from "./themeText";
|
||||
import { useI18N } from "@/core/i18n";
|
||||
|
||||
interface IEmptyProps {
|
||||
state: RequestStateCode
|
||||
onRetry?: () => void;
|
||||
}
|
||||
export default function ListEmpty(props: IEmptyProps) {
|
||||
const { state, onRetry } = props;
|
||||
|
||||
const colors = useColors();
|
||||
const { t } = useI18N();
|
||||
|
||||
if (state === RequestStateCode.FINISHED || state === RequestStateCode.PARTLY_DONE) {
|
||||
return <View style={style.wrapper}>
|
||||
<ThemeText fontSize="title">
|
||||
{t("common.emptyList")}
|
||||
</ThemeText>
|
||||
</View>;
|
||||
} else if (state === RequestStateCode.PENDING_FIRST_PAGE) {
|
||||
return <View style={style.wrapper}>
|
||||
<ActivityIndicator animating color={colors.text} size={fontSizeConst.appbar}/>
|
||||
<ThemeText
|
||||
fontSize="title"
|
||||
fontWeight="semibold">
|
||||
{t("common.loading")}
|
||||
</ThemeText>
|
||||
</View>;
|
||||
} else if (state === RequestStateCode.ERROR) {
|
||||
return <View style={style.wrapper}>
|
||||
<ThemeText fontSize="title">
|
||||
{t("common.error")}
|
||||
</ThemeText>
|
||||
<TouchableOpacity onPress={onRetry} style={style.retryButton}>
|
||||
<ThemeText>{t("common.clickToRetry")}</ThemeText>
|
||||
</TouchableOpacity>
|
||||
</View>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
wrapper: {
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
minHeight: rpx(540),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
gap: rpx(36),
|
||||
},
|
||||
retryButton: {
|
||||
paddingVertical: rpx(24),
|
||||
paddingHorizontal: rpx(48),
|
||||
borderRadius: rpx(36),
|
||||
backgroundColor: "rgba(128, 128, 128, 0.2)",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,74 @@
|
||||
import React from "react";
|
||||
import { ActivityIndicator, StyleSheet, Text, View } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import { fontSizeConst } from "@/constants/uiConst";
|
||||
import ThemeText from "./themeText";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import { RequestStateCode } from "@/constants/commonConst";
|
||||
import { Pressable } from "react-native-gesture-handler";
|
||||
import { useI18N } from "@/core/i18n";
|
||||
|
||||
|
||||
interface IProps {
|
||||
state: RequestStateCode;
|
||||
onRetry?: () => void;
|
||||
}
|
||||
|
||||
export default function ListFooter(props: IProps) {
|
||||
const { state } = props;
|
||||
|
||||
const colors = useColors();
|
||||
const { t } = useI18N();
|
||||
|
||||
|
||||
if (state === RequestStateCode.ERROR) {
|
||||
return <View style={style.wrapper} >
|
||||
<Pressable hitSlop={{
|
||||
top: rpx(36),
|
||||
bottom: rpx(36),
|
||||
left: rpx(72),
|
||||
right: rpx(72),
|
||||
}} onPress={props.onRetry}>
|
||||
<ThemeText fontSize="content" fontColor="textSecondary">
|
||||
{t("common.failToLoad")}<Text style={[style.underline, {
|
||||
textDecorationColor: colors.textSecondary,
|
||||
}]}>{" "}{t("common.clickToRetry")}</Text>
|
||||
</ThemeText>
|
||||
</Pressable>
|
||||
</View>;
|
||||
} else if (state === RequestStateCode.PENDING_REST_PAGE || state === RequestStateCode.PARTLY_DONE) {
|
||||
return <View style={style.wrapper}>
|
||||
<ActivityIndicator
|
||||
animating
|
||||
color={colors.textSecondary}
|
||||
size={fontSizeConst.appbar}
|
||||
/>
|
||||
<ThemeText fontColor='textSecondary'>{t("common.loading")}</ThemeText>
|
||||
</View>;
|
||||
} else if (state === RequestStateCode.FINISHED) {
|
||||
return <View style={style.wrapper}>
|
||||
<ThemeText fontSize="content" fontColor="textSecondary">
|
||||
{t("common.listReachEnd")}
|
||||
</ThemeText>
|
||||
</View>;
|
||||
}
|
||||
|
||||
// PENDING_FIRST_PAGE, IDLE
|
||||
return null;
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
wrapper: {
|
||||
width: "100%",
|
||||
height: rpx(120),
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
columnGap: rpx(24),
|
||||
},
|
||||
underline: {
|
||||
textDecorationLine: "underline",
|
||||
textDecorationStyle: "solid",
|
||||
},
|
||||
|
||||
});
|
||||
@@ -0,0 +1,351 @@
|
||||
import React, { ReactNode } from "react";
|
||||
import {
|
||||
StyleProp,
|
||||
StyleSheet,
|
||||
TextProps,
|
||||
TextStyle,
|
||||
TouchableHighlight,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
ViewStyle,
|
||||
} from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import useColors, { CustomizedColors } from "@/hooks/useColors";
|
||||
import ThemeText from "./themeText";
|
||||
import {
|
||||
fontSizeConst,
|
||||
fontWeightConst,
|
||||
iconSizeConst,
|
||||
} from "@/constants/uiConst";
|
||||
import FastImage from "./fastImage";
|
||||
import { ImageStyle } from "react-native-fast-image";
|
||||
import Icon, { IIconName } from "@/components/base/icon.tsx";
|
||||
|
||||
interface IListItemProps {
|
||||
// 是否有左右边距
|
||||
withHorizontalPadding?: boolean;
|
||||
// 左边距
|
||||
leftPadding?: number;
|
||||
// 右边距
|
||||
rightPadding?: number;
|
||||
// height:
|
||||
style?: StyleProp<ViewStyle>;
|
||||
// 高度类型
|
||||
heightType?: "big" | "small" | "smallest" | "normal" | "none";
|
||||
children?: ReactNode;
|
||||
onPress?: () => void;
|
||||
onLongPress?: () => void;
|
||||
}
|
||||
|
||||
const defaultPadding = rpx(24);
|
||||
const defaultActionWidth = rpx(80);
|
||||
|
||||
const Size = {
|
||||
big: rpx(120),
|
||||
normal: rpx(108),
|
||||
small: rpx(96),
|
||||
smallest: rpx(72),
|
||||
none: undefined,
|
||||
};
|
||||
|
||||
function ListItem(props: IListItemProps) {
|
||||
const {
|
||||
withHorizontalPadding,
|
||||
leftPadding = defaultPadding,
|
||||
rightPadding = defaultPadding,
|
||||
style,
|
||||
heightType = "normal",
|
||||
children,
|
||||
onPress,
|
||||
onLongPress,
|
||||
} = props;
|
||||
|
||||
const defaultStyle: StyleProp<ViewStyle> = {
|
||||
paddingLeft: withHorizontalPadding ? leftPadding : 0,
|
||||
paddingRight: withHorizontalPadding ? rightPadding : 0,
|
||||
height: Size[heightType],
|
||||
};
|
||||
|
||||
const colors = useColors();
|
||||
|
||||
return (
|
||||
<TouchableHighlight
|
||||
style={styles.container}
|
||||
underlayColor={colors.listActive}
|
||||
onPress={onPress}
|
||||
onLongPress={onLongPress}>
|
||||
<View style={[styles.container, defaultStyle, style]}>
|
||||
{children}
|
||||
</View>
|
||||
</TouchableHighlight>
|
||||
);
|
||||
}
|
||||
|
||||
interface IListItemTextProps {
|
||||
children?: number | string;
|
||||
fontSize?: keyof typeof fontSizeConst;
|
||||
fontColor?: keyof CustomizedColors;
|
||||
fontWeight?: keyof typeof fontWeightConst;
|
||||
width?: number;
|
||||
position?: "left" | "right" | "none";
|
||||
fixedWidth?: boolean;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
contentStyle?: StyleProp<TextStyle>;
|
||||
contentProps?: TextProps;
|
||||
}
|
||||
|
||||
function ListItemText(props: IListItemTextProps) {
|
||||
const {
|
||||
children,
|
||||
fontSize,
|
||||
fontWeight,
|
||||
fontColor,
|
||||
position = "left",
|
||||
fixedWidth,
|
||||
width,
|
||||
containerStyle,
|
||||
contentStyle,
|
||||
contentProps = {},
|
||||
} = props;
|
||||
|
||||
const defaultStyle: StyleProp<ViewStyle> = {
|
||||
marginRight: position === "left" ? defaultPadding : 0,
|
||||
marginLeft: position === "right" ? defaultPadding : 0,
|
||||
width: fixedWidth ? width ?? defaultActionWidth : undefined,
|
||||
flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.actionBase, defaultStyle, containerStyle]}>
|
||||
<ThemeText
|
||||
fontSize={fontSize}
|
||||
style={contentStyle}
|
||||
fontWeight={fontWeight}
|
||||
fontColor={fontColor}
|
||||
{...contentProps}>
|
||||
{children}
|
||||
</ThemeText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
interface IListItemIconProps {
|
||||
icon: IIconName;
|
||||
iconSize?: number;
|
||||
width?: number;
|
||||
position?: "left" | "right" | "none";
|
||||
fixedWidth?: boolean;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
contentStyle?: StyleProp<TextStyle>;
|
||||
onPress?: () => void;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
function ListItemIcon(props: IListItemIconProps) {
|
||||
const {
|
||||
icon,
|
||||
iconSize = iconSizeConst.normal,
|
||||
position = "left",
|
||||
fixedWidth,
|
||||
width,
|
||||
containerStyle,
|
||||
contentStyle,
|
||||
onPress,
|
||||
color,
|
||||
} = props;
|
||||
|
||||
const colors = useColors();
|
||||
|
||||
const defaultStyle: StyleProp<ViewStyle> = {
|
||||
marginRight: position === "left" ? defaultPadding : 0,
|
||||
marginLeft: position === "right" ? defaultPadding : 0,
|
||||
width: fixedWidth ? width ?? defaultActionWidth : undefined,
|
||||
flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined,
|
||||
};
|
||||
|
||||
const innerContent = (
|
||||
<View style={[styles.actionBase, defaultStyle, containerStyle]}>
|
||||
<Icon
|
||||
name={icon}
|
||||
size={iconSize}
|
||||
style={contentStyle}
|
||||
color={color || colors.text}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
return onPress ? (
|
||||
<TouchableOpacity onPress={onPress}>{innerContent}</TouchableOpacity>
|
||||
) : (
|
||||
innerContent
|
||||
);
|
||||
}
|
||||
|
||||
interface IListItemImageProps {
|
||||
uri?: string;
|
||||
fallbackImg?: number;
|
||||
imageSize?: number;
|
||||
width?: number;
|
||||
position?: "left" | "right";
|
||||
fixedWidth?: boolean;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
contentStyle?: StyleProp<ImageStyle>;
|
||||
maskIcon?: IIconName | null;
|
||||
}
|
||||
|
||||
function ListItemImage(props: IListItemImageProps) {
|
||||
const {
|
||||
uri,
|
||||
fallbackImg,
|
||||
position = "left",
|
||||
fixedWidth,
|
||||
width,
|
||||
containerStyle,
|
||||
contentStyle,
|
||||
maskIcon,
|
||||
} = props;
|
||||
|
||||
const defaultStyle: StyleProp<ViewStyle> = {
|
||||
marginRight: position === "left" ? defaultPadding : 0,
|
||||
marginLeft: position === "right" ? defaultPadding : 0,
|
||||
width: fixedWidth ? width ?? defaultActionWidth : undefined,
|
||||
flexBasis: fixedWidth ? width ?? defaultActionWidth : undefined,
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={[styles.actionBase, defaultStyle, containerStyle]}>
|
||||
<FastImage
|
||||
style={[styles.leftImage, contentStyle]}
|
||||
source={uri}
|
||||
placeholderSource={fallbackImg}
|
||||
/>
|
||||
{maskIcon ? (
|
||||
<View style={[styles.leftImage, styles.imageMask]}>
|
||||
<Icon
|
||||
name={maskIcon}
|
||||
size={iconSizeConst.normal}
|
||||
color="red"
|
||||
/>
|
||||
</View>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
interface IContentProps {
|
||||
title?: ReactNode;
|
||||
children?: ReactNode;
|
||||
description?: ReactNode;
|
||||
containerStyle?: StyleProp<ViewStyle>;
|
||||
}
|
||||
|
||||
function Content(props: IContentProps) {
|
||||
const {
|
||||
children,
|
||||
title = children,
|
||||
description = null,
|
||||
containerStyle,
|
||||
} = props;
|
||||
|
||||
let realTitle;
|
||||
let realDescription;
|
||||
|
||||
if (typeof title === "string" || typeof title === "number") {
|
||||
realTitle = <ThemeText numberOfLines={1}>{title}</ThemeText>;
|
||||
} else {
|
||||
realTitle = title;
|
||||
}
|
||||
|
||||
if (typeof description === "string" || typeof description === "number") {
|
||||
realDescription = (
|
||||
<ThemeText
|
||||
numberOfLines={1}
|
||||
fontSize="description"
|
||||
fontColor="textSecondary"
|
||||
style={styles.contentDesc}>
|
||||
{description}
|
||||
</ThemeText>
|
||||
);
|
||||
} else {
|
||||
realDescription = description;
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[styles.itemContentContainer, containerStyle]}>
|
||||
{realTitle}
|
||||
{realDescription}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
export function ListItemHeader(props: { children?: ReactNode }) {
|
||||
const { children } = props;
|
||||
return (
|
||||
<ListItem
|
||||
withHorizontalPadding
|
||||
heightType="smallest"
|
||||
style={styles.listItemHeader}>
|
||||
{typeof children === "string" ? (
|
||||
<ThemeText
|
||||
fontSize="subTitle"
|
||||
fontColor="textSecondary"
|
||||
fontWeight="bold">
|
||||
{children}
|
||||
</ThemeText>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</ListItem>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
/** listitem */
|
||||
container: {
|
||||
width: "100%",
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
/** left */
|
||||
actionBase: {
|
||||
height: "100%",
|
||||
flexShrink: 0,
|
||||
flexGrow: 0,
|
||||
flexBasis: 0,
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
|
||||
leftImage: {
|
||||
width: rpx(80),
|
||||
height: rpx(80),
|
||||
borderRadius: rpx(16),
|
||||
},
|
||||
imageMask: {
|
||||
position: "absolute",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "#00000022",
|
||||
},
|
||||
itemContentContainer: {
|
||||
flex: 1,
|
||||
height: "100%",
|
||||
justifyContent: "center",
|
||||
},
|
||||
contentDesc: {
|
||||
marginTop: rpx(16),
|
||||
},
|
||||
|
||||
listItemHeader: {
|
||||
marginTop: rpx(20),
|
||||
},
|
||||
});
|
||||
|
||||
ListItem.Size = Size;
|
||||
ListItem.ListItemIcon = ListItemIcon;
|
||||
ListItem.ListItemImage = ListItemImage;
|
||||
ListItem.ListItemText = ListItemText;
|
||||
ListItem.Content = Content;
|
||||
|
||||
export default ListItem;
|
||||
@@ -0,0 +1,45 @@
|
||||
import React from "react";
|
||||
import { ActivityIndicator, StyleSheet, View } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import ThemeText from "./themeText";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import { useI18N } from "@/core/i18n";
|
||||
|
||||
interface ILoadingProps {
|
||||
text?: string;
|
||||
showText?: boolean;
|
||||
height?: number;
|
||||
color?: string;
|
||||
}
|
||||
export default function Loading(props: ILoadingProps) {
|
||||
const { showText = true, height, text, color } = props;
|
||||
const colors = useColors();
|
||||
const { t } = useI18N();
|
||||
|
||||
return (
|
||||
<View style={[style.wrapper, { height }]}>
|
||||
<ActivityIndicator animating color={color ?? colors.text} />
|
||||
{showText ? (
|
||||
<ThemeText
|
||||
color={color}
|
||||
fontSize="title"
|
||||
fontWeight="semibold"
|
||||
style={style.text}>
|
||||
{text ?? t("common.loading")}
|
||||
</ThemeText>
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
wrapper: {
|
||||
width: "100%",
|
||||
flex: 1,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
},
|
||||
text: {
|
||||
marginTop: rpx(48),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import ThemeText from "@/components/base/themeText";
|
||||
import { useI18N } from "@/core/i18n";
|
||||
|
||||
interface IProps {
|
||||
notSupportType?: string;
|
||||
}
|
||||
|
||||
export default function NoPlugin(props: IProps) {
|
||||
const { t } = useI18N();
|
||||
|
||||
return (
|
||||
<View style={style.wrapper}>
|
||||
<ThemeText fontSize="title">
|
||||
{props.notSupportType ? t("noPlugin.titleWithType", {
|
||||
type: props.notSupportType,
|
||||
}) : t("noPlugin.title")}
|
||||
</ThemeText>
|
||||
<ThemeText
|
||||
style={style.mt}
|
||||
fontSize="subTitle"
|
||||
fontColor="textSecondary">
|
||||
{t("noPlugin.description")}
|
||||
</ThemeText>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
wrapper: {
|
||||
width: rpx(750),
|
||||
flex: 1,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
mt: {
|
||||
marginTop: rpx(24),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import React, { memo } from "react";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import Image from "./image";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import Theme from "@/core/theme";
|
||||
|
||||
function PageBackground() {
|
||||
const theme = Theme.useTheme();
|
||||
const background = Theme.useBackground();
|
||||
const colors = useColors();
|
||||
|
||||
return (
|
||||
<>
|
||||
<View
|
||||
style={[
|
||||
style.wrapper,
|
||||
{
|
||||
backgroundColor:
|
||||
colors?.pageBackground ?? colors.background,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
{!theme.id.startsWith("p-") && background?.url ? (
|
||||
<Image
|
||||
uri={background.url}
|
||||
style={[
|
||||
style.wrapper,
|
||||
{
|
||||
opacity: background?.opacity ?? 0.6,
|
||||
},
|
||||
]}
|
||||
blurRadius={background?.blur ?? 20}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default memo(PageBackground, () => true);
|
||||
|
||||
const style = StyleSheet.create({
|
||||
wrapper: {
|
||||
position: "absolute",
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import React from "react";
|
||||
import { StyleSheet, TextProps } from "react-native";
|
||||
import ThemeText from "./themeText";
|
||||
import { fontSizeConst } from "@/constants/uiConst";
|
||||
|
||||
interface IParagraphProps extends TextProps {}
|
||||
export default function Paragraph(props: IParagraphProps) {
|
||||
return <ThemeText style={styles.container} {...props} />;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
fontSize: fontSizeConst.content,
|
||||
lineHeight: fontSizeConst.content * 1.8,
|
||||
marginVertical: 2,
|
||||
letterSpacing: 0.25,
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,129 @@
|
||||
import React from "react";
|
||||
import { Pressable, StyleSheet, View } from "react-native";
|
||||
import rpx from "@/utils/rpx";
|
||||
import { iconSizeConst } from "@/constants/uiConst";
|
||||
import { ROUTE_PATH, useNavigate } from "@/core/router";
|
||||
import ThemeText from "./themeText";
|
||||
import useColors from "@/hooks/useColors";
|
||||
import { showPanel } from "../panels/usePanel";
|
||||
import IconButton from "./iconButton";
|
||||
import TrackPlayer from "@/core/trackPlayer";
|
||||
import Toast from "@/utils/toast";
|
||||
import Icon from "@/components/base/icon.tsx";
|
||||
import MusicSheet, { useSheetIsStarred } from "@/core/musicSheet";
|
||||
import { MusicRepeatMode } from "@/constants/repeatModeConst";
|
||||
import { useI18N } from "@/core/i18n";
|
||||
|
||||
interface IProps {
|
||||
musicList: IMusic.IMusicItem[] | null;
|
||||
canStar?: boolean;
|
||||
musicSheet?: IMusic.IMusicSheetItem | null;
|
||||
}
|
||||
export default function (props: IProps) {
|
||||
const { musicList, canStar, musicSheet } = props;
|
||||
|
||||
const sheetName = musicSheet?.title;
|
||||
const sheetId = musicSheet?.id;
|
||||
|
||||
const colors = useColors();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useI18N();
|
||||
|
||||
const starred = useSheetIsStarred(musicSheet);
|
||||
|
||||
return (
|
||||
<View style={style.topWrapper}>
|
||||
<Pressable
|
||||
style={style.playAll}
|
||||
onPress={() => {
|
||||
if (musicList) {
|
||||
let defaultPlayMusic = musicList[0];
|
||||
if (
|
||||
TrackPlayer.repeatMode ===
|
||||
MusicRepeatMode.SHUFFLE
|
||||
) {
|
||||
defaultPlayMusic =
|
||||
musicList[
|
||||
Math.floor(Math.random() * musicList.length)
|
||||
];
|
||||
}
|
||||
TrackPlayer.playWithReplacePlayList(
|
||||
defaultPlayMusic,
|
||||
musicList,
|
||||
);
|
||||
}
|
||||
}}>
|
||||
<Icon
|
||||
name="play-circle"
|
||||
style={style.playAllIcon}
|
||||
size={iconSizeConst.normal}
|
||||
color={colors.text}
|
||||
/>
|
||||
<ThemeText fontWeight="bold">{t("playAllBar.title")}</ThemeText>
|
||||
</Pressable>
|
||||
{canStar && musicSheet ? (
|
||||
<IconButton
|
||||
name={starred ? "heart" : "heart-outline"}
|
||||
sizeType={"normal"}
|
||||
color={starred ? "#e31639" : undefined}
|
||||
style={style.optionButton}
|
||||
onPress={async () => {
|
||||
if (!starred) {
|
||||
MusicSheet.starMusicSheet(musicSheet);
|
||||
Toast.success(t("toast.hasStarred"));
|
||||
} else {
|
||||
MusicSheet.unstarMusicSheet(musicSheet);
|
||||
Toast.success(t("toast.hasUnstarred"));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
<IconButton
|
||||
name="folder-plus"
|
||||
sizeType={"normal"}
|
||||
style={style.optionButton}
|
||||
onPress={async () => {
|
||||
showPanel("AddToMusicSheet", {
|
||||
musicItem: musicList ?? [],
|
||||
newSheetDefaultName: sheetName,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
<IconButton
|
||||
name="pencil-square"
|
||||
sizeType={"normal"}
|
||||
style={style.optionButton}
|
||||
onPress={async () => {
|
||||
navigate(ROUTE_PATH.MUSIC_LIST_EDITOR, {
|
||||
musicList: musicList,
|
||||
musicSheet: {
|
||||
title: sheetName,
|
||||
id: sheetId,
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
const style = StyleSheet.create({
|
||||
/** playall */
|
||||
topWrapper: {
|
||||
height: rpx(84),
|
||||
paddingHorizontal: rpx(24),
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
playAll: {
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
alignItems: "center",
|
||||
},
|
||||
playAllIcon: {
|
||||
marginRight: rpx(12),
|
||||
},
|
||||
optionButton: {
|
||||
marginLeft: rpx(36),
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,79 @@
|
||||
import React, { ReactNode, useEffect, useRef } from "react";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import { atom, useAtomValue, useSetAtom } from "jotai";
|
||||
|
||||
interface IPortalNode {
|
||||
key: string | null;
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
const portalsAtom = atom<IPortalNode[]>([]);
|
||||
|
||||
interface IPortalProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
export default function Portal(props: IPortalProps) {
|
||||
const { children } = props;
|
||||
|
||||
const keyRef = useRef<string | null>(null);
|
||||
const setPortalsAtoms = useSetAtom(portalsAtom);
|
||||
|
||||
useEffect(() => {
|
||||
if (!keyRef.current) {
|
||||
// mount
|
||||
keyRef.current = Math.random().toString().slice(2);
|
||||
// console.log("MOUNT!", keyRef.current);
|
||||
setPortalsAtoms(portals => [
|
||||
...portals,
|
||||
{ key: keyRef.current, children },
|
||||
]);
|
||||
} else {
|
||||
// update
|
||||
// console.log("UPDATE!", keyRef.current);
|
||||
setPortalsAtoms(portals =>
|
||||
portals.map(it =>
|
||||
it.key === keyRef.current ? { ...it, children } : it,
|
||||
),
|
||||
);
|
||||
}
|
||||
}, [children]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (keyRef.current) {
|
||||
// console.log("UNMOUNT!", keyRef.current);
|
||||
setPortalsAtoms(portals =>
|
||||
portals.filter(it => it.key !== keyRef.current),
|
||||
);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
portalContainer: {
|
||||
zIndex: 20000,
|
||||
},
|
||||
});
|
||||
const composedStyle = [StyleSheet.absoluteFill, styles.portalContainer];
|
||||
|
||||
export function PortalHost() {
|
||||
const portals = useAtomValue(portalsAtom);
|
||||
|
||||
return (
|
||||
<>
|
||||
{portals.map(({ key, children }) => (
|
||||
<View
|
||||
key={key}
|
||||
collapsable={false}
|
||||
pointerEvents="box-none"
|
||||
style={composedStyle}>
|
||||
{children}
|
||||
</View>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||