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
17 changes: 11 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,10 +204,10 @@ For complex logic, it is recommended to extract it into functions under the `src

### Reuse shared page controls

Use `components/common/*/*.css` for sidebars, search fields, and compact buttons, and `components/common/dropdown/*` for
dropdown variants. `templates/base/head_navbar.tmpl` loads the shared styles and controllers for all pages. Keep page
layout rules in the page stylesheet and pass the page's existing routes, permissions, and form values into the shared
controls.
Use `components/common/*/*.css` for sidebars, search fields, selects, and compact buttons, and
`components/common/dropdown/*` for dropdown variants. `templates/base/head_navbar.tmpl` loads the shared styles and
controllers for all pages. Keep page layout rules in the page stylesheet and pass the page's existing routes,
permissions, and form values into the shared controls.

- **Sidebar:** add `theme-sidebar-layout` and a unique `data-theme-sidebar` storage key to the page, then use
`theme-sidebar`, `theme-sidebar-content`, and `theme-sidebar-main` for its children. Include
Expand All @@ -229,8 +229,13 @@ controls.
native checkboxes, and action menus omit selection indicators. The controller handles Escape, outside clicks, and
opening one menu at a time. See `components/repository/filter/filter`, `components/repository/sort/sort`, and
`org/action_menu_items`.
- **Buttons:** add `theme-control` for standard 32px controls or `theme-control-small` for 28px controls. Keep native
`primary`, `secondary`, and permission checks on actions.
- **Buttons:** keep Forgejo's native `ui button` classes on buttons Forgejo already renders; the theme styles those
globally. For a button the theme adds, use `button theme-control` (32px, 12px inline padding) instead of sizing it in
a page stylesheet, with `theme-control-wide` for the 16px padding GitHub uses on profile pages or
`theme-control-small` for 28px controls. Use `theme-icon-button` only for icon-only controls. Keep native `primary`,
`secondary`, and permission checks on actions, and leave page stylesheets to layout such as `flex` and `order`.
- **Select:** wrap a native `<select class="theme-select">` in `<span class="theme-select-wrap">`. The options stay in
the page template. See `templates/user/profile_packages.tmpl`.

For repository filtering, add `data-theme-repository-search` to the form and render
`components/repository/results/results` after it. `repository-search.js` updates that result fragment through the same
Expand Down
4 changes: 4 additions & 0 deletions components/common/button/button.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
cursor: pointer;
}

.button.theme-control-wide {
padding: 5px 16px;
}

.button.theme-control-small {
height: 28px;
min-height: 28px;
Expand Down
41 changes: 41 additions & 0 deletions components/common/select/select.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.theme-select-wrap {
position: relative;
display: inline-flex;
flex: none;
min-width: 0;
}

.theme-select-wrap::after {
content: "";
position: absolute;
top: 14px;
right: 12px;
border: 4px solid transparent;
border-top-color: var(--color-text);
border-bottom: 0;
pointer-events: none;
}

/* :is() keeps one specificity for selects inside and outside Forgejo's .ui.form. */
:is(.ui.form, body) .theme-select-wrap > .theme-select {
appearance: none;
transition-property: border-color, box-shadow, background-color, color;
height: 32px;
max-width: 100%;
margin: 0;
padding: 5px 32px 5px 16px;
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
background: var(--color-button);
color: var(--color-text);
box-shadow: none;
font-size: 14px;
font-weight: 500;
line-height: 20px;
cursor: pointer;
}

:is(.ui.form, body) .theme-select-wrap > .theme-select:focus-visible {
outline: 2px solid var(--github-borderColor-accent-emphasis);
outline-offset: 2px;
}
19 changes: 19 additions & 0 deletions components/common/select/select.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { readFileSync, readdirSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
import { expect, it } from "vitest";

const root = fileURLToPath(new URL("../../..", import.meta.url));

it("styles every template select through the shared select", () => {
const selects = ["templates", "components"].flatMap(directory =>
readdirSync(join(root, directory), { recursive: true })
.map(file => `${directory}/${String(file)}`)
.filter(file => file.endsWith(".tmpl"))
.flatMap(file => readFileSync(join(root, file), "utf8").match(/<select\b[^>]*>/g) ?? [])
);
expect(selects.length).toBeGreaterThan(0);
for (const select of selects) expect(select).toContain('class="theme-select"');
const stylesheet = readFileSync(join(root, "components/common/select/select.css"), "utf8");
expect(stylesheet).toContain(".theme-select-wrap > .theme-select");
});
50 changes: 2 additions & 48 deletions styles/pages/profile-subpages.css
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,6 @@

:is(.github-profile, .github-org) .profile-search-submit.button {
flex: none;
min-height: 32px;
margin: 0;
padding: 5px 16px;
line-height: 20px;
}

:is(.github-profile, .github-org) .profile-subnav {
Expand Down Expand Up @@ -305,7 +301,6 @@
line-height: 20px;
}

:is(.github-profile, .github-org) .profile-search-controls > .button,
:is(.github-profile, .github-org) .profile-projects .button {
min-height: 32px;
margin: 0;
Expand All @@ -318,47 +313,6 @@
box-shadow: none;
}

:is(.github-profile, .github-org) .profile-select-wrap {
position: relative;
display: inline-flex;
flex: none;
min-width: 0;
}

:is(.github-profile, .github-org) .profile-select-wrap::after {
content: "";
position: absolute;
top: 14px;
right: 12px;
border: 4px solid transparent;
border-top-color: var(--color-text);
border-bottom: 0;
pointer-events: none;
}

:is(.github-profile, .github-org) .ui.form .profile-select {
appearance: none;
transition-property: border-color, box-shadow, background-color, color;
height: 32px;
max-width: 100%;
margin: 0;
padding: 5px 32px 5px 16px;
border: 1px solid var(--color-secondary);
border-radius: var(--border-radius);
background: var(--color-button);
color: var(--color-text);
box-shadow: none;
font-size: 14px;
font-weight: 500;
line-height: 20px;
cursor: pointer;
}

:is(.github-profile, .github-org) .ui.form .profile-select:focus-visible {
outline: 2px solid var(--github-borderColor-accent-emphasis);
outline-offset: 2px;
}

:is(.github-profile, .github-org) .profile-package-row {
border: 1px solid var(--color-secondary);
border-bottom: 0;
Expand Down Expand Up @@ -531,11 +485,11 @@
flex-basis: 100%;
}

:is(.github-profile, .github-org) .profile-select-wrap {
:is(.github-profile, .github-org) .profile-search-controls > .theme-select-wrap {
flex: 1;
}

:is(.github-profile, .github-org) .ui.form .profile-select {
:is(.github-profile, .github-org) .profile-search-controls .theme-select {
width: 100%;
}

Expand Down
11 changes: 5 additions & 6 deletions styles/pages/user-profile.css
Original file line number Diff line number Diff line change
Expand Up @@ -443,13 +443,12 @@

:is(.github-profile, .github-org) .profile-repo-toolbar > .profile-new-repo.button {
flex: none;
min-height: 32px;
margin: 0;
padding: 5px 16px;
box-shadow: none;
font-size: 14px;
font-weight: 500;
line-height: 20px;
}

/* The organization overview keeps GitHub's wider padding; its repository list uses the standard control. */
.github-org .profile-repo-toolbar > .profile-new-repo.button {
padding: 5px 16px;
}

@media (max-width: 767px) {
Expand Down
1 change: 1 addition & 0 deletions templates/base/component_assets.tmpl
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/components/common/button/button.css">
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/components/common/list-heading/list-heading.css">
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/components/common/search/search.css">
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/components/common/select/select.css">
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/components/common/sidebar/sidebar.css">
<script src="{{AssetUrlPrefix}}/js/components/common/sidebar/sidebar.js" defer></script>
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/components/common/dropdown/dropdown.css">
Expand Down
6 changes: 3 additions & 3 deletions templates/user/profile_package_versions.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,20 @@
<form class="ui form ignore-dirty profile-list-search">
<div class="profile-search-controls">
{{template "components/common/search/search" dict "Kind" "plain" "Value" .Query "Placeholder" (ctx.Locale.Tr "search.package_kind")}}
<span class="profile-select-wrap"><select class="profile-select" name="sort" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort"}}">
<span class="theme-select-wrap"><select class="theme-select" name="sort" aria-label="{{ctx.Locale.Tr "repo.issues.filter_sort"}}">
<option value="version_asc"{{if eq .Sort "version_asc"}} selected="selected"{{end}}>{{ctx.Locale.Tr "filter.string.asc"}}</option>
<option value="version_desc"{{if eq .Sort "version_desc"}} selected="selected"{{end}}>{{ctx.Locale.Tr "filter.string.desc"}}</option>
<option value="created_asc"{{if eq .Sort "created_asc"}} selected="selected"{{end}}>{{ctx.Locale.Tr "repo.issues.filter_sort.oldest"}}</option>
<option value="created_desc"{{if or (eq .Sort "") (eq .Sort "created_desc")}} selected="selected"{{end}}>{{ctx.Locale.Tr "repo.issues.filter_sort.latest"}}</option>
</select></span>
{{if eq .PackageDescriptor.Package.Type "container"}}
<span class="profile-select-wrap"><select class="profile-select" name="tagged" aria-label="{{ctx.Locale.Tr "packages.filter.container.tagged"}}">
<span class="theme-select-wrap"><select class="theme-select" name="tagged" aria-label="{{ctx.Locale.Tr "packages.filter.container.tagged"}}">
{{$isTagged := or (eq .Tagged "") (eq .Tagged "tagged")}}
<option value="tagged"{{if $isTagged}} selected="selected"{{end}}>{{ctx.Locale.Tr "packages.filter.container.tagged"}}</option>
<option value="untagged"{{if not $isTagged}} selected="selected"{{end}}>{{ctx.Locale.Tr "packages.filter.container.untagged"}}</option>
</select></span>
{{end}}
<button class="secondary button" type="submit">{{ctx.Locale.Tr "search.search"}}</button>
<button class="secondary button theme-control theme-control-wide" type="submit">{{ctx.Locale.Tr "search.search"}}</button>
</div>
</form>
<div class="profile-package-list">
Expand Down
4 changes: 2 additions & 2 deletions templates/user/profile_packages.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
<form class="ui form ignore-dirty profile-list-search">
<div class="profile-search-controls">
{{template "components/common/search/search" dict "Kind" "plain" "Value" .Query "Placeholder" (ctx.Locale.Tr "search.package_kind")}}
<span class="profile-select-wrap"><select class="profile-select" name="type" aria-label="{{ctx.Locale.Tr "packages.filter.type"}}">
<span class="theme-select-wrap"><select class="theme-select" name="type" aria-label="{{ctx.Locale.Tr "packages.filter.type"}}">
<option value="">{{ctx.Locale.Tr "packages.filter.type"}}</option>
<option value="all">{{ctx.Locale.Tr "packages.filter.type.all"}}</option>
{{range $type := .AvailableTypes}}
<option{{if eq $.PackageType $type}} selected="selected"{{end}} value="{{$type}}">{{$type.Name}}</option>
{{end}}
</select></span>
<button class="secondary button" type="submit">{{ctx.Locale.Tr "search.search"}}</button>
<button class="secondary button theme-control theme-control-wide" type="submit">{{ctx.Locale.Tr "search.search"}}</button>
</div>
</form>
{{end}}
Expand Down
2 changes: 1 addition & 1 deletion templates/user/profile_projects.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
{{ctx.Locale.PrettyNumber .ClosedCount}}&nbsp;{{ctx.Locale.Tr "repo.issues.closed_title"}}
</a>
</div>
<a class="primary button" href="{{$.Link}}/new">{{ctx.Locale.Tr "repo.projects.new"}}</a>
<a class="primary button theme-control theme-control-wide" href="{{$.Link}}/new">{{ctx.Locale.Tr "repo.projects.new"}}</a>
</div>
{{end}}

Expand Down
4 changes: 2 additions & 2 deletions templates/user/profile_repository_search.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
{{if .Org}}<input type="hidden" name="tab" value="overview">{{else if .TabName}}<input type="hidden" name="tab" value="{{.TabName}}">{{end}}
{{$placeholder := ctx.Locale.Tr "search.repo_kind"}}{{if .Org}}{{$placeholder = "Find a repository…"}}{{end}}
{{template "components/common/search/search" dict "Kind" "plain" "Class" "profile-repo-query" "Value" .Keyword "Placeholder" $placeholder "Label" (ctx.Locale.Tr "search.repo_kind")}}
{{if .PageIsProfileStarList}}<button type="submit" class="secondary button profile-search-submit">{{ctx.Locale.Tr "search.search"}}</button>{{end}}
{{if .PageIsProfileStarList}}<button type="submit" class="secondary button theme-control theme-control-wide profile-search-submit">{{ctx.Locale.Tr "search.search"}}</button>{{end}}
{{template "components/repository/filter/filter" .}}
{{template "components/repository/sort/sort" dict "Data" .}}
</form>
{{if and .IsSigned (eq .SignedUserID .ContextUser.ID) (eq .TabName "repositories")}}
<a class="primary button profile-new-repo" href="{{AppSubUrl}}/repo/create">{{svg "octicon-repo" 16}}{{ctx.Locale.Tr "new_repo.link"}}</a>
<a class="primary button theme-control theme-control-wide profile-new-repo" href="{{AppSubUrl}}/repo/create">{{svg "octicon-repo" 16}}{{ctx.Locale.Tr "new_repo.link"}}</a>
{{end}}
{{if .Org}}{{template "org/repository_actions" .}}{{end}}
</div>
Loading