feat: implement state and components for text config

This commit is contained in:
2026-02-06 02:26:14 +05:00
parent a063a2d752
commit 5244de7cc0
11 changed files with 128 additions and 68 deletions
+7 -6
View File
@@ -6,27 +6,28 @@
import ColorPicker from "$components/ui/ColorPicker.svelte";
import RangeSlider from "$components/ui/RangeSlider.svelte";
import SelectFont from "$components/ui/SelectFont.svelte";
import { textConfigState } from "$states/textConfig.svelte";
</script>
<Card title="Настройки текста">
<SettingsGrid>
<SettingsRow label="Размер">
<RangeSlider />
<RangeSlider bind:value={textConfigState.fontSize} min={10} max={100} step={1} />
</SettingsRow>
<SettingsRow label="Шрифт">
<SelectFont />
<SelectFont bind:value={textConfigState.fontFamily} />
</SettingsRow>
<SettingsRow label="Цвет">
<ColorPicker />
<ColorPicker bind:value={textConfigState.color} />
</SettingsRow>
<SettingsRow label="Выравнивание">
<Alignment />
<Alignment bind:align={textConfigState.align} />
</SettingsRow>
<SettingsRow label="Отступы">
<RangeSlider />
<RangeSlider bind:value={textConfigState.paddingX} min={0} max={100} step={1} />
</SettingsRow>
<SettingsRow label="Смещение">
<RangeSlider />
<RangeSlider bind:value={textConfigState.offsetY} min={-100} max={100} step={1} />
</SettingsRow>
</SettingsGrid>
</Card>
+10 -3
View File
@@ -1,16 +1,23 @@
<script lang="ts">
import type { TextAlign } from "$lib/types/text";
import IconAlignCenter from "./Icons/IconAlignCenter.svelte";
import IconAlignLeft from "./Icons/IconAlignLeft.svelte";
import IconAlignRight from "./Icons/IconAlignRight.svelte";
interface Props {
align: TextAlign;
}
let { align = $bindable("left") }: Props = $props();
</script>
<button class="align-btn active">
<button class="align-btn" class:active={align === "left"} onclick={() => (align = "left")}>
<IconAlignLeft />
</button>
<button class="align-btn">
<button class="align-btn" class:active={align === "center"} onclick={() => (align = "center")}>
<IconAlignCenter />
</button>
<button class="align-btn">
<button class="align-btn" class:active={align === "right"} onclick={() => (align = "right")}>
<IconAlignRight />
</button>
+14 -2
View File
@@ -1,5 +1,15 @@
<input type="color" value="#ffffff" class="color-input" />
<span class="color-value">#ffffff</span>
<script lang="ts">
import type { HexColor } from "$lib/types/text";
interface Props {
value: HexColor;
}
let { value = $bindable() }: Props = $props();
</script>
<input type="color" bind:value class="color-input" />
<span class="color-value">{value}</span>
<style>
.color-input {
@@ -10,6 +20,8 @@
cursor: pointer;
transition: var(--transition);
background: var(--bg-primary);
padding: 0px 8px;
flex: 1;
}
.color-input:hover {
+16 -2
View File
@@ -1,5 +1,19 @@
<input type="range" id="fontSize" min="12" max="48" value="18" class="slider" />
<span class="value-display" id="fontSizeValue">18</span>
<script lang="ts">
import type { ChangeEventHandler } from "svelte/elements";
interface Props {
value: number;
min?: number;
max?: number;
step?: number;
onchange?: ChangeEventHandler<HTMLInputElement>;
}
let { value = $bindable(), min = 0, max = 100, step = 1, onchange = () => {} }: Props = $props();
</script>
<input type="range" {min} {max} {step} bind:value class="slider" {onchange} />
<span class="value-display">{value}</span>
<style>
.slider {
+9 -1
View File
@@ -1,4 +1,12 @@
<select class="select-input">
<script lang="ts">
interface Props {
value: string;
}
let { value = $bindable() }: Props = $props();
</script>
<select class="select-input" bind:value>
<option value="Arial">Arial</option>
<option value="Verdana">Verdana</option>
<option value="Georgia">Georgia</option>
-48
View File
@@ -1,48 +0,0 @@
export type TextAlign = "left" | "center" | "right";
export interface TextItem {
id: string;
text: string;
fontSize: number;
fontFamily: string;
color: string;
textAlign: TextAlign;
paddingX: number;
verticalOffset: number;
}
export interface Panel {
id: string;
backgroundImage: string;
text: TextItem;
height: number;
createdAt: Date;
updatedAt: Date;
}
export interface ImageUploadResult {
success: boolean;
image?: string;
error?: string;
}
export interface CropOptions {
width: number;
height: number;
x?: number;
y?: number;
}
export interface ImageCropResult {
success: boolean;
croppedImage?: string;
error?: string;
}
export interface UIState {
isLoading: boolean;
error: string | undefined;
currentStep: "upload" | "crop" | "text" | "preview";
showCropModal: boolean;
showTextManager: boolean;
}
+3
View File
@@ -0,0 +1,3 @@
export type TextAlign = "left" | "center" | "right";
export type HexColor = `#${string}`;
+2 -2
View File
@@ -24,7 +24,7 @@
<style>
:root {
--bg-primary: #ffffff;
--bg-secondary: #fafafa;
--bg-secondary: #dddddd;
--bg-card: #ffffff;
--bg-hover: #f5f5f5;
@@ -50,7 +50,7 @@
:global([data-theme="dark"]) {
--bg-primary: #0a0a0a;
--bg-secondary: #1a1a1a;
--bg-secondary: #333333;
--bg-card: #1a1a1a;
--bg-hover: #2a2a2a;
+63
View File
@@ -0,0 +1,63 @@
import type { HexColor, TextAlign } from "$lib/types/text";
export type TextConfig = {
fontSize: number;
fontFamily: string;
color: HexColor;
align: TextAlign;
paddingX: number;
offsetY: number;
};
function createState() {
const defaults: TextConfig = {
fontSize: 24,
fontFamily: "Arial",
color: "#ffffff",
align: "center",
paddingX: 10,
offsetY: 0,
};
let state: TextConfig = $state({ ...defaults });
return {
get fontSize() {
return state.fontSize;
},
set fontSize(value: number) {
state.fontSize = value;
},
get fontFamily() {
return state.fontFamily;
},
set fontFamily(fontFamily: string) {
state.fontFamily = this.fontFamily;
},
get color() {
return state.color;
},
set color(color: HexColor) {
state.color = color;
},
get align() {
return state.align;
},
set align(align: TextAlign) {
state.align = align;
},
get paddingX() {
return state.paddingX;
},
set paddingX(paddingX: number) {
state.paddingX = paddingX;
},
get offsetY() {
return state.offsetY;
},
set offsetY(offsetY: number) {
state.offsetY = offsetY;
},
};
}
export const textConfigState = createState();
+2 -2
View File
@@ -5,7 +5,7 @@ export interface TextItem {
const defaultTexts: Array<TextItem> = ["About me", "Links", "Projects"].map((text, idx) => ({ text, id: idx }));
function createTextsState() {
function createState() {
let texts: Array<TextItem> = $state(defaultTexts);
let nextId = $state(defaultTexts.length);
@@ -23,4 +23,4 @@ function createTextsState() {
};
}
export const textsState = createTextsState();
export const textsState = createState();
+2 -2
View File
@@ -1,6 +1,6 @@
export type Theme = "dark" | "light";
function createThemeState() {
function createState() {
let current: Theme = $state("dark");
return {
@@ -13,4 +13,4 @@ function createThemeState() {
};
}
export const themeState = createThemeState();
export const themeState = createState();