Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ the configuration; each release says how to upgrade.
pill switches, and the Feed and Audit filters as chips.
- Portal: forms (users, login, profile, settings, Node configuration, the Node dialogs) use the
same fields, and every action button is rounded to match.
- Portal: the tracker editors, Publish, Add Torrent and Explore follow too — every form control in
the Portal now uses the same fields.

### Fixed
- Portal: the language selector no longer spills out of the sidebar; it sits on its own row
Expand Down
28 changes: 8 additions & 20 deletions portal/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,26 +71,14 @@ export function Layout({
const langSelect = (
<label className="flex items-center justify-between gap-2 px-1">
<span className="shrink-0 text-xs uppercase tracking-wide text-slate-500">{t("lang.label")}</span>
<span className="relative min-w-0">
<select
value={lang}
onChange={(e) => setLang(e.target.value as Lang)}
className="w-full min-w-0 cursor-pointer appearance-none truncate rounded-full border border-slate-700 bg-slate-950 py-1 pl-3 pr-7 text-xs text-slate-300 outline-none transition-colors hover:text-slate-200 focus-visible:ring-2 focus-visible:ring-emerald-500/70 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-900"
>
<option value="pt-BR">Português</option>
<option value="en">English</option>
</select>
<svg
aria-hidden="true"
fill="none"
width="10"
height="10"
viewBox="0 0 10 10"
className="pointer-events-none absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-500"
>
<path d="M2 3.5 5 6.5 8 3.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
</span>
<select
value={lang}
onChange={(e) => setLang(e.target.value as Lang)}
className="sg-field-sm sg-select min-w-0"
>
<option value="pt-BR">Português</option>
<option value="en">English</option>
</select>
</label>
);

Expand Down
8 changes: 4 additions & 4 deletions portal/src/components/TrackerDescriptionEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const LANGUAGE_NAME_KEY = {
} as const;

const btnCls =
"rounded-lg border border-slate-700 bg-slate-900 px-3 py-1.5 text-xs font-medium text-slate-200 transition hover:border-sky-600 hover:text-sky-200 disabled:opacity-40";
"rounded-full border border-slate-700 bg-slate-900 px-3 py-1.5 text-xs font-medium text-slate-200 transition hover:border-sky-600 hover:text-sky-200 disabled:opacity-40";
const stepCls =
"rounded px-1.5 py-0.5 text-xs text-slate-400 transition hover:bg-slate-800 hover:text-sky-300 disabled:opacity-30 disabled:hover:bg-transparent disabled:hover:text-slate-400";

Expand Down Expand Up @@ -149,7 +149,7 @@ export function TrackerDescriptionEditor({
value={draft.tag}
onChange={(e) => setDraft((d) => ({ ...d, tag: e.target.value }))}
aria-label={t("trackers.trackerDescriptionEditor.mediaInfoTagAria")}
className="rounded-lg border border-slate-700 bg-slate-950 px-2 py-1 text-xs text-white outline-none focus:border-emerald-500"
className="sg-field-sm sg-select"
>
{MEDIAINFO_TAGS.map((tag) => (
<option key={tag} value={tag}>
Expand All @@ -164,7 +164,7 @@ export function TrackerDescriptionEditor({
value={draft.lang}
onChange={(e) => setDraft((d) => ({ ...d, lang: e.target.value }))}
aria-label={t("trackers.trackerDescriptionEditor.languageLabel")}
className="rounded-lg border border-slate-700 bg-slate-950 px-2 py-1 text-xs text-white outline-none focus:border-emerald-500"
className="sg-field-sm sg-select"
>
{CONTENT_LANGUAGES.map((lang) => (
<option key={lang} value={lang}>
Expand Down Expand Up @@ -245,7 +245,7 @@ function BlockList({
checked={b.on}
onChange={() => onToggle(i)}
aria-label={t("trackers.trackerDescriptionEditor.includeBlockAria", { label: blockLabel(t, b.id), kind: t(KIND_TITLE_KEY[kind]) })}
className="accent-emerald-500"
className="sg-check"
/>
<span className={`w-24 shrink-0 ${b.on ? "text-slate-200" : "text-slate-500 line-through"}`}>{blockLabel(t, b.id)}</span>
<span className="min-w-0 flex-1 truncate text-slate-500" title={blockHint(t, b.id)}>
Expand Down
6 changes: 3 additions & 3 deletions portal/src/components/TrackerImageHostEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ const EXAMPLE = {
// only those who can write (trackers:write) see the edit controls.

const btnCls =
"rounded-lg border border-slate-700 bg-slate-900 px-3 py-1.5 text-xs font-medium text-slate-200 transition hover:border-sky-600 hover:text-sky-200 disabled:opacity-40";
const inputCls =
"w-full rounded-lg border border-slate-700 bg-slate-950 px-2 py-1 text-sm text-white outline-none focus:border-emerald-500";
"rounded-full border border-slate-700 bg-slate-900 px-3 py-1.5 text-xs font-medium text-slate-200 transition hover:border-sky-600 hover:text-sky-200 disabled:opacity-40";
// Form-control base classes (#22).
const inputCls = "sg-field w-full";

export function TrackerImageHostEditor({
tracker,
Expand Down
8 changes: 4 additions & 4 deletions portal/src/components/TrackerProfileEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ const ORIGIN_BADGE: Record<TermOrigin, OriginBadgeDef> = {
},
};

const inputCls =
"w-full rounded-lg border border-slate-700 bg-slate-950 px-2 py-1 text-sm text-white outline-none focus:border-emerald-500";
// Form-control base classes (#22).
const inputCls = "sg-field w-full";

export function TrackerProfileEditor({
tracker,
Expand Down Expand Up @@ -303,7 +303,7 @@ export function TrackerProfileEditor({
onChange={(e) => patchRow(axis, r.key, { kind: e.target.value as DraftTerm["kind"] })}
aria-label={t("trackers.trackerProfileEditor.rows.kindAria")}
title={t("trackers.trackerProfileEditor.rows.kindTitle")}
className={`${inputCls} w-24 shrink-0 text-xs`}
className={`${inputCls} sg-select w-24 shrink-0 text-xs`}
>
<option value="">—</option>
{(Object.keys(KIND_LABEL_KEY) as TermKind[]).map((k) => (
Expand Down Expand Up @@ -515,7 +515,7 @@ function Proposal({
disabled={!ok}
onChange={() => onToggle(key)}
aria-label={t("trackers.trackerProfileEditor.proposal.applyAria", { label: t(labelKey), id: it.id })}
className="accent-sky-500"
className="sg-check"
/>
<span className="w-12 shrink-0 font-mono text-slate-400">{it.id}</span>
<input
Expand Down
10 changes: 5 additions & 5 deletions portal/src/components/TrackerProfileExchange.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { useT, rich } from "../i18n";
// overwritten without them asking, and asking is term by term.

const btnCls =
"rounded-lg border border-slate-700 bg-slate-900 px-3 py-1.5 text-xs font-medium text-slate-200 transition hover:border-sky-600 hover:text-sky-200 disabled:opacity-40";
"rounded-full border border-slate-700 bg-slate-900 px-3 py-1.5 text-xs font-medium text-slate-200 transition hover:border-sky-600 hover:text-sky-200 disabled:opacity-40";

export function TrackerProfileExchange({
tracker,
Expand Down Expand Up @@ -180,7 +180,7 @@ export function TrackerProfileExchange({
accept=".json,application/json"
onChange={onPick}
aria-label={t("trackers.trackerProfileExchange.fileAria")}
className="hidden"
className="sg-field hidden"
/>
</>
)}
Expand Down Expand Up @@ -303,7 +303,7 @@ export function ImportPreviewView({
checked={overwrite.has(key)}
onChange={() => onToggle(key)}
aria-label={t("trackers.trackerProfileExchange.useFileItemAria", { label: t(labelKey), id: it.id })}
className="accent-emerald-500"
className="sg-check"
/>
<span className="text-slate-400">{t("trackers.trackerProfileExchange.useFromFile")}</span>
</label>
Expand Down Expand Up @@ -389,7 +389,7 @@ function DescriptionImportRow({ item, asked, onToggle }: { item: ImportDescripti
checked={asked}
onChange={onToggle}
aria-label={t("trackers.trackerProfileExchange.descUseFileAria")}
className="accent-emerald-500"
className="sg-check"
/>
<span className="text-slate-400">{t("trackers.trackerProfileExchange.useFromFile")}</span>
</label>
Expand Down Expand Up @@ -451,7 +451,7 @@ function ImageHostImportRow({ item, asked, onToggle }: { item: ImportImageHost;
checked={asked}
onChange={onToggle}
aria-label={t("trackers.trackerProfileExchange.imageHostUseFileAria")}
className="accent-emerald-500"
className="sg-check"
/>
<span className="text-slate-400">{t("trackers.trackerProfileExchange.useFromFile")}</span>
</label>
Expand Down
6 changes: 3 additions & 3 deletions portal/src/components/TrackersPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ export function TrackersPanel({ can, onLogout }: { can: (perm: string) => boolea
}
}

const inputCls =
"w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-white outline-none focus:border-emerald-500";
// Form-control base classes (#22).
const inputCls = "sg-field w-full";

return (
<div className="max-w-2xl space-y-4 rounded-2xl border border-slate-800 bg-slate-900/60 p-6">
Expand Down Expand Up @@ -281,7 +281,7 @@ export function TrackersPanel({ can, onLogout }: { can: (perm: string) => boolea
type="checkbox"
checked={form.isDefault}
onChange={(e) => set("isDefault")(e.target.checked)}
className="accent-emerald-500"
className="sg-check"
/>
{t("trackers.trackersPanel.form.setDefault")}
</label>
Expand Down
12 changes: 6 additions & 6 deletions portal/src/pages/AddTorrent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ function AddExisting({ onLogout }: { onLogout: () => void }) {
type="file"
accept=".torrent"
multiple
className="hidden"
className="sg-field hidden"
onChange={(e) => {
addFiles(Array.from(e.target.files ?? []));
e.target.value = ""; // allows re-selecting the same file
Expand Down Expand Up @@ -275,7 +275,7 @@ function AddExisting({ onLogout }: { onLogout: () => void }) {
onChange={(e) => setMagnet(e.target.value)}
placeholder={"magnet:?xt=urn:btih:…\nmagnet:?xt=urn:btih:…"}
rows={3}
className="w-full resize-y rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 font-mono text-xs text-white outline-none focus:border-emerald-500"
className="sg-textarea text-xs w-full resize-y font-mono"
/>
</div>

Expand Down Expand Up @@ -569,7 +569,7 @@ function PublishContent({ onLogout }: { onLogout: () => void }) {
ref={inputRef}
type="file"
multiple
className="hidden"
className="sg-field hidden"
onChange={(e) => {
addFiles(Array.from(e.target.files ?? []));
e.target.value = "";
Expand Down Expand Up @@ -603,7 +603,7 @@ function PublishContent({ onLogout }: { onLogout: () => void }) {
value={name}
onChange={(e) => setName(e.target.value)}
placeholder={t("torrents.addTorrent.torrentNamePlaceholder")}
className="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-white outline-none focus:border-emerald-500"
className="sg-field w-full"
/>
{selTracker?.source && (
<p className="mt-1 text-xs text-sky-300/80">
Expand All @@ -618,7 +618,7 @@ function PublishContent({ onLogout }: { onLogout: () => void }) {
<select
value={trackerId}
onChange={(e) => setTrackerId(e.target.value)}
className="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-white outline-none focus:border-emerald-500"
className="sg-field sg-select w-full"
>
<option value="public">{t("torrents.addTorrent.optionPublic")}</option>
{trackers.map((tr) => (
Expand All @@ -634,7 +634,7 @@ function PublishContent({ onLogout }: { onLogout: () => void }) {
<select
value={forceNode}
onChange={(e) => setForceNode(e.target.value)}
className="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-white outline-none focus:border-emerald-500"
className="sg-field sg-select w-full"
>
<option value="">{t("torrents.addTorrent.optionSchedulerChooses")}</option>
{nodes
Expand Down
12 changes: 6 additions & 6 deletions portal/src/pages/Explore.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export function Explore({ can, onLogout }: { can: (perm: string) => boolean; onL
<select
value={trackerId}
onChange={(e) => setTrackerId(e.target.value)}
className="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-white outline-none focus:border-emerald-500"
className="sg-field sg-select w-full"
>
{integ.map((t) => (
<option key={t.id} value={t.id}>
Expand All @@ -279,7 +279,7 @@ export function Explore({ can, onLogout }: { can: (perm: string) => boolean; onL
onChange={(e) => setQ(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && doSearch()}
placeholder={t("explore.explore.search.placeholder")}
className="w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-base text-white outline-none focus:border-emerald-500"
className="sg-field w-full text-base"
/>
<button
onClick={doSearch}
Expand All @@ -294,7 +294,7 @@ export function Explore({ can, onLogout }: { can: (perm: string) => boolean; onL
value={fType}
onChange={(e) => setFType(e.target.value as MediaType | "")}
title={t("explore.explore.filters.typeTitle")}
className="rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-white outline-none focus:border-emerald-500"
className="sg-field sg-select"
>
<option value="">{t("explore.explore.filters.allTypes")}</option>
<option value="movie">🎬 {t("explore.explore.media.moviePlural")}</option>
Expand All @@ -308,7 +308,7 @@ export function Explore({ can, onLogout }: { can: (perm: string) => boolean; onL
value={fSize}
onChange={(e) => setFSize(Number(e.target.value))}
title={t("explore.explore.filters.sizeTitle")}
className="rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-white outline-none focus:border-emerald-500"
className="sg-field sg-select"
>
<option value={0}>{t("explore.explore.filters.anySize")}</option>
<option value={104857600}>{"> 100 MB"}</option>
Expand All @@ -322,13 +322,13 @@ export function Explore({ can, onLogout }: { can: (perm: string) => boolean; onL
onChange={(e) => setFSeeds(Number(e.target.value) || 0)}
placeholder={t("explore.explore.filters.minSeedsPlaceholder")}
title={t("explore.explore.filters.minSeedsTitle")}
className="w-28 rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-white outline-none focus:border-emerald-500"
className="sg-field w-28"
/>
<select
value={fStatus}
onChange={(e) => setFStatus(e.target.value as "all" | "avail" | "have")}
title={t("explore.explore.filters.statusTitle")}
className="rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-white outline-none focus:border-emerald-500"
className="sg-field sg-select"
>
<option value="all">{t("explore.explore.filters.statusAll")}</option>
<option value="avail">{t("explore.explore.filters.statusAvail")}</option>
Expand Down
2 changes: 1 addition & 1 deletion portal/src/pages/publish/TaxonomySelects.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export function TaxonomySelects({
aria-label={t(labelKey)}
value={value}
onChange={(e) => onChange(axis, Number(e.target.value))}
className={inputCls}
className={`${inputCls} sg-select`}
>
{/* Resolution is optional in UNIT3D; category and type are not, but 0 is also the
state "not yet chosen" — better than a default that no one checked. */}
Expand Down
12 changes: 6 additions & 6 deletions portal/src/pages/publish/TrackerUploadPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,8 @@ export function TrackerUploadPanel({
setTimeout(() => setFormSteps(null), 5000); // disappears on its own after the operator sees it
}

const inputCls =
"w-full rounded-lg border border-slate-700 bg-slate-950 px-3 py-2 text-sm text-white outline-none focus:border-emerald-500";
// Form-control base classes (#22).
const inputCls = "sg-field w-full";
const num = (v: string) => (v === "" ? 0 : Number(v) || 0);
// The category role comes from the profile (kind), not the number: the "Games" category ID is 5
// on one site and another on another. Category without role = no special fields.
Expand Down Expand Up @@ -644,15 +644,15 @@ export function TrackerUploadPanel({
)}
</div>
)}
<textarea value={form.description} onChange={(e) => set("description", e.target.value)} rows={6} className={`${inputCls} resize-y font-mono text-xs`} />
<textarea value={form.description} onChange={(e) => set("description", e.target.value)} rows={6} className="sg-textarea w-full resize-y font-mono text-xs" />
</div>

{!isGame && (
<div>
<label className="mb-1 block text-xs text-slate-400">
{mediaInfo ? t("publish.trackerUploadPanel.fields.mediainfoAvailable") : t("publish.trackerUploadPanel.fields.mediainfoMissing")}
</label>
<textarea value={form.mediainfo} onChange={(e) => set("mediainfo", e.target.value)} rows={5} className={`${inputCls} resize-y font-mono text-xs`} />
<textarea value={form.mediainfo} onChange={(e) => set("mediainfo", e.target.value)} rows={5} className="sg-textarea w-full resize-y font-mono text-xs" />
</div>
)}

Expand All @@ -662,7 +662,7 @@ export function TrackerUploadPanel({
<input value={form.keywords} onChange={(e) => set("keywords", e.target.value)} className={inputCls} />
</div>
<label className="mt-4 flex items-center gap-2 text-xs text-slate-300">
<input type="checkbox" checked={form.anonymous} onChange={(e) => set("anonymous", e.target.checked)} className="accent-emerald-500" />
<input type="checkbox" checked={form.anonymous} onChange={(e) => set("anonymous", e.target.checked)} className="sg-check" />
{t("publish.trackerUploadPanel.fields.anonymous")}
</label>
</div>
Expand Down Expand Up @@ -696,7 +696,7 @@ export function TrackerUploadPanel({
))}
</ul>
<label className="mt-3 flex items-center gap-2 text-xs text-amber-200">
<input type="checkbox" checked={dupeAck} onChange={(e) => setDupeAck(e.target.checked)} className="accent-amber-500" />
<input type="checkbox" checked={dupeAck} onChange={(e) => setDupeAck(e.target.checked)} className="sg-check" />
{t("publish.trackerUploadPanel.dupes.ack")}
</label>
</div>
Expand Down
Loading