feat(sidebar-avatar): Creat SidebarIndicator & Refactor Sidebar UI (#44)
This PR introduces several modifications to enhance the UI and code structure. Here are the details: 1. Added SidebarIndicator to improve the sidebar navigation experience. 2. Removed the BarIcon components from the Character at Bar. They have been replaced with the SidebarAvatar component. This change aims to refactor the BarIcon into UI components (TODO: Implement the UI component refactor and sidebar state flow) 3. Update shape and color of NewCharButton 4. ~~Refactored the getCharImage function by removing unused code, improving code cleanliness and maintainability.~~ * Reverted. Because, it's in use in so many places that it's probably too big a scope to cover in this PR. These changes aim to improve the UI and code structure. The Avatar UI has been updated to a circular shape, but it can be easily reverted to a rectangular shape based on your preference. Your feedback on this aspect would be greatly appreciated. Thank you for your attention to this pull request. Please let me know if you have any questions, suggestions, or concerns. Thank you! ## New Sidebar (Indicator, NewCharButton) Demo https://github.com/kwaroran/RisuAI/assets/34825352/6f709aed-3330-4c68-b2e6-7024607faaf8
This commit is contained in:
@@ -1,13 +1,16 @@
|
|||||||
|
<!-- TODO: REMOVE AND REFACTOR TO BASE BUTTON UI COMPONENT -->
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export let onClick = () => {};
|
||||||
|
export let additionalStyle: string | Promise<string> = "";
|
||||||
|
</script>
|
||||||
|
|
||||||
{#await additionalStyle}
|
{#await additionalStyle}
|
||||||
<button on:click={onClick} class="ico"><slot/></button>
|
<button on:click={onClick} class="ico"><slot /></button>
|
||||||
{:then as}
|
{:then as}
|
||||||
<button on:click={onClick} class="ico" style={as}><slot/></button>
|
<button on:click={onClick} class="ico" style={as}><slot /></button>
|
||||||
{/await}
|
{/await}
|
||||||
<script lang="ts">
|
|
||||||
export let onClick = () => {}
|
|
||||||
export let additionalStyle:string|Promise<string> = ''
|
|
||||||
</script>
|
|
||||||
<style>
|
<style>
|
||||||
.ico {
|
.ico {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@@ -15,13 +18,16 @@
|
|||||||
height: 3.5rem;
|
height: 3.5rem;
|
||||||
width: 3.5rem;
|
width: 3.5rem;
|
||||||
min-height: 3.5rem;
|
min-height: 3.5rem;
|
||||||
margin-top: 0.5rem;
|
|
||||||
--tw-shadow-color: 0, 0, 0;
|
--tw-shadow-color: 0, 0, 0;
|
||||||
--tw-shadow: 0 10px 15px -3px rgba(var(--tw-shadow-color), 0.1), 0 4px 6px -2px rgba(var(--tw-shadow-color), 0.05);
|
--tw-shadow: 0 10px 15px -3px rgba(var(--tw-shadow-color), 0.1),
|
||||||
-webkit-box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
0 4px 6px -2px rgba(var(--tw-shadow-color), 0.05);
|
||||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
-webkit-box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
|
||||||
|
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000),
|
||||||
|
var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgba(107, 114, 128, var(--tw-bg-opacity)); display: flex;
|
background-color: rgba(107, 114, 128, var(--tw-bg-opacity));
|
||||||
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
transition-property: background-color, border-color, color, fill, stroke;
|
transition-property: background-color, border-color, color, fill, stroke;
|
||||||
|
|||||||
@@ -1,189 +1,288 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { CharEmotion, SizeStore, selectedCharID, settingsOpen, sideBarStore } from "../../ts/stores";
|
import {
|
||||||
|
CharEmotion,
|
||||||
|
SizeStore,
|
||||||
|
selectedCharID,
|
||||||
|
settingsOpen,
|
||||||
|
sideBarStore,
|
||||||
|
} from "../../ts/stores";
|
||||||
import { DataBase } from "../../ts/database";
|
import { DataBase } from "../../ts/database";
|
||||||
import BarIcon from "./BarIcon.svelte";
|
import BarIcon from "./BarIcon.svelte";
|
||||||
import { Plus, User, X, Settings, Users, Edit3Icon, ArrowUp, ArrowDown, ListIcon, LayoutGridIcon, PlusIcon} from 'lucide-svelte'
|
import SidebarIndicator from "./SidebarIndicator.svelte";
|
||||||
import { characterFormatUpdate, createNewCharacter, createNewGroup, getCharImage } from "../../ts/characters";
|
import {
|
||||||
import {importCharacter} from 'src/ts/characterCards'
|
Plus,
|
||||||
import SettingsDom from './Settings.svelte'
|
User,
|
||||||
|
X,
|
||||||
|
Settings,
|
||||||
|
Users,
|
||||||
|
Edit3Icon,
|
||||||
|
ArrowUp,
|
||||||
|
ArrowDown,
|
||||||
|
ListIcon,
|
||||||
|
LayoutGridIcon,
|
||||||
|
PlusIcon,
|
||||||
|
} from "lucide-svelte";
|
||||||
|
import {
|
||||||
|
characterFormatUpdate,
|
||||||
|
createNewCharacter,
|
||||||
|
createNewGroup,
|
||||||
|
getCharImage,
|
||||||
|
} from "../../ts/characters";
|
||||||
|
import { importCharacter } from "src/ts/characterCards";
|
||||||
|
import SettingsDom from "./Settings.svelte";
|
||||||
import CharConfig from "./CharConfig.svelte";
|
import CharConfig from "./CharConfig.svelte";
|
||||||
import { language } from "../../lang";
|
import { language } from "../../lang";
|
||||||
import Botpreset from "../Others/botpreset.svelte";
|
import Botpreset from "../Others/botpreset.svelte";
|
||||||
import { onDestroy } from "svelte";
|
import { onDestroy } from "svelte";
|
||||||
import {isEqual} from 'lodash'
|
import { isEqual } from "lodash";
|
||||||
let openPresetList =false
|
import SidebarAvatar from "./SidebarAvatar.svelte";
|
||||||
let sideBarMode = 0
|
import BaseRoundedButton from "../UI/BaseRoundedButton.svelte";
|
||||||
let editMode = false
|
let openPresetList = false;
|
||||||
let menuMode = 0
|
let sideBarMode = 0;
|
||||||
export let openGrid = () => {}
|
let editMode = false;
|
||||||
|
let menuMode = 0;
|
||||||
|
export let openGrid = () => {};
|
||||||
|
|
||||||
|
function createScratch() {
|
||||||
function createScratch(){
|
|
||||||
reseter();
|
reseter();
|
||||||
const cid = createNewCharacter()
|
const cid = createNewCharacter();
|
||||||
selectedCharID.set(-1)
|
selectedCharID.set(-1);
|
||||||
}
|
}
|
||||||
function createGroup(){
|
function createGroup() {
|
||||||
reseter();
|
reseter();
|
||||||
const cid = createNewGroup()
|
const cid = createNewGroup();
|
||||||
selectedCharID.set(-1)
|
selectedCharID.set(-1);
|
||||||
}
|
}
|
||||||
async function createImport(){
|
async function createImport() {
|
||||||
reseter();
|
reseter();
|
||||||
const cid = await importCharacter()
|
const cid = await importCharacter();
|
||||||
selectedCharID.set(-1)
|
selectedCharID.set(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeChar(index:number){
|
function changeChar(index: number) {
|
||||||
reseter();
|
reseter();
|
||||||
characterFormatUpdate(index)
|
characterFormatUpdate(index);
|
||||||
selectedCharID.set(index)
|
selectedCharID.set(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
function reseter(){
|
function reseter() {
|
||||||
menuMode = 0;
|
menuMode = 0;
|
||||||
sideBarMode = 0;
|
sideBarMode = 0;
|
||||||
editMode = false
|
editMode = false;
|
||||||
settingsOpen.set(false)
|
settingsOpen.set(false);
|
||||||
CharEmotion.set({})
|
CharEmotion.set({});
|
||||||
}
|
}
|
||||||
|
|
||||||
let charImages:string[] = []
|
let charImages: string[] = [];
|
||||||
|
|
||||||
const unsub = DataBase.subscribe((db) => {
|
const unsub = DataBase.subscribe((db) => {
|
||||||
let newCharImages:string[] = []
|
let newCharImages: string[] = [];
|
||||||
for(const cha of db.characters){
|
for (const cha of db.characters) {
|
||||||
newCharImages.push(cha.image ?? '')
|
newCharImages.push(cha.image ?? "");
|
||||||
}
|
}
|
||||||
if(!isEqual(charImages, newCharImages)){
|
if (!isEqual(charImages, newCharImages)) {
|
||||||
charImages = newCharImages
|
charImages = newCharImages;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
onDestroy(unsub)
|
|
||||||
|
|
||||||
|
onDestroy(unsub);
|
||||||
</script>
|
</script>
|
||||||
<div class="w-20 flex flex-col bg-bgcolor text-white items-center overflow-y-scroll h-full shadow-lg min-w-20 overflow-x-hidden"
|
|
||||||
class:editMode={editMode}>
|
<div
|
||||||
<button class="bg-gray-500 w-14 min-w-14 flex justify-center h-8 items-center rounded-b-md cursor-pointer hover:bg-green-500 transition-colors absolute top-0" on:click={() => {
|
class="flex h-full w-20 min-w-20 flex-col items-center overflow-x-hidden overflow-y-scroll bg-bgcolor text-white shadow-lg gap-2"
|
||||||
menuMode = 1 - menuMode
|
class:editMode
|
||||||
}}><ListIcon/></button>
|
>
|
||||||
<div class="w-14 min-w-14 h-8 min-h-8 bg-transparent"></div>
|
<button
|
||||||
|
class="absolute top-0 flex h-8 w-14 min-w-14 cursor-pointer items-center justify-center rounded-b-md bg-gray-500 transition-colors hover:bg-green-500"
|
||||||
|
on:click={() => {
|
||||||
|
menuMode = 1 - menuMode;
|
||||||
|
}}><ListIcon /></button
|
||||||
|
>
|
||||||
|
<div class="h-8 min-h-8 w-14 min-w-14 bg-transparent" />
|
||||||
{#if menuMode === 0}
|
{#if menuMode === 0}
|
||||||
{#each charImages as charimg, i}
|
{#each charImages as charimg, i}
|
||||||
<div class="flex items-center">
|
<div class="group relative flex items-center px-2">
|
||||||
{#if charimg !== ''}
|
<SidebarIndicator
|
||||||
<BarIcon onClick={() => {changeChar(i)}} additionalStyle={getCharImage($DataBase.characters[i].image, 'css')}>
|
isActive={$selectedCharID === i && sideBarMode !== 1}
|
||||||
</BarIcon>
|
/>
|
||||||
|
{#if charimg !== ""}
|
||||||
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
|
<div
|
||||||
|
on:click={() => {
|
||||||
|
changeChar(i);
|
||||||
|
}}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
changeChar(i);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
{#await getCharImage($DataBase.characters[i].image, "plain") then img}
|
||||||
|
<SidebarAvatar src={img} size="56" />
|
||||||
|
{:catch}
|
||||||
|
<SidebarAvatar size="56" src="https://via.placeholder.com/150" />
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<BarIcon onClick={() => {changeChar(i)}} additionalStyle={i === $selectedCharID ? 'background:#44475a' : ''}>
|
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||||
|
<div
|
||||||
</BarIcon>
|
on:click={() => {
|
||||||
|
changeChar(i);
|
||||||
|
}}
|
||||||
|
on:keydown={(e) => {
|
||||||
|
if (e.key === "Enter") {
|
||||||
|
changeChar(i);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<SidebarAvatar size="56" src="https://via.placeholder.com/150" />
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if editMode}
|
{#if editMode}
|
||||||
<div class="flex flex-col mt-2">
|
<div class="mt-2 flex flex-col">
|
||||||
<button on:click={() => {
|
<button
|
||||||
let chars = $DataBase.characters
|
on:click={() => {
|
||||||
if(chars[i-1]){
|
let chars = $DataBase.characters;
|
||||||
const currentchar = chars[i]
|
if (chars[i - 1]) {
|
||||||
chars[i] = chars[i-1]
|
const currentchar = chars[i];
|
||||||
chars[i-1] = currentchar
|
chars[i] = chars[i - 1];
|
||||||
$DataBase.characters = chars
|
chars[i - 1] = currentchar;
|
||||||
|
$DataBase.characters = chars;
|
||||||
}
|
}
|
||||||
}}>
|
}}
|
||||||
<ArrowUp size={20}/>
|
>
|
||||||
|
<ArrowUp size={20} />
|
||||||
</button>
|
</button>
|
||||||
<button on:click={() => {
|
<button
|
||||||
let chars = $DataBase.characters
|
on:click={() => {
|
||||||
if(chars[i+1]){
|
let chars = $DataBase.characters;
|
||||||
const currentchar = chars[i]
|
if (chars[i + 1]) {
|
||||||
chars[i] = chars[i+1]
|
const currentchar = chars[i];
|
||||||
chars[i+1] = currentchar
|
chars[i] = chars[i + 1];
|
||||||
$DataBase.characters = chars
|
chars[i + 1] = currentchar;
|
||||||
|
$DataBase.characters = chars;
|
||||||
}
|
}
|
||||||
}}>
|
}}
|
||||||
<ArrowDown size={22}/>
|
>
|
||||||
|
<ArrowDown size={22} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<BarIcon onClick={() => {
|
<div class="flex flex-col items-center space-y-2 px-2">
|
||||||
if(sideBarMode === 1){
|
<BaseRoundedButton
|
||||||
|
onClick={() => {
|
||||||
|
if (sideBarMode === 1) {
|
||||||
reseter();
|
reseter();
|
||||||
sideBarMode = 0
|
sideBarMode = 0;
|
||||||
}
|
} else {
|
||||||
else{
|
|
||||||
reseter();
|
reseter();
|
||||||
sideBarMode = 1
|
sideBarMode = 1;
|
||||||
}
|
}
|
||||||
}}><PlusIcon/></BarIcon>
|
}}
|
||||||
|
><svg viewBox="0 0 24 24" width="1.2em" height="1.2em"
|
||||||
|
><path
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 6v6m0 0v6m0-6h6m-6 0H6"
|
||||||
|
/></svg
|
||||||
|
></BaseRoundedButton
|
||||||
|
>
|
||||||
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<BarIcon onClick={() => {
|
<BarIcon
|
||||||
if($settingsOpen){
|
onClick={() => {
|
||||||
|
if ($settingsOpen) {
|
||||||
reseter();
|
reseter();
|
||||||
settingsOpen.set(false)
|
settingsOpen.set(false);
|
||||||
|
} else {
|
||||||
|
reseter();
|
||||||
|
settingsOpen.set(true);
|
||||||
}
|
}
|
||||||
else{
|
}}><Settings /></BarIcon
|
||||||
|
>
|
||||||
|
<BarIcon
|
||||||
|
onClick={() => {
|
||||||
reseter();
|
reseter();
|
||||||
settingsOpen.set(true)
|
openGrid();
|
||||||
}
|
}}><LayoutGridIcon /></BarIcon
|
||||||
}}><Settings/></BarIcon>
|
>
|
||||||
<BarIcon onClick={() => {
|
|
||||||
reseter();
|
|
||||||
openGrid()
|
|
||||||
}}><LayoutGridIcon/></BarIcon>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div class="w-96 p-6 flex flex-col bg-darkbg text-gray-200 overflow-y-auto overflow-x-hidden setting-area" class:flex-grow={($SizeStore.w <= 1000)} class:minw96={($SizeStore.w > 1000)}>
|
<div
|
||||||
<button class="flex w-full justify-end text-gray-200" on:click={() => {sideBarStore.set(false)}}>
|
class="setting-area flex w-96 flex-col overflow-y-auto overflow-x-hidden bg-darkbg p-6 text-gray-200"
|
||||||
<button class="p-0 bg-transparent border-none text-gray-200"><X/></button>
|
class:flex-grow={$SizeStore.w <= 1000}
|
||||||
|
class:minw96={$SizeStore.w > 1000}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="flex w-full justify-end text-gray-200"
|
||||||
|
on:click={() => {
|
||||||
|
sideBarStore.set(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<button class="border-none bg-transparent p-0 text-gray-200"><X /></button>
|
||||||
</button>
|
</button>
|
||||||
{#if sideBarMode === 0}
|
{#if sideBarMode === 0}
|
||||||
{#if $selectedCharID < 0 || $settingsOpen}
|
{#if $selectedCharID < 0 || $settingsOpen}
|
||||||
<SettingsDom bind:openPresetList/>
|
<SettingsDom bind:openPresetList />
|
||||||
{:else}
|
{:else}
|
||||||
<CharConfig />
|
<CharConfig />
|
||||||
{/if}
|
{/if}
|
||||||
{:else if sideBarMode === 1}
|
{:else if sideBarMode === 1}
|
||||||
<h2 class="title font-bold text-xl mt-2">Create</h2>
|
<h2 class="title mt-2 text-xl font-bold">Create</h2>
|
||||||
<button
|
<button
|
||||||
on:click={createScratch}
|
on:click={createScratch}
|
||||||
class="drop-shadow-lg p-5 border-borderc border-solid mt-2 flex justify-center items-center ml-2 mr-2 border-1 hover:bg-selected text-lg">
|
class="ml-2 mr-2 mt-2 flex items-center justify-center border-1 border-solid border-borderc p-5 text-lg drop-shadow-lg hover:bg-selected"
|
||||||
|
>
|
||||||
{language.createfromScratch}
|
{language.createfromScratch}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
on:click={createImport}
|
on:click={createImport}
|
||||||
class="drop-shadow-lg p-5 border-borderc border-solid mt-2 flex justify-center items-center ml-2 mr-2 border-1 hover:bg-selected text-lg">
|
class="ml-2 mr-2 mt-2 flex items-center justify-center border-1 border-solid border-borderc p-5 text-lg drop-shadow-lg hover:bg-selected"
|
||||||
|
>
|
||||||
{language.importCharacter}
|
{language.importCharacter}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
on:click={createGroup}
|
on:click={createGroup}
|
||||||
class="drop-shadow-lg p-3 border-borderc border-solid mt-2 flex justify-center items-center ml-2 mr-2 border-1 hover:bg-selected">
|
class="ml-2 mr-2 mt-2 flex items-center justify-center border-1 border-solid border-borderc p-3 drop-shadow-lg hover:bg-selected"
|
||||||
|
>
|
||||||
{language.createGroup}
|
{language.createGroup}
|
||||||
</button>
|
</button>
|
||||||
<h2 class="title font-bold text-xl mt-4">Edit</h2>
|
<h2 class="title mt-4 text-xl font-bold">Edit</h2>
|
||||||
<button
|
<button
|
||||||
on:click={() => {editMode = !editMode;$selectedCharID = -1}}
|
on:click={() => {
|
||||||
class="drop-shadow-lg p-3 border-borderc border-solid mt-2 flex justify-center items-center ml-2 mr-2 border-1 hover:bg-selected">
|
editMode = !editMode;
|
||||||
|
$selectedCharID = -1;
|
||||||
|
}}
|
||||||
|
class="ml-2 mr-2 mt-2 flex items-center justify-center border-1 border-solid border-borderc p-3 drop-shadow-lg hover:bg-selected"
|
||||||
|
>
|
||||||
{language.editOrder}
|
{language.editOrder}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{#if openPresetList}
|
||||||
|
<Botpreset
|
||||||
|
close={() => {
|
||||||
|
openPresetList = false;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.minw96 {
|
.minw96 {
|
||||||
min-width: 24rem; /* 384px */
|
min-width: 24rem; /* 384px */
|
||||||
}
|
}
|
||||||
.title{
|
.title {
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
.editMode{
|
.editMode {
|
||||||
min-width: 6rem;
|
min-width: 6rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
{#if openPresetList}
|
|
||||||
<Botpreset close={() => {openPresetList = false}}/>
|
|
||||||
{/if}
|
|
||||||
24
src/lib/SideBars/SidebarAvatar.svelte
Normal file
24
src/lib/SideBars/SidebarAvatar.svelte
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
<script>
|
||||||
|
export let src;
|
||||||
|
export let size = "22";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="flex shrink-0 items-center justify-center">
|
||||||
|
{#if src}
|
||||||
|
<img
|
||||||
|
{src}
|
||||||
|
class="bg-skin-border sidebar-avatar rounded-full object-cover"
|
||||||
|
style:width={size + "px"}
|
||||||
|
style:height={size + "px"}
|
||||||
|
style:minWidth={size + "px"}
|
||||||
|
alt="avatar"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
|
<div
|
||||||
|
class="bg-skin-border sidebar-avatar rounded-full"
|
||||||
|
style:width={size + "px"}
|
||||||
|
style:height={size + "px"}
|
||||||
|
style:minWidth={size + "px"}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
17
src/lib/SideBars/SidebarIndicator.svelte
Normal file
17
src/lib/SideBars/SidebarIndicator.svelte
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isActive: boolean;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="
|
||||||
|
group-hover:bg-white
|
||||||
|
absolute
|
||||||
|
left-[-4px]
|
||||||
|
h-[8px]
|
||||||
|
w-[8px]
|
||||||
|
rounded-full
|
||||||
|
transition-all
|
||||||
|
duration-300
|
||||||
|
{isActive ? 'bg-white !h-[20px]' : 'group-hover:h-[10px]'}
|
||||||
|
"
|
||||||
|
/>
|
||||||
16
src/lib/UI/BaseRoundedButton.svelte
Normal file
16
src/lib/UI/BaseRoundedButton.svelte
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let isDisabled: boolean = false;
|
||||||
|
export let onClick: () => void;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
disabled={isDisabled}
|
||||||
|
on:click={onClick}
|
||||||
|
class="flex h-[56px] w-[56px] cursor-pointer select-none items-center justify-center
|
||||||
|
transition-colors rounded-full
|
||||||
|
border border-gray-500 text-gray-300
|
||||||
|
hover:border-gray-300
|
||||||
|
{isDisabled ? '!cursor-not-allowed' : ''}"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
Reference in New Issue
Block a user