chore: standardize web controls
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { Input } from "$components/ui/index.js";
|
||||
import { Trash2 } from "$components/ui/icons.js";
|
||||
import { getContext, onMount } from "svelte";
|
||||
import Dialog from "$components/ui/dialog.svelte";
|
||||
@@ -106,7 +107,7 @@
|
||||
<div class="admin-form" data-dialog-content>
|
||||
<div class="admin-dialog-form-section">
|
||||
<AdminField label={at("ad_label_source", {}, "Источник")}>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="telegram_ads"
|
||||
@@ -118,7 +119,7 @@
|
||||
label={at("ad_label_param", {}, "start-параметр")}
|
||||
hint={at("ad_hint_param", {}, "Передаётся в /start, должен быть уникален")}
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="ads_summer25"
|
||||
@@ -129,7 +130,7 @@
|
||||
</div>
|
||||
<div class="admin-dialog-form-section">
|
||||
<AdminField label={at("ad_label_cost", {}, "Стоимость, RUB")}>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
step="0.01"
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
AdminEmptyState,
|
||||
AdminSelect,
|
||||
} from "$components/patterns/admin/index.js";
|
||||
import { Checkbox } from "$components/ui/index.js";
|
||||
import { Checkbox, ColorInput, FileInput, Input, RangeInput } from "$components/ui/index.js";
|
||||
import { Switch } from "$components/ui/primitives.js";
|
||||
import { getContext, onDestroy, onMount } from "svelte";
|
||||
|
||||
@@ -444,10 +444,9 @@
|
||||
|
||||
<div class="appearance-controls">
|
||||
<section class="appearance-control-card">
|
||||
<input
|
||||
bind:this={logoFileInput}
|
||||
<FileInput
|
||||
bind:element={logoFileInput}
|
||||
class="appearance-file-input"
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml,image/x-icon"
|
||||
onchange={handleLogoFileChange}
|
||||
/>
|
||||
@@ -461,7 +460,7 @@
|
||||
{at("appearance_logo_upload_file", {}, "Загрузить файл")}
|
||||
</AdminButton>
|
||||
<div class="appearance-url-row">
|
||||
<input
|
||||
<Input
|
||||
class="input appearance-control"
|
||||
type="url"
|
||||
placeholder="https://example.com/logo.png"
|
||||
@@ -490,7 +489,7 @@
|
||||
<span>{at("appearance_use_emoji_logo", {}, "Использовать emoji-логотип")}</span>
|
||||
</label>
|
||||
<div class="appearance-emoji-grid">
|
||||
<input
|
||||
<Input
|
||||
class="input appearance-control"
|
||||
type="text"
|
||||
maxlength="8"
|
||||
@@ -547,10 +546,9 @@
|
||||
>{at("appearance_use_custom_favicon", {}, "Использовать отдельную favicon")}</span
|
||||
>
|
||||
</label>
|
||||
<input
|
||||
bind:this={faviconFileInput}
|
||||
<FileInput
|
||||
bind:element={faviconFileInput}
|
||||
class="appearance-file-input"
|
||||
type="file"
|
||||
accept="image/png,image/jpeg,image/gif,image/webp,image/svg+xml,image/x-icon,.ico"
|
||||
onchange={handleFaviconFileChange}
|
||||
/>
|
||||
@@ -564,7 +562,7 @@
|
||||
{at("appearance_favicon_upload_file", {}, "Загрузить favicon")}
|
||||
</AdminButton>
|
||||
<div class="appearance-url-row">
|
||||
<input
|
||||
<Input
|
||||
class="input appearance-control"
|
||||
type="url"
|
||||
placeholder="https://example.com/icon.png"
|
||||
@@ -657,18 +655,17 @@
|
||||
</span>
|
||||
<label class="admin-theme-card-option appearance-color-row">
|
||||
<span>{at("appearance_theme_accent", {}, "Accent")}</span>
|
||||
<input
|
||||
class="admin-color"
|
||||
class:is-empty={!isThemeAccentSet(theme)}
|
||||
type="color"
|
||||
<ColorInput
|
||||
class={`admin-color${!isThemeAccentSet(theme) ? " is-empty" : ""}`}
|
||||
value={pickerHex(theme.tokens?.accent)}
|
||||
ariaLabel={at("appearance_theme_accent", {}, "Accent")}
|
||||
title={isThemeAccentSet(theme)
|
||||
? theme.tokens?.accent
|
||||
: at("appearance_theme_accent_empty", {}, "Не задан")}
|
||||
onclick={() => openThemeAccentPicker(theme)}
|
||||
oninput={(event) => setThemeAccent(theme, event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input appearance-color-text"
|
||||
type="text"
|
||||
placeholder={at("appearance_theme_accent_placeholder", {}, "Не задан")}
|
||||
@@ -693,17 +690,17 @@
|
||||
"Логотип на главной и входе"
|
||||
)}</span
|
||||
>
|
||||
<input
|
||||
<RangeInput
|
||||
class="appearance-logo-scale-range"
|
||||
type="range"
|
||||
min="50"
|
||||
max="300"
|
||||
step="5"
|
||||
ariaLabel={at("appearance_theme_home_logo_scale", {}, "Home logo scale")}
|
||||
value={homeLogoScale(theme)}
|
||||
oninput={(event) => setThemeHomeLogoScale(theme, event.currentTarget.value)}
|
||||
onValueChange={(value) => setThemeHomeLogoScale(theme, value)}
|
||||
/>
|
||||
<span class="appearance-logo-scale-value">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="50"
|
||||
@@ -814,7 +811,7 @@
|
||||
background: color-mix(in srgb, var(--admin-surface-2) 40%, transparent);
|
||||
}
|
||||
|
||||
.appearance-file-input {
|
||||
:global(.appearance-file-input) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -949,7 +946,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.appearance-color-text {
|
||||
:global(.appearance-color-text) {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
@@ -959,7 +956,7 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.appearance-logo-scale-range {
|
||||
:global(.appearance-logo-scale-range) {
|
||||
width: 100%;
|
||||
accent-color: var(--accent);
|
||||
}
|
||||
@@ -971,14 +968,14 @@
|
||||
color: var(--admin-text);
|
||||
}
|
||||
|
||||
.appearance-logo-scale-value .input {
|
||||
:global(.appearance-logo-scale-value .input) {
|
||||
width: 70px;
|
||||
min-height: 32px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.admin-color.is-empty {
|
||||
:global(.admin-color.is-empty) {
|
||||
opacity: 0.42;
|
||||
filter: grayscale(1);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
AdminTable,
|
||||
AdminTableSkeleton,
|
||||
} from "$components/patterns/admin/index.js";
|
||||
import { Checkbox, RadioGroup, RadioGroupItem } from "$components/ui/index.js";
|
||||
import { Checkbox, FileInput, RadioGroup, RadioGroupItem } from "$components/ui/index.js";
|
||||
import {
|
||||
CheckCircle2,
|
||||
Database,
|
||||
@@ -178,10 +178,9 @@
|
||||
? at("backups_uploading", {}, "Загрузка...")
|
||||
: at("backups_upload", {}, "Загрузить архив")}
|
||||
</AdminButton>
|
||||
<input
|
||||
bind:this={fileInput}
|
||||
<FileInput
|
||||
bind:element={fileInput}
|
||||
class="backups-file-input"
|
||||
type="file"
|
||||
accept=".zip,application/zip"
|
||||
on:change={uploadSelectedFile}
|
||||
/>
|
||||
@@ -362,7 +361,7 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.backups-file-input {
|
||||
:global(.backups-file-input) {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { Textarea } from "$components/ui/index.js";
|
||||
import { Send } from "$components/ui/icons.js";
|
||||
import { getContext } from "svelte";
|
||||
import { Label } from "$components/ui/primitives.js";
|
||||
@@ -31,12 +32,12 @@
|
||||
<Label.Root class="admin-field-label">
|
||||
<span>{at("broadcast_label_text", {}, "Текст сообщения")}</span>
|
||||
<small>{at("broadcast_hint_text", {}, "Поддерживается HTML-разметка Telegram")}</small>
|
||||
<textarea
|
||||
<Textarea
|
||||
class="admin-textarea"
|
||||
rows="6"
|
||||
value={broadcastText}
|
||||
on:input={(e) => broadcastStore.updateField({ broadcastText: e.target.value })}
|
||||
></textarea>
|
||||
/>
|
||||
</Label.Root>
|
||||
<div style="display:flex; gap:8px; align-items:center;">
|
||||
<AdminButton
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { Input } from "$components/ui/index.js";
|
||||
import { getContext, onMount } from "svelte";
|
||||
import {
|
||||
AdminButton,
|
||||
@@ -31,7 +32,7 @@
|
||||
|
||||
<div class="admin-toolbar admin-toolbar-card">
|
||||
<div class="admin-toolbar-search admin-toolbar-search-actions">
|
||||
<input
|
||||
<Input
|
||||
type="search"
|
||||
class="input"
|
||||
placeholder={at("logs_user_filter_placeholder", {}, "Фильтр по ID пользователя")}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { Input } from "$components/ui/index.js";
|
||||
import { Trash2 } from "$components/ui/icons.js";
|
||||
import { getContext, onMount } from "svelte";
|
||||
import Dialog from "$components/ui/dialog.svelte";
|
||||
@@ -110,7 +111,7 @@
|
||||
<div class="admin-form" data-dialog-content>
|
||||
<div class="admin-dialog-form-section">
|
||||
<AdminField label={at("promo_label_code", {}, "Код")}>
|
||||
<input
|
||||
<Input
|
||||
type="text"
|
||||
class="input"
|
||||
value={promoDraft.code}
|
||||
@@ -122,7 +123,7 @@
|
||||
<div class="admin-dialog-form-section">
|
||||
<div class="admin-form-row-2">
|
||||
<AdminField label={at("promo_label_bonus_days", {}, "Бонус (дней)")}>
|
||||
<input
|
||||
<Input
|
||||
type="number"
|
||||
class="input"
|
||||
min="1"
|
||||
@@ -131,7 +132,7 @@
|
||||
/>
|
||||
</AdminField>
|
||||
<AdminField label={at("promo_label_max_activations", {}, "Макс. активаций")}>
|
||||
<input
|
||||
<Input
|
||||
type="number"
|
||||
class="input"
|
||||
min="1"
|
||||
@@ -141,7 +142,7 @@
|
||||
</AdminField>
|
||||
</div>
|
||||
<AdminField label={at("promo_label_valid_days", {}, "Срок действия (дней от текущего)")}>
|
||||
<input
|
||||
<Input
|
||||
type="number"
|
||||
class="input"
|
||||
min="1"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { ColorInput, FileInput, Input, Textarea } from "$components/ui/index.js";
|
||||
import {
|
||||
Check,
|
||||
ChevronRight,
|
||||
@@ -515,13 +516,13 @@
|
||||
>
|
||||
</div>
|
||||
{:else if field.type === "color"}
|
||||
<input
|
||||
<ColorInput
|
||||
class="admin-color"
|
||||
type="color"
|
||||
value={valueFor(field) || "#00fe7a"}
|
||||
ariaLabel={fieldLabelText(field)}
|
||||
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
value={valueFor(field) || ""}
|
||||
@@ -560,7 +561,7 @@
|
||||
onValueChange={(value) => settingsStore.markDirty(field.key, value)}
|
||||
/>
|
||||
{:else if field.type === "int" || field.type === "float"}
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
step={field.type === "float" ? "0.1" : "1"}
|
||||
@@ -571,19 +572,18 @@
|
||||
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
||||
/>
|
||||
{:else if field.type === "text"}
|
||||
<textarea
|
||||
<Textarea
|
||||
class="admin-setting-textarea"
|
||||
rows="4"
|
||||
placeholder={fieldPlaceholderText(field)}
|
||||
value={valueFor(field) ?? ""}
|
||||
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
||||
></textarea>
|
||||
/>
|
||||
{:else if field.type === "json"}
|
||||
<div class="admin-json-toolbar">
|
||||
<input
|
||||
<FileInput
|
||||
id={"json-file-" + field.key}
|
||||
class="admin-json-file-input"
|
||||
type="file"
|
||||
accept="application/json,.json"
|
||||
onchange={(event) => handleJsonFile(field, event)}
|
||||
/>
|
||||
@@ -605,16 +605,16 @@
|
||||
</AdminButton>
|
||||
{/if}
|
||||
</div>
|
||||
<textarea
|
||||
<Textarea
|
||||
class="admin-setting-textarea admin-setting-json-textarea"
|
||||
rows="10"
|
||||
spellcheck="false"
|
||||
placeholder={fieldPlaceholderText(field)}
|
||||
value={valueFor(field) ?? ""}
|
||||
oninput={(e) => settingsStore.markDirty(field.key, e.currentTarget.value)}
|
||||
></textarea>
|
||||
/>
|
||||
{:else if field.secret}
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type={revealed ? "text" : "password"}
|
||||
placeholder={secretPlaceholder(field)}
|
||||
@@ -631,7 +631,7 @@
|
||||
{#if revealed}<EyeOff size={13} />{:else}<Eye size={13} />{/if}
|
||||
</AdminButton>
|
||||
{:else}
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder={fieldPlaceholderText(field)}
|
||||
@@ -816,7 +816,7 @@
|
||||
<div class="admin-icon-picker-toolbar">
|
||||
<label class="admin-icon-picker-search">
|
||||
<Search size={15} />
|
||||
<input
|
||||
<Input
|
||||
bind:value={iconPickerSearch}
|
||||
class="input"
|
||||
type="text"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
import { TicketMessageBubble } from "$components/patterns/webapp/index.js";
|
||||
import Dialog from "$components/ui/dialog.svelte";
|
||||
import { Search } from "$components/ui/icons.js";
|
||||
import { ScrollArea, Skeleton } from "$components/ui/index.js";
|
||||
import { Input, ScrollArea, Skeleton } from "$components/ui/index.js";
|
||||
|
||||
export let at = (key) => key;
|
||||
export let initialTicketId = null;
|
||||
@@ -215,7 +215,7 @@
|
||||
<div class="support-admin-toolbar admin-toolbar-card">
|
||||
<label class="support-admin-search">
|
||||
<Search size={16} />
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="search"
|
||||
placeholder={at("support_search", {}, "Поиск")}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { Input } from "$components/ui/index.js";
|
||||
import { Tabs, Switch, Label } from "$components/ui/primitives.js";
|
||||
import Dialog from "$components/ui/dialog.svelte";
|
||||
import { Plus, Save, Trash2, X } from "$components/ui/icons.js";
|
||||
@@ -71,7 +72,7 @@
|
||||
"Латиницей, без пробелов. Используется в платежах и подписках, менять после публикации не рекомендуется"
|
||||
)}</small
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="standard"
|
||||
@@ -127,7 +128,7 @@
|
||||
<div class="admin-form-row admin-form-row-2">
|
||||
<Label.Root class="admin-field-label">
|
||||
<span>{at("tariff_label_name_ru", {}, "Название · RU")}</span>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder={at("tariff_placeholder_name_ru", {}, "Стандарт")}
|
||||
@@ -136,7 +137,7 @@
|
||||
</Label.Root>
|
||||
<Label.Root class="admin-field-label">
|
||||
<span>{at("tariff_label_name_en", {}, "Название · EN")}</span>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder={at("tariff_placeholder_name_en", {}, "Standard")}
|
||||
@@ -148,7 +149,7 @@
|
||||
<div class="admin-form-row admin-form-row-2">
|
||||
<Label.Root class="admin-field-label">
|
||||
<span>{at("tariff_label_desc_ru", {}, "Описание · RU")}</span>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder={at("tariff_placeholder_desc_ru", {}, "Базовый набор серверов")}
|
||||
@@ -157,7 +158,7 @@
|
||||
</Label.Root>
|
||||
<Label.Root class="admin-field-label">
|
||||
<span>{at("tariff_label_desc_en", {}, "Описание · EN")}</span>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder={at("tariff_placeholder_desc_en", {}, "Base server pool")}
|
||||
@@ -211,7 +212,7 @@
|
||||
"Сколько устройств может одновременно использовать подписку. Пусто — взять значение из .env, 0 — без ограничений"
|
||||
)}</small
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -229,7 +230,7 @@
|
||||
"Сколько GB включено в тариф на каждый месяц. 0 — безлимитный трафика. Сверху можно докупать пакеты на вкладке «Докупки»"
|
||||
)}</small
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -248,7 +249,7 @@
|
||||
"По этому курсу остаток подписки пересчитывается в гигабайты при переходе пользователя с тарифа «Период» на «Трафик»"
|
||||
)}</small
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -287,7 +288,7 @@
|
||||
"Эта строка заменит «Premium-серверы» в кабинете, докупках и карточках лимитов."
|
||||
)}</small
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder={at("tariff_placeholder_premium_name_ru", {}, "Premium-серверы")}
|
||||
@@ -303,7 +304,7 @@
|
||||
"Опционально для английского интерфейса."
|
||||
)}</small
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder={at("tariff_placeholder_premium_name_en", {}, "Premium servers")}
|
||||
@@ -359,7 +360,7 @@
|
||||
"Сколько GB через premium-сквады включено в тариф каждый месяц. 0 или пусто — отдельного premium-лимита нет (premium-нодами можно пользоваться без ограничения)"
|
||||
)}</small
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -409,7 +410,7 @@
|
||||
{/if}
|
||||
{#each tariffDraft.premiumTopupRubRows as row, index}
|
||||
<div class="admin-row-editor-line">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0.1"
|
||||
@@ -418,7 +419,7 @@
|
||||
bind:value={row.gb}
|
||||
aria-label={at("tariff_col_volume_gb", {}, "Объём premium-пакета в GB")}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -449,7 +450,7 @@
|
||||
{/if}
|
||||
{#each tariffDraft.premiumTopupStarsRows as row, index}
|
||||
<div class="admin-row-editor-line">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0.1"
|
||||
@@ -458,7 +459,7 @@
|
||||
bind:value={row.gb}
|
||||
aria-label={at("tariff_col_volume_gb", {}, "Объём premium-пакета в GB")}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -533,7 +534,7 @@
|
||||
</div>
|
||||
{#each tariffDraft.periodRows as row, index}
|
||||
<div class="admin-row-editor-line admin-row-editor-6">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="1"
|
||||
@@ -541,7 +542,7 @@
|
||||
bind:value={row.months}
|
||||
aria-label={at("tariff_col_period_months", {}, "Срок (месяцы)")}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -550,7 +551,7 @@
|
||||
bind:value={row.rub}
|
||||
aria-label={at("tariff_label_price_rub", {}, "Цена в рублях")}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -559,7 +560,7 @@
|
||||
bind:value={row.stars}
|
||||
aria-label={at("tariff_label_price_stars", {}, "Цена в Telegram Stars")}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -568,7 +569,7 @@
|
||||
bind:value={row.referral_inviter}
|
||||
aria-label={at("tariff_label_ref_inviter", {}, "Бонус приглашающему")}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -629,7 +630,7 @@
|
||||
{/if}
|
||||
{#each tariffDraft.trafficRubRows as row, index}
|
||||
<div class="admin-row-editor-line">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0.1"
|
||||
@@ -638,7 +639,7 @@
|
||||
bind:value={row.gb}
|
||||
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -669,7 +670,7 @@
|
||||
{/if}
|
||||
{#each tariffDraft.trafficStarsRows as row, index}
|
||||
<div class="admin-row-editor-line">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0.1"
|
||||
@@ -678,7 +679,7 @@
|
||||
bind:value={row.gb}
|
||||
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -743,7 +744,7 @@
|
||||
{/if}
|
||||
{#each tariffDraft.topupRubRows as row, index}
|
||||
<div class="admin-row-editor-line">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0.1"
|
||||
@@ -752,7 +753,7 @@
|
||||
bind:value={row.gb}
|
||||
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -783,7 +784,7 @@
|
||||
{/if}
|
||||
{#each tariffDraft.topupStarsRows as row, index}
|
||||
<div class="admin-row-editor-line">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0.1"
|
||||
@@ -792,7 +793,7 @@
|
||||
bind:value={row.gb}
|
||||
aria-label={at("tariff_col_volume_gb", {}, "Объём пакета в GB")}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -867,7 +868,7 @@
|
||||
{/if}
|
||||
{#each tariffDraft.hwidRubRows as row, index}
|
||||
<div class="admin-row-editor-line">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="1"
|
||||
@@ -880,7 +881,7 @@
|
||||
"Сколько устройств добавляет пакет"
|
||||
)}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -911,7 +912,7 @@
|
||||
{/if}
|
||||
{#each tariffDraft.hwidStarsRows as row, index}
|
||||
<div class="admin-row-editor-line">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="1"
|
||||
@@ -924,7 +925,7 @@
|
||||
"Сколько устройств добавляет пакет"
|
||||
)}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { Input } from "$components/ui/index.js";
|
||||
import {
|
||||
ChevronRight,
|
||||
RefreshCw,
|
||||
@@ -403,7 +404,7 @@
|
||||
<code>TRIAL_DURATION_DAYS</code>
|
||||
</div>
|
||||
<div class="admin-setting-control">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -439,7 +440,7 @@
|
||||
<code>TRIAL_TRAFFIC_LIMIT_GB</code>
|
||||
</div>
|
||||
<div class="admin-setting-control">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -591,7 +592,7 @@
|
||||
onValueChange={handleTrialSquadSelect}
|
||||
/>
|
||||
{/key}
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder={valueForKey("USER_SQUAD_UUIDS", settingsDirty, settingsFieldMap) ||
|
||||
@@ -837,7 +838,7 @@
|
||||
<Switch.Thumb class="admin-switch-thumb" />
|
||||
</Switch.Root>
|
||||
</div>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -845,7 +846,7 @@
|
||||
value={valueForKey(rubKey, settingsDirty, settingsFieldMap)}
|
||||
oninput={(event) => setSetting(rubKey, event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -853,7 +854,7 @@
|
||||
value={valueForKey(starsKey, settingsDirty, settingsFieldMap)}
|
||||
oninput={(event) => setSetting(starsKey, event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -861,7 +862,7 @@
|
||||
value={valueForKey(inviterKey, settingsDirty, settingsFieldMap)}
|
||||
oninput={(event) => setSetting(inviterKey, event.currentTarget.value)}
|
||||
/>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -877,7 +878,7 @@
|
||||
<label class="admin-field-label admin-field-label-compact">
|
||||
<span>{at("tariffs_legacy_traffic_packages", {}, "Traffic packages")}</span>
|
||||
<small>{at("tariffs_legacy_traffic_hint", {}, "Format: 10:199,50:799")}</small>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
value={valueForKey("TRAFFIC_PACKAGES", settingsDirty, settingsFieldMap)}
|
||||
@@ -889,7 +890,7 @@
|
||||
>{at("tariffs_legacy_stars_traffic_packages", {}, "Traffic packages, Stars")}</span
|
||||
>
|
||||
<small>{at("tariffs_legacy_traffic_hint", {}, "Format: 10:199,50:799")}</small>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="text"
|
||||
value={valueForKey("STARS_TRAFFIC_PACKAGES", settingsDirty, settingsFieldMap)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { Input, Textarea } from "$components/ui/index.js";
|
||||
import { ChevronRight, Languages, Plus, Search, X } from "$components/ui/icons.js";
|
||||
import { AdminBadge, AdminButton, AdminEmptyState } from "$components/patterns/admin/index.js";
|
||||
import { getContext, onDestroy, onMount } from "svelte";
|
||||
@@ -396,7 +397,7 @@
|
||||
|
||||
{#if expanded}
|
||||
<div class="admin-translation-locale-body" transition:slide={{ duration: 130 }}>
|
||||
<textarea
|
||||
<Textarea
|
||||
class="admin-setting-textarea admin-translation-textarea"
|
||||
rows="3"
|
||||
spellcheck="false"
|
||||
@@ -404,7 +405,7 @@
|
||||
value={localeValue(item, lang, dirtyEntry)}
|
||||
oninput={(event) =>
|
||||
translationsStore.markDirty(lang, item.key, event.currentTarget.value)}
|
||||
></textarea>
|
||||
/>
|
||||
<div class="admin-translation-base">
|
||||
<small>{baseKind(item, lang)}</small>
|
||||
<span title={baseValue(item, lang)}>{baseValue(item, lang) || "-"}</span>
|
||||
@@ -457,7 +458,7 @@
|
||||
<div class="admin-translations-toolbar">
|
||||
<label class="admin-translations-search">
|
||||
<Search size={15} />
|
||||
<input
|
||||
<Input
|
||||
bind:value={search}
|
||||
class="input"
|
||||
type="text"
|
||||
@@ -502,7 +503,7 @@
|
||||
addLanguage();
|
||||
}}
|
||||
>
|
||||
<input
|
||||
<Input
|
||||
bind:value={newLanguageCode}
|
||||
class="input"
|
||||
type="text"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script>
|
||||
import { Label, Separator, Tabs } from "$components/ui/primitives.js";
|
||||
import { Checkbox } from "$components/ui/index.js";
|
||||
import { Checkbox, Input, Textarea } from "$components/ui/index.js";
|
||||
import Dialog from "$components/ui/dialog.svelte";
|
||||
import {
|
||||
AdminBadge,
|
||||
@@ -632,7 +632,7 @@
|
||||
<Label.Root class="admin-field-label admin-extend-field">
|
||||
<span>{at("user_label_extend", {}, "Продлить подписку")}</span>
|
||||
<div class="admin-extend-control">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="1"
|
||||
@@ -662,7 +662,7 @@
|
||||
<span>{at("user_premium_override_bonus", {}, "Доп. премиум-трафик, GB")}</span
|
||||
>
|
||||
<small>{at("user_premium_override_bonus_hint", {}, "")}</small>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -741,7 +741,7 @@
|
||||
>{at("user_regular_override_bonus", {}, "Доп. основной трафик, GB")}</span
|
||||
>
|
||||
<small>{at("user_regular_override_bonus_hint", {}, "")}</small>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -837,7 +837,7 @@
|
||||
<Label.Root class="admin-field-label admin-extend-field">
|
||||
<span>{at("user_traffic_grant_gb", {}, "ГБ к выдаче")}</span>
|
||||
<div class="admin-extend-control">
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
type="number"
|
||||
min="0"
|
||||
@@ -869,12 +869,12 @@
|
||||
"Поддерживается HTML-разметка Telegram"
|
||||
)}</small
|
||||
>
|
||||
<textarea
|
||||
<Textarea
|
||||
class="admin-textarea"
|
||||
rows="3"
|
||||
placeholder={at("user_placeholder_msg", {}, "Текст сообщения")}
|
||||
bind:value={$usersStore.userMessageDraft}
|
||||
></textarea>
|
||||
/>
|
||||
</Label.Root>
|
||||
<div class="admin-message-actions">
|
||||
<AdminButton
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { Input } from "$components/ui/index.js";
|
||||
import { Label } from "$components/ui/primitives.js";
|
||||
import {
|
||||
AdminBadge,
|
||||
@@ -107,7 +108,7 @@
|
||||
|
||||
<div class="admin-toolbar admin-toolbar-users">
|
||||
<div class="admin-toolbar-search">
|
||||
<input
|
||||
<Input
|
||||
type="search"
|
||||
class="input"
|
||||
placeholder={at("users_search_placeholder", {}, "ID, @username или email")}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
export let value = "#000000";
|
||||
export let disabled = false;
|
||||
export let ariaLabel = "";
|
||||
let className = "";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<input
|
||||
bind:value
|
||||
class={cn("ui-color-input", className)}
|
||||
type="color"
|
||||
{disabled}
|
||||
aria-label={ariaLabel}
|
||||
on:input
|
||||
on:change
|
||||
{...$$restProps}
|
||||
/>
|
||||
@@ -0,0 +1,19 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
|
||||
export let element = null;
|
||||
export let accept = undefined;
|
||||
export let disabled = false;
|
||||
let className = "";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<input
|
||||
bind:this={element}
|
||||
class={cn("ui-file-input", className)}
|
||||
type="file"
|
||||
{accept}
|
||||
{disabled}
|
||||
on:change
|
||||
{...$$restProps}
|
||||
/>
|
||||
@@ -2,11 +2,14 @@ export { default as AttentionDot } from "./attention-dot.svelte";
|
||||
export { default as Badge } from "./badge.svelte";
|
||||
export { default as Button } from "./button.svelte";
|
||||
export { default as Checkbox } from "./checkbox.svelte";
|
||||
export { default as ColorInput } from "./color-input.svelte";
|
||||
export { default as Dialog } from "./dialog.svelte";
|
||||
export { default as FileInput } from "./file-input.svelte";
|
||||
export { default as Input } from "./input.svelte";
|
||||
export { default as LegacyCard } from "./card.svelte";
|
||||
export { default as RadioGroup } from "./radio-group.svelte";
|
||||
export { default as RadioGroupItem } from "./radio-group-item.svelte";
|
||||
export { default as RangeInput } from "./range-input.svelte";
|
||||
export { default as Skeleton } from "./skeleton.svelte";
|
||||
export { default as Spinner } from "./spinner.svelte";
|
||||
export { default as ScrollArea } from "./scroll-area.svelte";
|
||||
|
||||
@@ -1 +1,11 @@
|
||||
export { Accordion, Label, Select, Separator, Switch, Tabs, Tooltip } from "bits-ui";
|
||||
export {
|
||||
Accordion,
|
||||
Label,
|
||||
ScrollArea,
|
||||
Select,
|
||||
Separator,
|
||||
Slider,
|
||||
Switch,
|
||||
Tabs,
|
||||
Tooltip,
|
||||
} from "bits-ui";
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { Slider } from "./primitives.js";
|
||||
|
||||
export let value = 0;
|
||||
export let min = undefined;
|
||||
export let max = undefined;
|
||||
export let step = undefined;
|
||||
export let disabled = false;
|
||||
export let ariaLabel = "";
|
||||
export let onValueChange = () => {};
|
||||
export let onValueCommit = () => {};
|
||||
let className = "";
|
||||
export { className as class };
|
||||
|
||||
$: sliderValue = Number(value ?? min ?? 0);
|
||||
$: sliderMin = min === undefined ? undefined : Number(min);
|
||||
$: sliderMax = max === undefined ? undefined : Number(max);
|
||||
$: sliderStep = step === undefined ? undefined : Number(step);
|
||||
|
||||
function handleValueChange(next) {
|
||||
value = next;
|
||||
onValueChange(next);
|
||||
}
|
||||
</script>
|
||||
|
||||
<Slider.Root
|
||||
class={cn("ui-range-input", className)}
|
||||
type="single"
|
||||
value={sliderValue}
|
||||
min={sliderMin}
|
||||
max={sliderMax}
|
||||
step={sliderStep}
|
||||
{disabled}
|
||||
onValueChange={handleValueChange}
|
||||
{onValueCommit}
|
||||
{...$$restProps}
|
||||
>
|
||||
<Slider.Range class="ui-range-input__range" />
|
||||
<Slider.Thumb class="ui-range-input__thumb" aria-label={ariaLabel} />
|
||||
</Slider.Root>
|
||||
|
||||
<style>
|
||||
:global(.ui-range-input) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 18px;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
:global(.ui-range-input::before) {
|
||||
content: "";
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: 0;
|
||||
height: 5px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--admin-border-strong, var(--border)) 70%, transparent);
|
||||
}
|
||||
|
||||
:global(.ui-range-input__range) {
|
||||
position: absolute;
|
||||
height: 5px;
|
||||
border-radius: 999px;
|
||||
background: var(--accent);
|
||||
}
|
||||
|
||||
:global(.ui-range-input__thumb) {
|
||||
display: block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border: 2px solid var(--accent);
|
||||
border-radius: 999px;
|
||||
background: var(--admin-surface, var(--panel));
|
||||
box-shadow: 0 1px 6px rgba(0, 0, 0, 0.24);
|
||||
outline: none;
|
||||
transition:
|
||||
box-shadow 0.14s ease,
|
||||
transform 0.14s ease;
|
||||
}
|
||||
|
||||
:global(.ui-range-input__thumb:hover) {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
:global(.ui-range-input__thumb:focus-visible) {
|
||||
box-shadow: 0 0 0 4px color-mix(in srgb, var(--accent) 24%, transparent);
|
||||
}
|
||||
|
||||
:global(.ui-range-input[data-disabled]) {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
@@ -1,15 +1,86 @@
|
||||
<script>
|
||||
import { cn } from "$lib/utils.js";
|
||||
import { ScrollArea as ScrollAreaPrimitive } from "./primitives.js";
|
||||
|
||||
export let maxHeight = "100%";
|
||||
export let element = null;
|
||||
export let type = "auto";
|
||||
let className = "";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<div
|
||||
bind:this={element}
|
||||
class={`scroll-area ${className}`.trim()}
|
||||
<ScrollAreaPrimitive.Root
|
||||
class={cn("scroll-area scroll-area--mono", className)}
|
||||
style={`max-height:${maxHeight};`}
|
||||
{type}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</div>
|
||||
<ScrollAreaPrimitive.Viewport bind:ref={element} class="scroll-area__viewport">
|
||||
<slot />
|
||||
</ScrollAreaPrimitive.Viewport>
|
||||
<ScrollAreaPrimitive.Scrollbar class="scroll-area__scrollbar" orientation="vertical">
|
||||
<ScrollAreaPrimitive.Thumb class="scroll-area__thumb" />
|
||||
</ScrollAreaPrimitive.Scrollbar>
|
||||
<ScrollAreaPrimitive.Scrollbar
|
||||
class="scroll-area__scrollbar scroll-area__scrollbar--horizontal"
|
||||
orientation="horizontal"
|
||||
>
|
||||
<ScrollAreaPrimitive.Thumb class="scroll-area__thumb" />
|
||||
</ScrollAreaPrimitive.Scrollbar>
|
||||
<ScrollAreaPrimitive.Corner class="scroll-area__corner" />
|
||||
</ScrollAreaPrimitive.Root>
|
||||
|
||||
<style>
|
||||
:global(.scroll-area) {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:global(.scroll-area__viewport) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: inherit;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
:global(.scroll-area__scrollbar) {
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
touch-action: none;
|
||||
user-select: none;
|
||||
padding: 2px;
|
||||
transition: background 0.14s ease;
|
||||
}
|
||||
|
||||
:global(.scroll-area__scrollbar[data-orientation="vertical"]) {
|
||||
width: 10px;
|
||||
}
|
||||
|
||||
:global(.scroll-area__scrollbar[data-orientation="horizontal"]) {
|
||||
flex-direction: column;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
:global(.scroll-area__thumb) {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
border-radius: 999px;
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--admin-muted, var(--muted)) 34%,
|
||||
var(--admin-border, var(--border))
|
||||
);
|
||||
}
|
||||
|
||||
:global(.scroll-area__thumb:hover) {
|
||||
background: color-mix(
|
||||
in srgb,
|
||||
var(--admin-muted, var(--muted)) 48%,
|
||||
var(--admin-border, var(--border))
|
||||
);
|
||||
}
|
||||
|
||||
:global(.scroll-area__corner) {
|
||||
background: transparent;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { ArrowLeft, RefreshCw } from "$components/ui/icons.js";
|
||||
|
||||
import Button from "$components/ui/button.svelte";
|
||||
import { Input } from "$components/ui/index.js";
|
||||
import { StatusMessage } from "$components/patterns/webapp/index.js";
|
||||
|
||||
export let code = "";
|
||||
@@ -29,7 +30,7 @@
|
||||
</header>
|
||||
<div class="otp-wrap">
|
||||
<label class="otp-input-wrap">
|
||||
<input
|
||||
<Input
|
||||
bind:value={code}
|
||||
inputmode="numeric"
|
||||
autocomplete="one-time-code"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import { Check, ChevronsUpDown, LifeBuoy, MessageSquarePlus } from "$components/ui/icons.js";
|
||||
import Button from "$components/ui/button.svelte";
|
||||
import Card from "$components/ui/card.svelte";
|
||||
import { Skeleton } from "$components/ui/index.js";
|
||||
import { Input, Skeleton, Textarea } from "$components/ui/index.js";
|
||||
import { TicketCard } from "$components/patterns/webapp/index.js";
|
||||
import { Select, Tabs } from "$components/ui/primitives.js";
|
||||
import {
|
||||
@@ -169,7 +169,7 @@
|
||||
<div class="support-create-panel-inner" in:fade={{ duration: 140 }}>
|
||||
<label class="support-field">
|
||||
<span>{t("wa_support_subject")}</span>
|
||||
<input
|
||||
<Input
|
||||
class="input"
|
||||
bind:value={subject}
|
||||
maxlength={maxSubjectLength}
|
||||
@@ -255,13 +255,13 @@
|
||||
|
||||
<label class="support-field">
|
||||
<span>{t("wa_support_message")}</span>
|
||||
<textarea
|
||||
<Textarea
|
||||
class="textarea support-message-input"
|
||||
bind:value={body}
|
||||
maxlength={maxBodyLength}
|
||||
rows="5"
|
||||
placeholder={t("wa_support_message_placeholder")}
|
||||
></textarea>
|
||||
/>
|
||||
<small>{body.length}/{maxBodyLength}</small>
|
||||
</label>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user