mirror of
https://github.com/gogs/gogs.git
synced 2026-05-28 21:30:36 +00:00
feat(web): adopt Pierre theme palette and reorder sign-in tab stops (#8287)
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
dist/
|
||||
.moon/cache/
|
||||
node_modules/
|
||||
.agents/skills/
|
||||
.claude/skills
|
||||
|
||||
# Runtime data
|
||||
log/
|
||||
|
||||
@@ -71,6 +71,11 @@ func Toggle(options *ToggleOptions) macaron.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
if isWebPath(c.Req.URL.Path) {
|
||||
c.ServeWeb()
|
||||
return
|
||||
}
|
||||
|
||||
c.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
|
||||
c.RedirectSubpath("/user/sign-in")
|
||||
return
|
||||
@@ -84,6 +89,10 @@ func Toggle(options *ToggleOptions) macaron.Handler {
|
||||
// Redirect to log in page if auto-signin info is provided and has not signed in.
|
||||
if !options.SignOutRequired && !c.IsLogged && !isAPIPath(c.Req.URL.Path) &&
|
||||
len(c.GetCookie(conf.Security.CookieUsername)) > 0 {
|
||||
if isWebPath(c.Req.URL.Path) {
|
||||
c.ServeWeb()
|
||||
return
|
||||
}
|
||||
c.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
|
||||
c.RedirectSubpath("/user/sign-in")
|
||||
return
|
||||
@@ -103,6 +112,20 @@ func isAPIPath(url string) bool {
|
||||
return strings.HasPrefix(url, "/api/")
|
||||
}
|
||||
|
||||
func isWebPath(p string) bool {
|
||||
p = strings.TrimPrefix(p, conf.Server.Subpath)
|
||||
switch {
|
||||
case p == "/user/sign-in",
|
||||
strings.HasPrefix(p, "/assets/"),
|
||||
strings.HasPrefix(p, "/src/"),
|
||||
strings.HasPrefix(p, "/node_modules/"),
|
||||
strings.HasPrefix(p, "/@"),
|
||||
strings.HasPrefix(p, "/img/"):
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type AuthStore interface {
|
||||
// GetAccessTokenBySHA1 returns the access token with given SHA1. It returns
|
||||
// database.ErrAccessTokenNotExist when not found.
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": 1,
|
||||
"skills": {
|
||||
"shadcn": {
|
||||
"source": "shadcn/ui",
|
||||
"sourceType": "github",
|
||||
"skillPath": "skills/shadcn/SKILL.md",
|
||||
"computedHash": "80a6226e78f6d1fe464214ae0ef449d49d8ffaa3e7704f011e9b418c678ad4d1"
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-3
@@ -17,7 +17,7 @@ Don't mix sans and mono within the same UI surface for arbitrary reasons. If a c
|
||||
|
||||
## Color hierarchy
|
||||
|
||||
Palettes are derived from [Happy Hues](https://www.happyhues.co): palette 6 for light, palette 13 for dark. Dark mode is opt-in via the `.dark` class on `:root` (see `lib/theme.ts`), not media-query driven, so the user's stored preference always wins. The `@custom-variant dark` rule in `index.css` lets utilities like `dark:...` target the same class.
|
||||
Palettes are adapted from [Pierre Theme](https://github.com/pierrecomputer/theme)'s "Light" and "Dark" (non-soft) variants. The dark-mode input background is bumped slightly above Pierre's value (`#262626` instead of `#1d1d1d`) so form fields read as edged elements outside an IDE panel context. Dark mode is opt-in via the `.dark` class on `:root` (see `lib/theme.ts`), not media-query driven, so the user's stored preference always wins. The `@custom-variant dark` rule in `index.css` lets utilities like `dark:...` target the same class.
|
||||
|
||||
Use these tokens. Don't introduce raw hex values in components.
|
||||
|
||||
@@ -32,8 +32,8 @@ Use these tokens. Don't introduce raw hex values in components.
|
||||
|
||||
**Accents and state**
|
||||
|
||||
- `--color-primary` / `--color-primary-foreground`: brand purple. Reserved for genuine brand emphasis. Don't use it to mean "primary action" between two peer links (see the peer-item rule below).
|
||||
- `--color-secondary` / `--color-secondary-foreground`: muted brand support. Available for chips, tags, low-emphasis fills.
|
||||
- `--color-primary` / `--color-primary-foreground`: brand blue (`#009fff` in both modes). Reserved for genuine brand emphasis. Don't use it to mean "primary action" between two peer links (see the peer-item rule below). Note: white-on-primary contrast is 2.84:1, which is below WCAG AA in both modes since the token is identical light and dark. Avoid using primary as a fill for body-sized text. Use it for chrome accents, ring/focus, and large CTAs only.
|
||||
- `--color-secondary` / `--color-secondary-foreground`: neutral support fill. Available for chips, tags, low-emphasis fills.
|
||||
- `--color-destructive` / `--color-destructive-foreground`: error and danger. The 404 page uses `text-(--color-destructive)` on the `fatal:` token, always paired with the word itself (color is never the sole signal).
|
||||
- `--color-ring`: keyboard focus ring color. Don't override per-component. If a default ring looks wrong, fix it at the token level.
|
||||
|
||||
|
||||
+36
-36
@@ -32,50 +32,50 @@
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* Theme palettes are derived from Happy Hues (https://www.happyhues.co):
|
||||
light mode = palette 6, dark mode = palette 13. */
|
||||
/* Theme palettes are adapted from Pierre Theme's "Light" and "Dark"
|
||||
(non-soft) variants (https://github.com/pierrecomputer/theme). */
|
||||
:root {
|
||||
color-scheme: light dark;
|
||||
|
||||
--color-background: #fffffe;
|
||||
--color-foreground: #2b2c34;
|
||||
--color-card: #fffffe;
|
||||
--color-card-foreground: #2b2c34;
|
||||
--color-popover: #fffffe;
|
||||
--color-popover-foreground: #2b2c34;
|
||||
--color-primary: #059669;
|
||||
--color-primary-foreground: #fffffe;
|
||||
--color-secondary: #d1d1e9;
|
||||
--color-secondary-foreground: #2b2c34;
|
||||
--color-surface: #ececf6;
|
||||
--color-muted-foreground: #5f6172;
|
||||
--color-destructive: #c2392f;
|
||||
--color-destructive-foreground: #fffffe;
|
||||
--color-border: #d1d1e9;
|
||||
--color-input: #c8c8d8;
|
||||
--color-ring: #059669;
|
||||
--color-background: #ffffff;
|
||||
--color-foreground: #0a0a0a;
|
||||
--color-card: #ffffff;
|
||||
--color-card-foreground: #0a0a0a;
|
||||
--color-popover: #ffffff;
|
||||
--color-popover-foreground: #0a0a0a;
|
||||
--color-primary: #009fff;
|
||||
--color-primary-foreground: #ffffff;
|
||||
--color-secondary: #f5f5f5;
|
||||
--color-secondary-foreground: #0a0a0a;
|
||||
--color-surface: #f5f5f5;
|
||||
--color-muted-foreground: #737373;
|
||||
--color-destructive: #d52c36;
|
||||
--color-destructive-foreground: #ffffff;
|
||||
--color-border: #e5e5e5;
|
||||
--color-input: #d4d4d4;
|
||||
--color-ring: #009fff;
|
||||
}
|
||||
|
||||
:root.dark {
|
||||
color-scheme: dark;
|
||||
|
||||
--color-background: #16161a;
|
||||
--color-foreground: #fffffe;
|
||||
--color-card: #242629;
|
||||
--color-card-foreground: #fffffe;
|
||||
--color-popover: #242629;
|
||||
--color-popover-foreground: #fffffe;
|
||||
--color-primary: #059669;
|
||||
--color-primary-foreground: #fffffe;
|
||||
--color-secondary: #5c5f68;
|
||||
--color-secondary-foreground: #fffffe;
|
||||
--color-surface: #33363c;
|
||||
--color-muted-foreground: #94a1b2;
|
||||
--color-destructive: #d63653;
|
||||
--color-destructive-foreground: #fffffe;
|
||||
--color-border: #5c5f68;
|
||||
--color-input: #5c5f68;
|
||||
--color-ring: #059669;
|
||||
--color-background: #0a0a0a;
|
||||
--color-foreground: #fafafa;
|
||||
--color-card: #171717;
|
||||
--color-card-foreground: #fafafa;
|
||||
--color-popover: #171717;
|
||||
--color-popover-foreground: #fafafa;
|
||||
--color-primary: #009fff;
|
||||
--color-primary-foreground: #ffffff;
|
||||
--color-secondary: #1d1d1d;
|
||||
--color-secondary-foreground: #fafafa;
|
||||
--color-surface: #1d1d1d;
|
||||
--color-muted-foreground: #8a8a8a;
|
||||
--color-destructive: #ff6762;
|
||||
--color-destructive-foreground: #ffffff;
|
||||
--color-border: #1d1d1d;
|
||||
--color-input: #262626;
|
||||
--color-ring: #009fff;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
|
||||
@@ -81,6 +81,7 @@ export function SignIn() {
|
||||
if (!body.error && !body.fields) {
|
||||
setFormError(t("sign_in_failed"));
|
||||
}
|
||||
setSubmitting(false);
|
||||
return;
|
||||
}
|
||||
const data = (await res.json()) as SignInResponse;
|
||||
@@ -91,7 +92,6 @@ export function SignIn() {
|
||||
window.location.assign(data.redirectTo || subUrl("/"));
|
||||
} catch {
|
||||
setFormError(t("sign_in_failed"));
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
})();
|
||||
@@ -124,6 +124,7 @@ export function SignIn() {
|
||||
autoComplete="username"
|
||||
required
|
||||
autoFocus
|
||||
tabIndex={1}
|
||||
placeholder={t("username_placeholder")}
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
@@ -141,7 +142,9 @@ export function SignIn() {
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<Label htmlFor="password">{t("password")}</Label>
|
||||
<Button variant="link" size="inline" asChild>
|
||||
<a href={subUrl("/user/forget_password")}>{t("forget_password")}</a>
|
||||
<a href={subUrl("/user/forget_password")} tabIndex={7}>
|
||||
{t("forget_password")}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
<div className="relative">
|
||||
@@ -152,6 +155,7 @@ export function SignIn() {
|
||||
type={showPassword ? "text" : "password"}
|
||||
autoComplete="current-password"
|
||||
required
|
||||
tabIndex={2}
|
||||
placeholder={t("password_placeholder")}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
@@ -161,6 +165,7 @@ export function SignIn() {
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={3}
|
||||
onClick={() => setShowPassword((v) => !v)}
|
||||
aria-label={showPassword ? t("hide_password") : t("show_password")}
|
||||
aria-pressed={showPassword}
|
||||
@@ -180,7 +185,7 @@ export function SignIn() {
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<Label htmlFor="login_source">{t("auth_source")}</Label>
|
||||
<Select value={String(loginSource)} onValueChange={(v) => setLoginSource(Number(v))}>
|
||||
<SelectTrigger id="login_source">
|
||||
<SelectTrigger id="login_source" tabIndex={4}>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -196,18 +201,25 @@ export function SignIn() {
|
||||
)}
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<Checkbox id="remember" checked={remember} onCheckedChange={(v) => setRemember(v === true)} />
|
||||
<Checkbox
|
||||
id="remember"
|
||||
tabIndex={5}
|
||||
checked={remember}
|
||||
onCheckedChange={(v) => setRemember(v === true)}
|
||||
/>
|
||||
<Label htmlFor="remember" className="cursor-pointer font-normal">
|
||||
{t("remember_me")}
|
||||
</Label>
|
||||
</div>
|
||||
|
||||
<div className="mt-2 flex flex-col gap-3">
|
||||
<Button type="submit" disabled={submitting} className="w-full">
|
||||
<Button type="submit" disabled={submitting} tabIndex={6} className="w-full">
|
||||
{submitting ? t("sign_in_submitting") : t("sign_in")}
|
||||
</Button>
|
||||
<Button variant="link" size="inline" asChild className="self-center">
|
||||
<a href={subUrl("/user/sign_up")}>{t("sign_up_now")}</a>
|
||||
<a href={subUrl("/user/sign_up")} tabIndex={8}>
|
||||
{t("sign_up_now")}
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
Reference in New Issue
Block a user