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/
|
dist/
|
||||||
.moon/cache/
|
.moon/cache/
|
||||||
node_modules/
|
node_modules/
|
||||||
|
.agents/skills/
|
||||||
|
.claude/skills
|
||||||
|
|
||||||
# Runtime data
|
# Runtime data
|
||||||
log/
|
log/
|
||||||
|
|||||||
@@ -71,6 +71,11 @@ func Toggle(options *ToggleOptions) macaron.Handler {
|
|||||||
return
|
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.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
|
||||||
c.RedirectSubpath("/user/sign-in")
|
c.RedirectSubpath("/user/sign-in")
|
||||||
return
|
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.
|
// 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) &&
|
if !options.SignOutRequired && !c.IsLogged && !isAPIPath(c.Req.URL.Path) &&
|
||||||
len(c.GetCookie(conf.Security.CookieUsername)) > 0 {
|
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.SetCookie("redirect_to", url.QueryEscape(conf.Server.Subpath+c.Req.RequestURI), 0, conf.Server.Subpath)
|
||||||
c.RedirectSubpath("/user/sign-in")
|
c.RedirectSubpath("/user/sign-in")
|
||||||
return
|
return
|
||||||
@@ -103,6 +112,20 @@ func isAPIPath(url string) bool {
|
|||||||
return strings.HasPrefix(url, "/api/")
|
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 {
|
type AuthStore interface {
|
||||||
// GetAccessTokenBySHA1 returns the access token with given SHA1. It returns
|
// GetAccessTokenBySHA1 returns the access token with given SHA1. It returns
|
||||||
// database.ErrAccessTokenNotExist when not found.
|
// 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
|
## 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.
|
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**
|
**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-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`: muted brand support. Available for chips, tags, low-emphasis fills.
|
- `--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-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.
|
- `--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;
|
--radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Theme palettes are derived from Happy Hues (https://www.happyhues.co):
|
/* Theme palettes are adapted from Pierre Theme's "Light" and "Dark"
|
||||||
light mode = palette 6, dark mode = palette 13. */
|
(non-soft) variants (https://github.com/pierrecomputer/theme). */
|
||||||
:root {
|
:root {
|
||||||
color-scheme: light dark;
|
color-scheme: light dark;
|
||||||
|
|
||||||
--color-background: #fffffe;
|
--color-background: #ffffff;
|
||||||
--color-foreground: #2b2c34;
|
--color-foreground: #0a0a0a;
|
||||||
--color-card: #fffffe;
|
--color-card: #ffffff;
|
||||||
--color-card-foreground: #2b2c34;
|
--color-card-foreground: #0a0a0a;
|
||||||
--color-popover: #fffffe;
|
--color-popover: #ffffff;
|
||||||
--color-popover-foreground: #2b2c34;
|
--color-popover-foreground: #0a0a0a;
|
||||||
--color-primary: #059669;
|
--color-primary: #009fff;
|
||||||
--color-primary-foreground: #fffffe;
|
--color-primary-foreground: #ffffff;
|
||||||
--color-secondary: #d1d1e9;
|
--color-secondary: #f5f5f5;
|
||||||
--color-secondary-foreground: #2b2c34;
|
--color-secondary-foreground: #0a0a0a;
|
||||||
--color-surface: #ececf6;
|
--color-surface: #f5f5f5;
|
||||||
--color-muted-foreground: #5f6172;
|
--color-muted-foreground: #737373;
|
||||||
--color-destructive: #c2392f;
|
--color-destructive: #d52c36;
|
||||||
--color-destructive-foreground: #fffffe;
|
--color-destructive-foreground: #ffffff;
|
||||||
--color-border: #d1d1e9;
|
--color-border: #e5e5e5;
|
||||||
--color-input: #c8c8d8;
|
--color-input: #d4d4d4;
|
||||||
--color-ring: #059669;
|
--color-ring: #009fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root.dark {
|
:root.dark {
|
||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
|
|
||||||
--color-background: #16161a;
|
--color-background: #0a0a0a;
|
||||||
--color-foreground: #fffffe;
|
--color-foreground: #fafafa;
|
||||||
--color-card: #242629;
|
--color-card: #171717;
|
||||||
--color-card-foreground: #fffffe;
|
--color-card-foreground: #fafafa;
|
||||||
--color-popover: #242629;
|
--color-popover: #171717;
|
||||||
--color-popover-foreground: #fffffe;
|
--color-popover-foreground: #fafafa;
|
||||||
--color-primary: #059669;
|
--color-primary: #009fff;
|
||||||
--color-primary-foreground: #fffffe;
|
--color-primary-foreground: #ffffff;
|
||||||
--color-secondary: #5c5f68;
|
--color-secondary: #1d1d1d;
|
||||||
--color-secondary-foreground: #fffffe;
|
--color-secondary-foreground: #fafafa;
|
||||||
--color-surface: #33363c;
|
--color-surface: #1d1d1d;
|
||||||
--color-muted-foreground: #94a1b2;
|
--color-muted-foreground: #8a8a8a;
|
||||||
--color-destructive: #d63653;
|
--color-destructive: #ff6762;
|
||||||
--color-destructive-foreground: #fffffe;
|
--color-destructive-foreground: #ffffff;
|
||||||
--color-border: #5c5f68;
|
--color-border: #1d1d1d;
|
||||||
--color-input: #5c5f68;
|
--color-input: #262626;
|
||||||
--color-ring: #059669;
|
--color-ring: #009fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export function SignIn() {
|
|||||||
if (!body.error && !body.fields) {
|
if (!body.error && !body.fields) {
|
||||||
setFormError(t("sign_in_failed"));
|
setFormError(t("sign_in_failed"));
|
||||||
}
|
}
|
||||||
|
setSubmitting(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const data = (await res.json()) as SignInResponse;
|
const data = (await res.json()) as SignInResponse;
|
||||||
@@ -91,7 +92,6 @@ export function SignIn() {
|
|||||||
window.location.assign(data.redirectTo || subUrl("/"));
|
window.location.assign(data.redirectTo || subUrl("/"));
|
||||||
} catch {
|
} catch {
|
||||||
setFormError(t("sign_in_failed"));
|
setFormError(t("sign_in_failed"));
|
||||||
} finally {
|
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@@ -124,6 +124,7 @@ export function SignIn() {
|
|||||||
autoComplete="username"
|
autoComplete="username"
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
|
tabIndex={1}
|
||||||
placeholder={t("username_placeholder")}
|
placeholder={t("username_placeholder")}
|
||||||
value={username}
|
value={username}
|
||||||
onChange={(e) => setUsername(e.target.value)}
|
onChange={(e) => setUsername(e.target.value)}
|
||||||
@@ -141,7 +142,9 @@ export function SignIn() {
|
|||||||
<div className="flex items-center justify-between gap-3">
|
<div className="flex items-center justify-between gap-3">
|
||||||
<Label htmlFor="password">{t("password")}</Label>
|
<Label htmlFor="password">{t("password")}</Label>
|
||||||
<Button variant="link" size="inline" asChild>
|
<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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -152,6 +155,7 @@ export function SignIn() {
|
|||||||
type={showPassword ? "text" : "password"}
|
type={showPassword ? "text" : "password"}
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
required
|
required
|
||||||
|
tabIndex={2}
|
||||||
placeholder={t("password_placeholder")}
|
placeholder={t("password_placeholder")}
|
||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
@@ -161,6 +165,7 @@ export function SignIn() {
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
tabIndex={3}
|
||||||
onClick={() => setShowPassword((v) => !v)}
|
onClick={() => setShowPassword((v) => !v)}
|
||||||
aria-label={showPassword ? t("hide_password") : t("show_password")}
|
aria-label={showPassword ? t("hide_password") : t("show_password")}
|
||||||
aria-pressed={showPassword}
|
aria-pressed={showPassword}
|
||||||
@@ -180,7 +185,7 @@ export function SignIn() {
|
|||||||
<div className="flex flex-col gap-1.5">
|
<div className="flex flex-col gap-1.5">
|
||||||
<Label htmlFor="login_source">{t("auth_source")}</Label>
|
<Label htmlFor="login_source">{t("auth_source")}</Label>
|
||||||
<Select value={String(loginSource)} onValueChange={(v) => setLoginSource(Number(v))}>
|
<Select value={String(loginSource)} onValueChange={(v) => setLoginSource(Number(v))}>
|
||||||
<SelectTrigger id="login_source">
|
<SelectTrigger id="login_source" tabIndex={4}>
|
||||||
<SelectValue />
|
<SelectValue />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@@ -196,18 +201,25 @@ export function SignIn() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<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">
|
<Label htmlFor="remember" className="cursor-pointer font-normal">
|
||||||
{t("remember_me")}
|
{t("remember_me")}
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="mt-2 flex flex-col gap-3">
|
<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")}
|
{submitting ? t("sign_in_submitting") : t("sign_in")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="link" size="inline" asChild className="self-center">
|
<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>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user