feat: implement base text manager and view components
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import type { Snippet } from "svelte";
|
||||
|
||||
interface Props {
|
||||
children: Snippet;
|
||||
}
|
||||
|
||||
let { children }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="input-group">
|
||||
{@render children()}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.input-group {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,16 @@
|
||||
<script lang="ts">
|
||||
import Badge from "$components/ui/Badge.svelte";
|
||||
import Button from "$components/ui/Button.svelte";
|
||||
import IconArrowLeft from "$components/ui/Icons/IconArrowLeft.svelte";
|
||||
import IconArrowRight from "$components/ui/Icons/IconArrowRight.svelte";
|
||||
import IconUpload from "$components/ui/Icons/IconUpload.svelte";
|
||||
import Card from "./Card.svelte";
|
||||
</script>
|
||||
|
||||
<div class="panel-bar">
|
||||
<Card title="Фоновое изображение">фоновое изображение</Card>
|
||||
<Card title="Фоновое изображение">
|
||||
<Button label="Загрузить изображение" icon={IconUpload} onclick={() => {}} type="secondary" />
|
||||
</Card>
|
||||
|
||||
{#snippet panelTitle()}
|
||||
Панели <Badge text="0" />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</script>
|
||||
|
||||
<div class="text-bar">
|
||||
<Card title="Тексты панелей"><TextManager /></Card>
|
||||
<TextManager />
|
||||
|
||||
<Card title="Настройки текста">Настройки текста</Card>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import Button from "$components/ui/Button.svelte";
|
||||
import IconCross from "$components/ui/Icons/IconCross.svelte";
|
||||
|
||||
interface Props {
|
||||
text: string;
|
||||
ondelete: () => void;
|
||||
}
|
||||
|
||||
let { text = $bindable(), ondelete }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div class="text-item">
|
||||
<input type="text" bind:value={text} />
|
||||
<Button icon={IconCross} type="danger" on:click={ondelete} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.text-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius);
|
||||
transition: var(--transition);
|
||||
}
|
||||
|
||||
.text-item:hover {
|
||||
border-color: var(--border-hover);
|
||||
}
|
||||
|
||||
.text-item input {
|
||||
flex: 1;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
transition: var(--transition);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.text-item input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
interface Props {
|
||||
text: string;
|
||||
onenter: () => void;
|
||||
}
|
||||
|
||||
let { text = $bindable(), onenter }: Props = $props();
|
||||
|
||||
function handleKeyboard(event: KeyboardEvent) {
|
||||
if (event.key === "Enter") {
|
||||
onenter();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<input bind:value={text} class="text-input" type="text" placeholder="Введите текст..." onkeydown={handleKeyboard} />
|
||||
|
||||
<style>
|
||||
.text-input {
|
||||
flex: 1;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--radius);
|
||||
font-size: 14px;
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
transition: var(--transition);
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.text-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.text-input::placeholder {
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,39 @@
|
||||
<script lang="ts">
|
||||
import Card from "$components/layout/Card.svelte";
|
||||
import InputGroup from "$components/layout/InputGroup.svelte";
|
||||
import Button from "$components/ui/Button.svelte";
|
||||
import IconPlus from "$components/ui/Icons/IconPlus.svelte";
|
||||
import { textsState } from "$states/texts.state.svelte";
|
||||
import TextInlineEdit from "./TextInlineEdit.svelte";
|
||||
import TextInput from "./TextInput.svelte";
|
||||
|
||||
let text: string = $state("");
|
||||
|
||||
function addText() {
|
||||
console.log(text);
|
||||
text = "";
|
||||
}
|
||||
|
||||
function deleteText(id: number) {}
|
||||
</script>
|
||||
|
||||
<Card title="Тексты панелей">
|
||||
<InputGroup>
|
||||
<TextInput bind:text onenter={addText} />
|
||||
<Button icon={IconPlus} onclick={addText} />
|
||||
</InputGroup>
|
||||
<div class="texts-list">
|
||||
{#each textsState.texts as { text, id } (id)}
|
||||
<TextInlineEdit {text} ondelete={() => deleteText(id)} />
|
||||
{/each}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<style>
|
||||
/* Texts List */
|
||||
.texts-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,110 @@
|
||||
<script lang="ts">
|
||||
import type { Component } from "svelte";
|
||||
import type { MouseEventHandler } from "svelte/elements";
|
||||
|
||||
interface Props {
|
||||
icon: Component;
|
||||
disabled?: boolean;
|
||||
label?: string;
|
||||
type?: "primary" | "secondary" | "danger" | "outline";
|
||||
onclick: MouseEventHandler<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
let { icon: Icon, disabled = false, label, type = "primary", onclick }: Props = $props();
|
||||
</script>
|
||||
|
||||
<button
|
||||
class="btn"
|
||||
class:btn-primary={type === "primary"}
|
||||
class:btn-secondary={type === "secondary"}
|
||||
class:btn-outline={type === "outline"}
|
||||
class:btn-danger={type === "danger"}
|
||||
{disabled}
|
||||
{onclick}
|
||||
>
|
||||
<Icon />
|
||||
{label}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
.btn {
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: var(--radius);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: var(--transition);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-family: inherit;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn :global(svg) {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: var(--accent-hover);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn-outline:hover:not(:disabled) {
|
||||
background: var(--bg-hover);
|
||||
border-color: var(--accent-primary);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
|
||||
.btn-outline:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: var(--danger);
|
||||
color: white;
|
||||
padding: 6px 10px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: var(--transition);
|
||||
min-width: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
</style>
|
||||
@@ -1,23 +1,30 @@
|
||||
export interface TextsState {
|
||||
texts: string[];
|
||||
addText(text: string): void;
|
||||
removeText(text: string): void;
|
||||
export interface TextItem {
|
||||
text: string;
|
||||
id: number;
|
||||
}
|
||||
|
||||
const defaultTexts: string[] = ["About me", "Links", "Projects"];
|
||||
export interface TextsState {
|
||||
texts: Array<TextItem>;
|
||||
addText(text: string): void;
|
||||
removeText(id: number): void;
|
||||
}
|
||||
|
||||
const defaultTexts: Array<TextItem> = ["About me", "Links", "Projects"].map((text, idx) => ({ text, id: idx }));
|
||||
|
||||
function createTextsState(): TextsState {
|
||||
let texts: string[] = $state(defaultTexts);
|
||||
let texts: Array<TextItem> = $state(defaultTexts);
|
||||
let nextId = $state(defaultTexts.length);
|
||||
|
||||
return {
|
||||
get texts() {
|
||||
return texts;
|
||||
},
|
||||
addText(text: string) {
|
||||
texts = [...texts, text];
|
||||
texts.push({ text, id: nextId });
|
||||
nextId++;
|
||||
},
|
||||
removeText(text: string) {
|
||||
texts = texts.filter((t) => t !== text);
|
||||
removeText(id: number) {
|
||||
texts.filter((textItem) => textItem.id === id);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user