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,36 +1,42 @@
|
||||
<!-- TODO: REMOVE AND REFACTOR TO BASE BUTTON UI COMPONENT -->
|
||||
|
||||
<script lang="ts">
|
||||
export let onClick = () => {};
|
||||
export let additionalStyle: string | Promise<string> = "";
|
||||
</script>
|
||||
|
||||
{#await additionalStyle}
|
||||
<button on:click={onClick} class="ico"><slot/></button>
|
||||
{:then as}
|
||||
<button on:click={onClick} class="ico" style={as}><slot/></button>
|
||||
<button on:click={onClick} class="ico"><slot /></button>
|
||||
{:then as}
|
||||
<button on:click={onClick} class="ico" style={as}><slot /></button>
|
||||
{/await}
|
||||
<script lang="ts">
|
||||
export let onClick = () => {}
|
||||
export let additionalStyle:string|Promise<string> = ''
|
||||
</script>
|
||||
<style>
|
||||
.ico {
|
||||
cursor: pointer;
|
||||
border-radius: 0.375rem;
|
||||
height: 3.5rem;
|
||||
width: 3.5rem;
|
||||
min-height: 3.5rem;
|
||||
margin-top: 0.5rem;
|
||||
--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);
|
||||
-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;
|
||||
background-color: rgba(107, 114, 128, var(--tw-bg-opacity)); display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition-property: background-color, border-color, color, fill, stroke;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.ico:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(16, 185, 129, var(--tw-bg-opacity));
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.ico {
|
||||
cursor: pointer;
|
||||
border-radius: 0.375rem;
|
||||
height: 3.5rem;
|
||||
width: 3.5rem;
|
||||
min-height: 3.5rem;
|
||||
--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);
|
||||
-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;
|
||||
background-color: rgba(107, 114, 128, var(--tw-bg-opacity));
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
transition-property: background-color, border-color, color, fill, stroke;
|
||||
transition-duration: 150ms;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.ico:hover {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgba(16, 185, 129, var(--tw-bg-opacity));
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,189 +1,288 @@
|
||||
<script lang="ts">
|
||||
import { CharEmotion, SizeStore, selectedCharID, settingsOpen, sideBarStore } from "../../ts/stores";
|
||||
import { DataBase } from "../../ts/database";
|
||||
import BarIcon from "./BarIcon.svelte";
|
||||
import { Plus, 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 { language } from "../../lang";
|
||||
import Botpreset from "../Others/botpreset.svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
import {isEqual} from 'lodash'
|
||||
let openPresetList =false
|
||||
let sideBarMode = 0
|
||||
let editMode = false
|
||||
let menuMode = 0
|
||||
export let openGrid = () => {}
|
||||
import {
|
||||
CharEmotion,
|
||||
SizeStore,
|
||||
selectedCharID,
|
||||
settingsOpen,
|
||||
sideBarStore,
|
||||
} from "../../ts/stores";
|
||||
import { DataBase } from "../../ts/database";
|
||||
import BarIcon from "./BarIcon.svelte";
|
||||
import SidebarIndicator from "./SidebarIndicator.svelte";
|
||||
import {
|
||||
Plus,
|
||||
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 { language } from "../../lang";
|
||||
import Botpreset from "../Others/botpreset.svelte";
|
||||
import { onDestroy } from "svelte";
|
||||
import { isEqual } from "lodash";
|
||||
import SidebarAvatar from "./SidebarAvatar.svelte";
|
||||
import BaseRoundedButton from "../UI/BaseRoundedButton.svelte";
|
||||
let openPresetList = false;
|
||||
let sideBarMode = 0;
|
||||
let editMode = false;
|
||||
let menuMode = 0;
|
||||
export let openGrid = () => {};
|
||||
|
||||
function createScratch() {
|
||||
reseter();
|
||||
const cid = createNewCharacter();
|
||||
selectedCharID.set(-1);
|
||||
}
|
||||
function createGroup() {
|
||||
reseter();
|
||||
const cid = createNewGroup();
|
||||
selectedCharID.set(-1);
|
||||
}
|
||||
async function createImport() {
|
||||
reseter();
|
||||
const cid = await importCharacter();
|
||||
selectedCharID.set(-1);
|
||||
}
|
||||
|
||||
function createScratch(){
|
||||
reseter();
|
||||
const cid = createNewCharacter()
|
||||
selectedCharID.set(-1)
|
||||
function changeChar(index: number) {
|
||||
reseter();
|
||||
characterFormatUpdate(index);
|
||||
selectedCharID.set(index);
|
||||
}
|
||||
|
||||
function reseter() {
|
||||
menuMode = 0;
|
||||
sideBarMode = 0;
|
||||
editMode = false;
|
||||
settingsOpen.set(false);
|
||||
CharEmotion.set({});
|
||||
}
|
||||
|
||||
let charImages: string[] = [];
|
||||
|
||||
const unsub = DataBase.subscribe((db) => {
|
||||
let newCharImages: string[] = [];
|
||||
for (const cha of db.characters) {
|
||||
newCharImages.push(cha.image ?? "");
|
||||
}
|
||||
function createGroup(){
|
||||
reseter();
|
||||
const cid = createNewGroup()
|
||||
selectedCharID.set(-1)
|
||||
}
|
||||
async function createImport(){
|
||||
reseter();
|
||||
const cid = await importCharacter()
|
||||
selectedCharID.set(-1)
|
||||
if (!isEqual(charImages, newCharImages)) {
|
||||
charImages = newCharImages;
|
||||
}
|
||||
});
|
||||
|
||||
function changeChar(index:number){
|
||||
reseter();
|
||||
characterFormatUpdate(index)
|
||||
selectedCharID.set(index)
|
||||
}
|
||||
|
||||
function reseter(){
|
||||
menuMode = 0;
|
||||
sideBarMode = 0;
|
||||
editMode = false
|
||||
settingsOpen.set(false)
|
||||
CharEmotion.set({})
|
||||
}
|
||||
|
||||
let charImages:string[] = []
|
||||
|
||||
const unsub = DataBase.subscribe((db) => {
|
||||
let newCharImages:string[] = []
|
||||
for(const cha of db.characters){
|
||||
newCharImages.push(cha.image ?? '')
|
||||
}
|
||||
if(!isEqual(charImages, newCharImages)){
|
||||
charImages = newCharImages
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
onDestroy(unsub)
|
||||
|
||||
onDestroy(unsub);
|
||||
</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}>
|
||||
<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={() => {
|
||||
menuMode = 1 - menuMode
|
||||
}}><ListIcon/></button>
|
||||
<div class="w-14 min-w-14 h-8 min-h-8 bg-transparent"></div>
|
||||
{#if menuMode === 0}
|
||||
|
||||
<div
|
||||
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"
|
||||
class:editMode
|
||||
>
|
||||
<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}
|
||||
{#each charImages as charimg, i}
|
||||
<div class="flex items-center">
|
||||
{#if charimg !== ''}
|
||||
<BarIcon onClick={() => {changeChar(i)}} additionalStyle={getCharImage($DataBase.characters[i].image, 'css')}>
|
||||
</BarIcon>
|
||||
{:else}
|
||||
<BarIcon onClick={() => {changeChar(i)}} additionalStyle={i === $selectedCharID ? 'background:#44475a' : ''}>
|
||||
|
||||
</BarIcon>
|
||||
{/if}
|
||||
{#if editMode}
|
||||
<div class="flex flex-col mt-2">
|
||||
<button on:click={() => {
|
||||
let chars = $DataBase.characters
|
||||
if(chars[i-1]){
|
||||
const currentchar = chars[i]
|
||||
chars[i] = chars[i-1]
|
||||
chars[i-1] = currentchar
|
||||
$DataBase.characters = chars
|
||||
}
|
||||
}}>
|
||||
<ArrowUp size={20}/>
|
||||
</button>
|
||||
<button on:click={() => {
|
||||
let chars = $DataBase.characters
|
||||
if(chars[i+1]){
|
||||
const currentchar = chars[i]
|
||||
chars[i] = chars[i+1]
|
||||
chars[i+1] = currentchar
|
||||
$DataBase.characters = chars
|
||||
}
|
||||
}}>
|
||||
<ArrowDown size={22}/>
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<BarIcon onClick={() => {
|
||||
if(sideBarMode === 1){
|
||||
reseter();
|
||||
sideBarMode = 0
|
||||
}
|
||||
else{
|
||||
reseter();
|
||||
sideBarMode = 1
|
||||
}
|
||||
}}><PlusIcon/></BarIcon>
|
||||
{:else}
|
||||
<BarIcon onClick={() => {
|
||||
if($settingsOpen){
|
||||
reseter();
|
||||
settingsOpen.set(false)
|
||||
}
|
||||
else{
|
||||
reseter();
|
||||
settingsOpen.set(true)
|
||||
}
|
||||
}}><Settings/></BarIcon>
|
||||
<BarIcon onClick={() => {
|
||||
reseter();
|
||||
openGrid()
|
||||
}}><LayoutGridIcon/></BarIcon>
|
||||
{/if}
|
||||
</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)}>
|
||||
<button class="flex w-full justify-end text-gray-200" on:click={() => {sideBarStore.set(false)}}>
|
||||
<button class="p-0 bg-transparent border-none text-gray-200"><X/></button>
|
||||
</button>
|
||||
{#if sideBarMode === 0}
|
||||
{#if $selectedCharID < 0 || $settingsOpen}
|
||||
<SettingsDom bind:openPresetList/>
|
||||
<div class="group relative flex items-center px-2">
|
||||
<SidebarIndicator
|
||||
isActive={$selectedCharID === i && sideBarMode !== 1}
|
||||
/>
|
||||
{#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}
|
||||
<CharConfig />
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<div
|
||||
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}
|
||||
{:else if sideBarMode === 1}
|
||||
<h2 class="title font-bold text-xl mt-2">Create</h2>
|
||||
<button
|
||||
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">
|
||||
{language.createfromScratch}
|
||||
</button>
|
||||
<button
|
||||
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">
|
||||
{language.importCharacter}
|
||||
</button>
|
||||
<button
|
||||
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">
|
||||
{language.createGroup}
|
||||
</button>
|
||||
<h2 class="title font-bold text-xl mt-4">Edit</h2>
|
||||
<button
|
||||
on:click={() => {editMode = !editMode;$selectedCharID = -1}}
|
||||
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">
|
||||
{language.editOrder}
|
||||
</button>
|
||||
{/if}
|
||||
{#if editMode}
|
||||
<div class="mt-2 flex flex-col">
|
||||
<button
|
||||
on:click={() => {
|
||||
let chars = $DataBase.characters;
|
||||
if (chars[i - 1]) {
|
||||
const currentchar = chars[i];
|
||||
chars[i] = chars[i - 1];
|
||||
chars[i - 1] = currentchar;
|
||||
$DataBase.characters = chars;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ArrowUp size={20} />
|
||||
</button>
|
||||
<button
|
||||
on:click={() => {
|
||||
let chars = $DataBase.characters;
|
||||
if (chars[i + 1]) {
|
||||
const currentchar = chars[i];
|
||||
chars[i] = chars[i + 1];
|
||||
chars[i + 1] = currentchar;
|
||||
$DataBase.characters = chars;
|
||||
}
|
||||
}}
|
||||
>
|
||||
<ArrowDown size={22} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex flex-col items-center space-y-2 px-2">
|
||||
<BaseRoundedButton
|
||||
onClick={() => {
|
||||
if (sideBarMode === 1) {
|
||||
reseter();
|
||||
sideBarMode = 0;
|
||||
} else {
|
||||
reseter();
|
||||
sideBarMode = 1;
|
||||
}
|
||||
}}
|
||||
><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}
|
||||
<BarIcon
|
||||
onClick={() => {
|
||||
if ($settingsOpen) {
|
||||
reseter();
|
||||
settingsOpen.set(false);
|
||||
} else {
|
||||
reseter();
|
||||
settingsOpen.set(true);
|
||||
}
|
||||
}}><Settings /></BarIcon
|
||||
>
|
||||
<BarIcon
|
||||
onClick={() => {
|
||||
reseter();
|
||||
openGrid();
|
||||
}}><LayoutGridIcon /></BarIcon
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
<div
|
||||
class="setting-area flex w-96 flex-col overflow-y-auto overflow-x-hidden bg-darkbg p-6 text-gray-200"
|
||||
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>
|
||||
{#if sideBarMode === 0}
|
||||
{#if $selectedCharID < 0 || $settingsOpen}
|
||||
<SettingsDom bind:openPresetList />
|
||||
{:else}
|
||||
<CharConfig />
|
||||
{/if}
|
||||
{:else if sideBarMode === 1}
|
||||
<h2 class="title mt-2 text-xl font-bold">Create</h2>
|
||||
<button
|
||||
on:click={createScratch}
|
||||
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}
|
||||
</button>
|
||||
<button
|
||||
on:click={createImport}
|
||||
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}
|
||||
</button>
|
||||
<button
|
||||
on:click={createGroup}
|
||||
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}
|
||||
</button>
|
||||
<h2 class="title mt-4 text-xl font-bold">Edit</h2>
|
||||
<button
|
||||
on:click={() => {
|
||||
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}
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.minw96 {
|
||||
min-width: 24rem; /* 384px */
|
||||
}
|
||||
.title{
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.editMode{
|
||||
min-width: 6rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
{#if openPresetList}
|
||||
<Botpreset close={() => {openPresetList = false}}/>
|
||||
{/if}
|
||||
<Botpreset
|
||||
close={() => {
|
||||
openPresetList = false;
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.minw96 {
|
||||
min-width: 24rem; /* 384px */
|
||||
}
|
||||
.title {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.editMode {
|
||||
min-width: 6rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
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