mirror of
https://github.com/rishikanthc/Scriberr.git
synced 2026-07-01 08:15:46 +00:00
Refine settings UI controls
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&family=Literata:ital,opsz,wght@0,7..72,200..900;1,7..72,200..900&display=swap"
|
||||
href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;500;600&family=Literata:ital,opsz,wght@0,7..72,200..900;1,7..72,200..900&display=swap"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Check, X } from "lucide-react";
|
||||
import { AppButton, IconButton } from "@/shared/ui/Button";
|
||||
import { Select, type SelectOption } from "@/shared/ui/Select";
|
||||
import {
|
||||
defaultProfileParams,
|
||||
familyForModel,
|
||||
@@ -60,11 +61,12 @@ export function ASRProfileDialog({ open, profile, models, onClose, onSave }: ASR
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState("");
|
||||
|
||||
const modelOptions = useMemo(() => {
|
||||
const modelOptions = useMemo<SelectOption[]>(() => {
|
||||
const source = models.length ? models : fallbackModels;
|
||||
return source.map((model) => ({
|
||||
value: model.id,
|
||||
label: `${model.name}${model.installed ? "" : " (downloads on use)"}`,
|
||||
label: model.name,
|
||||
description: model.installed ? "Installed locally" : "Downloads on use",
|
||||
}));
|
||||
}, [models]);
|
||||
|
||||
@@ -202,15 +204,8 @@ function TextField({ label, value, onChange, placeholder }: { label: string; val
|
||||
);
|
||||
}
|
||||
|
||||
function SelectField({ label, value, options, onChange }: { label: string; value: string; options: Array<{ value: string; label: string }>; onChange: (value: string) => void }) {
|
||||
return (
|
||||
<label className="scr-control">
|
||||
<span>{label}</span>
|
||||
<select className="scr-select" value={value} onChange={(event) => onChange(event.target.value)}>
|
||||
{options.map((option) => <option key={option.value} value={option.value}>{option.label}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
);
|
||||
function SelectField({ label, value, options, onChange }: { label: string; value: string; options: SelectOption[]; onChange: (value: string) => void }) {
|
||||
return <Select label={label} value={value} options={options} onChange={onChange} />;
|
||||
}
|
||||
|
||||
function NumberField({ label, value, min, max, onChange }: { label: string; value: number; min: number; max: number; onChange: (value: number) => void }) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { Edit3, Plus, Settings2, Star, Trash2 } from "lucide-react";
|
||||
import { Edit3, Plus, Trash2 } from "lucide-react";
|
||||
import { Sidebar } from "@/features/home/components/HomePage";
|
||||
import { AppButton, IconButton } from "@/shared/ui/Button";
|
||||
import { ConfirmDialog } from "@/shared/ui/ConfirmDialog";
|
||||
import { EmptyState } from "@/shared/ui/EmptyState";
|
||||
import { ASRProfileDialog } from "../components/ASRProfileDialog";
|
||||
import {
|
||||
@@ -23,6 +24,8 @@ export function Settings() {
|
||||
const [error, setError] = useState("");
|
||||
const [editingProfile, setEditingProfile] = useState<TranscriptionProfile | null>(null);
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [profileToDelete, setProfileToDelete] = useState<TranscriptionProfile | null>(null);
|
||||
const [deleting, setDeleting] = useState(false);
|
||||
const [models, setModels] = useState<TranscriptionModel[]>([]);
|
||||
|
||||
const loadProfiles = useCallback(async () => {
|
||||
@@ -59,10 +62,19 @@ export function Settings() {
|
||||
await loadProfiles();
|
||||
};
|
||||
|
||||
const handleDelete = async (profile: TranscriptionProfile) => {
|
||||
if (!window.confirm(`Delete "${profile.name}"?`)) return;
|
||||
await deleteProfile(profile.id);
|
||||
await loadProfiles();
|
||||
const confirmDelete = async () => {
|
||||
if (!profileToDelete) return;
|
||||
setDeleting(true);
|
||||
setError("");
|
||||
try {
|
||||
await deleteProfile(profileToDelete.id);
|
||||
setProfileToDelete(null);
|
||||
await loadProfiles();
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Could not delete profile.");
|
||||
} finally {
|
||||
setDeleting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -118,7 +130,7 @@ export function Settings() {
|
||||
setEditingProfile(profile);
|
||||
setDialogOpen(true);
|
||||
}}
|
||||
onDelete={() => void handleDelete(profile)}
|
||||
onDelete={() => setProfileToDelete(profile)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
@@ -141,6 +153,17 @@ export function Settings() {
|
||||
}}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
<ConfirmDialog
|
||||
open={Boolean(profileToDelete)}
|
||||
title="Delete profile?"
|
||||
description={profileToDelete ? `This will remove "${profileToDelete.name}" from your saved ASR profiles.` : ""}
|
||||
confirmLabel="Delete"
|
||||
busy={deleting}
|
||||
onCancel={() => {
|
||||
if (!deleting) setProfileToDelete(null);
|
||||
}}
|
||||
onConfirm={() => void confirmDelete()}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -156,15 +179,11 @@ function ProfileRow({ profile, isDefault, onEdit, onDelete }: { profile: Transcr
|
||||
return (
|
||||
<article className="scr-profile-row">
|
||||
<button className="scr-profile-main" type="button" onClick={onEdit}>
|
||||
<div className="scr-profile-icon">
|
||||
<Settings2 size={18} aria-hidden="true" />
|
||||
</div>
|
||||
<div className="scr-profile-copy">
|
||||
<div className="scr-profile-title-row">
|
||||
<h3 className="scr-profile-title">{profile.name}</h3>
|
||||
{isDefault ? (
|
||||
<span className="scr-profile-badge">
|
||||
<Star size={12} aria-hidden="true" />
|
||||
Default
|
||||
</span>
|
||||
) : null}
|
||||
|
||||
@@ -74,10 +74,10 @@
|
||||
--color-brand-900: var(--brand-900);
|
||||
--color-brand-950: var(--brand-950);
|
||||
|
||||
--font-sans: 'Poppins', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
--font-display: 'Poppins', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-sans: 'Manrope', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
|
||||
--font-display: 'Manrope', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-mono: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
--font-inter: 'Poppins', sans-serif;
|
||||
--font-inter: 'Manrope', sans-serif;
|
||||
--font-reading: 'Literata', serif;
|
||||
/* For Transcripts */
|
||||
|
||||
|
||||
41
web/frontend/src/shared/ui/ConfirmDialog.tsx
Normal file
41
web/frontend/src/shared/ui/ConfirmDialog.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { AlertTriangle, X } from "lucide-react";
|
||||
import { AppButton, IconButton } from "./Button";
|
||||
|
||||
type ConfirmDialogProps = {
|
||||
open: boolean;
|
||||
title: string;
|
||||
description: string;
|
||||
confirmLabel: string;
|
||||
busy?: boolean;
|
||||
onCancel: () => void;
|
||||
onConfirm: () => void;
|
||||
};
|
||||
|
||||
export function ConfirmDialog({ open, title, description, confirmLabel, busy, onCancel, onConfirm }: ConfirmDialogProps) {
|
||||
if (!open) return null;
|
||||
|
||||
return (
|
||||
<div className="scr-modal-backdrop" role="presentation">
|
||||
<section className="scr-confirm-modal" role="alertdialog" aria-modal="true" aria-labelledby="scr-confirm-title" aria-describedby="scr-confirm-copy">
|
||||
<header className="scr-confirm-header">
|
||||
<span className="scr-confirm-mark" aria-hidden="true">
|
||||
<AlertTriangle size={18} />
|
||||
</span>
|
||||
<IconButton label="Close confirmation" onClick={onCancel}>
|
||||
<X size={17} aria-hidden="true" />
|
||||
</IconButton>
|
||||
</header>
|
||||
<div className="scr-confirm-body">
|
||||
<h2 id="scr-confirm-title" className="scr-confirm-title">{title}</h2>
|
||||
<p id="scr-confirm-copy" className="scr-confirm-copy">{description}</p>
|
||||
</div>
|
||||
<footer className="scr-confirm-footer">
|
||||
<AppButton variant="secondary" onClick={onCancel}>Cancel</AppButton>
|
||||
<AppButton className="scr-button-danger" onClick={onConfirm} disabled={busy}>
|
||||
{busy ? "Deleting..." : confirmLabel}
|
||||
</AppButton>
|
||||
</footer>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
94
web/frontend/src/shared/ui/Select.tsx
Normal file
94
web/frontend/src/shared/ui/Select.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { useEffect, useId, useRef, useState } from "react";
|
||||
import { Check, ChevronDown } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export type SelectOption = {
|
||||
value: string;
|
||||
label: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
type SelectProps = {
|
||||
label?: string;
|
||||
value: string;
|
||||
options: SelectOption[];
|
||||
onChange: (value: string) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function Select({ label, value, options, onChange, className }: SelectProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const id = useId();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const selected = options.find((option) => option.value === value) || options[0];
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const closeOnOutside = (event: PointerEvent) => {
|
||||
if (!containerRef.current?.contains(event.target as Node)) {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
const closeOnEscape = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") {
|
||||
setOpen(false);
|
||||
}
|
||||
};
|
||||
document.addEventListener("pointerdown", closeOnOutside);
|
||||
document.addEventListener("keydown", closeOnEscape);
|
||||
return () => {
|
||||
document.removeEventListener("pointerdown", closeOnOutside);
|
||||
document.removeEventListener("keydown", closeOnEscape);
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
const choose = (nextValue: string) => {
|
||||
onChange(nextValue);
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={cn("scr-select-field", className)} ref={containerRef}>
|
||||
{label ? <span className="scr-select-label">{label}</span> : null}
|
||||
<button
|
||||
id={id}
|
||||
className="scr-select-trigger"
|
||||
type="button"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded={open}
|
||||
onClick={() => setOpen((current) => !current)}
|
||||
>
|
||||
<span className="scr-select-value">
|
||||
<span>{selected?.label || "Select"}</span>
|
||||
{selected?.description ? <small>{selected.description}</small> : null}
|
||||
</span>
|
||||
<ChevronDown className="scr-select-chevron" size={16} aria-hidden="true" />
|
||||
</button>
|
||||
|
||||
{open ? (
|
||||
<div className="scr-select-menu" role="listbox" aria-labelledby={id}>
|
||||
{options.map((option) => {
|
||||
const active = option.value === value;
|
||||
return (
|
||||
<button
|
||||
key={option.value}
|
||||
className="scr-select-option"
|
||||
data-active={active}
|
||||
type="button"
|
||||
role="option"
|
||||
aria-selected={active}
|
||||
onClick={() => choose(option.value)}
|
||||
>
|
||||
<span>
|
||||
<span>{option.label}</span>
|
||||
{option.description ? <small>{option.description}</small> : null}
|
||||
</span>
|
||||
{active ? <Check size={16} aria-hidden="true" /> : null}
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -93,6 +93,7 @@
|
||||
background: var(--scr-surface-canvas);
|
||||
color: var(--scr-text-primary);
|
||||
font-family: var(--scr-font-sans);
|
||||
font-size: 15px;
|
||||
line-height: var(--scr-line-base);
|
||||
}
|
||||
|
||||
@@ -654,12 +655,14 @@
|
||||
|
||||
.scr-input {
|
||||
width: 100%;
|
||||
height: 42px;
|
||||
height: 40px;
|
||||
border: 1px solid var(--scr-border-strong);
|
||||
border-radius: var(--scr-radius-sm);
|
||||
background: var(--scr-surface-panel);
|
||||
color: var(--scr-text-primary);
|
||||
font-size: 0.9375rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
line-height: var(--scr-line-base);
|
||||
outline: none;
|
||||
padding: 0 var(--scr-space-3);
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease;
|
||||
@@ -764,7 +767,7 @@
|
||||
|
||||
.scr-settings-title {
|
||||
color: var(--scr-text-primary);
|
||||
font-size: 1rem;
|
||||
font-size: 1.0625rem;
|
||||
font-weight: 600;
|
||||
line-height: var(--scr-line-tight);
|
||||
}
|
||||
@@ -828,7 +831,7 @@
|
||||
|
||||
.scr-settings-heading {
|
||||
color: var(--scr-text-primary);
|
||||
font-size: 1.125rem;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
line-height: var(--scr-line-tight);
|
||||
}
|
||||
@@ -837,8 +840,8 @@
|
||||
max-width: 560px;
|
||||
margin-top: 0.375rem;
|
||||
color: var(--scr-text-secondary);
|
||||
font-size: 0.8125rem;
|
||||
line-height: var(--scr-line-relaxed);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.58;
|
||||
}
|
||||
|
||||
.scr-settings-new-profile {
|
||||
@@ -858,7 +861,7 @@
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: var(--scr-space-3);
|
||||
min-height: 72px;
|
||||
min-height: 70px;
|
||||
border: 1px solid var(--scr-border-subtle);
|
||||
border-radius: var(--scr-radius-md);
|
||||
background: var(--scr-surface-raised);
|
||||
@@ -876,9 +879,8 @@
|
||||
|
||||
.scr-profile-main {
|
||||
display: grid;
|
||||
grid-template-columns: 42px minmax(0, 1fr);
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
align-items: center;
|
||||
gap: var(--scr-space-3);
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
@@ -888,16 +890,6 @@
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.scr-profile-icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: var(--scr-radius-sm);
|
||||
background: var(--scr-brand-muted);
|
||||
color: var(--scr-brand-solid);
|
||||
}
|
||||
|
||||
.scr-profile-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -912,7 +904,7 @@
|
||||
.scr-profile-title {
|
||||
overflow: hidden;
|
||||
color: var(--scr-text-primary);
|
||||
font-size: 0.9375rem;
|
||||
font-size: 0.96875rem;
|
||||
font-weight: 600;
|
||||
line-height: var(--scr-line-tight);
|
||||
text-overflow: ellipsis;
|
||||
@@ -922,16 +914,16 @@
|
||||
.scr-profile-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
height: 22px;
|
||||
border: 1px solid var(--scr-brand-border);
|
||||
height: 18px;
|
||||
border: 1px solid color-mix(in srgb, var(--scr-brand-solid) 20%, transparent);
|
||||
border-radius: 999px;
|
||||
background: var(--scr-brand-muted);
|
||||
background: color-mix(in srgb, var(--scr-brand-solid) 8%, transparent);
|
||||
color: var(--scr-brand-ink);
|
||||
flex: 0 0 auto;
|
||||
font-size: 0.6875rem;
|
||||
font-size: 0.625rem;
|
||||
font-weight: 500;
|
||||
padding: 0 var(--scr-space-2);
|
||||
line-height: 1;
|
||||
padding: 0 0.4375rem;
|
||||
}
|
||||
|
||||
.dark .scr-profile-badge {
|
||||
@@ -1033,7 +1025,7 @@
|
||||
|
||||
.scr-modal-title {
|
||||
color: var(--scr-text-primary);
|
||||
font-size: 1.25rem;
|
||||
font-size: 1.375rem;
|
||||
font-weight: 600;
|
||||
line-height: var(--scr-line-tight);
|
||||
}
|
||||
@@ -1066,7 +1058,7 @@
|
||||
|
||||
.scr-settings-section-title {
|
||||
color: var(--scr-text-primary);
|
||||
font-size: 0.875rem;
|
||||
font-size: 0.9375rem;
|
||||
font-weight: 600;
|
||||
line-height: var(--scr-line-tight);
|
||||
}
|
||||
@@ -1081,7 +1073,7 @@
|
||||
display: grid;
|
||||
gap: 0.375rem;
|
||||
color: var(--scr-text-secondary);
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
line-height: var(--scr-line-tight);
|
||||
}
|
||||
@@ -1108,7 +1100,6 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.scr-select,
|
||||
.scr-textarea {
|
||||
width: 100%;
|
||||
border: 1px solid var(--scr-border-strong);
|
||||
@@ -1122,23 +1113,159 @@
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease;
|
||||
}
|
||||
|
||||
.scr-select {
|
||||
height: 42px;
|
||||
padding: 0 var(--scr-space-3);
|
||||
}
|
||||
|
||||
.scr-textarea {
|
||||
min-height: 82px;
|
||||
padding: var(--scr-space-3);
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.scr-select:focus-visible,
|
||||
.scr-textarea:focus-visible {
|
||||
border-color: var(--scr-brand-solid);
|
||||
box-shadow: 0 0 0 3px var(--scr-brand-muted);
|
||||
}
|
||||
|
||||
.scr-select-field {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 0.375rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.scr-select-label {
|
||||
color: var(--scr-text-secondary);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 500;
|
||||
line-height: var(--scr-line-tight);
|
||||
}
|
||||
|
||||
.scr-select-trigger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--scr-space-3);
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
border: 1px solid var(--scr-border-strong);
|
||||
border-radius: var(--scr-radius-sm);
|
||||
background: var(--scr-surface-panel);
|
||||
color: var(--scr-text-primary);
|
||||
cursor: pointer;
|
||||
padding: 0.4375rem var(--scr-space-3);
|
||||
text-align: left;
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease, background-color 160ms ease;
|
||||
}
|
||||
|
||||
.scr-select-trigger:hover,
|
||||
.scr-select-trigger:focus-visible,
|
||||
.scr-select-trigger[aria-expanded="true"] {
|
||||
border-color: var(--scr-brand-border);
|
||||
box-shadow: 0 0 0 3px var(--scr-brand-muted);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.scr-select-value {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
color: var(--scr-text-primary);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.scr-select-value span,
|
||||
.scr-select-value small {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.scr-select-value small {
|
||||
margin-top: 0.125rem;
|
||||
color: var(--scr-text-tertiary);
|
||||
font-size: 0.75rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.scr-select-chevron {
|
||||
flex: 0 0 auto;
|
||||
color: var(--scr-text-tertiary);
|
||||
transition: transform 160ms ease;
|
||||
}
|
||||
|
||||
.scr-select-trigger[aria-expanded="true"] .scr-select-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.scr-select-menu {
|
||||
position: absolute;
|
||||
top: calc(100% + 0.375rem);
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: 120;
|
||||
display: grid;
|
||||
gap: 0.25rem;
|
||||
max-height: 280px;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--scr-border-strong);
|
||||
border-radius: var(--scr-radius-lg);
|
||||
background: var(--scr-surface-raised);
|
||||
box-shadow: var(--scr-shadow-float);
|
||||
padding: 0.375rem;
|
||||
}
|
||||
|
||||
.scr-select-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--scr-space-3);
|
||||
min-height: 42px;
|
||||
border: 0;
|
||||
border-radius: var(--scr-radius-sm);
|
||||
background: transparent;
|
||||
color: var(--scr-text-secondary);
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 0.625rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.scr-select-option > span {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.scr-select-option > span > span,
|
||||
.scr-select-option small {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.scr-select-option > span > span {
|
||||
color: var(--scr-text-primary);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.scr-select-option small {
|
||||
margin-top: 0.125rem;
|
||||
color: var(--scr-text-secondary);
|
||||
font-size: 0.8125rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.scr-select-option:hover,
|
||||
.scr-select-option:focus-visible,
|
||||
.scr-select-option[data-active="true"] {
|
||||
background: var(--scr-surface-muted);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.scr-select-option[data-active="true"] {
|
||||
color: var(--scr-text-primary);
|
||||
}
|
||||
|
||||
.scr-check-row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -1203,6 +1330,70 @@
|
||||
padding: 0 var(--scr-space-2);
|
||||
}
|
||||
|
||||
.scr-button-danger {
|
||||
background: var(--scr-color-danger);
|
||||
color: var(--scr-color-white);
|
||||
}
|
||||
|
||||
.scr-button-danger:hover,
|
||||
.scr-button-danger:focus-visible {
|
||||
background: color-mix(in srgb, var(--scr-color-danger) 88%, var(--scr-color-black));
|
||||
color: var(--scr-color-white);
|
||||
}
|
||||
|
||||
.scr-confirm-modal {
|
||||
width: min(100%, 420px);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--scr-border-strong);
|
||||
border-radius: var(--scr-radius-xl);
|
||||
background: var(--scr-surface-raised);
|
||||
box-shadow: var(--scr-shadow-float);
|
||||
}
|
||||
|
||||
.scr-confirm-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: var(--scr-space-4) var(--scr-space-4) 0;
|
||||
}
|
||||
|
||||
.scr-confirm-mark {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, var(--scr-color-danger) 10%, transparent);
|
||||
color: var(--scr-color-danger);
|
||||
}
|
||||
|
||||
.scr-confirm-body {
|
||||
display: grid;
|
||||
gap: var(--scr-space-2);
|
||||
padding: var(--scr-space-3) var(--scr-space-5) var(--scr-space-5);
|
||||
}
|
||||
|
||||
.scr-confirm-title {
|
||||
color: var(--scr-text-primary);
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
line-height: var(--scr-line-tight);
|
||||
}
|
||||
|
||||
.scr-confirm-copy {
|
||||
color: var(--scr-text-secondary);
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.55;
|
||||
}
|
||||
|
||||
.scr-confirm-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--scr-space-2);
|
||||
border-top: 1px solid var(--scr-border-subtle);
|
||||
padding: var(--scr-space-4) var(--scr-space-5);
|
||||
}
|
||||
|
||||
@keyframes scr-shimmer {
|
||||
to {
|
||||
background-position: -220% 0;
|
||||
|
||||
Reference in New Issue
Block a user