Slight UI refactor
This commit is contained in:
129
src/lib/components/ProfileList.svelte
Normal file
129
src/lib/components/ProfileList.svelte
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { profiles, activeProfileId } from '$lib/stores/mudStore';
|
||||||
|
import { ModalHelper } from '$lib/utils/ModalHelper';
|
||||||
|
import type { MudProfile } from '$lib/profiles/ProfileManager';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
saveProfile: { profile: MudProfile },
|
||||||
|
deleteProfile: { profileId: string },
|
||||||
|
editProfile: { profile: MudProfile }
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// Create a new profile
|
||||||
|
function createProfile() {
|
||||||
|
console.log('New Profile button clicked - using full profile editor');
|
||||||
|
try {
|
||||||
|
ModalHelper.showProfileEditor(
|
||||||
|
(profile) => {
|
||||||
|
console.log('Profile saved from profile editor:', profile);
|
||||||
|
dispatch('saveProfile', { profile });
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
console.log('Profile creation cancelled');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error showing profile editor:', error);
|
||||||
|
alert('Error showing modal: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit an existing profile
|
||||||
|
function editProfile(profile: MudProfile) {
|
||||||
|
dispatch('editProfile', { profile });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete a profile
|
||||||
|
function deleteProfile(profileId: string) {
|
||||||
|
dispatch('deleteProfile', { profileId });
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="panel-profiles"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="tab-profiles"
|
||||||
|
tabindex="0">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>MUD Profiles</h3>
|
||||||
|
<button
|
||||||
|
class="btn btn-sm btn-primary"
|
||||||
|
on:click={createProfile}>New Profile</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="profile-list">
|
||||||
|
{#if $profiles.length === 0}
|
||||||
|
<p class="no-items">No profiles found. Create one to get started.</p>
|
||||||
|
{:else}
|
||||||
|
{#each $profiles as profile (profile.id)}
|
||||||
|
<div class="profile-item" class:active={$activeProfileId === profile.id}>
|
||||||
|
<span class="profile-name">{profile.name}</span>
|
||||||
|
<div class="profile-actions">
|
||||||
|
<button class="btn btn-sm" on:click={() => editProfile(profile)} aria-label="Edit profile {profile.name}">✏️</button>
|
||||||
|
<button class="btn btn-sm" on:click={() => deleteProfile(profile.id)} aria-label="Delete profile {profile.name}">🗑️</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
[role="tabpanel"]:focus {
|
||||||
|
outline: 2px solid var(--color-primary);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="tabpanel"]:focus:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-item {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--color-input-bg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-item.active {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
background-color: rgba(33, 150, 243, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-items {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
118
src/lib/components/Sidebar.svelte
Normal file
118
src/lib/components/Sidebar.svelte
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import SidebarTabs from './SidebarTabs.svelte';
|
||||||
|
import ProfileList from './ProfileList.svelte';
|
||||||
|
import TriggerList from './TriggerList.svelte';
|
||||||
|
import SettingsPanel from './SettingsPanel.svelte';
|
||||||
|
import type { MudProfile } from '$lib/profiles/ProfileManager';
|
||||||
|
import type { Trigger } from '$lib/triggers/TriggerSystem';
|
||||||
|
|
||||||
|
export let sidebarTab: 'profiles' | 'triggers' | 'settings' = 'profiles';
|
||||||
|
|
||||||
|
// Create event dispatcher
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
saveProfile: { profile: MudProfile },
|
||||||
|
deleteProfile: { profileId: string },
|
||||||
|
editProfile: { profile: MudProfile },
|
||||||
|
saveTrigger: { trigger: Trigger },
|
||||||
|
deleteTrigger: { triggerId: string },
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// Emit events to parent
|
||||||
|
function handleSaveProfile(event: CustomEvent<{profile: MudProfile}>) {
|
||||||
|
const profile = event.detail.profile;
|
||||||
|
dispatch('saveProfile', { profile });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteProfile(event: CustomEvent<{profileId: string}>) {
|
||||||
|
if (confirm(`Are you sure you want to delete this profile?`)) {
|
||||||
|
dispatch('deleteProfile', { profileId: event.detail.profileId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEditProfile(event: CustomEvent<{profile: MudProfile}>) {
|
||||||
|
dispatch('editProfile', { profile: event.detail.profile });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSaveTrigger(event: CustomEvent<{trigger: Trigger}>) {
|
||||||
|
dispatch('saveTrigger', { trigger: event.detail.trigger });
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDeleteTrigger(event: CustomEvent<{triggerId: string}>) {
|
||||||
|
if (confirm(`Are you sure you want to delete this trigger?`)) {
|
||||||
|
dispatch('deleteTrigger', { triggerId: event.detail.triggerId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleTabChange(event: CustomEvent<{tab: 'profiles' | 'triggers' | 'settings'}>) {
|
||||||
|
sidebarTab = event.detail.tab;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<aside class="mud-sidebar">
|
||||||
|
<SidebarTabs activeTab={sidebarTab} on:tabChange={handleTabChange} />
|
||||||
|
|
||||||
|
<div class="sidebar-content">
|
||||||
|
{#if sidebarTab === 'profiles'}
|
||||||
|
<ProfileList
|
||||||
|
on:saveProfile={handleSaveProfile}
|
||||||
|
on:deleteProfile={handleDeleteProfile}
|
||||||
|
on:editProfile={handleEditProfile}
|
||||||
|
/>
|
||||||
|
{:else if sidebarTab === 'triggers'}
|
||||||
|
<TriggerList
|
||||||
|
on:saveTrigger={handleSaveTrigger}
|
||||||
|
on:deleteTrigger={handleDeleteTrigger}
|
||||||
|
/>
|
||||||
|
{:else if sidebarTab === 'settings'}
|
||||||
|
<div
|
||||||
|
id="panel-settings"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="tab-settings"
|
||||||
|
tabindex="0">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>Settings</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<SettingsPanel />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.mud-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 250px;
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-content {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="tabpanel"]:focus {
|
||||||
|
outline: 2px solid var(--color-primary);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="tabpanel"]:focus:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
133
src/lib/components/SidebarTabs.svelte
Normal file
133
src/lib/components/SidebarTabs.svelte
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
export let activeTab: 'profiles' | 'triggers' | 'settings' = 'profiles';
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
tabChange: { tab: 'profiles' | 'triggers' | 'settings' }
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// Handle keyboard navigation for sidebar tabs
|
||||||
|
function handleSidebarTabKeydown(event: KeyboardEvent) {
|
||||||
|
// Define the tab order
|
||||||
|
const tabs = ['profiles', 'triggers', 'settings'];
|
||||||
|
const currentIndex = tabs.indexOf(activeTab);
|
||||||
|
|
||||||
|
switch (event.key) {
|
||||||
|
case 'ArrowRight':
|
||||||
|
case 'ArrowDown':
|
||||||
|
// Move to next tab
|
||||||
|
event.preventDefault();
|
||||||
|
const nextIndex = (currentIndex + 1) % tabs.length;
|
||||||
|
dispatch('tabChange', { tab: tabs[nextIndex] as any });
|
||||||
|
document.getElementById(`tab-${tabs[nextIndex]}`)?.focus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'ArrowLeft':
|
||||||
|
case 'ArrowUp':
|
||||||
|
// Move to previous tab
|
||||||
|
event.preventDefault();
|
||||||
|
const prevIndex = (currentIndex - 1 + tabs.length) % tabs.length;
|
||||||
|
dispatch('tabChange', { tab: tabs[prevIndex] as any });
|
||||||
|
document.getElementById(`tab-${tabs[prevIndex]}`)?.focus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Home':
|
||||||
|
// Move to first tab
|
||||||
|
event.preventDefault();
|
||||||
|
dispatch('tabChange', { tab: tabs[0] as any });
|
||||||
|
document.getElementById(`tab-${tabs[0]}`)?.focus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'End':
|
||||||
|
// Move to last tab
|
||||||
|
event.preventDefault();
|
||||||
|
dispatch('tabChange', { tab: tabs[tabs.length - 1] as any });
|
||||||
|
document.getElementById(`tab-${tabs[tabs.length - 1]}`)?.focus();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Enter':
|
||||||
|
case ' ':
|
||||||
|
// Activate current tab
|
||||||
|
event.preventDefault();
|
||||||
|
// The click handler will handle the tab change
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Let other keys function normally
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="sidebar-tabs" role="tablist" aria-label="Sidebar tabs">
|
||||||
|
<button
|
||||||
|
class="sidebar-tab"
|
||||||
|
id="tab-profiles"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="panel-profiles"
|
||||||
|
aria-selected={activeTab === 'profiles'}
|
||||||
|
class:active={activeTab === 'profiles'}
|
||||||
|
tabindex={activeTab === 'profiles' ? "0" : "-1"}
|
||||||
|
on:click={() => dispatch('tabChange', { tab: 'profiles' })}
|
||||||
|
on:keydown={handleSidebarTabKeydown}>
|
||||||
|
Profiles
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="sidebar-tab"
|
||||||
|
id="tab-triggers"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="panel-triggers"
|
||||||
|
aria-selected={activeTab === 'triggers'}
|
||||||
|
class:active={activeTab === 'triggers'}
|
||||||
|
tabindex={activeTab === 'triggers' ? "0" : "-1"}
|
||||||
|
on:click={() => dispatch('tabChange', { tab: 'triggers' })}
|
||||||
|
on:keydown={handleSidebarTabKeydown}>
|
||||||
|
Triggers
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="sidebar-tab"
|
||||||
|
id="tab-settings"
|
||||||
|
role="tab"
|
||||||
|
aria-controls="panel-settings"
|
||||||
|
aria-selected={activeTab === 'settings'}
|
||||||
|
class:active={activeTab === 'settings'}
|
||||||
|
tabindex={activeTab === 'settings' ? "0" : "-1"}
|
||||||
|
on:click={() => dispatch('tabChange', { tab: 'settings' })}
|
||||||
|
on:keydown={handleSidebarTabKeydown}>
|
||||||
|
Settings
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.sidebar-tabs {
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tab {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tab.active {
|
||||||
|
color: var(--color-text);
|
||||||
|
border-bottom: 2px solid var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tab:focus {
|
||||||
|
outline: 2px solid var(--color-primary);
|
||||||
|
outline-offset: -2px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-tab:focus:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
184
src/lib/components/TriggerList.svelte
Normal file
184
src/lib/components/TriggerList.svelte
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
import { triggers } from '$lib/stores/mudStore';
|
||||||
|
import { ModalHelper } from '$lib/utils/ModalHelper';
|
||||||
|
import { TriggerSystem } from '$lib/triggers/TriggerSystem';
|
||||||
|
import type { Trigger } from '$lib/triggers/TriggerSystem';
|
||||||
|
|
||||||
|
// Component state
|
||||||
|
let triggerSystem = new TriggerSystem();
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
saveTrigger: { trigger: Trigger },
|
||||||
|
deleteTrigger: { triggerId: string },
|
||||||
|
}>();
|
||||||
|
|
||||||
|
// Create a new trigger
|
||||||
|
function createTrigger() {
|
||||||
|
console.log('Creating new trigger using ModalHelper');
|
||||||
|
try {
|
||||||
|
ModalHelper.showTriggerEditor(
|
||||||
|
(trigger) => {
|
||||||
|
console.log('Trigger saved from trigger editor:', trigger);
|
||||||
|
dispatch('saveTrigger', { trigger });
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
console.log('Trigger creation cancelled');
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error showing trigger editor:', error);
|
||||||
|
alert('Error showing modal: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Edit an existing trigger
|
||||||
|
function editTrigger(trigger: Trigger) {
|
||||||
|
console.log('Editing trigger using ModalHelper:', trigger);
|
||||||
|
try {
|
||||||
|
ModalHelper.showTriggerEditor(
|
||||||
|
(updatedTrigger) => {
|
||||||
|
console.log('Trigger updated from trigger editor:', updatedTrigger);
|
||||||
|
dispatch('saveTrigger', { trigger: updatedTrigger });
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
console.log('Trigger edit cancelled');
|
||||||
|
},
|
||||||
|
trigger
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error showing trigger editor:', error);
|
||||||
|
alert('Error showing modal: ' + error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete trigger
|
||||||
|
function deleteTrigger(triggerId: string) {
|
||||||
|
dispatch('deleteTrigger', { triggerId });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle trigger enabled state
|
||||||
|
function toggleTriggerEnabled(triggerId: string, enabled: boolean) {
|
||||||
|
triggerSystem.setTriggerEnabled(triggerId, enabled);
|
||||||
|
// Reload triggers to reflect changes
|
||||||
|
const allTriggers = triggerSystem.getTriggers();
|
||||||
|
triggers.set(allTriggers);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="panel-triggers"
|
||||||
|
role="tabpanel"
|
||||||
|
aria-labelledby="tab-triggers"
|
||||||
|
tabindex="0">
|
||||||
|
<div class="sidebar-header">
|
||||||
|
<h3>Triggers</h3>
|
||||||
|
<button class="btn btn-sm btn-primary" on:click={createTrigger}>New Trigger</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="trigger-list">
|
||||||
|
{#if $triggers.length === 0}
|
||||||
|
<p class="no-items">No triggers found. Create one to get started.</p>
|
||||||
|
{:else}
|
||||||
|
{#each $triggers as trigger (trigger.id)}
|
||||||
|
<div class="trigger-item" class:disabled={!trigger.isEnabled}>
|
||||||
|
<span class="trigger-name">{trigger.name}</span>
|
||||||
|
<div class="trigger-info">
|
||||||
|
<span class="trigger-pattern">{trigger.pattern}</span>
|
||||||
|
{#if trigger.soundFile}
|
||||||
|
<span class="trigger-sound" aria-label="Has sound">🔊</span>
|
||||||
|
{/if}
|
||||||
|
{#if trigger.highlightColor}
|
||||||
|
<span class="trigger-highlight" style="background-color: {trigger.highlightColor}" aria-label="Highlight color"></span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="trigger-actions">
|
||||||
|
<button class="btn btn-sm" on:click={() => toggleTriggerEnabled(trigger.id, !trigger.isEnabled)} aria-label="{trigger.isEnabled ? 'Disable' : 'Enable'} trigger {trigger.name}">
|
||||||
|
{trigger.isEnabled ? '✓' : '✗'}
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm" on:click={() => editTrigger(trigger)} aria-label="Edit trigger {trigger.name}">✏️</button>
|
||||||
|
<button class="btn btn-sm" on:click={() => deleteTrigger(trigger.id)} aria-label="Delete trigger {trigger.name}">🗑️</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
[role="tabpanel"]:focus {
|
||||||
|
outline: 2px solid var(--color-primary);
|
||||||
|
outline-offset: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="tabpanel"]:focus:not(:focus-visible) {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-item {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: var(--color-input-bg);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-item.disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-pattern {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-highlight {
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.trigger-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-items {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
}
|
}
|
||||||
import MudMdi from '$lib/components/MudMdi.svelte';
|
import MudMdi from '$lib/components/MudMdi.svelte';
|
||||||
import KeyboardShortcutsHelp from '$lib/components/KeyboardShortcutsHelp.svelte';
|
import KeyboardShortcutsHelp from '$lib/components/KeyboardShortcutsHelp.svelte';
|
||||||
import SettingsPanel from '$lib/components/SettingsPanel.svelte';
|
import Sidebar from '$lib/components/Sidebar.svelte';
|
||||||
import { ModalHelper } from '$lib/utils/ModalHelper';
|
import { ModalHelper } from '$lib/utils/ModalHelper';
|
||||||
import {
|
import {
|
||||||
profiles,
|
profiles,
|
||||||
@@ -31,11 +31,9 @@
|
|||||||
// Component state
|
// Component state
|
||||||
let profileManager: ProfileManager;
|
let profileManager: ProfileManager;
|
||||||
let triggerSystem: TriggerSystem;
|
let triggerSystem: TriggerSystem;
|
||||||
let showTriggerEditor = false;
|
let showKeyboardShortcutsHelp = false;
|
||||||
let editingTrigger: Trigger | null = null;
|
|
||||||
let showSidebar = true;
|
let showSidebar = true;
|
||||||
let sidebarTab: 'profiles' | 'triggers' | 'settings' = 'profiles';
|
let sidebarTab: 'profiles' | 'triggers' | 'settings' = 'profiles';
|
||||||
let showKeyboardShortcutsHelp = false;
|
|
||||||
|
|
||||||
// Save profile from component
|
// Save profile from component
|
||||||
function saveProfile(event) {
|
function saveProfile(event) {
|
||||||
@@ -252,49 +250,8 @@
|
|||||||
|
|
||||||
// Delete profile
|
// Delete profile
|
||||||
function deleteProfile(profileId: string) {
|
function deleteProfile(profileId: string) {
|
||||||
if (confirm(`Are you sure you want to delete this profile?`)) {
|
profileManager.removeProfile(profileId);
|
||||||
profileManager.removeProfile(profileId);
|
loadProfiles();
|
||||||
loadProfiles();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new trigger
|
|
||||||
function createTrigger() {
|
|
||||||
console.log('Creating new trigger using ModalHelper');
|
|
||||||
try {
|
|
||||||
ModalHelper.showTriggerEditor(
|
|
||||||
(trigger) => {
|
|
||||||
console.log('Trigger saved from trigger editor:', trigger);
|
|
||||||
saveTrigger({ detail: { trigger } });
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
console.log('Trigger creation cancelled');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error showing trigger editor:', error);
|
|
||||||
alert('Error showing modal: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Edit an existing trigger
|
|
||||||
function editTrigger(trigger: Trigger) {
|
|
||||||
console.log('Editing trigger using ModalHelper:', trigger);
|
|
||||||
try {
|
|
||||||
ModalHelper.showTriggerEditor(
|
|
||||||
(updatedTrigger) => {
|
|
||||||
console.log('Trigger updated from trigger editor:', updatedTrigger);
|
|
||||||
saveTrigger({ detail: { trigger: updatedTrigger } });
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
console.log('Trigger edit cancelled');
|
|
||||||
},
|
|
||||||
trigger
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error showing trigger editor:', error);
|
|
||||||
alert('Error showing modal: ' + error.message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save trigger
|
// Save trigger
|
||||||
@@ -302,20 +259,16 @@
|
|||||||
const trigger = event.detail.trigger;
|
const trigger = event.detail.trigger;
|
||||||
triggerSystem.addTrigger(trigger);
|
triggerSystem.addTrigger(trigger);
|
||||||
loadTriggers();
|
loadTriggers();
|
||||||
showTriggerEditor = false;
|
|
||||||
editingTrigger = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete trigger
|
// Delete trigger
|
||||||
function deleteTrigger(triggerId: string) {
|
function deleteTrigger(triggerId: string) {
|
||||||
if (confirm(`Are you sure you want to delete this trigger?`)) {
|
triggerSystem.removeTrigger(triggerId);
|
||||||
triggerSystem.removeTrigger(triggerId);
|
loadTriggers();
|
||||||
loadTriggers();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle keyboard shortcuts help
|
// Toggle keyboard shortcuts help
|
||||||
function toggleKeyboardShortcutsHelp() {
|
function toggleKeyboardShortcutsHelp() {
|
||||||
showKeyboardShortcutsHelp = !showKeyboardShortcutsHelp;
|
showKeyboardShortcutsHelp = !showKeyboardShortcutsHelp;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -351,58 +304,6 @@
|
|||||||
fontSize: size
|
fontSize: size
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle keyboard navigation for sidebar tabs
|
|
||||||
function handleSidebarTabKeydown(event: KeyboardEvent) {
|
|
||||||
// Define the tab order
|
|
||||||
const tabs = ['profiles', 'triggers', 'settings'];
|
|
||||||
const currentIndex = tabs.indexOf(sidebarTab);
|
|
||||||
|
|
||||||
switch (event.key) {
|
|
||||||
case 'ArrowRight':
|
|
||||||
case 'ArrowDown':
|
|
||||||
// Move to next tab
|
|
||||||
event.preventDefault();
|
|
||||||
const nextIndex = (currentIndex + 1) % tabs.length;
|
|
||||||
sidebarTab = tabs[nextIndex];
|
|
||||||
document.getElementById(`tab-${tabs[nextIndex]}`)?.focus();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'ArrowLeft':
|
|
||||||
case 'ArrowUp':
|
|
||||||
// Move to previous tab
|
|
||||||
event.preventDefault();
|
|
||||||
const prevIndex = (currentIndex - 1 + tabs.length) % tabs.length;
|
|
||||||
sidebarTab = tabs[prevIndex];
|
|
||||||
document.getElementById(`tab-${tabs[prevIndex]}`)?.focus();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Home':
|
|
||||||
// Move to first tab
|
|
||||||
event.preventDefault();
|
|
||||||
sidebarTab = tabs[0];
|
|
||||||
document.getElementById(`tab-${tabs[0]}`)?.focus();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'End':
|
|
||||||
// Move to last tab
|
|
||||||
event.preventDefault();
|
|
||||||
sidebarTab = tabs[tabs.length - 1];
|
|
||||||
document.getElementById(`tab-${tabs[tabs.length - 1]}`)?.focus();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Enter':
|
|
||||||
case ' ':
|
|
||||||
// Activate current tab
|
|
||||||
event.preventDefault();
|
|
||||||
// The click handler will handle the tab change
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Let other keys function normally
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="mud-client">
|
<div class="mud-client">
|
||||||
@@ -420,147 +321,14 @@
|
|||||||
|
|
||||||
<div class="mud-content">
|
<div class="mud-content">
|
||||||
{#if showSidebar}
|
{#if showSidebar}
|
||||||
<aside class="mud-sidebar">
|
<Sidebar
|
||||||
<div class="sidebar-tabs" role="tablist" aria-label="Sidebar tabs">
|
bind:sidebarTab={sidebarTab}
|
||||||
<button
|
on:saveProfile={saveProfile}
|
||||||
class="sidebar-tab"
|
on:deleteProfile={(e) => deleteProfile(e.detail.profileId)}
|
||||||
id="tab-profiles"
|
on:editProfile={(e) => editProfile(e.detail.profile)}
|
||||||
role="tab"
|
on:saveTrigger={saveTrigger}
|
||||||
aria-controls="panel-profiles"
|
on:deleteTrigger={(e) => deleteTrigger(e.detail.triggerId)}
|
||||||
aria-selected={sidebarTab === 'profiles'}
|
/>
|
||||||
class:active={sidebarTab === 'profiles'}
|
|
||||||
tabindex={sidebarTab === 'profiles' ? "0" : "-1"}
|
|
||||||
on:click={() => sidebarTab = 'profiles'}
|
|
||||||
on:keydown={handleSidebarTabKeydown}>
|
|
||||||
Profiles
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="sidebar-tab"
|
|
||||||
id="tab-triggers"
|
|
||||||
role="tab"
|
|
||||||
aria-controls="panel-triggers"
|
|
||||||
aria-selected={sidebarTab === 'triggers'}
|
|
||||||
class:active={sidebarTab === 'triggers'}
|
|
||||||
tabindex={sidebarTab === 'triggers' ? "0" : "-1"}
|
|
||||||
on:click={() => sidebarTab = 'triggers'}
|
|
||||||
on:keydown={handleSidebarTabKeydown}>
|
|
||||||
Triggers
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="sidebar-tab"
|
|
||||||
id="tab-settings"
|
|
||||||
role="tab"
|
|
||||||
aria-controls="panel-settings"
|
|
||||||
aria-selected={sidebarTab === 'settings'}
|
|
||||||
class:active={sidebarTab === 'settings'}
|
|
||||||
tabindex={sidebarTab === 'settings' ? "0" : "-1"}
|
|
||||||
on:click={() => sidebarTab = 'settings'}
|
|
||||||
on:keydown={handleSidebarTabKeydown}>
|
|
||||||
Settings
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="sidebar-content">
|
|
||||||
{#if sidebarTab === 'profiles'}
|
|
||||||
<div
|
|
||||||
id="panel-profiles"
|
|
||||||
role="tabpanel"
|
|
||||||
aria-labelledby="tab-profiles"
|
|
||||||
tabindex="0">
|
|
||||||
<div class="sidebar-header">
|
|
||||||
<h3>MUD Profiles</h3>
|
|
||||||
<button
|
|
||||||
class="btn btn-sm btn-primary"
|
|
||||||
on:click={() => {
|
|
||||||
console.log('New Profile button clicked - using full profile editor');
|
|
||||||
try {
|
|
||||||
ModalHelper.showProfileEditor(
|
|
||||||
(profile) => {
|
|
||||||
console.log('Profile saved from profile editor:', profile);
|
|
||||||
// Use the same saveProfile function for consistency
|
|
||||||
saveProfile({ detail: { profile } });
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
console.log('Profile creation cancelled');
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error showing profile editor:', error);
|
|
||||||
alert('Error showing modal: ' + error.message);
|
|
||||||
}
|
|
||||||
}}>New Profile</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="profile-list">
|
|
||||||
{#if $profiles.length === 0}
|
|
||||||
<p class="no-items">No profiles found. Create one to get started.</p>
|
|
||||||
{:else}
|
|
||||||
{#each $profiles as profile (profile.id)}
|
|
||||||
<div class="profile-item" class:active={$activeProfileId === profile.id}>
|
|
||||||
<span class="profile-name">{profile.name}</span>
|
|
||||||
<div class="profile-actions">
|
|
||||||
<button class="btn btn-sm" on:click={() => editProfile(profile)} aria-label="Edit profile {profile.name}">✏️</button>
|
|
||||||
<button class="btn btn-sm" on:click={() => deleteProfile(profile.id)} aria-label="Delete profile {profile.name}">🗑️</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else if sidebarTab === 'triggers'}
|
|
||||||
<div
|
|
||||||
id="panel-triggers"
|
|
||||||
role="tabpanel"
|
|
||||||
aria-labelledby="tab-triggers"
|
|
||||||
tabindex="0">
|
|
||||||
<div class="sidebar-header">
|
|
||||||
<h3>Triggers</h3>
|
|
||||||
<button class="btn btn-sm btn-primary" on:click={createTrigger}>New Trigger</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="trigger-list">
|
|
||||||
{#if $triggers.length === 0}
|
|
||||||
<p class="no-items">No triggers found. Create one to get started.</p>
|
|
||||||
{:else}
|
|
||||||
{#each $triggers as trigger (trigger.id)}
|
|
||||||
<div class="trigger-item" class:disabled={!trigger.isEnabled}>
|
|
||||||
<span class="trigger-name">{trigger.name}</span>
|
|
||||||
<div class="trigger-info">
|
|
||||||
<span class="trigger-pattern">{trigger.pattern}</span>
|
|
||||||
{#if trigger.soundFile}
|
|
||||||
<span class="trigger-sound" aria-label="Has sound">🔊</span>
|
|
||||||
{/if}
|
|
||||||
{#if trigger.highlightColor}
|
|
||||||
<span class="trigger-highlight" style="background-color: {trigger.highlightColor}" aria-label="Highlight color"></span>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<div class="trigger-actions">
|
|
||||||
<button class="btn btn-sm" on:click={() => triggerSystem.setTriggerEnabled(trigger.id, !trigger.isEnabled)} aria-label="{trigger.isEnabled ? 'Disable' : 'Enable'} trigger {trigger.name}">
|
|
||||||
{trigger.isEnabled ? '✓' : '✗'}
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-sm" on:click={() => editTrigger(trigger)} aria-label="Edit trigger {trigger.name}">✏️</button>
|
|
||||||
<button class="btn btn-sm" on:click={() => deleteTrigger(trigger.id)} aria-label="Delete trigger {trigger.name}">🗑️</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/each}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{:else if sidebarTab === 'settings'}
|
|
||||||
<div
|
|
||||||
id="panel-settings"
|
|
||||||
role="tabpanel"
|
|
||||||
aria-labelledby="tab-settings"
|
|
||||||
tabindex="0">
|
|
||||||
<div class="sidebar-header">
|
|
||||||
<h3>Settings</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<SettingsPanel />
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<main class="mud-main">
|
<main class="mud-main">
|
||||||
@@ -605,254 +373,9 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mud-sidebar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
width: 250px;
|
|
||||||
border-right: 1px solid var(--color-border);
|
|
||||||
background-color: var(--color-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-tabs {
|
|
||||||
display: flex;
|
|
||||||
border-bottom: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-tab {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.5rem;
|
|
||||||
text-align: center;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-tab.active {
|
|
||||||
color: var(--color-text);
|
|
||||||
border-bottom: 2px solid var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-tab:focus {
|
|
||||||
outline: 2px solid var(--color-primary);
|
|
||||||
outline-offset: -2px;
|
|
||||||
position: relative;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-tab:focus:not(:focus-visible) {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
[role="tabpanel"]:focus {
|
|
||||||
outline: 2px solid var(--color-primary);
|
|
||||||
outline-offset: -2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[role="tabpanel"]:focus:not(:focus-visible) {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-content {
|
|
||||||
flex: 1;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-list, .trigger-list, .settings-list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-item, .trigger-item, .setting-item {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: var(--border-radius);
|
|
||||||
background-color: var(--color-input-bg);
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.setting-description {
|
|
||||||
flex-basis: 100%;
|
|
||||||
margin-top: 5px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-item.active {
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
background-color: rgba(33, 150, 243, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-name, .trigger-name, .setting-name {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-actions, .trigger-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger-item.disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger-pattern {
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.trigger-highlight {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid var(--color-border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-items {
|
|
||||||
color: var(--color-text-muted);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mud-main {
|
.mud-main {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
</style>
|
||||||
/* Switch styling */
|
|
||||||
.switch {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
width: 40px;
|
|
||||||
height: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.switch input {
|
|
||||||
opacity: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider {
|
|
||||||
position: absolute;
|
|
||||||
cursor: pointer;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #ccc;
|
|
||||||
transition: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider:before {
|
|
||||||
position: absolute;
|
|
||||||
content: "";
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
left: 4px;
|
|
||||||
bottom: 4px;
|
|
||||||
background-color: white;
|
|
||||||
transition: 0.4s;
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider {
|
|
||||||
background-color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
input:focus + .slider {
|
|
||||||
box-shadow: 0 0 1px var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
input:checked + .slider:before {
|
|
||||||
transform: translateX(16px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider.round {
|
|
||||||
border-radius: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slider.round:before {
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-control {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-control input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.range-value {
|
|
||||||
min-width: 40px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form elements for modals */
|
|
||||||
.form-row {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons button {
|
|
||||||
padding: 8px 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons button:first-child {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons button:last-child {
|
|
||||||
background-color: #2196f3;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user